WPF Datagrid Check all check boxes issue

Apr 1, 2009 at 7:35 AM
Edited Apr 1, 2009 at 7:38 AM

Hi All,

 Am fed up with an issue in wpf datagrid . I have a data grid which is having check boxes in first column . Also i have a checkbox outside the Datagrid . When am cheking the checkbox outside the datagrid, i want all the checkboxes inside the grid . I tried implementing this in the following way(Code attached along with this) , but it is showing strange behaviour . Some checkboxes in rows randomly getting checked(if some more clearly says first 10 rows are getting checked after that say 5 not checked again next 10 getting checked  and this process continues till the end of records). I have tried solving this issue using the helper class in Vinsibal's demo application , but still it is showing same behaviour.  I dont know why this happening . Am attaching my code here .
Please suggest a solution
Regards
sreeraj
-----------XAML For Grid------------------

<dg:DataGrid Grid.Row="1" AutoGenerateColumns="False" AlternatingRowBackground="SkyBlue" RowDetailsVisibilityMode="Collapsed"
                     AllowDrop="False" HeadersVisibility="All"
                     ColumnHeaderHeight="30" FontFamily="Verdana" FontSize="12"    Background="#FFFFFFFF"
                     OpacityMask="#FFF1E3E3" Foreground="#FF000000"
                     BorderThickness="1" SnapsToDevicePixels="False" CanUserDeleteRows="False" CanUserAddRows="False"
                     ClipToBounds="False" RowHeight="25"
                      ScrollViewer.CanContentScroll="True" Padding="0"
                     VerticalContentAlignment="Center" MinWidth="0"  HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Visible" GridLinesVisibility="All" Name="grdWorkOrders" Margin="0,0,0,52" Height="150" VerticalAlignment="Top" EnableColumnVirtualization="False">
                       
                        <dg:DataGrid.Columns>
                            <dg:DataGridTemplateColumn  MinWidth="30">
                                <dg:DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <CheckBox x:Name="chkMain"></CheckBox>
                                    </DataTemplate>
                                </dg:DataGridTemplateColumn.CellTemplate>
                            </dg:DataGridTemplateColumn>
                            <!--<dg:DataGridCheckBoxColumn    Header="Access"></dg:DataGridCheckBoxColumn>-->
                            <dg:DataGridTextColumn Binding="{Binding Path=ActivityName}" Header="AIRCRAFT"       FontSize="10" MinWidth="110" />
                            <dg:DataGridTextColumn Binding="{Binding Path=Description}" Header="FLEET"   FontSize="10" MinWidth="40" />
                            <dg:DataGridTextColumn Binding="{Binding Path=Description}" Header="MAINTENANCE FLEET"   FontSize="10" MinWidth="40" />
                            <dg:DataGridCheckBoxColumn  Header="RESPONSABILITY"></dg:DataGridCheckBoxColumn>
                        </dg:DataGrid.Columns>
 
                    </dg:DataGrid>

-----------Code Behind --------------------

for (int nCntr = 0; nCntr < grdWorkOrders.Items.Count; nCntr++)
            {              
                var cntr = MyDataGrid.ItemContainerGenerator.ContainerFromIndex(nCntr);
                DataGridRow ObjROw = (DataGridRow)cntr;
                if (ObjROw == null)
                {
                    ObjROw = (DataGridRow)MyDataGrid.ItemContainerGenerator.ContainerFromIndex(nCntr);
 
                }
                else
                {
                    FrameworkElement objElement = MyDataGrid.Columns[0].GetCellContent(ObjROw);
                    if (objElement != null)
                    {
                        if (objElement.GetType().ToString().EndsWith("CheckBox"))
                        {
                            CheckBox objChk = (CheckBox)objElement;
                            objChk.IsChecked = true;
                        }
                    }                  
                }
 
            }

 

