Modification en un seul clic dans WPF DataGrid


92

Je veux que l'utilisateur puisse mettre la cellule en mode d'édition et mettre en évidence la ligne dans laquelle la cellule est contenue en un seul clic. Par défaut, il s'agit d'un double clic.

Comment puis-je remplacer ou mettre en œuvre cela?


Utilisez-vous le DataGrid trouvé dans la boîte à outils WPF?
myermian

4
Serait-il possible pour vous de nous donner un peu plus d'informations sur ce que vous avez essayé et comment cela ne fonctionne pas?
Zach Johnson

Réponses:


76

Voici comment j'ai résolu ce problème:

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

Ce DataGrid est lié à un CollectionViewSource (contenant des objets Personne factice ).

La magie opère ici: DataGridCell.Selected = "DataGridCell_Selected" .

J'accroche simplement l'événement sélectionné de la cellule DataGrid et j'appelle BeginEdit () sur le DataGrid.

Voici le code derrière le gestionnaire d'événements:

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
Vous pouvez contourner le problème de ligne déjà sélectionné en définissant la SelectionUnitpropriété sur le DataGrid sur Cell.
Matt Winckler

Supposons que j'ai une zone de texte dans mon DataGridCell. Après avoir appelé grd.BeginEdit(e), je veux que la zone de texte de cette cellule ait le focus. Comment puis je faire ça? J'ai essayé d'appeler FindName("txtBox")à la fois le DataGridCell et le DataGrid, mais il renvoie null pour moi.
user1214135

GotFocus = "DataGrid_GotFocus" semble manquer?
synergetic

4
Cela fonctionne bien, mais je ne recommanderais pas de le faire. J'ai utilisé cela dans mon projet et j'ai décidé de revenir au comportement DG standard. À l'avenir, lorsque votre DG grandira et deviendra complexe, vous rencontrerez des problèmes de validation, l'ajout de nouvelles lignes et d'autres comportements étranges.
white.zaz

1
@ white.zaz était-il satisfait du client après avoir rétrogradé au comportement DG standard? Parce que la principale raison posée à cette question était que l'édition dans les capacités standard de DG n'est pas conviviale car il faut trop de clics pour que DG passe en mode d'édition.
AEMLoviji

42

La réponse de Micael Bergeron a été un bon début pour moi pour trouver une solution qui fonctionne pour moi. Pour permettre l'édition en un seul clic également pour les cellules de la même ligne qui était déjà en mode d'édition, j'ai dû l'ajuster un peu. Utiliser SelectionUnit Cell n'était pas une option pour moi.

Au lieu d'utiliser l'événement DataGridCell.Selected qui n'est déclenché que pour la première fois que l'utilisateur clique sur la cellule d'une ligne, j'ai utilisé l'événement DataGridCell.GotFocus.

<DataGrid DataGridCell.GotFocus="DataGrid_CellGotFocus" />

Si vous le faites, vous aurez toujours la bonne cellule focalisée et en mode édition, mais aucun contrôle dans la cellule ne sera focalisé, ce que j'ai résolu comme ceci

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
les cases à cocher ne semblent pas fonctionner pour moi? je dois encore les doublecliquer
Thomas Klammer

9

De: 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>

CODE-DERRIÈRE:

//
// 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
cela ne fonctionne pas dans certains cas, et c'est plus compliqué que la solution Micael Bergerons.
SwissCoder

Pour moi, c'était presque la solution. J'avais besoin d'ajouter un gestionnaire d'événements "PreviewMouseLeftButtonUp" et y mettre exactement le même code.
Néstor Sánchez A.

cela ne fonctionne pas non plus une fois que vous avez une combobox. le clic de prévisualisation voit des clics sur la fenêtre contextuelle de la zone de liste déroulante, puis l'appel cell.focus vire tout. La solution la plus simple consiste à ajouter une section qui examine la source d'origine des événements de souris, en utilisant FindVisualParent pour voir si elle se trouve à l'intérieur de la grille de données. sinon, ne faites aucun des autres travaux.
John Gardner

7

