Как выполнить выбор флажка одним щелчком в WPF DataGrid?

144

У меня есть DataGrid с первым столбцом в качестве текстового столбца и вторым столбцом в качестве столбца CheckBox. Я хочу, чтобы я установил флажок. Это нужно проверить.

Но для выбора требуется два щелчка, при первом щелчке выбирается ячейка, при втором щелчке устанавливается флажок. Как сделать так, чтобы флажок ставился / снимался одним щелчком мыши.

Я использую WPF 4.0. Столбцы в DataGrid создаются автоматически.

Принц Ашитака
источник
4
Дубликат: stackoverflow.com/questions/1225836/… , но у этого заголовок получше
surfen

Ответы:

193

Для флажка DataGrid одним щелчком вы можете просто поместить внутрь обычный флажок DataGridTemplateColumnи установить его UpdateSourceTrigger=PropertyChanged.

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Константин Салаватов
источник
4
ВАУ - я рад, что дочитал до конца. Это работает отлично и значительно менее сложно, ИМО это должно быть отмечено как ответ.
Tod
2
Это также работает для ComboBox. Как в: way, НАМНОГО лучше, чем DataGridComboBoxColumn.
user1454265 05
2
Это не так, когда я использую пробел для проверки / снятия отметки и стрелки для перехода в другую ячейку.
Yola
1
Я интерпретировал этот ответ, что вы должны привязать «IsSelected», но это неправда! Вы можете просто использовать DataGridTemplateColumn.CellTemplateс вашей собственной Binding и он будет работать !! Ответ @ weidian-huang помог мне понять это, спасибо!
Патрик
62

Я решил это с помощью следующего стиля:

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

Конечно, это можно адаптировать для конкретных столбцов ...

Джим Адорно
источник
8
Ницца. Я изменил его на MultiTrigger и добавил условие ReadOnly = False, но базовый подход работал в моем простом случае, когда навигация с клавиатуры не важна.
MarcE
Добавление этого стиля в мою сетку вызывает исключение Operation is not valid, пока ItemsSource используется. Вместо этого открывайте и изменяйте элементы с помощью ItemsControl.ItemsSource.
Alkampfer
1
Это самый чистый способ, который я когда-либо видел! Ницца! (для IsReadOnly = "True" MultiTrigger выполнит эту работу)
FooBarTheLittle 02
2
Это решение имеет неожиданное / нежелательное поведение. См stackoverflow.com/q/39004317/2881450
jHilscher
2
Чтобы привязка работала, вам потребуется UpdateSourceTrigger = PropertyChanged
AQuirky
27

Во-первых, я знаю, что это довольно старый вопрос, но я все же подумал, что попробую ответить на него.

У меня была такая же проблема пару дней назад, и я нашел удивительно короткое решение для нее (см. Этот блог ). По сути, все, что вам нужно сделать, это заменить DataGridCheckBoxColumnопределение в вашем XAML следующим:

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

Положительные стороны этого решения очевидны - оно работает только с XAML; таким образом, он эффективно удерживает вас от обременения вашего кода дополнительной логикой пользовательского интерфейса и помогает вам поддерживать свой статус в глазах фанатиков MVVM;).

Прииду Нимре
источник
1
Это похоже на ответ Константина Салаватова, и этот сработал для меня. +1 за включение образца кода там, где его нет. Спасибо за хороший ответ на старый вопрос.
Don Herod
1
Проблема заключается в том, что если вы сделаете это со столбцами со списком, маленькая кнопка раскрывающегося списка будет видна для всех ячеек в этом столбце все время. Не только когда вы нажимаете на ячейку.
user3690202
19

Чтобы ответ Константина Салаватова работал AutoGenerateColumns, добавьте обработчик события в DataGrid's AutoGeneratingColumnсо следующим кодом:

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

Это сделает все DataGridавтоматически сгенерированные столбцы флажков доступными для редактирования "одним щелчком".

Аллон Гуралнек
источник
Спасибо за заполнение автогенерированного столбца, это удобно указывает мне подходящее направление.
el2iot2
17

На основе блога, упомянутого в ответе Гоблина, но измененного для работы в .NET 4.0 и с режимом выбора строк.

Обратите внимание, что это также ускоряет редактирование DataGridComboBoxColumn - за счет перехода в режим редактирования и отображения раскрывающегося списка при однократном щелчке или вводе текста.

