2017-06-21 9 views
0

J'ai un ListView avec quelques colonnes. La première colonne est un type de case à cocher. En outre, j'ai mis une case à cocher sur la ligne d'en-tête ListView pour sélectionner/désélectionner tous les éléments ListView à la fois.WPVM MVVM: le paramètre ICommand est parfois nul (pas toujours)

C'est la vue (XAML):

<Grid> 
    <Style x:Key="alternatingStyle" TargetType="ListViewItem"> 
     <Setter Property="IsSelected" Value="{Binding IsSelected}"/> 
     <Style.Triggers>       
      <Trigger Property="ItemsControl.AlternationIndex" Value="0"> 
       <Setter Property="Background" Value="LightBlue" /> 
      </Trigger> 
      <Trigger Property="ItemsControl.AlternationIndex" Value="1"> 
       <Setter Property="Background" Value="LightGray" /> 
      </Trigger> 
     </Style.Triggers> 
    </Style> 
    </Grid.Resources> 

    <ListView Margin="10" Name="lvUsers" AlternationCount="2" ItemContainerStyle="{StaticResource alternatingStyle}" ItemsSource="{Binding Path=Items}" SelectionMode="Extended"> 
     <ListView.View> 
      <GridView> 
       <!-- Checkbox header --> 
       <GridViewColumn> 

        <GridViewColumn.Header> 
         <CheckBox x:Name="CheckAll" Command="{Binding CheckAllCommand}" 
            CommandParameter="{Binding IsChecked, ElementName=CheckAll}" /> 
        </GridViewColumn.Header> 

        <GridViewColumn.CellTemplate> 
         <DataTemplate> 
          <StackPanel Orientation="Horizontal"> 
           <CheckBox IsChecked="{Binding IsSelected}" /> 
          </StackPanel> 
         </DataTemplate> 
        </GridViewColumn.CellTemplate> 
       </GridViewColumn> 

       <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" /> 
       <GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" /> 
       <GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" /> 
      </GridView> 
     </ListView.View> 
    </ListView> 
</Grid> 

Parfois (pas toujours) quand je cocher/décocher la case en-tête de listview afin de sélectionner/désélectionner tous les éléments du listview je reçois un exception de type:

Object reference not set to an instance of an object. 

J'ai découvert que le paramètre booléen passé à ICommand « CheckAllCommand » est nul quand j'essaie de faire une conversion en booléen il tombe en panne, voir code modèle de vue plus tard:

code-behind (xaml.cs):

public partial class MainWindow: ViewBaseControl 
    { 
     public MainWindow(ViewModelSession vm):base(vm) 
     { 
      // DataContext = new myViewModel(); <-- Data context is not initialized here, it is done automatically in the ViewBaseControl class 

      InitializeComponent(); 
     } 
    } 

classe ViewBaseControl:

public class ViewBaseControl : UserControl 
{   
    [Obsolete("To use below constructor", true)] 
    public ViewBaseControl() 
    { 

    } 

    public ViewBaseControl(ViewModelSession vm) 
    { 
     DataContext = vm; 
     Focusable = true; 

     Loaded += (sender, e) => 
      MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); 
    } 

    public ViewModelSession VM 
    { 
     get { return DataContext as ViewModelSession; } 
    } 
} 

Voir Modèle:

public class myViewModel : ViewModelSession, INotifyPropertyChanged 
{ 
    private DataModel _data = null; 

    private ObservableCollection<DataModel> items = null; 

    public myViewModel() 
    { 
     this.Load(); 
    } 

    public void Load() 
    { 
     items = new ObservableCollection<DataModel>(); 
     items.Add(new DataModel() { IsSelected = false, Name = "John Doe", Age = 42, Mail = "[email protected]" }); 
     items.Add(new DataModel() { IsSelected = false, Name = "Jane Doe", Age = 39, Mail = "[email protected]" }); 
     items.Add(new DataModel() { IsSelected = false, Name = "Sammy Doe", Age = 7, Mail = "[email protected]" }); 
    } 

    public ObservableCollection<DataModel> Items 
    { 
     get 
     { 
      return this.items; 
     } 
    } 

