WPF MVVM:ICommand参数有时为null(并非总是)

时间:2017-06-21 14:31:41

标签: c# wpf listview mvvm icommand

我有一些带有一些列的ListView。第一列是复选框类型。另外,我在ListView标题行中放置了一个复选框,用于一次选择/取消选择所有ListView项目。

这是视图(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>

有时(并非总是)当我选中/取消选中listview标题中的复选框以选择/取消选择列表视图中的所有项目时,我得到类型的例外:

Object reference not set to an instance of an object.

我发现传递给icommand“CheckAllCommand”的boolean参数为null所以当我尝试转换为boolean以便它崩溃时,请参阅后面的视图模型代码:

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

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 = "john@doe-family.com" });
        items.Add(new DataModel() { IsSelected = false, Name = "Jane Doe", Age = 39, Mail = "jane@doe-family.com" });
        items.Add(new DataModel() { IsSelected = false, Name = "Sammy Doe", Age = 7, Mail = "sammy.doe@gmail.com" });
    }

    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的参数“param”有时为null(并非总是):

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

知道我为什么会变空?

2 个答案:

答案 0 :(得分: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>

答案 1 :(得分:0)

IsChecked的{​​{1}}属性确实是CheckBox,但除非您将Nullable<bool>属性设置为{,否则它不应该返回null {1}}。

您可以尝试这种绑定:

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