0
votes

I am developing a WPF application in which I need to send SMS to only those contacts that are checked in a DataGrid.

After checking the desired contacts in the DataGrid, I am facing problems when I scroll down/up. The checked property of the checkboxes in the DataGrid gets changed randomly.

I came across some solutions that suggested to add the following properties:

  • VirtualizingStackPanel.VirtualizationMode="Standard" in DataGrid
  • VirtualizingStackPanel.IsVirtualizing="True" in DataGrid (When I set it to False, the application becomes totally unresponsive)
  • UpdateSourceTrigger="PropertyChanged" in CheckBox Binding
  • EnableRowVirtualization="True" in DataGrid
  • EnableColumnVirtualization="True" in DataGrid

I tried them all, but none of them worked.

XAML:

<StackPanel Orientation="Horizontal">
        <DataGrid x:Name="smsgrid" VirtualizingStackPanel.VirtualizationMode="Standard" VirtualizingStackPanel.IsVirtualizing="True" Margin="10,20,0,10" AutoGenerateColumns="False" IsReadOnly="True" CanUserResizeColumns="False" CanUserReorderColumns="False" CanUserSortColumns="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <CheckBox x:Name="chkbox" Checked="chkbox_Checked" Unchecked="chkbox_Checked"></CheckBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="ID" Binding="{Binding ID}"></DataGridTextColumn>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}"></DataGridTextColumn>
                <DataGridTextColumn Header="Mobile no." Binding="{Binding Mobile1}"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
        ...
<StackPanel>

I am using the StackPanel to stack a LogIn page horizontally (not pasted in the above XAML) and a List to store the mobile number if the corresponding checkbox is selected. I also noted that the values in the List remains unchanged as the values of the checked property of checkboxes changes.

It would be really nice if I get a solution for avoiding the automatic checking and unchecking of checkboxes in the DataGrid.

Thank You!

2

2 Answers

1
votes

In my understanding when IsVirtualizing = true:

  • UI elements that are not visible are created only when you scroll to unhide them
  • when you unhide a new row then it is created, checked or unchecked so the event is also triggered

If you turn off virtualization the performance will drop down proportionally to number of items in your datagrid because all of them will have to be handled in UI even if they are not visible.

My workaround without turning off virtualization is to only allow execution of checkbox checked/unchecked events in the moment after you clicked on datagrid items and disable execution when you start scrolling:

In your .xaml.cs:

bool _allowChckboxEvent = true;

private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
   if (_allowChckboxEvent == false) return;
   //OnChecked stuff
}

private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
   if (_allowChckboxEvent == false) return;
   //OnUnchecked stuff
}

//Enable checkbox events on left button click
private void MyDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
   _allowChckboxEvent = true;
}

//Disable checkbox events when user starts scrolling the datagrid
private void MyDataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
   _allowChckboxEvent = false;
}

In your .xaml:

<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding MyViewModel}" AutoGenerateColumns="False" PreviewMouseLeftButtonDown="MyDataGrid_PreviewMouseLeftButtonDown" ScrollViewer.ScrollChanged="MyDataGrid_ScrollChanged">
 <DataGrid.Columns>
  <DataGridTemplateColumn Header="IsVisibile">
   <DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
     <CheckBox HorizontalAlignment="Center" IsChecked="{Binding IsVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"></CheckBox>
    </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
  </DataGridTemplateColumn>
 </DataGrid.Columns>
</DataGrid>

I hope it helps. If someone has more elegant solution I'll be happy to learn :)

EDIT: Solution 2

In the previous example you can't be sure which event will be triggered first: checked/unchecked or scrollchanged. Second solution works way better.

In your .xaml.cs:

bool _allowChckboxEvent = true;

private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
   if (_allowChckboxEvent == false) return;
   //OnChecked stuff
   _allowChckboxEvent = false; //checked/unchecked events are disabled right after youd stuff is done
}

private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
   if (_allowChckboxEvent == false) return;
   //OnUnchecked stuff
   _allowChckboxEvent = false; //checked/unchecked events are disabled after your stuff is done
}

//Enable checkbox events right before you check/uncheck checkbox
private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
   _allowChckboxEvent = true;
}

In your .xaml:

<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding MyViewModel}" AutoGenerateColumns="False">
 <DataGrid.Columns>
  <DataGridTemplateColumn Header="IsVisibile">
   <DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
     <CheckBox HorizontalAlignment="Center" IsChecked="{Binding IsVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" PreviewMouseLeftButtonDown="CheckBox_PreviewMouseLeftButtonDown" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"></CheckBox>
    </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
  </DataGridTemplateColumn>
 </DataGrid.Columns>
</DataGrid>
0
votes

It's better not to disable Virtualization since your application becomes totally unresponsive.

You can directly use DataGridCheckBoxColumn, no need the heavy DataGridTemplateColumn

<DataGridCheckBoxColumn Binding="{Binding NeedSendSms}"/>

then in the ViewModel you can get all contacts with NeedSendSms is true.

If you still want to use DataGridTemplateColumn, then you need bind IsChecked property of CheckBox to avoid automatic checking and unchecking.

<DataGridTemplateColumn>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding NeedSendSms}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>