164

I have a DataGrid with first column as text column and second column as CheckBox column. What I want is, if I click the check box. It should get checked.

But, it takes two click to get selected, for first click the cell is getting selected, for the second clicks the check box is getting checked. How to make the check box to get checked/unchecked with a single click.

I'm using WPF 4.0. Columns in the DataGrid are AutoGenerated.

1

14 Answers 14

232

For single click DataGrid checkbox you can just put regular checkbox control inside DataGridTemplateColumn and set UpdateSourceTrigger=PropertyChanged.

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Sign up to request clarification or add additional context in comments.

4 Comments

WOW - I'm glad I read to the end. This works perfectly and is considerably less complicated, IMO this should be marked as the answer.
This also works for ComboBox. As in: way, WAY better than DataGridComboBoxColumn.
It doesn't when i use space bar to check/uncheck and arrows to move to another cell.
I have interpreted this answer that you have to Bind "IsSelected", but thats not true! You can just use DataGridTemplateColumn.CellTemplate with your own Binding and it will work!! The answer of @weidian-huang helped me to understand that, thanks!
69

I solved this with the following Style:

<Style TargetType="DataGridCell">
     <Style.Triggers>
         <Trigger Property="IsMouseOver" Value="True">
             <Setter Property="IsEditing" Value="True" />
         </Trigger>
     </Style.Triggers>
 </Style>

It's of course possible to adapt this further for specific columns ...

7 Comments

Nice. I changed it to a MultiTrigger and added a condition for ReadOnly=False but the basic approach worked for my simple case where keyboard navigation isn't important.
Adding that style to my grid raise an exception of Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
This is the cleanest way I've seen so far! Nice! (for IsReadOnly="True" a MultiTrigger will do the job)
This solution has some unexpected/unwanted behaviour. See stackoverflow.com/q/39004317/2881450
For the binding to work, you will need a UpdateSourceTrigger=PropertyChanged
|
35

First of, I know this is a pretty old question but I still thought I'd try and answer it.

I had the same problem a couple of days ago and came across a surprisingly short solution for it (see this blog). Basically, all you need to do is replace the DataGridCheckBoxColumn definition in your XAML with the following:

<DataGridTemplateColumn Header="MyCheckBoxColumnHeader">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Path=MyViewModelProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

The upside of this solution is obvious - it's XAML-only; thus it effectively refrains your from burdening your code-behind with additional UI logic.

5 Comments

This is similar to Konstantin Salavatov's answer and this one worked for me. +1 for including the code sample where his did not. Thanks for a good answer to an old question.
The problem with this is that if you do it with combobox columns, the little dropdown button will be visible for all cells in that column, all the time. Not just when you click on the cell.
This still requires 2 clicks for me
I figured it out. You can't use this solution in conjunction with certain DataGrid configuations like DataGridCell.Selected="DataGridCell_Selected" SelectionUnit="Cell"
But…… why does this work?
23

To make Konstantin Salavatov's answer work with AutoGenerateColumns, add an event handler to the DataGrid's AutoGeneratingColumn with the following code:

if (e.Column is DataGridCheckBoxColumn && !e.Column.IsReadOnly)
{
    var checkboxFactory = new FrameworkElementFactory(typeof(CheckBox));
    checkboxFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center);
    checkboxFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
    checkboxFactory.SetBinding(ToggleButton.IsCheckedProperty, new Binding(e.PropertyName) { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });

    e.Column = new DataGridTemplateColumn
        {
            Header = e.Column.Header,
            CellTemplate = new DataTemplate { VisualTree = checkboxFactory },
            SortMemberPath = e.Column.SortMemberPath
        };
}

This will make all of DataGrid's auto-generated checkbox columns be "single click" editable.

2 Comments

Thanks for filling in an autogenerated column approach, this handily points me in a suitable direction.
Thank you for this! In .NET 7, ToggleButton.IsCheckedProperty is not available, so I changed it to CheckBox.IsCheckedProperty
19

Based on blog referenced in Goblin's answer, but modified to work in .NET 4.0 and with Row-Selection Mode.

