Редактировать одним щелчком мыши в WPF DataGrid

92

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

Как мне переопределить или реализовать это?

Остин
источник
Вы используете DataGrid из набора инструментов WPF?
myermian
4
Не могли бы вы дать нам немного больше информации о том, что вы пробовали и как это не работает?
Зак Джонсон,

Ответы:

76

Вот как я решил эту проблему:

<DataGrid DataGridCell.Selected="DataGridCell_Selected" 
          ItemsSource="{Binding Source={StaticResource itemView}}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Nom" Binding="{Binding Path=Name}"/>
        <DataGridTextColumn Header="Age" Binding="{Binding Path=Age}"/>
    </DataGrid.Columns>
</DataGrid>

Этот DataGrid привязан к CollectionViewSource (содержащему фиктивные объекты Person ).

Здесь происходит волшебство: DataGridCell.Selected = "DataGridCell_Selected" .

Я просто подключаю выбранное событие ячейки DataGrid и вызываю BeginEdit () в DataGrid.

Вот код обработчика событий:

private void DataGridCell_Selected(object sender, RoutedEventArgs e)
{
    // Lookup for the source to be DataGridCell
    if (e.OriginalSource.GetType() == typeof(DataGridCell))
    {
        // Starts the Edit on the row;
        DataGrid grd = (DataGrid)sender;
        grd.BeginEdit(e);
    }
}
Микаэль Бержерон
источник
8
Вы можете обойти проблему с уже выбранной строкой, установив для SelectionUnitсвойства DataGrid значение Cell.
Мэтт Винклер,
Предположим, у меня есть TextBox в моей DataGridCell. После вызова grd.BeginEdit(e)я хочу, чтобы текстовое поле в этой ячейке было в фокусе. Как я могу это сделать? Я попытался вызвать FindName("txtBox")как DataGridCell, так и DataGrid, но он вернул мне значение null.
user1214135
GotFocus = "DataGrid_GotFocus" отсутствует?
synergetic
4
Это хорошо работает, но я бы не рекомендовал это делать. Я использовал это в своем проекте и решил вернуться к стандартному поведению DG. В будущем, когда ваша DG будет расти и усложняться, у вас будут проблемы с проверкой, добавлением новых строк и другим странным поведением.
white.zaz
1
@ white.zaz был доволен клиентом после того, как вы сделали откат к стандартному поведению DG? Поскольку основная причина, по которой задан этот вопрос, заключалась в том, что редактирование в стандартных возможностях DG неудобно для пользователя, так как на него нужно нажимать слишком много раз, прежде чем DG перейдет в режим редактирования.
AEMLoviji 02
42

Ответ от Микаэля Бержерона стал для меня хорошим началом поиска решения, которое работает для меня. Чтобы разрешить редактирование одним щелчком также для ячеек в той же строке, которая уже находится в режиме редактирования, мне пришлось немного отрегулировать ее. Использование SelectionUnit Cell не было для меня вариантом.

Вместо использования события DataGridCell.Selected, которое запускается только при первом щелчке по ячейке строки, я использовал событие DataGridCell.GotFocus.

<DataGrid DataGridCell.GotFocus="DataGrid_CellGotFocus" />

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

private void DataGrid_CellGotFocus(object sender, RoutedEventArgs e)
{
    // Lookup for the source to be DataGridCell
    if (e.OriginalSource.GetType() == typeof(DataGridCell))
    {
        // Starts the Edit on the row;
        DataGrid grd = (DataGrid)sender;
        grd.BeginEdit(e);

        Control control = GetFirstChildByType<Control>(e.OriginalSource as DataGridCell);
        if (control != null)
        {
            control.Focus();
        }
    }
}

private T GetFirstChildByType<T>(DependencyObject prop) where T : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(prop); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild((prop), i) as DependencyObject;
        if (child == null)
            continue;

        T castedProp = child as T;
        if (castedProp != null)
            return castedProp;

        castedProp = GetFirstChildByType<T>(child);

        if (castedProp != null)
            return castedProp;
    }
    return null;
}
user2134678
источник
3
флажки у меня не работают? Мне все еще нужно их дважды щелкнуть
Томас Кламмер
9

