2017-06-21 42 views
0

我有一個ListView的列。第一列是複選框類型。另外,我在ListView標題行上放置了一個複選框,以便一次選擇/取消選擇所有ListView項目。WPF MVVM:ICommand參數有時爲空(並非總是)

這是視圖(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> 

有時(並不總是)當我檢查/取消選中在列表視圖標題中的複選框以選擇/取消選擇列表視圖,我得到一個範圍內的所有項目類型的異常:

Object reference not set to an instance of an object. 

我發現,傳遞給ICommand的「CheckAllCommand」布爾參數爲空,所以當我嘗試做一個轉換爲布爾所以它崩潰,見後面視圖模型代碼:

代碼隱藏(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(); 
     } 
    } 

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; } 
    } 
} 

視圖模型

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); 
      } 
     } 
    } 
} 

問題是在這裏,參數 「參數」 傳遞給RelayCommand有時是零(不總是):

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 class DataModel 
{ 
    public bool IsSelected 
    { 
     get; 
     set; 
    } 

    public string Name 
    { 
     get; 
     set; 
    } 

    public int Age 
    { 
     get; 
     set; 
    } 

    public string Mail 
    { 
     get; 
     set; 
    } 
} 

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 
} 
} 

任何想法爲什麼我變得空?

+1

正如您在上一個問題的評論中所說的,使用AllChecked屬性替換CheckAllCommand並在屬性設置器中調用SelectUnselectAll會簡單很多。你爲什麼如此抗拒改進?也就是說,提出一個關於NullReferenceException的問題很可能很快就會被封閉爲這樣的重複:https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-doi-i-fix -它。我已經做到了,但我不想一直都是壞人。 – Clemens

+0

並且在ViewModel中又有所有過時的屬性,並且DataModel沒有實現INotifyPropertyChanged。這只是無知嗎? – Clemens

+0

@Clemens正如我在另一篇文章中所說的,你的工作方式可以通過在數據模型中實現inotifyproperty,我已經接受了你的答案,但是這個解決方案並不喜歡我。正如你在這裏看到的,數據模型並沒有實現INotifyProperty,但是視圖模型確實如此。現在通過改變selectUnselectall方法,在這裏看到,選中/取消選中listview中的所有項目正在工作。現在的問題是icommand。是的,我已經看到您對使用屬性的評論,但我喜歡將icommands與mvvm模式結合使用。 – user1624552

回答

0

目前並不需要比這更在您的視圖模型:

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控件(不完全ListViewItem的風格):

<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

一個CheckBoxIsChecked屬性是確實Nullable<bool>但它不應該返回null,除非您將IsThreeState屬性設置爲true

你可以試試這個結合:

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

而且,你不必叫ToString()param才能夠將其轉換爲一個bool。此代碼不能給你任何NullReferenceException

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

Convert.ToBoolean(null)回報false