Notice that it also speeds up DataGridComboBoxColumn editing - by entering edit mode and displaying dropdown on single click or text input.

XAML:

        <Style TargetType="{x:Type DataGridCell}">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown" />
            <EventSetter Event="PreviewTextInput" Handler="DataGridCell_PreviewTextInput" />
        </Style>

Code-behind:

    private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        GridColumnFastEdit(cell, e);
    }

    private void DataGridCell_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        GridColumnFastEdit(cell, e);
    }

    private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
    {
        if (cell == null || cell.IsEditing || cell.IsReadOnly)
            return;

        DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
        if (dataGrid == null)
            return;

        if (!cell.IsFocused)
        {
            cell.Focus();
        }

        if (cell.Content is CheckBox)
        {
            if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
            {
                if (!cell.IsSelected)
                    cell.IsSelected = true;
            }
            else
            {
                DataGridRow row = FindVisualParent<DataGridRow>(cell);
                if (row != null && !row.IsSelected)
                {
                    row.IsSelected = true;
                }
            }
        }
        else
        {
            ComboBox cb = cell.Content as ComboBox;
            if (cb != null)
            {
                //DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
                dataGrid.BeginEdit(e);
                cell.Dispatcher.Invoke(
                 DispatcherPriority.Background,
                 new Action(delegate { }));
                cb.IsDropDownOpen = true;
            }
        }
    }


    private static T FindVisualParent<T>(UIElement element) where T : UIElement
    {
        UIElement parent = element;
        while (parent != null)
        {
            T correctlyTyped = parent as T;
            if (correctlyTyped != null)
            {
                return correctlyTyped;
            }

            parent = VisualTreeHelper.GetParent(parent) as UIElement;
        }
        return null;
    }

7 Comments

This solution worked best for me. My bound ViewModel was not updating with the other solutions.
@surfen, Is I need to put the above style and the code in every page and its codebehind, if I have many pages which has datagrid in it.Is it possible to use the style and code in a common place instead of creating it in every page
Why do you need to dispatch an empty Action?
@user3690202 It's like DoEvents in Windows.Forms. After calling BeginEdit you need to wait for the cell to actually enter the edit mode.
@JiříSkála - I don't recall ever needing to do this in my solutions to this problem, but I understand what you are saying - thanks!
|
14

Yet another simple solution is to add this style to your DataGridColumn.The body of your style can be empty.

<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>

3 Comments

Pressing the space bar to check / uncheck will move the CheckBox from the left to the middle. Adding <Setter Property="HorizontalAlignment" Value="Center"/> in the style will prevent the CheckBox from moving.
Great solution, but why does it work ? An explanation would be appreciated. Does it work also for other DataGridXxxColumns ?
This should be the top. So simple yet the most effective out of all of them
12

There is a much simpler solution here.

          <DataGridTemplateColumn MinWidth="20" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Grid>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>

If you use DataGridCheckBoxColumn to implement, first click is to focus, second click is to check.

But using DataGridTemplateColumn to implement needs one click only.

The difference of using DataGridComboboxBoxColumn and implementation by DataGridTemplateColumn is also similar.

2 Comments

Good explanation for me and worked instantly, thanks!
With the solution, hitting the space bar to check/unckeck doesn't work anymore.
11

Base on Jim Adorno answer and comments on his post, this is solution with MultiTrigger:

<Style TargetType="DataGridCell">
  <Style.Triggers>
    <MultiTrigger>
      <MultiTrigger.Conditions>
    <Condition Property="IsReadOnly" Value="False" />
    <Condition Property="IsMouseOver" Value="True" />
      </MultiTrigger.Conditions>
      <Setter Property="IsEditing" Value="True" />
    </MultiTrigger>
  </Style.Triggers>
</Style>

Comments

10

I've tried these suggestions, and plenty of others I've found on other sites, but none of them quite worked for me. In the end, I created the following solution.

I've created my own DataGrid-inherited control, and simply added this code to it:

