Clique único para editar em WPF DataGrid


92

Quero que o usuário seja capaz de colocar a célula no modo de edição e destacar a linha na qual a célula está contida com um único clique. Por padrão, é um clique duplo.

Como faço para substituir ou implementar isso?


Você está usando o DataGrid encontrado no WPF Toolkit?
myermian

4
Seria possível dar-nos um pouco mais de informação sobre o que experimentou e como não funciona?
Zach Johnson

Respostas:


76

Aqui está como resolvi esse 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 um CollectionViewSource (contendo objetos Person fictícios ).

A mágica acontece lá: DataGridCell.Selected = "DataGridCell_Selected" .

Simplesmente conecto o Evento Selecionado da célula DataGrid e chamo BeginEdit () no DataGrid.

Aqui está o código por trás do manipulador 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);
    }
}

8
Você pode contornar o problema de linha já selecionada definindo a SelectionUnitpropriedade no DataGrid como Cell.
Matt Winckler

Suponha que eu tenha um TextBox em meu DataGridCell. Depois que eu ligar grd.BeginEdit(e), quero que o TextBox dessa célula tenha o foco. Como eu posso fazer isso? Eu tentei chamar FindName("txtBox")o DataGridCell e o DataGrid, mas ele retornou null para mim.
user1214135

GotFocus = "DataGrid_GotFocus" parece estar faltando?
sinérgico de

4
Isso funciona bem, mas eu não recomendaria fazer isso. Usei isso em meu projeto e decidi voltar ao comportamento padrão de GD. No futuro, quando seu DG crescer e se tornar complexo, você terá problemas com validação, adicionando novas linhas e outros comportamentos estranhos.
white.zaz

1
@ white.zaz ficou satisfeito com o cliente depois que você reverteu o comportamento padrão do DG? Porque o principal motivo para fazer essa pergunta foi que a edição em recursos padrão do DG não é amigável, pois precisa ser clicada muitas vezes antes que o DG chegue ao modo Editar.
AEMLoviji

42

A resposta de Micael Bergeron foi um bom começo para eu encontrar uma solução que está funcionando para mim. Para permitir a edição com um único clique também para células na mesma linha que já estão no modo de edição, tive que ajustar um pouco. Usar o SelectionUnit Cell não era uma opção para mim.

Em vez de usar o evento DataGridCell.Selected, que só é disparado pela primeira vez em que uma célula da linha é clicada, usei o evento DataGridCell.GotFocus.

<DataGrid DataGridCell.GotFocus="DataGrid_CellGotFocus" />

Se você fizer isso, você terá sempre a célula correta focada e no modo de edição, mas nenhum controle na célula estará focado, isso eu resolvi assim

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
as caixas de seleção não parecem funcionar para mim? eu ainda tenho que clicar duas vezes neles
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 POR TRÁ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;
}

1
isso não funciona em certos casos, e é mais complicado do que a solução de Micael Bergerons.
SwissCoder

Para mim, essa quase foi a solução. Eu precisava adicionar um manipulador de eventos "PreviewMouseLeftButtonUp" e colocar exatamente o mesmo código.
Néstor Sánchez A.

isso também não funciona quando você tem uma caixa de combinação. o clique de visualização vê cliques no pop-up da caixa de combinação, então a chamada de cell.focus estraga tudo. A correção mais fácil é adicionar uma seção que examina a fonte original dos eventos do mouse, usando FindVisualParent para ver se está dentro do datagrid. se não, não faça nenhum dos outros trabalhos.
John Gardner

7

A solução de http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing funcionou muito bem para mim, mas eu a habilitei para cada DataGrid usando um estilo definido em um ResourceDictionary. Para usar manipuladores em dicionários de recursos, você precisa adicionar um arquivo code-behind a ele. Veja como você faz:

Este é um Dicionário 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>

Observe o atributo x: Class no elemento raiz. Crie um arquivo de classe. Neste exemplo, seria DataGridStyles.xaml.cs . Coloque este código dentro de:

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 está morto (limite de 15 caracteres)
Blechdose de

4

eu prefiro assim com base na sugestão de Dušan Knežević. você clica em e pronto))

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

Isso não funciona se uma caixa de combinação for usada como um modelo de edição, eu presumiria que outras opções, como a caixa de seleção de que os eventos de captura do mouse também quebrariam
Steve

Para mim, isso funciona com colunas combobox, mas a caixa de texto da "nova linha de item" (última linha) tem um comportamento estranho: no primeiro clique, obtenho o foco de entrada e posso digitar coisas. Quando movo o mouse para fora da célula , o valor da caixa de texto desaparece. Ao digitar mais, o Texto recém-inserido é salvo corretamente (ele cria uma nova entrada conforme desejado). Isso também ocorre mesmo com um ComboboxColumn.
FrankM

Inicialmente parecia estar funcionando bem, mas baguncei totalmente o meu DataGrid, quando tento ordenar, todos esses valores sumiram, sem esse código tudo funciona bem com ordenação.
Chandraprakash

3

Resolvi isso adicionando um gatilho que define a propriedade IsEditing de DataGridCell como True quando o mouse está sobre ele. Isso resolveu a maioria dos meus problemas. Também funciona com caixas de combinação.

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