От: http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing

XAML:

<!-- SINGLE CLICK EDITING -->
<Style TargetType="{x:Type dg:DataGridCell}">
    <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
</Style>

КОД ЗА:

//
// SINGLE CLICK EDITING
//
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    DataGridCell cell = sender as DataGridCell;
    if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
    {
        if (!cell.IsFocused)
        {
            cell.Focus();
        }
        DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
        if (dataGrid != null)
        {
            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;
                }
            }
        }
    }
}

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;
}
миерманский
источник
1
в некоторых случаях это не работает и является более сложным, чем решение Микаэля Бержерона.
SwissCoder
Для меня это было почти решением. Мне нужно было добавить обработчик события PreviewMouseLeftButtonUp и поместить туда точно такой же код.
Нестор Санчес А.
это не работает, если у вас есть поле со списком. щелчок предварительного просмотра видит щелчки во всплывающем окне со списком, затем вызов cell.focus все портит. Самое простое исправление - добавить раздел, который смотрит на исходный источник событий мыши, используя FindVisualParent, чтобы увидеть, находится ли он внутри таблицы данных. если нет, не делайте другой работы.
Джон Гарднер,
7

Решение из http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing отлично сработало для меня, но я включил его для каждой DataGrid, используя стиль, определенный в ResourceDictionary. Чтобы использовать обработчики в словарях ресурсов, вам необходимо добавить в него файл кода программной части. Вот как это сделать:

Это словарь ресурсов DataGridStyles.xaml :

    <ResourceDictionary x:Class="YourNamespace.DataGridStyles"
                x:ClassModifier="public"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Style TargetType="DataGrid">
            <!-- Your DataGrid style definition goes here -->

            <!-- Cell style -->
            <Setter Property="CellStyle">
                <Setter.Value>
                    <Style TargetType="DataGridCell">                    
                        <!-- Your DataGrid Cell style definition goes here -->
                        <!-- Single Click Editing -->
                        <EventSetter Event="PreviewMouseLeftButtonDown"
                                 Handler="DataGridCell_PreviewMouseLeftButtonDown" />
                    </Style>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

Обратите внимание на атрибут x: Class в корневом элементе. Создайте файл класса. В этом примере это DataGridStyles.xaml.cs . Поместите этот код внутрь:

using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;

namespace YourNamespace
{
    partial class DataGridStyles : ResourceDictionary
    {

        public DataGridStyles()
        {
          InitializeComponent();
        }

     // The code from the myermian's answer goes here.
}
Андикки
источник
ссылка мертва (не более 15 символов)
Blechdose
4

Я предпочитаю этот способ, основанный на предложении Душана Кнежевича. нажимаешь вот и все))

<DataGrid.Resources>

    <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
        <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsMouseOver"
                                   Value="True" />
                        <Condition Property="IsReadOnly"
                                   Value="False" />
                    </MultiTrigger.Conditions>
                    <MultiTrigger.Setters>
                        <Setter Property="IsEditing"
                                Value="True" />
                    </MultiTrigger.Setters>
                </MultiTrigger>
        </Style.Triggers>
    </Style>

</DataGrid.Resources>
Рене Ханкель
источник
Это не работает, если поле со списком используется в качестве шаблона редактирования, я бы предположил, что другие, такие как флажок, который также будет нарушать захват событий мыши, также сломаются
Стив
Для меня это работает со столбцами со списком, но текстовое поле «новой строки элемента» (последняя строка) имеет странное поведение: при первом щелчке я получаю фокус ввода и могу вводить текст. Когда я перемещаю мышь из ячейки , значение текстового поля исчезает. При дальнейшем вводе вновь введенный текст сохраняется правильно (при желании создается новая запись). Это происходит даже с ComboboxColumn.
FrankM
Сначала кажется, что работает нормально, но мой Datagrid полностью испортился, когда я пытаюсь выполнить сортировку, все эти значения исчезают, без этого кода все работает нормально с сортировкой.
Чандрапракаш,
3

