我有一個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
}
}
任何想法爲什麼我變得空?
正如您在上一個問題的評論中所說的,使用AllChecked屬性替換CheckAllCommand並在屬性設置器中調用SelectUnselectAll會簡單很多。你爲什麼如此抗拒改進?也就是說,提出一個關於NullReferenceException的問題很可能很快就會被封閉爲這樣的重複:https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-doi-i-fix -它。我已經做到了,但我不想一直都是壞人。 – Clemens
並且在ViewModel中又有所有過時的屬性,並且DataModel沒有實現INotifyPropertyChanged。這只是無知嗎? – Clemens
@Clemens正如我在另一篇文章中所說的,你的工作方式可以通過在數據模型中實現inotifyproperty,我已經接受了你的答案,但是這個解決方案並不喜歡我。正如你在這裏看到的,數據模型並沒有實現INotifyProperty,但是視圖模型確實如此。現在通過改變selectUnselectall方法,在這裏看到,選中/取消選中listview中的所有項目正在工作。現在的問題是icommand。是的,我已經看到您對使用屬性的評論,但我喜歡將icommands與mvvm模式結合使用。 – user1624552