Як виконати вибір одного кліку в WPF DataGrid?


143

У мене є DataGrid з першим стовпцем у вигляді текстового та другим стовпцем як стовпцем CheckBox. Що я хочу, якщо я натискаю прапорець. Це слід перевірити.

Але, щоб вибрати, потрібно два клацання, для першого клацання клітина вибирається, для другого клацання прапорець перевіряється. Як зробити прапорець, щоб перевірити / зняти галочку одним натисканням кнопки.

Я використовую WPF 4.0. Стовпці в DataGrid є автогенерованими.


4
Дублікат: stackoverflow.com/questions/1225836/… , але цей кращий заголовок
серфін

Відповіді:


189

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

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>

4
WOW - Я радий, що читаю до кінця. Це прекрасно працює і значно складніше, ІМО це має бути позначено як відповідь.
Тод

2
Це також працює для ComboBox. Як і в: спосіб, ШЛИШЕ краще, ніж DataGridComboBoxColumn.
user1454265

2
Це не коли я використовую пробіл, щоб перевірити / зняти прапорець, а стрілки перейти до іншої комірки.
Йола

1
Я інтерпретував цю відповідь, що ви повинні зв’язати "IsSelected", але це неправда! Ви можете просто використовувати DataGridTemplateColumn.CellTemplateзі своїм власним прив'язкою, і це спрацює !! Відповідь @ weidian-huang допомогла мені зрозуміти це, дякую!
AstralisSomnium

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

Додавання цього стилю до моєї сітки піднять виняток "Операція" недійсна, поки використовується ItemSource. Отримуйте доступ та змінюйте елементи замість ItemsControl.ItemsSource замість цього.
Алькампфер

1
Це найчистіший спосіб, який я бачив досі! Приємно! (для IsReadOnly = "Правда", MultiTrigger зробить цю роботу)
FooBarTheLittle

2
Це рішення має деяку несподівану / небажану поведінку. Див stackoverflow.com/q/39004317/2881450
jHilscher

2
Для прив’язки до роботи вам знадобиться UpdateSourceTrigger =
PropertiesChanged

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 за включення зразка коду, де його не було. Дякую за гарну відповідь на старе запитання.
Дон Ірод

1
Проблема з цим полягає в тому, що якщо ви робите це з колонками комбінації, маленька кнопка, що випадає, буде видно весь час у цій колонці. Не тільки при натисканні на клітинку.
користувач3690202

18

Для того, щоб відповідь Костянтина Салават в роботі з AutoGenerateColumns, додати обробник подій до DataGridAutoGeneratingColumnза допомогою наступного коду:

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, чи потрібно вводити вищевказаний стиль та код на кожну сторінку та її код позаду, якщо у мене багато сторінок, на яких є сітка даних. Чи можливо використовувати стиль та код у загальному місці замість того, щоб створювати його у кожна сторінка
Ангел

Чому вам потрібно відправити порожню Дію?
користувач3690202

@ user3690202 Це як DoEvents у Windows.Forms. Після виклику BeginEdit вам потрібно дочекатися, коли клітинка дійсно перейде в режим редагування.
Іржі Скала

@ JiříSkála - Я не пригадую, щоб мені колись потрібно було робити це у своїх рішеннях цієї проблеми, але я розумію, що ви говорите - спасибі!
користувач3690202

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
Дякую, я спробував купу "рішень", але це перше, що, здається, справді спрацьовує кожен раз. І це прекрасно вписується в мою архітектуру додатків.
Гуге

Це рішення призводить до проблем оновлення прив'язки, тоді як тут: wpf.codeplex.com/wikipage?title=Single-Click%20Редагування не має.
Джастін Саймон

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також схожа.


Гарне пояснення для мене і працювали миттєво, дякую!
AstralisSomnium

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. Смішно, як це працює краще, ніж визначена колонка.
кенджара

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>

5

Ще одне просте рішення - додати цей стиль до вашої DataGridColumn. Тіло вашого стилю може бути порожнім.

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

2
Якщо натиснути пробіл, щоб перевірити / зняти прапорець, перемістіть CheckBox зліва на середину. Додавання <Setter Properties = "HorizontalAlignment" Value = "Center" /> у стилі запобіжить переміщенню CheckBox.
Янтінгхен

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.