Я решил эту проблему, добавив триггер, который устанавливает для свойства IsEditing DataGridCell значение True при наведении указателя мыши на него. Это решило большинство моих проблем. Он также работает с комбинированными списками.

<Style TargetType="DataGridCell">
     <Style.Triggers>
         <Trigger Property="IsMouseOver" Value="True">
             <Setter Property="IsEditing" Value="True" />
         </Trigger>
     </Style.Triggers>
 </Style>
Душан Кнежевич
источник
1
Не работает ... он теряет редактирование, как только мышь покидает ячейку. Итак, вы 1) щелкните левой кнопкой мыши по ячейке, которую хотите отредактировать. 2) уберите мышь с пути 3) Начните печатать. Ваш ввод не работает, потому что ячейка больше не находится в режиме редактирования.
Скарсник
1
у меня тоже не работает. предотвращает редактирование текстового
поля
Но с этим подходом есть одна проблема: я заблокировал 1-й столбец для редактирования, при таком подходе это делает 1-й столбец также редактируемым!
Чандрапракаш,
3

Я ищу редактирование ячейки одним щелчком мыши в MVVM, и это другой способ сделать это.

  1. Добавление поведения в xaml

    <UserControl xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                 xmlns:myBehavior="clr-namespace:My.Namespace.To.Behavior">
    
        <DataGrid>
            <i:Interaction.Behaviors>
                <myBehavior:EditCellOnSingleClickBehavior/>
            </i:Interaction.Behaviors>
        </DataGrid>
    </UserControl>
    
  2. Класс EditCellOnSingleClickBehavior расширяет System.Windows.Interactivity.Behavior;

    public class EditCellOnSingleClick : Behavior<DataGrid>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.LoadingRow += this.OnLoadingRow;
            this.AssociatedObject.UnloadingRow += this.OnUnloading;
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.LoadingRow -= this.OnLoadingRow;
            this.AssociatedObject.UnloadingRow -= this.OnUnloading;
        }
    
        private void OnLoadingRow(object sender, DataGridRowEventArgs e)
        {
            e.Row.GotFocus += this.OnGotFocus;
        }
    
        private void OnUnloading(object sender, DataGridRowEventArgs e)
        {
            e.Row.GotFocus -= this.OnGotFocus;
        }
    
        private void OnGotFocus(object sender, RoutedEventArgs e)
        {
            this.AssociatedObject.BeginEdit(e);
        }
    }
    

Вуаля!

Жуан Антуан
источник
1

Есть две проблемы с ответом user2134678. Один очень незначительный и не имеет функционального воздействия. Другой довольно значительный.

Первая проблема заключается в том, что GotFocus на самом деле вызывается для DataGrid, а не для DataGridCell на практике. Квалификатор DataGridCell в XAML является избыточным.

Основная проблема, которую я обнаружил с ответом, заключается в том, что поведение клавиши Enter нарушено. Enter должен переместить вас в следующую ячейку под текущей ячейкой в ​​обычном поведении DataGrid. Однако на самом деле за кулисами происходит то, что событие GotFocus будет вызываться дважды. Однажды текущая ячейка теряет фокус, а однажды новая ячейка получает фокус. Но пока BeginEdit вызывается для этой первой ячейки, следующая ячейка никогда не будет активирована. В результате у вас есть возможность редактировать одним щелчком мыши, но любой, кто буквально не щелкает по сетке, будет неудобен, и разработчик пользовательского интерфейса не должен предполагать, что все пользователи используют мыши. (Пользователи клавиатуры могут как бы обойти это, используя Tab, но это все равно означает, что они прыгают через обручи, которые им не должны быть.)