Apr 1, 2009 at 8:32 AM
Wouldn't it be easier to iterate over  MyDataGrid.Items and set the property that the checkbox's IsChecked property is bound to? Or use DataGridCheckBoxColumn, if you aren't already.
Coordinator
Apr 1, 2009 at 1:54 PM
The issue you are running into is the row virtualization.  While turning off virtualization will solve the problem, it will make the perf go way down.  You could take a different approach:

     <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <CheckBox Content="Check All" IsChecked="{Binding IsCheckedAll}"/>
        <dg:DataGrid x:Name="MyDataGrid"
                     Grid.Row="1"
                     AutoGenerateColumns="False"                                      
                     SelectionUnit="FullRow"
                     Background="Transparent"
                     ItemsSource="{Binding Items}"
                     RowBackground="White"
                     AlternatingRowBackground="LightGray"
                     AlternationCount="2"
                     CanUserAddRows="False">          
            <dg:DataGrid.Columns>
                <dg:DataGridCheckBoxColumn Header="Check Me" Binding="{Binding Path=IsChecked}" />
                <dg:DataGridTextColumn Header="Data1" Binding="{Binding Path=Data1}" />
                <dg:DataGridTextColumn Header="Data2" Binding="{Binding Path=Data2}" />
            </dg:DataGrid.Columns>
        </dg:DataGrid>
    </Grid>

    public partial class Window3 : Window
    {
        ViewModel _vm;

        public Window3()
        {
            InitializeComponent();

            _vm = new ViewModel();
            this.DataContext = _vm;
        }
    }

    public class ViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<DummyClass> _items;
        private bool _isCheckedAll;

        public ViewModel()
        {
            Items = new ObservableCollection<DummyClass>
            {
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },
                new DummyClass { Data1 = "testData", Data2 = "testData" },               
                new DummyClass { Data1 = "testData", Data2 = "testData" }
            };
        }

        public ObservableCollection<DummyClass> Items
        {
            get { return _items; }
            set
            {
                _items = value;
                OnPropertyChanged("Items");
            }
        }

        public bool IsCheckedAll
        {
            get { return _isCheckedAll; }
            set
            {
                _isCheckedAll = value;

                foreach (var item in Items)
                    item.IsChecked = _isCheckedAll;

                OnPropertyChanged("IsCheckedAll");
            }
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion INotifyPropertyChanged
    }

    public class DummyClass : INotifyPropertyChanged
    {
        private string _data1;
        private string _data2;
        private bool _isChecked;

        public string Data1
        {
            get { return _data1; }
            set
            {
                _data1 = value;
                OnPropertyChanged("Data1");
            }
        }

        public string Data2
        {
            get { return _data2; }
            set
            {
                _data2 = value;
                OnPropertyChanged("Data2");
            }
        }

        public bool IsChecked
        {
            get { return _isChecked; }
            set
            {
                _isChecked = value;
                OnPropertyChanged("IsChecked");
            }
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion INotifyPropertyChanged
    }

Apr 6, 2009 at 12:38 PM
Hello Vinsibal ,
Sorry for the late reply , the issue solved .
Thank you very much
regards
sreeraj
Aug 20, 2009 at 8:16 PM

Vincent-

This was a great post. While I am not using a "Check All" column like the example above. I am using a DataGrid tied to a view model much like you show above. Each row has a column that tracks a processing state, an Enum on the view model that is converted to bool for the grid's consumption in a check box column.

So far so good. Binding is working well. CRUD is firing with immediate state updates to oracle whenever the checkbox changes.

Next I have a button in the row header. This button fires a modal window when selected, presenting the user with a different view. As of now, it is Enabled and Disabled on a DataTrigger checking if the row IsSelected. Works well.

So where I am at now is I would like to add the check box column's current value to these triggers for the row header button... so that when the check box is checked the button is disabled. And if the user unchecks the row, the button enables so the user can pop open their edit window.

For some reason I am getting the DataTrigger/Trigger wrong.

?

 

Aug 21, 2009 at 11:14 PM

So I simplified the check box binding a little bit. Only 2 common states so I switched it to a bool property, got rid of the converter... still having trouble working the Trigger/DataTrigger however...

The key is how do I make my RowHeaderStyle conscious of the value of the CheckBoxColumn in the same row it is in? That's what I am trying to trigger. The other possibility is to bind it do the IsProcessed bool property value on the view model (the one the check box is bound to) via a DataTrigger. It seems like that should work, but who knows.

Any ideas guys?

Aug 21, 2009 at 11:28 PM

Ok, I feel dumb. But hopefully you'll get a laugh out of this one. Maybe it will help someone.

My code was correct. I was binding the trigger properly as I thought, but I did not know that the Triggers are processed/applied top to bottom in XAML.

<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGridRow}}, Path=IsSelected}" Value="False">
    <Setter TargetName="EditButton" Property="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGridRow}}, Path=IsSelected}" Value="True">
    <Setter TargetName="EditButton" Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:DataGridRow}}, Path=DataContext.CurrentFC42State}" Value="True">
    <Setter TargetName="EditButton" Property="IsEnabled" Value="False" />
</DataTrigger>

The issue was that my last trigger above was originally the first of the three, meaning it would process, and then IsSelected would occur afterwards and turn it back on.

Is there a more concise way of doing this trigger logic - always toggle button enabled on IsSelected, yet if selected, make sure the row's data context is in a particular state?

-T