1
Não funciona ... ele perde a edição assim que o mouse sai da célula. Então você 1) clique com o botão esquerdo na célula que deseja editar. 2) mova o mouse para fora do caminho 3) Comece a digitar. Sua digitação não funciona porque a célula não está mais no modo de edição.
Skarsnik

1
não funciona para mim também. impede a edição da caixa de texto para mim
Blechdose

Mas tem um problema com essa abordagem, eu havia travado a 1ª coluna para edição, com essa abordagem, isso está tornando a 1ª coluna também editável!
Chandraprakash

3

Procuro editar célula em um único clique no MVVM e esta é uma outra maneira de fazer isso.

  1. Adicionando comportamento no 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. A classe EditCellOnSingleClickBehavior extend 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

Existem dois problemas com a resposta do usuário2134678. Um é muito pequeno e não tem efeito funcional. O outro é bastante significativo.

O primeiro problema é que o GotFocus está realmente sendo chamado contra o DataGrid, não o DataGridCell na prática. O qualificador DataGridCell no XAML é redundante.

O principal problema que encontrei com a resposta é que o comportamento da tecla Enter está quebrado. Enter deve mover você para a próxima célula abaixo da célula atual no comportamento normal do DataGrid. No entanto, o que realmente acontece nos bastidores é que o evento GotFocus será chamado duas vezes. Uma vez a célula atual perdendo o foco e outra vez a nova célula ganhando foco. Mas, enquanto BeginEdit for chamado na primeira célula, a próxima célula nunca será ativada. O resultado é que você tem edição com um clique, mas qualquer um que não esteja literalmente clicando na grade ficará incomodado, e um designer de interface de usuário não deve presumir que todos os usuários estão usando mouses. (Os usuários de teclado podem contornar isso usando Tab, mas isso ainda significa que eles estão se esforçando para não precisar fazer isso.)

Então, a solução para esse problema? Manipule o evento KeyDown para a célula e se a tecla for a tecla Enter, defina um sinalizador que impeça o BeginEdit de disparar na primeira célula. Agora a tecla Enter se comporta como deveria.

Para começar, adicione o seguinte estilo ao seu DataGrid:

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

Aplique esse estilo à propriedade "CellStyle" nas colunas para as quais deseja habilitar um clique.

Então, no código atrás, você tem o seguinte em seu manipulador GotFocus (observe que estou usando VB aqui porque é isso que nosso cliente de "solicitação de grade de dados com um clique" queria como linguagem de desenvolvimento):

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

Em seguida, você adiciona seu manipulador para o 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

Agora você tem um DataGrid que não mudou nenhum comportamento fundamental da implementação pronta para uso e ainda oferece suporte à edição com um único clique.


0

Sei que estou um pouco atrasado para a festa, mas tive o mesmo problema e encontrei uma solução 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 você pode ver, escrevi meu próprio DataGridTextColumn herdando tudo do DataGridBoundColumn. Substituindo o Método GenerateElement e retornando um controle Textbox ali mesmo, o Método para gerar o Elemento de Edição nunca é chamado. Em um projeto diferente, usei isso para implementar uma coluna Datepicker, então isso deve funcionar para caixas de seleção e comboboxes também.

Isso não parece afetar o comportamento do restante dos datagrids ... Pelo menos, não notei nenhum efeito colateral nem recebi feedback negativo até agora.


-1

Atualizar

Uma solução simples se você concordar que sua célula permaneça uma caixa de texto (sem distinção entre o modo de edição e não edição). Desta forma, a edição com um único clique funciona imediatamente. Isso funciona com outros elementos como combobox e botões também. Caso contrário, use a solução abaixo da atualização.

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

Fim da atualização

Rant

Tentei tudo o que encontrei aqui e no google e até tentei criar minhas próprias versões. Mas cada resposta / solução funcionou principalmente para colunas de caixa de texto, mas não funcionou com todos os outros elementos (caixas de seleção, caixas de combinação, colunas de botões), ou mesmo quebrou essas outras colunas de elemento ou teve alguns outros efeitos colaterais. Obrigado microsoft por fazer o datagrid se comportar daquela maneira feia e nos forçar a criar esses hacks. Por isso decidi fazer uma versão que pode ser aplicada com um estilo a uma coluna de caixa de texto diretamente sem afetar as outras colunas.

Recursos

  • Nenhum código por trás. MVVM amigável.
  • Funciona ao clicar em diferentes células da caixa de texto na mesma linha ou em linhas diferentes.
  • As teclas TAB e ENTER funcionam.
  • Não afeta outras colunas.

Fontes

Usei esta solução e a resposta de @my e modifiquei-os para um comportamento vinculado. http://wpf-tutorial-net.blogspot.com/2016/05/wpf-datagrid-edit-cell-on-single-click.html

Como usá-lo

Adicione este estilo. Isso BasedOné importante quando você usa alguns estilos sofisticados para seu datagrid e não quer perdê-los.

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

Aplique o estilo CellStylea cada um de seus DataGridTextColumnsassim:

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

E agora adicione esta classe ao mesmo namespace que seu MainViewModel (ou um namespace diferente. Mas então você precisará usar um outro prefixo de namespace que não seja local). Bem-vindo ao feio mundo do código clichê de comportamentos anexados.

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;

 }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.