Итак, решение этой проблемы? Обработайте событие KeyDown для ячейки и, если Key является клавишей Enter, установите флаг, который не позволяет BeginEdit запускать первую ячейку. Теперь клавиша Enter ведет себя как надо.

Для начала добавьте в DataGrid следующий стиль:

<DataGrid.Resources>
    <Style TargetType="{x:Type DataGridCell}" x:Key="SingleClickEditingCellStyle">
        <EventSetter Event="KeyDown" Handler="DataGridCell_KeyDown" />
    </Style>
</DataGrid.Resources>

Примените этот стиль к свойству CellStyle, столбцы, для которых вы хотите включить один щелчок.

Затем в коде, стоящем за вами, в вашем обработчике GotFocus есть следующее (обратите внимание, что я использую здесь VB, потому что это то, что наш клиент, "запрашивающий сетку данных одним щелчком", хотел в качестве языка разработки):

Private _endEditing As Boolean = False

Private Sub DataGrid_GotFocus(ByVal sender As Object, ByVal e As RoutedEventArgs)
    If Me._endEditing Then
        Me._endEditing = False
        Return
    End If

    Dim cell = TryCast(e.OriginalSource, DataGridCell)

    If cell Is Nothing Then
        Return
    End If

    If cell.IsReadOnly Then
        Return
    End If

    DirectCast(sender, DataGrid).BeginEdit(e)
    .
    .
    .

Затем вы добавляете свой обработчик для события KeyDown:

Private Sub DataGridCell_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
    If e.Key = Key.Enter Then
        Me._endEditing = True
    End If
End Sub

Теперь у вас есть DataGrid, который не изменил фундаментального поведения готовой реализации, но поддерживает редактирование одним щелчком мыши.

GrantA
источник
0

Я знаю, что немного опаздываю на вечеринку, но у меня была та же проблема, и я нашел другое решение:

     public class DataGridTextBoxColumn : DataGridBoundColumn
 {
  public DataGridTextBoxColumn():base()
  {
  }

  protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
  {
   throw new NotImplementedException("Should not be used.");
  }

  protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
  {
   var control = new TextBox();
   control.Style = (Style)Application.Current.TryFindResource("textBoxStyle");
   control.FontSize = 14;
   control.VerticalContentAlignment = VerticalAlignment.Center;
   BindingOperations.SetBinding(control, TextBox.TextProperty, Binding);
    control.IsReadOnly = IsReadOnly;
   return control;
  }
 }

        <DataGrid Grid.Row="1" x:Name="exportData" Margin="15" VerticalAlignment="Stretch" ItemsSource="{Binding CSVExportData}" Style="{StaticResource dataGridStyle}">
        <DataGrid.Columns >
            <local:DataGridTextBoxColumn Header="Sample ID" Binding="{Binding SampleID}" IsReadOnly="True"></local:DataGridTextBoxColumn>
            <local:DataGridTextBoxColumn Header="Analysis Date" Binding="{Binding Date}" IsReadOnly="True"></local:DataGridTextBoxColumn>
            <local:DataGridTextBoxColumn Header="Test" Binding="{Binding Test}" IsReadOnly="True"></local:DataGridTextBoxColumn>
            <local:DataGridTextBoxColumn Header="Comment" Binding="{Binding Comment}"></local:DataGridTextBoxColumn>
        </DataGrid.Columns>
    </DataGrid>

Как видите, я написал свой собственный столбец DataGridTextColumn, унаследовавший все от DataGridBoundColumn. Переопределяя метод GenerateElement и возвращая элемент управления Textbox прямо здесь, метод для создания элемента редактирования никогда не вызывается. В другом проекте я использовал это для реализации столбца Datepicker, поэтому он должен работать и для флажков и полей со списком.

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

Троиллий
источник
-1

Обновить

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

<DataGridTemplateColumn Header="My Column header">
   <DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
         <TextBox Text="{Binding MyProperty } />
      </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Конец обновления

Rant