XAML:

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

Код программной части:

    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;
    }
серфить
источник
Это решение сработало для меня лучше всего. Моя привязанная ViewModel не обновлялась другими решениями.
BrokeMyLegBiking
@surfen, Мне нужно разместить вышеуказанный стиль и код на каждой странице и ее коде, если у меня много страниц, в которых есть datagrid. Можно ли использовать стиль и код в общем месте вместо того, чтобы создавать его в каждая страница
Angel
Почему вам нужно отправлять пустое действие?
user3690202
@ user3690202 Это как DoEvents в Windows.Forms. После вызова BeginEdit вам нужно дождаться, пока ячейка действительно войдет в режим редактирования.
Йиржи Скала
@ JiříSkála - Я не припоминаю, чтобы мне когда-либо приходилось делать это в моих решениях этой проблемы, но я понимаю, что вы говорите - спасибо!
user3690202
10

Я пробовал эти предложения и множество других, которые нашел на других сайтах, но ни один из них не помог мне. В итоге я создал следующее решение.

Я создал свой собственный элемент управления, унаследованный от DataGrid, и просто добавил к нему этот код:

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

Что все это делает?

Итак, каждый раз, когда мы щелкаем любую ячейку в нашей DataGrid, мы видим, содержит ли эта ячейка элемент управления CheckBox. Если это так , мы установим фокус на этот CheckBox и переключим его значение. .

Мне кажется, это работает, и это хорошее решение, которое легко использовать повторно.

Жаль, что нам нужно написать код. Объяснение того, что первый щелчок мышью (на CheckBox DataGrid) «игнорируется», поскольку WPF использует его для перевода строки в режим редактирования, может показаться логичным, но в реальном мире это идет вразрез с тем, как работает каждое реальное приложение.

Если пользователь видит на своем экране флажок, он должен иметь возможность щелкнуть по нему один раз, чтобы установить / снять флажок. Конец истории.

Майк Гледхилл
источник
1
Спасибо, я пробовал кучу "решений", но это первое, что действительно работает каждый раз. И это прекрасно вписывается в архитектуру моего приложения.
Guge
Это решение приводит к проблемам с обновлением привязки, тогда как здесь: wpf.codeplex.com/wikipage?title=Single-Click%20Editing - нет.
Джастин Саймон
2
слишком сложно. смотри мой ответ. :)
Константин Салаватов
1
Спустя 5 лет этот код все еще экономит время для социальной жизни :) Для некоторых простых требований достаточно решения @KonstantinSalavatov. В моем случае я смешал свой код с решением Майка, чтобы добиться динамической связи событий с обработчиками, моя сетка имеет динамическое количество столбцов, при одном щелчке мыши в определенной ячейке изменения должны сохраняться в базе данных.
Fer R
8

Здесь есть гораздо более простое решение.

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

Если вы используете DataGridCheckBoxColumnдля реализации, первый щелчок - это фокусировка, второй - проверка.

Но DataGridTemplateColumnдля использования достаточно одного щелчка мышки.

Аналогичная разница в использовании DataGridComboboxBoxColumnи реализации DataGridTemplateColumn.

Вейдиан Хуанг
источник
Хорошее объяснение для меня и сработало мгновенно, спасибо!
Патрик
8

Я решил с этим:

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

Флажок активируется одним щелчком мыши!

Дарлан Д.
источник
2
Мне не нужно было заключать флажок в ViewBox, но этот ответ сработал для меня.
JGeerWM
3
Для меня это гораздо более чистое решение, чем принятый ответ. Viewbox тоже не нужен. Забавно, как это работает лучше, чем определенный столбец флажка.
kenjara 08
6

Основываясь на ответе Джима Адорно и комментариях к его сообщению, это решение с 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>
Рафаль Спачер
источник
6

Еще одно простое решение - добавить этот стиль в ваш DataGridColumn. Тело вашего стиля может быть пустым.

<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
Амир Хосейн Резаи
источник
2
Нажатие клавиши пробела для отметки / снятия отметки переместит флажок слева в середину. Добавление в стиль <Setter Property = "HorizontalAlignment" Value = "Center" /> стиля предотвратит перемещение флажка.
YantingChen
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
TotPeRo
источник