Редагування одним клацанням у WPF DataGrid


92

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

Як це замінити чи реалізувати?


Чи використовуєте Ви DataGrid, знайдений у WPF Toolkit?
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.
Matt Winckler,

Припустимо, у моєму DataGridCell є TextBox. Після дзвінка grd.BeginEdit(e)я хочу, щоб TextBox у цій комірці мав фокус. Як я можу це зробити? Я спробував зателефонувати FindName("txtBox")як DataGridCell, так і DataGrid, але він повертає для мене значення null.
user1214135

GotFocus = "DataGrid_GotFocus", здається, відсутній?
синергетика

4
Це працює добре, але я б не рекомендував це робити. Я використав це у своєму проекті і вирішив повернутися до стандартної поведінки DG. У майбутньому, коли ваш DG буде рости та ускладнюватися, ви будете отримувати проблеми з валідацією, додаванням нових рядків та іншою дивною поведінкою.
white.zaz

1
@ white.zaz був задоволений клієнтом після того, як ви зробили відкат до стандартної поведінки DG? Оскільки основною причиною цього запитання було те, що редагування у стандартних можливостях DG не є зручним для користувача, оскільки для цього потрібно натиснути занадто багато разів, перш ніж DG перейде в режим редагування.
AEMLoviji

42

Відповідь Мікаеля Бержерона став для мене хорошим початком пошуку рішення, яке працює на мене. Щоб дозволити редагування одним кліком також для клітинок у тому самому рядку, що вже перебуває в режимі редагування, мені довелося трохи його відрегулювати. Використання комірки SelectionUnit для мене не було можливості.

Замість того, щоб використовувати подію 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;
}

3
прапорці, здається, не працюють для мене? я все одно маю двічі клацнути на них
Thomas Klammer

9

З: http://wpf.codeplex.com/wikipage?title=Single-Click%20Редагування

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
у мене теж не працює. перешкоджає редагуванню текстового поля для мене
Blechdose

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


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>

Кінець оновлення

Рант

Я спробував усе, що знайшов тут і в 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;
        }
    }
}

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

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