public class DataGridWithNavigation : Microsoft.Windows.Controls.DataGrid
{
    public DataGridWithNavigation()
    {
        EventManager.RegisterClassHandler(typeof(DataGridCell), 
            DataGridCell.PreviewMouseLeftButtonDownEvent,
            new RoutedEventHandler(this.OnPreviewMouseLeftButtonDown));
    }


    private void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
        {
          DependencyObject obj = FindFirstControlInChildren(cell, "CheckBox");
            if (obj != null)
            {
                System.Windows.Controls.CheckBox cb = (System.Windows.Controls.CheckBox)obj;
                cb.Focus();
                cb.IsChecked = !cb.IsChecked;
            }
        }
    }

    public DependencyObject FindFirstControlInChildren(DependencyObject obj, string controlType)
    {
        if (obj == null)
            return null;

        // Get a list of all occurrences of a particular type of control (eg "CheckBox") 
        IEnumerable<DependencyObject> ctrls = FindInVisualTreeDown(obj, controlType);
        if (ctrls.Count() == 0)
            return null;

        return ctrls.First();
    }

    public IEnumerable<DependencyObject> FindInVisualTreeDown(DependencyObject obj, string type)
    {
        if (obj != null)
        {
            if (obj.GetType().ToString().EndsWith(type))
            {
                yield return obj;
            }

            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                foreach (var child in FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type))
                {
                    if (child != null)
                    {
                        yield return child;
                    }
                }
            }
        }
        yield break;
    }
}

What does all this do ?

Well, each time we click on any cell in our DataGrid, we see if the cell contains a CheckBox control within it. If it does, then we'll set the focus to that CheckBox and toggle it's value.

This seems to work for me, and is a nice, easily reusable solution.

It is disappointing that we need to write code to do this though. The explanation that the first mouse click (on a DataGrid's CheckBox) is "ignored" as WPF uses it to put the row into Edit mode might sound logical, but in the real-world, this goes against the way every real application works.

If a user sees a checkbox on their screen, they should be able to click on it once to tick/untick it. End of story.

4 Comments

Thanks, I've tried a bunch of "solutions", but this is the first that seems to really work every time. And it fits beautifully into my application architecture.
This solution results in issues updating the binding, whereas the one here: wpf.codeplex.com/wikipage?title=Single-Click%20Editing does not.
too complicated. see my answer. :)
After 5 years this code still saving time for social life :) For some simple requirements, @KonstantinSalavatov solution is enough. In my case I mixed my code with Mike's solution to achieve dynamic event association with handlers, my grid have dynamic number of columns, with one click in specific cell must store in the database the changes.
8

I solved with this:

<DataGridTemplateColumn>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Viewbox Height="25">
                <CheckBox IsChecked="{Binding TheProperty, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
            </Viewbox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

The checkbox active on single click!

2 Comments

I did not need to wrap the Checkbox in a ViewBox, but this answer worked for me.
This to me is a much cleaner solution than the accepted answer. No need for Viewbox either. Funny how this works better than the defined Checkbox column.
2

This is a very old question, but it pops up on top of google search results, so I will leave my answer here.

I didn't want to modify the template to keep the default behaviour like sorting etc. I also wanted a simple and clean solution without resorting to events, code behind and other similar stuff, so my solution uses styles only.

CheckBox column declaration inside the DataGrid:

<DataGridCheckBoxColumn Header="Check"
                    Binding="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                    ElementStyle="{StaticResource DataGridCheckBoxElement}" 
                    EditingElementStyle="{StaticResource DataGridCheckBoxEditingElement}" />

Styles declaration:

<Style x:Key="DataGridCheckBoxElement" TargetType="CheckBox">
    <Setter Property="HorizontalAlignment" Value="Center"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
</Style>

<Style x:Key="DataGridCheckBoxEditingElement" TargetType="CheckBox">
    <Setter Property="HorizontalAlignment" Value="Center"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
</Style>


Why this works?

