Editar con un solo clic en WPF DataGrid

92

Quiero que el usuario pueda poner la celda en modo de edición y resaltar la fila en la que está contenida la celda con un solo clic. Por defecto, esto es doble clic.

¿Cómo anulo o implemento esto?

Austin
fuente
¿Está utilizando el DataGrid que se encuentra en el kit de herramientas de WPF?
myermian
4
¿Sería posible que nos brinde un poco más de información sobre lo que ha probado y cómo no funciona?
Zach Johnson

Respuestas:

76

Así es como resolví este problema:

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

Este DataGrid está vinculado a CollectionViewSource (que contiene objetos Person ficticios ).

La magia sucede allí: DataGridCell.Selected = "DataGridCell_Selected" .

Simplemente engancho el evento seleccionado de la celda DataGrid y llamo a BeginEdit () en el DataGrid.

Aquí está el código subyacente para el controlador de eventos:

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);
    }
}
Micael Bergeron
fuente
8
Puede solucionar el problema de la fila ya seleccionada estableciendo la SelectionUnitpropiedad en DataGrid en Cell.
Matt Winckler
Supongamos que tengo un TextBox en mi DataGridCell. Después de llamar grd.BeginEdit(e), quiero que el TextBox en esa celda tenga el foco. ¿Cómo puedo hacer eso? Intenté llamar FindName("txtBox")tanto a DataGridCell como a DataGrid, pero me devuelve nulo.
user1214135
GotFocus = "DataGrid_GotFocus" parece faltar?
sinérgico
4
Esto funciona bien, pero no recomendaría hacerlo. He usado esto en mi proyecto y decidí retroceder al comportamiento estándar de DG. En el futuro, cuando su DG crezca y se vuelva complejo, tendrá problemas de validación, agregar nuevas filas y otros comportamientos extraños.
white.zaz
1
¿@ white.zaz estaba contento con el cliente después de que hiciste la reversión al comportamiento estándar de DG? Porque la razón principal por la que se hizo esta pregunta fue que la edición en las capacidades estándar de DG no es fácil de usar, ya que necesita hacer clic demasiadas veces antes de que DG entre en el modo de edición.
AEMLoviji
42

La respuesta de Micael Bergeron fue un buen comienzo para encontrar una solución que me funcione. Para permitir la edición con un solo clic también para las celdas en la misma fila que ya está en modo de edición, tuve que ajustarlo un poco. Usar SelectionUnit Cell no fue una opción para mí.

En lugar de usar el evento DataGridCell.Selected, que solo se activa por primera vez, se hace clic en la celda de una fila, utilicé el evento DataGridCell.GotFocus.

<DataGrid DataGridCell.GotFocus="DataGrid_CellGotFocus" />

Si lo hace, siempre tendrá la celda correcta enfocada y en modo de edición, pero no se enfocará ningún control en la celda, esto lo resolví así

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;
}
usuario2134678
fuente
3
¿Parece que las casillas de verificación no funcionan para mí? todavía tengo que hacer doble clic en ellos
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>

CÓDIGO DETRÁS:

//
// 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;
}
myermian
fuente
1
esto no funciona en ciertos casos, y es más complicada que la solución de Micael Bergerons.
SwissCoder
Para mí, esta casi fue la solución. Necesitaba agregar un controlador de eventos "PreviewMouseLeftButtonUp" y poner allí exactamente el mismo código.
Néstor Sánchez A.
esto tampoco funciona una vez que tienes un cuadro combinado. el clic de vista previa ve clics en la ventana emergente del cuadro combinado, luego la llamada cell.focus arruina todo. La solución más fácil es agregar una sección que observe la fuente original de los eventos del mouse, usando FindVisualParent en eso para ver si está dentro de la cuadrícula de datos. si no, no hagas ningún otro trabajo.
John Gardner
7

La solución de http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing funcionó muy bien para mí, pero la habilité para cada DataGrid usando un estilo definido en un ResourceDictionary. Para utilizar controladores en los diccionarios de recursos, debe agregarle un archivo de código subyacente. Así es como lo haces:

Este es un diccionario de recursos 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>

Tenga en cuenta el atributo x: Class en el elemento raíz. Crea un archivo de clase. En este ejemplo, sería DataGridStyles.xaml.cs . Pon este código dentro:

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.
}
Andikki
fuente
el enlace está muerto (límite de 15 caracteres)
Blechdose
4

Prefiero esta forma según la sugerencia de Dušan Knežević. haces clic en y eso es todo))

<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>
René Hankel
fuente
Esto no funciona si se usa un cuadro combinado como plantilla de edición, supongo que otras como la casilla de verificación que captura los eventos del mouse también se romperían
Steve
Para mí, esto funciona con columnas de cuadro combinado, pero el cuadro de texto de la "nueva línea de elemento" (última línea) tiene un comportamiento extraño: al primer clic obtengo el foco de entrada y puedo escribir cosas. Cuando muevo el mouse fuera de la celda , el valor del cuadro de texto desaparece. Al escribir más, el texto recién ingresado se guarda correctamente (crea una nueva entrada como se desea). Esto también ocurre incluso con un ComboboxColumn.
FrankM
Inicialmente parece que funciona bien, pero arruinó totalmente mi Datagrid, cuando trato de ordenar, todos estos valores desaparecen, sin este código, todo funciona bien con la ordenación.
Chandraprakash
3

Lo resolví agregando un disparador que establece la propiedad IsEditing de DataGridCell en True cuando el mouse está sobre él. Resolvió la mayoría de mis problemas. También funciona con cuadros combinados.

<Style TargetType="DataGridCell">
     <Style.Triggers>
         <Trigger Property="IsMouseOver" Value="True">
             <Setter Property="IsEditing" Value="True" />
         </Trigger>
     </Style.Triggers>
 </Style>