La solution de http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing a très bien fonctionné pour moi, mais je l'ai activée pour chaque DataGrid en utilisant un style défini dans un ResourceDictionary. Pour utiliser des gestionnaires dans des dictionnaires de ressources, vous devez y ajouter un fichier code-behind. Voici comment procéder:

Ceci est un dictionnaire de ressources 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>

Notez l'attribut x: Class dans l'élément racine. Créez un fichier de classe. Dans cet exemple, ce serait DataGridStyles.xaml.cs . Mettez ce code à l'intérieur:

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

link is dead (limite de 15 caractères)
Blechdose le

4

je préfère cette méthode basée sur la suggestion de Dušan Knežević. vous cliquez et c'est tout))

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

Cela ne fonctionne pas si une zone de liste déroulante est utilisée comme modèle d'édition, je suppose que d'autres, comme la case à cocher qui capture les événements de la souris, seraient également cassées
Steve

Pour moi, cela fonctionne avec les colonnes de la zone de liste déroulante, mais la zone de texte de la "nouvelle ligne d'élément" (dernière ligne) a un comportement étrange: au premier clic, j'obtiens le focus d'entrée et je peux saisir des éléments. Quand je déplace la souris hors de la cellule , la valeur de la zone de texte disparaît. Lorsque vous tapez plus loin, le texte nouvellement entré est correctement enregistré (il crée une nouvelle entrée comme souhaité). Cela se produit également avec un ComboboxColumn.
FrankM

Au départ, cela semble fonctionner correctement, mais totalement gâché mon Datagrid, lorsque j'essaie de trier, toutes ces valeurs sont disparues, sans ce code, tout fonctionne bien avec le tri.
Chandraprakash

3

Je l'ai résolu en ajoutant un déclencheur qui définit la propriété IsEditing de DataGridCell sur True lorsque la souris est dessus. Cela a résolu la plupart de mes problèmes. Cela fonctionne aussi avec les comboboxes.

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

1
Ne fonctionne pas ... il perd l'édition dès que la souris quitte la cellule. Donc, vous 1) faites un clic gauche sur la cellule que vous souhaitez modifier. 2) déplacez la souris à l'écart 3) Commencez à taper. Votre saisie ne fonctionne pas car la cellule n'est plus en mode édition.
Skarsnik

1
ne fonctionne pas pour moi non plus. m'empêche de modifier la zone de texte
Blechdose

Mais il y a un problème avec cette approche, j'avais verrouillé la 1ère colonne pour l'édition, avec cette approche, cela rend la 1ère colonne également éditable!
Chandraprakash

3

Je recherche une cellule d'édition en un seul clic dans MVVM et c'est une autre façon de le faire.

  1. Ajout d'un comportement en 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. La classe EditCellOnSingleClickBehavior étend 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);
        }
    }

Voila!


1

Il y a deux problèmes avec la réponse de user2134678. L'un est très mineur et n'a aucun effet fonctionnel. L'autre est assez significatif.

Le premier problème est que le GotFocus est en fait appelé par rapport au DataGrid, et non au DataGridCell en pratique. Le qualificatif DataGridCell dans le XAML est redondant.

Le principal problème que j'ai trouvé avec la réponse est que le comportement de la touche Entrée est cassé. Entrée devrait vous déplacer vers la cellule suivante sous la cellule actuelle dans un comportement DataGrid normal. Cependant, ce qui se passe réellement dans les coulisses, c'est que l'événement GotFocus sera appelé deux fois. Une fois sur la cellule actuelle perdant la focalisation, et une fois sur la nouvelle cellule gagnant la concentration. Mais tant que BeginEdit est appelé sur cette première cellule, la cellule suivante ne sera jamais activée. Le résultat est que vous avez une modification en un clic, mais quiconque ne clique pas littéralement sur la grille sera incommodé, et un concepteur d'interface utilisateur ne doit pas supposer que tous les utilisateurs utilisent des souris. (Les utilisateurs de clavier peuvent en quelque sorte le contourner en utilisant Tab, mais cela signifie toujours qu'ils sautent à travers des cerceaux dont ils ne devraient pas avoir besoin.)

Alors, la solution à ce problème? Gérez l'événement KeyDown pour la cellule et si la clé est la clé Entrée, définissez un indicateur qui empêche BeginEdit de se déclencher sur la première cellule. Maintenant, la touche Entrée se comporte comme il se doit.

Pour commencer, ajoutez le style suivant à votre DataGrid:

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

Appliquez ce style à la propriété "CellStyle" des colonnes pour lesquelles vous souhaitez activer en un clic.

Ensuite, dans le code derrière vous avez ce qui suit dans votre gestionnaire GotFocus (notez que j'utilise VB ici parce que c'est ce que notre client "demande de grille de données en un clic" voulait comme langage de développement):

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)
    .
    .
    .