The key here is to use the ElementStyle on the DataGridCheckBoxColumn. I didn't dive into the default WPF styles, but my guess is that the default one contains the "double click" logic and simply assigning our own (even an empty one) makes it go away. Horizontal and vertical alignments are there to just make it look like the default one.

The second style (EditingElementStyle) is optional, but recommended if you are centering in the main ElementStyle. Otherwise the checkbox will be initially centered by the first style, but once you click two times inside the cell (outside of the checkbox) the second style is applied and the checkbox will move to the left (and back to the center when you click somewhere else).

Comments

1
<Style x:Key="StilCelula" TargetType="DataGridCell"> 
<Style.Triggers>
 <Trigger Property="IsMouseOver" Value="True">
   <Setter Property="IsEditing" 
     Value="{Binding RelativeSource={x:Static RelativeSource.Self}, 
     Converter={StaticResource CheckBoxColumnToEditingConvertor}}" />
 </Trigger>
</Style.Triggers>
<Style>
Imports System.Globalization
Public Class CheckBoxColumnToEditingConvertor
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert
        Try

            Return TypeOf TryCast(value, DataGridCell).Column Is DataGridCheckBoxColumn
        Catch ex As Exception
            Return Visibility.Collapsed
        End Try
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class

Comments

1

Here an approach with an own column class that is based on the default DataGridCheckBoxColumn class and can be used like the normal one. Just copy/paste.

public class DataGridCheckBoxColumn : System.Windows.Controls.DataGridCheckBoxColumn
{
    private static Style _noFocusEditElementStyle;

    static DataGridCheckBoxColumn()
    {
        ElementStyleProperty.OverrideMetadata(typeof(DataGridCheckBoxColumn), new FrameworkPropertyMetadata(NoFocusEditElementStyle));
        EditingElementStyleProperty.OverrideMetadata(typeof(DataGridCheckBoxColumn), new FrameworkPropertyMetadata(NoFocusEditElementStyle));
    }


    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);

        if (e.Property.Name == nameof(IsReadOnly))
        {
            ElementStyle = IsReadOnly ? DefaultElementStyle : NoFocusEditElementStyle;
            EditingElementStyle = IsReadOnly ? DefaultElementStyle : NoFocusEditElementStyle;
        }
    }

    public static Style NoFocusEditElementStyle
    {
        get
        {
            if (_noFocusEditElementStyle == null)
            {
                Style style = new Style(typeof(System.Windows.Controls.CheckBox));

                // When not in edit mode, the end-user should not be able to toggle the state
                style.Setters.Add(new Setter(UIElement.FocusableProperty, false));
                style.Setters.Add(new Setter(System.Windows.Controls.CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center));
                style.Setters.Add(new Setter(System.Windows.Controls.CheckBox.VerticalAlignmentProperty, VerticalAlignment.Top));

                style.Seal();
                _noFocusEditElementStyle = style;
            }

            return _noFocusEditElementStyle;
        }
    }
}

Usage with Read/Write Property:

<myNamespace:DataGridCheckBoxColumn Header="Name"
                    Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />

Usage with ReadOnly Property:

<myNamespace:DataGridCheckBoxColumn Header="Name"
                    IsReadOnly="True"
                    Binding="{Binding Name, Mode=OneWay}" />

Explanation:

  • The default column class has two style properties one for the edit mode and one for the view.
  • In case of ReadOnly we use the view style in both cases
  • In the other case in the edit mode we set the edit style
  • Instead of EditElementStyle I prefered a style where the checkbox does not get the focus (NoFocusEditElementStyle) since this behavior looks a little weird

Comments

0

I do it this way in code below:

    private void DataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        DataGridCell obj = (DataGridCell)Keyboard.FocusedElement;
        if (obj == null) { return; }
        if (obj.Content == null) { return; }
        if (obj.Content.GetType() == typeof(CheckBox))
        {
            CheckBox cb = (CheckBox)obj.Content;
            Point x = Mouse.GetPosition(cb);
            if (x.X < cb.ActualWidth && x.Y < cb.ActualHeight)
            {
                cb.IsChecked = (bool)cb.IsChecked ? false : true;
            }
        }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.