Dušan Knežević
fuente
1
No funciona ... pierde la edición tan pronto como el mouse sale de la celda. Entonces, 1) haga clic con el botón izquierdo en la celda que desea editar. 2) mueva el mouse fuera del camino 3) Empiece a escribir. Su escritura no funciona porque la celda ya no está en modo de edición.
Skarsnik
1
tampoco funciona para mí. me impide editar el cuadro de texto
Blechdose
Pero hay un problema con este enfoque, había bloqueado la primera columna para editar, con este enfoque, ¡esto hace que la primera columna también sea editable!
Chandraprakash
3

Estoy buscando editar la celda con un solo clic en MVVM y esta es otra forma de hacerlo.

  1. Agregar comportamiento 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 clase EditCellOnSingleClickBehavior extiende 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!

Jouan Antoine
fuente
1

Hay dos problemas con la respuesta de user2134678. Uno es muy pequeño y no tiene ningún efecto funcional. El otro es bastante significativo.

El primer problema es que GotFocus en realidad se llama contra DataGrid, no contra DataGridCell en la práctica. El calificador DataGridCell en XAML es redundante.

El principal problema que encontré con la respuesta es que el comportamiento de la tecla Enter está roto. Enter debería moverlo a la siguiente celda debajo de la celda actual en el comportamiento normal de DataGrid. Sin embargo, lo que realmente sucede detrás de escena es que el evento GotFocus se llamará dos veces. Una vez que la celda actual pierde el enfoque, y una vez que la nueva celda gana el foco. Pero mientras se llame a BeginEdit en esa primera celda, la siguiente celda nunca se activará. El resultado es que tiene la edición con un solo clic, pero cualquiera que no esté literalmente haciendo clic en la cuadrícula tendrá inconvenientes, y un diseñador de interfaz de usuario no debe asumir que todos los usuarios están usando ratones. (Los usuarios de teclado pueden sortearlo usando Tab, pero eso aún significa que están saltando obstáculos que no deberían necesitar).

Entonces, ¿la solución a este problema? Maneje el evento KeyDown para la celda y si la clave es la tecla Enter, establezca un indicador que impida que BeginEdit se active en la primera celda. Ahora la tecla Enter se comporta como debería.

Para empezar, agregue el siguiente estilo a su DataGrid:

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

Aplique ese estilo a la propiedad "CellStyle" de las columnas para las que desea habilitar un clic.

Luego, en el código detrás, tiene lo siguiente en su controlador GotFocus (tenga en cuenta que estoy usando VB aquí porque eso es lo que nuestro cliente de "solicitud de cuadrícula de datos con un solo clic" quería como lenguaje de desarrollo):

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

Luego agrega su controlador para el evento 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

Ahora tiene un DataGrid que no ha cambiado ningún comportamiento fundamental de la implementación lista para usar y, sin embargo, admite la edición con un solo clic.

GrantA
fuente
0

Sé que llegué un poco tarde a la fiesta, pero tuve el mismo problema y se me ocurrió una solución diferente:

     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>

Como puede ver, escribí mi propia DataGridTextColumn heredando todo lo relacionado con DataGridBoundColumn. Al anular el método GenerateElement y devolver un control Textbox allí mismo, nunca se llama al método para generar el elemento de edición. En un proyecto diferente, utilicé esto para implementar una columna Datepicker, por lo que también debería funcionar para casillas de verificación y cuadros combinados.

Esto no parece afectar el comportamiento del resto de las cuadrículas de datos. Al menos no he notado ningún efecto secundario ni he recibido comentarios negativos hasta ahora.

Troillius
fuente
-1

Actualizar

Una solución simple si está de acuerdo con que su celda siga siendo un cuadro de texto (sin distinguir entre el modo de edición y el de no editar). De esta manera, la edición con un solo clic funciona de inmediato. Esto también funciona con otros elementos como cuadro combinado y botones. De lo contrario, use la solución debajo de la actualización.

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

Fin de la actualización

Despotricar

Probé todo lo que encontré aquí y en Google e incluso intenté crear mis propias versiones. Pero cada respuesta / solución funcionó principalmente para columnas de cuadro de texto, pero no funcionó con todos los demás elementos (casillas de verificación, cuadros combinados, columnas de botones), o incluso rompió esas otras columnas de elementos o tuvo algunos otros efectos secundarios. Gracias microsoft por hacer que datagrid se comporte de esa manera tan fea y nos obligue a crear esos trucos. Por eso, decidí hacer una versión que se pueda aplicar con un estilo a una columna de cuadro de texto directamente sin afectar a otras columnas.

Caracteristicas

  • Sin código detrás. Compatible con MVVM.
  • Funciona al hacer clic en diferentes celdas de cuadro de texto en la misma fila o en filas diferentes.
  • Las teclas TAB y ENTER funcionan.
  • No afecta a otras columnas.

Fuentes

Usé esta solución y la respuesta de @ my y las modifiqué para que fueran un comportamiento adjunto. http://wpf-tutorial-net.blogspot.com/2016/05/wpf-datagrid-edit-cell-on-single-click.html

Cómo usarlo

Agrega este estilo. El BasedOnes importante cuando se utiliza algunos estilos de lujo para su cuadrícula de datos y usted no desea perder.

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

Aplica el estilo con CellStylea cada uno de tus DataGridTextColumnsasí:

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

Y ahora agregue esta clase al mismo espacio de nombres que su MainViewModel (o un espacio de nombres diferente. Pero luego deberá usar un prefijo de espacio de nombres diferente local). Bienvenido al feo mundo de códigos repetitivos de comportamientos adjuntos.

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