    private RelayCommand checkAllCommand; 
    public ICommand CheckAllCommand 
    { 
     get 
     { 
      return checkAllCommand ?? 
       (checkAllCommand = new RelayCommand(param => this.SelectUnselectAll(Convert.ToBoolean(param.ToString())))); // <-- this is the line that crashes when trying to convert boolean parameter "param" into boolean. Param is sometimes null, but not always. 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    private void NotifyPropertyChanged(String propertyName) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    public bool IsSelected 
    { 
     get 
     { 
      if (this._data == null) 
      { 
       return false; 
      } 

      return this._data.IsSelected; 
     } 

     set 
     { 
      if (this._data != null && value != this._data.IsSelected) 
      { 
       this._data.IsSelected = value; 
       NotifyPropertyChanged("IsSelected"); 
      } 
     } 
    } 

    public string Name 
    { 
     get 
     { 
      if (this._data == null) 
      { 
       return string.Empty; 
      } 

      return this._data.Name; 
     } 

     set 
     { 
      if (value != this._data.Name) 
      { 
       this._data.Name = value; 
       NotifyPropertyChanged("Name"); 
      } 
     } 
    } 

    public int Age 
    { 
     get 
     { 
      if (this._data == null) 
      { 
       return 0; 
      } 

      return this._data.Age; 
     } 

     set 
     { 
      if (value != this._data.Age) 
      { 
       this._data.Age = value; 
       NotifyPropertyChanged("Age"); 
      } 
     } 
    } 

    public string Mail 
    { 
     get 
     { 
      if (this._data == null) 
      { 
       return string.Empty; 
      } 

      return this._data.Mail; 
     } 

     set 
     { 
      if (value != this._data.Mail) 
      { 
       this._data.Mail = value; 
       NotifyPropertyChanged("Mail"); 
      } 
     } 
    } 

    private void SelectUnselectAll(bool isSelected) 
    {   
     for (int i = 0; i < this.items.Count; i++) 
     { 
      if (this.items[i].IsSelected != isSelected) 
      { 
       _data = new DataModel() 
       { 
        IsSelected = isSelected, 
        Name = this.items[i].Name, 
        Age = this.items[i].Age, 
        Mail = this.items[i].Mail 
       };      

       this.items.RemoveAt(i); 
       this.items.Insert(i, _data); 
      } 
     } 
    } 
} 

problème est ici, paramètre "PARAM" passé à RelayCommand est parfois nulle (pas toujours):

private RelayCommand checkAllCommand; 
public ICommand CheckAllCommand 
{ 
    get 
    { 
     return checkAllCommand ?? 
      (checkAllCommand = new RelayCommand(param => this.SelectUnselectAll(Convert.ToBoolean(param.ToString())))); // <-- this is the line that crashes when trying to convert boolean parameter "param" into boolean. Param is sometimes null, but not always. 
    } 
} 

Mon modèle de données :

public class DataModel 
{ 
    public bool IsSelected 
    { 
     get; 
     set; 
    } 

    public string Name 
    { 
     get; 
     set; 
    } 

    public int Age 
    { 
     get; 
     set; 
    } 

    public string Mail 
    { 
     get; 
     set; 
    } 
} 

La classe de RelayCommand:

public class RelayCommand : ICommand 
{ 
    #region Fields 

    readonly Action<object> _execute; 
    readonly Predicate<object> _canExecute; 

    #endregion // Fields 

    #region Constructors 

    /// <summary> 
    /// Creates a new command that can always execute. 
    /// </summary> 
    /// <param name="execute">The execution logic.</param> 
    public RelayCommand(Action<object> execute) 
     : this(execute, null) 
    { 
    } 

    /// <summary> 
    /// Creates a new command. 
    /// </summary> 
    /// <param name="execute">The execution logic.</param> 
    /// <param name="canExecute">The execution status logic.</param> 
    public RelayCommand(Action<object> execute, Predicate<object> canExecute) 
    { 
     if (execute == null) 
      throw new ArgumentNullException("execute"); 

     _execute = execute; 
     _canExecute = canExecute; 
    } 

    #endregion // Constructors 

    #region ICommand Members 

    [DebuggerStepThrough] 
    public bool CanExecute(object parameter) 
    { 
     return _canExecute == null ? true : _canExecute(parameter); 
    } 

    public event EventHandler CanExecuteChanged 
    { 
     add { CommandManager.RequerySuggested += value; } 
     remove { CommandManager.RequerySuggested -= value; } 
    } 

    public void Execute(object parameter) 
    { 
     _execute(parameter); 
    } 

    #endregion // ICommand Members 
} 
} 

Toute idée pourquoi je reçois nul?

+1

Comme déjà dit dans un commentaire sur votre précédente question, il serait beaucoup plus simple de remplacer CheckAllCommand par une propriété AllChecked, et d'appeler SelectUnselectAll dans le setter de la propriété. Pourquoi êtes-vous si résistant aux améliorations? Cela dit, une question demandant une exception NullReferenceException est * très probablement * bientôt fermée en double: https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix -il. Je l'aurais déjà fait, mais je ne veux pas être le méchant tout le temps. – Clemens

+0

Et il y a toutes les propriétés obsolètes à nouveau dans ViewModel, et DataModel n'implémente pas INotifyPropertyChanged. Est-ce juste de l'ignorance? – Clemens

+0

@Clemens comme je l'ai dit dans un autre post, votre workarond était ok en implémentant inotifyproperty dans le modèle de données, j'ai accepté votre réponse mais cette solution ne m'aime pas. comme vous le voyez ici, le modèle de données n'implémente pas INotifyProperty mais le modèle de vue le fait. Maintenant, en changeant la méthode selectUnselectall comme vous voyez ici, cochez/décochez tous les éléments dans listview fonctionnent. Le problème est maintenant avec icommand. Oui, j'ai vu votre commentaire sur l'utilisation d'une propriété mais j'aime combiner des icommands avec un pattern mvvm. – user1624552

Répondre

0

Il n'a pas besoin d'être plus que cela dans votre modèle de vue:

public class ViewModelBase : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

public class ItemData : ViewModelBase 
{ 
    public string Name { get; set; } 
    public string Age { get; set; } 
    public string Mail { get; set; } 

