0

I have a ListView with some columns. The first column is a checkbox type. Also, I have put a checkbox at ListView header row for select/unselect all ListView items at once.

This is the view (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>

Sometimes (not always) when I check/uncheck the checkbox at listview header in order to select/unselect all items within the listview I get an exception of type:

Object reference not set to an instance of an object.

I have discovered that boolean parameter passed to icommand "CheckAllCommand" is null so when I try to do a conversion into boolean so it crashes, see later view model code:

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 class:

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

View Model:

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

Problem is here, parameter "param" passed to RelayCommand is sometimes null (not always):

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

My data model:

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

    public string Name
    {
        get;
        set;
    }

    public int Age
    {
        get;
        set;
    }

    public string Mail
    {
        get;
        set;
    }
}

The RelayCommand class:

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

Any idea why I am getting null?

Benny
  • 2,233
  • 1
  • 22
  • 27
Willy
  • 9,848
  • 22
  • 141
  • 284
  • 1
    As already said in a comment on your previous question, it would be a lot simpler to replace the CheckAllCommand by an AllChecked property, and call SelectUnselectAll in the property setter. Why are you so resistant against improvements? That said, a question asking about a NullReferenceException is *very likely* soon closed as duplicat of this: https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it. I would have done it already, but I don't want to be the bad guy all the time. – Clemens Jun 21 '17 at 14:37
  • And there are all the obsolete properties again in ViewModel, and DataModel doesn't implement INotifyPropertyChanged. Is that just ignorance? – Clemens Jun 21 '17 at 14:41
  • @Clemens as i said in another post, your workarond was ok by implementing inotifyproperty in data model, I have accepted your answer but this solution does not like me. as you see here data model does not implement INotifyProperty but view model does. Now by changing selectUnselectall method as you see here, check/uncheck all items in listview are working. The problem now is with icommand. Yes, I have seen your comment about using a property but I like combining icommands with mvvm pattern. – Willy Jun 21 '17 at 14:48
  • Well you seem to be liking and disliking a lot of things, though without getting them to work. Do not work against basic framework concepts. You're making your life harder than necessary, and you create a lot of redundant question traffic here on SO. – Clemens Jun 21 '17 at 14:49

2 Answers2

0

There doesn't need to be more than this in your view model:

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

The ListView (without full 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>
Clemens
  • 123,504
  • 12
  • 155
  • 268
0

The IsChecked property of a CheckBox is a indeed Nullable<bool> but it shouldn't return null unless you set the IsThreeState property to true.

You could try this binding:

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

Also, you don't have to call ToString() on the param to be able to convert it to a bool. This code shouldn't give you any NullReferenceException:

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

Convert.ToBoolean(null) returns false.

mm8
  • 163,881
  • 10
  • 57
  • 88