Modifica con clic singolo in WPF DataGrid


92

Voglio che l'utente sia in grado di mettere la cella in modalità di modifica ed evidenziare la riga in cui è contenuta la cella con un solo clic. Per impostazione predefinita, questo è il doppio clic.

Come lo sovrascrivo o lo implemento?


Stai usando il DataGrid trovato nel Toolkit di WPF?
myermian

4
Potresti darci qualche informazione in più su quello che hai provato e su come non funziona?
Zach Johnson

Risposte:


76

Ecco come ho risolto questo 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>

Questo DataGrid è associato a un CollectionViewSource (contenente oggetti fittizi Person ).

La magia accade lì: DataGridCell.Selected = "DataGridCell_Selected" .

Ho semplicemente agganciato l'evento selezionato della cella DataGrid e chiamo BeginEdit () su DataGrid.

Ecco il codice sottostante per il gestore di eventi:

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
È possibile aggirare il problema di riga già selezionato impostando la SelectionUnitproprietà in DataGrid su Cell.
Matt Winckler

Supponiamo di avere un TextBox nel mio DataGridCell. Dopo aver chiamato grd.BeginEdit(e), voglio che il TextBox in quella cella abbia il fuoco. Come lo posso fare? Ho provato a chiamare FindName("txtBox")sia DataGridCell che DataGrid, ma per me restituisce null.
user1214135

GotFocus = "DataGrid_GotFocus" sembra mancare?
sinergico

4
Funziona bene, ma non consiglierei di farlo. L'ho usato nel mio progetto e ho deciso di tornare al comportamento DG standard. In futuro, quando il tuo DG crescerà e diventerà complesso, avrai problemi con la convalida, l'aggiunta di nuove righe e altri strani comportamenti.
white.zaz

1
@ white.zaz era soddisfatto del cliente dopo che hai eseguito il rollback al comportamento DG standard? Perché il motivo principale per porre questa domanda era che la modifica nelle funzionalità DG standard non è facile da usare poiché è necessario fare clic troppe volte prima che DG passi alla modalità Modifica.
AEMLoviji

42

La risposta di Micael Bergeron è stata un buon inizio per me per trovare una soluzione che funzioni per me. Per consentire la modifica con un solo clic anche per le celle nella stessa riga che è già in modalità di modifica, ho dovuto regolarla un po '. L'utilizzo di SelectionUnit Cell non era un'opzione per me.

Invece di utilizzare l'evento DataGridCell.Selected che viene attivato solo per la prima volta che si fa clic sulla cella di una riga, ho utilizzato l'evento DataGridCell.GotFocus.

<DataGrid DataGridCell.GotFocus="DataGrid_CellGotFocus" />

Se lo fai avrai sempre la cella corretta focalizzata e in modalità di modifica, ma nessun controllo nella cella sarà focalizzato, questo l'ho risolto in questo modo

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
le caselle di controllo non sembrano funzionare per me? Devo ancora fare doppio clic su di loro
Thomas Klammer

9

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

CODICE DIETRO:

//
// 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
questo non funziona in alcuni casi, ed è più complicata della soluzione di Micael Bergerons.
SwissCoder

Per me, questa era quasi la soluzione. Avevo bisogno di aggiungere un gestore di eventi "PreviewMouseLeftButtonUp" e inserire esattamente lo stesso codice.
Néstor Sánchez A.

anche questo non funziona una volta che hai una casella combinata. il clic di anteprima vede i clic sul popup della casella combinata, quindi la chiamata cell.focus rovina tutto. la soluzione più semplice è aggiungere una sezione che guarda la fonte originale degli eventi del mouse, usando FindVisualParent su quella per vedere se è all'interno del datagrid. in caso contrario, non fare nessuno degli altri lavori.
John Gardner

7

La soluzione da http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing ha funzionato alla grande per me, ma l'ho abilitata per ogni DataGrid utilizzando uno stile definito in ResourceDictionary. Per utilizzare i gestori nei dizionari delle risorse è necessario aggiungere un file code-behind ad esso. Ecco come lo fai:

Questo è un dizionario delle risorse 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>

Nota l'attributo x: Class nell'elemento root. Crea un file di classe. In questo esempio sarebbe DataGridStyles.xaml.cs . Metti questo codice all'interno:

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

il collegamento è morto (limite di 15 caratteri)
Blechdose

4

Preferisco questo modo in base al suggerimento di Dušan Knežević. fai clic su questo è tutto))

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

Questo non funziona se una casella combinata viene utilizzata come modello di modifica, presumo che anche altri come la casella di controllo che cattura gli eventi del mouse si interrompano
Steve

Per me questo funziona con le colonne della casella combinata, ma la casella di testo della "nuova riga elemento" (ultima riga) ha uno strano comportamento: al primo clic ottengo il focus di input e posso digitare cose. Quando sposto il mouse fuori dalla cella , il valore della casella di testo svanisce. Quando si digita ulteriormente, il testo appena inserito viene salvato correttamente (crea una nuova voce come desiderato). Ciò si verifica anche con una ComboboxColumn.
FrankM

Inizialmente sembra funzionare bene, ma ha completamente incasinato il mio Datagrid, quando provo a ordinare, tutti questi valori sono svaniti, senza questo codice tutto funziona bene con l'ordinamento.
Chandraprakash

3

L'ho risolto aggiungendo un trigger che imposta la proprietà IsEditing di DataGridCell su True quando il mouse è sopra di esso. Ha risolto la maggior parte dei miei problemi. Funziona anche con le combobox.

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