    private bool isSelected; 
    public bool IsSelected 
    { 
     get { return isSelected; } 
     set 
     { 
      isSelected = value; 
      NotifyPropertyChanged(); 
     } 
    } 
} 

public class ViewModel : ViewModelBase 
{ 
    public ObservableCollection<ItemData> Items { get; } 
     = new ObservableCollection<ItemData>(); 

    private bool allSelected; 
    public bool AllSelected 
    { 
     get { return allSelected; } 
     set 
     { 
      allSelected = value; 
      NotifyPropertyChanged(); 

      foreach (var item in Items) 
      { 
       item.IsSelected = value; 
      } 
     } 
    } 
} 

ListView (sans plein ListViewItem style):

<ListView ItemsSource="{Binding Items}"> 
    <ListView.ItemContainerStyle> 
     <Style TargetType="ListViewItem"> 
      <Setter Property="IsSelected" Value="{Binding IsSelected}"/> 
     </Style> 
    </ListView.ItemContainerStyle> 
    <ListView.View> 
     <GridView> 
      <GridViewColumn> 
       <GridViewColumn.Header> 
        <CheckBox IsChecked="{Binding AllSelected}"/> 
       </GridViewColumn.Header> 
       <GridViewColumn.CellTemplate> 
        <DataTemplate> 
         <StackPanel Orientation="Horizontal"> 
          <CheckBox IsChecked="{Binding IsSelected}" /> 
         </StackPanel> 
        </DataTemplate> 
       </GridViewColumn.CellTemplate> 
      </GridViewColumn> 
      <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" /> 
      <GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" /> 
      <GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" /> 
     </GridView> 
    </ListView.View> 
</ListView> 
0

La propriété IsChecked d'un CheckBox est un en effet Nullable<bool> mais il ne doit pas renvoyer null sauf si vous définissez la propriété IsThreeState sur true.

Vous pouvez essayer cette liaison:

<CheckBox x:Name="CheckAll" Command="{Binding CheckAllCommand}" 
          CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}" /> 

En outre, vous n'avez pas appeler ToString() sur le param de pouvoir le convertir en un bool.Ce code ne doit pas vous donner une NullReferenceException:

private RelayCommand checkAllCommand; 
public ICommand CheckAllCommand 
{ 
    get 
    { 
     return checkAllCommand ?? 
      (checkAllCommand = new RelayCommand(param => this.SelectUnselectAll(Convert.ToBoolean(param)))); 
    } 
} 

Convert.ToBoolean(null) retours false.