Я перепробовал все, что нашел здесь и в Google, и даже попытался создать свои собственные версии. Но каждый ответ / решение работал в основном для столбцов текстовых полей, но не работал со всеми другими элементами (флажками, полями со списком, столбцами кнопок) или даже нарушал эти столбцы других элементов или имел некоторые другие побочные эффекты. Спасибо, microsoft, за то, что datagrid заставили вести себя таким уродливым образом и заставили нас создавать такие хаки. Из-за этого я решил создать версию, которая может быть применена со стилем к столбцу текстового поля напрямую, не затрагивая другие столбцы.

Характеристики

  • Никакого кода позади. MVVM дружелюбен.
  • Он работает при нажатии на разные ячейки текстового поля в одной или разных строках.
  • Клавиши TAB и ENTER работают.
  • Это не влияет на другие столбцы.

Источники

Я использовал это решение и ответ @ my и изменил их, чтобы они были прикрепленным поведением. http://wpf-tutorial-net.blogspot.com/2016/05/wpf-datagrid-edit-cell-on-single-click.html

Как это использовать

Добавьте этот стиль. Это BasedOnважно, когда вы используете какие-то причудливые стили для своей сети данных и не хотите их потерять.

<Window.Resources>
    <Style x:Key="SingleClickEditStyle" TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource {x:Type DataGridCell}}">
        <Setter Property="local:DataGridTextBoxSingleClickEditBehavior.Enable" Value="True" />
    </Style>
</Window.Resources>

Примените стиль с CellStyleк каждому из них DataGridTextColumnsследующим образом:

<DataGrid ItemsSource="{Binding MyData}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="My Header" Binding="{Binding Comment}" CellStyle="{StaticResource SingleClickEditStyle}" />         
    </DataGrid.Columns>
</DataGrid>

А теперь добавьте этот класс в то же пространство имен, что и ваша MainViewModel (или в другое пространство имен. Но тогда вам нужно будет использовать другой префикс пространства имен, чем local). Добро пожаловать в мир уродливого шаблонного кода привязанного поведения.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace YourMainViewModelNameSpace
{
    public static class DataGridTextBoxSingleClickEditBehavior
    {
        public static readonly DependencyProperty EnableProperty = DependencyProperty.RegisterAttached(
            "Enable",
            typeof(bool),
            typeof(DataGridTextBoxSingleClickEditBehavior),
            new FrameworkPropertyMetadata(false, OnEnableChanged));


        public static bool GetEnable(FrameworkElement frameworkElement)
        {
            return (bool) frameworkElement.GetValue(EnableProperty);
        }


        public static void SetEnable(FrameworkElement frameworkElement, bool value)
        {
            frameworkElement.SetValue(EnableProperty, value);
        }


        private static void OnEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is DataGridCell dataGridCell)
                dataGridCell.PreviewMouseLeftButtonDown += DataGridCell_PreviewMouseLeftButtonDown;
        }


        private static void DataGridCell_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            EditCell(sender as DataGridCell, e);
        }

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

            if (dataGridCell.IsFocused == false)
                dataGridCell.Focus();

            var dataGrid = FindVisualParent<DataGrid>(dataGridCell);
            dataGrid?.BeginEdit(e);
        }


        private static T FindVisualParent<T>(UIElement element) where T : UIElement
        {
            var parent = VisualTreeHelper.GetParent(element) as UIElement;

            while (parent != null)
            {
                if (parent is T parentWithCorrectType)
                    return parentWithCorrectType;

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

            return null;
        }
    }
}
Blechdose
источник
-3
 <DataGridComboBoxColumn.CellStyle>
                        <Style TargetType="DataGridCell">
                            <Setter Property="cal:Message.Attach" 
                            Value="[Event MouseLeftButtonUp] = [Action ReachThisMethod($source)]"/>
                        </Style>
                    </DataGridComboBoxColumn.CellStyle>
 public void ReachThisMethod(object sender)
 {
     ((System.Windows.Controls.DataGridCell)(sender)).IsEditing = true;

 }
Ruwanthaka
источник