1
Non funziona ... perde la modifica non appena il mouse lascia la cella. Quindi 1) fai clic con il pulsante sinistro del mouse sulla cella che desideri modificare. 2) sposta il mouse fuori dal percorso 3) Inizia a digitare. La tua digitazione non funziona perché la cella non è più in modalità di modifica.
Skarsnik

1
non funziona neanche per me. mi impedisce di modificare la casella di testo
Blechdose

Ma c'è un problema con questo approccio, avevo bloccato la prima colonna per la modifica, con questo approccio, questo rende anche modificabile la prima colonna!
Chandraprakash

3

Sto cercando di modificare la cella con un solo clic in MVVM e questo è un altro modo per farlo.

  1. Aggiunta di comportamenti in 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 estende 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);
        }
    }

Ecco !


1

Ci sono due problemi con la risposta di user2134678. Uno è molto minore e non ha alcun effetto funzionale. L'altro è abbastanza significativo.

Il primo problema è che GotFocus viene effettivamente chiamato contro DataGrid, non DataGridCell in pratica. Il qualificatore DataGridCell in XAML è ridondante.

Il problema principale che ho riscontrato con la risposta è che il comportamento del tasto Invio non funziona. Invio dovrebbe spostarti alla cella successiva sotto la cella corrente nel normale comportamento di DataGrid. Tuttavia, ciò che accade effettivamente dietro le quinte è che l'evento GotFocus verrà chiamato due volte. Una volta la cellula attuale perdeva il focus, e una volta la nuova cellula guadagnava il focus. Ma finché BeginEdit viene chiamato su quella prima cella, la cella successiva non verrà mai attivata. Il risultato è che hai la modifica con un clic, ma chiunque non faccia letteralmente clic sulla griglia sarà disturbato e un progettista dell'interfaccia utente non dovrebbe presumere che tutti gli utenti utilizzino i mouse. (Gli utenti della tastiera possono aggirare il problema usando Tab, ma ciò significa comunque che stanno saltando attraverso i cerchi che non dovrebbero aver bisogno.)

Quindi la soluzione a questo problema? Gestisci l'evento KeyDown per la cella e se la chiave è il tasto Invio, imposta un flag che impedisce a BeginEdit di attivarsi sulla prima cella. Ora il tasto Invio si comporta come dovrebbe.

Per cominciare, aggiungi il seguente stile al tuo DataGrid:

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

Applica quello stile alla proprietà "CellStyle" delle colonne per le quali desideri abilitare un clic.

Quindi nel codice dietro di te hai quanto segue nel tuo gestore GotFocus (nota che sto usando VB qui perché questo è ciò che il nostro client di "richiesta della griglia di dati con un clic" voleva come linguaggio di sviluppo):

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

Quindi aggiungi il tuo gestore per l'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

Ora disponi di un DataGrid che non ha modificato alcun comportamento fondamentale dell'implementazione predefinita e supporta ancora la modifica con un clic.


0

So di essere un po 'in ritardo alla festa, ma ho avuto lo stesso problema e ho trovato una soluzione diversa:

     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>

Come puoi vedere ho scritto il mio DataGridTextColumn ereditando tutto dal DataGridBoundColumn. Eseguendo l'override del metodo GenerateElement e restituendo un controllo Textbox proprio lì, il metodo per la generazione dell'elemento di modifica non viene mai chiamato. In un progetto diverso l'ho usato per implementare una colonna Datepicker, quindi dovrebbe funzionare anche per le caselle di controllo e le caselle combinate.

Questo non sembra avere un impatto sul resto dei comportamenti dei datagrid .. Almeno non ho notato alcun effetto collaterale né ho ricevuto alcun feedback negativo finora.


-1

Aggiornare

Una soluzione semplice se stai bene con che la tua cella rimane una casella di testo (nessuna distinzione tra modalità di modifica e non di modifica). In questo modo la modifica con un solo clic funziona immediatamente. Funziona anche con altri elementi come combobox e pulsanti. Altrimenti usa la soluzione sotto l'aggiornamento.

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

Fine aggiornamento

Rant

Ho provato tutto ciò che ho trovato qui e su Google e ho persino provato a creare le mie versioni. Ma ogni risposta / soluzione ha funzionato principalmente per le colonne della casella di testo ma non ha funzionato con tutti gli altri elementi (caselle di controllo, caselle combinate, colonne dei pulsanti), o ha persino rotto quelle altre colonne degli elementi o ha avuto altri effetti collaterali. Grazie microsoft per aver fatto sì che datagrid si comportasse in quel modo orribile e ci costringesse a creare quegli hack. Per questo motivo ho deciso di creare una versione che può essere applicata direttamente con uno stile a una colonna di casella di testo senza influire sulle altre colonne.

Caratteristiche

  • Nessun codice dietro. Compatibile con MVVM.
  • Funziona quando si fa clic su celle di casella di testo diverse nella stessa riga o in righe diverse.
  • I tasti TAB e INVIO funzionano.
  • Non influisce su altre colonne.

Fonti

Ho usato questa soluzione e la risposta di @ my e le ho modificate in modo che fossero un comportamento allegato. http://wpf-tutorial-net.blogspot.com/2016/05/wpf-datagrid-edit-cell-on-single-click.html

Come usarlo

Aggiungi questo stile. Il BasedOnè importante quando si utilizzano alcuni stili di fantasia per il vostro datagrid e tu non vuoi perderli.

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

Applica lo stile con CellStylea ciascuno dei tuoi in DataGridTextColumnsquesto modo:

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

E ora aggiungi questa classe allo stesso spazio dei nomi di MainViewModel (o uno spazio dei nomi diverso. Ma poi dovrai usare un prefisso dello spazio dei nomi diverso da local). Benvenuti nel brutto mondo del codice boilerplate dei comportamenti allegati.

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;

 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.