Ensuite, vous ajoutez votre gestionnaire pour l'événement 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

Vous disposez maintenant d'un DataGrid qui n'a modifié aucun comportement fondamental de l'implémentation prête à l'emploi et qui prend en charge l'édition en un seul clic.


0

Je sais que je suis un peu en retard à la fête mais j'ai eu le même problème et j'ai trouvé une solution différente:

     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>

Comme vous pouvez le voir, j'ai écrit mon propre DataGridTextColumn en héritant de tout ce qui est le DataGridBoundColumn. En remplaçant la méthode GenerateElement et en retournant un contrôle Textbox juste là, la méthode de génération de l'élément d'édition n'est jamais appelée. Dans un autre projet, j'ai utilisé ceci pour implémenter une colonne Datepicker, donc cela devrait également fonctionner pour les cases à cocher et les comboboxes.

Cela ne semble pas avoir d'impact sur le reste des comportements des datagrids. Au moins, je n'ai pas remarqué d'effets secondaires ni de commentaires négatifs jusqu'à présent.


-1

Mettre à jour

Une solution simple si vous êtes d'accord que votre cellule reste une zone de texte (pas de distinction entre le mode édition et non-édition). De cette façon, l'édition en un seul clic fonctionne immédiatement. Cela fonctionne également avec d'autres éléments comme la liste déroulante et les boutons. Sinon, utilisez la solution ci-dessous la mise à jour.

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

Fin de la mise à jour

Rant

J'ai essayé tout ce que j'ai trouvé ici et sur Google et j'ai même essayé de créer mes propres versions. Mais chaque réponse / solution fonctionnait principalement pour les colonnes de zone de texte mais ne fonctionnait pas avec tous les autres éléments (cases à cocher, comboboxes, colonnes de boutons), ou même cassait ces autres colonnes d'éléments ou avait d'autres effets secondaires. Merci Microsoft d'avoir fait en sorte que datagrid se comporte de cette manière moche et nous force à créer ces hacks. Pour cette raison, j'ai décidé de créer une version qui peut être appliquée avec un style à une colonne de zone de texte directement sans affecter les autres colonnes.

Caractéristiques

  • Aucun code derrière. Compatible MVVM.
  • Cela fonctionne lorsque vous cliquez sur différentes cellules de zone de texte dans les mêmes lignes ou sur des lignes différentes.
  • Les touches TAB et ENTER fonctionnent.
  • Cela n'affecte pas les autres colonnes.

Sources

J'ai utilisé cette solution et la réponse de @ my et les ai modifiées pour qu'elles soient un comportement attaché. http://wpf-tutorial-net.blogspot.com/2016/05/wpf-datagrid-edit-cell-on-single-click.html

Comment l'utiliser

Ajoutez ce style. Le BasedOnest important lorsque vous utilisez des styles sophistiqués pour votre grille de données et que vous ne voulez pas les perdre.

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

Appliquez le style avec CellStyleà chacun de vos DataGridTextColumnscomme ceci:

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

Et maintenant, ajoutez cette classe au même espace de noms que votre MainViewModel (ou à un autre espace de noms. Mais vous devrez alors utiliser un autre préfixe d'espace de noms que local). Bienvenue dans le monde du code passe-partout laid des comportements attachés.

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;

 }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.