Come eseguire la selezione della casella di controllo Clic singolo in WPF DataGrid?


143

Ho un DataGrid con prima colonna come colonna di testo e seconda colonna come colonna CheckBox. Quello che voglio è, se faccio clic sulla casella di controllo. Dovrebbe essere controllato.

Tuttavia, bastano due clic per essere selezionati, per il primo clic viene selezionata la cella, per i secondi clic viene selezionata la casella di controllo. Come fare la casella di controllo per essere selezionato / deselezionato con un solo clic.

Sto usando WPF 4.0. Le colonne in DataGrid sono generate automaticamente.


4
Duplicato di: stackoverflow.com/questions/1225836/… , ma questo ha un titolo migliore
surfen

Risposte:


189

Per singolo clic DataGrid checkbox che si può semplicemente mettere regolare all'interno di controllo casella di controllo DataGridTemplateColumne set UpdateSourceTrigger=PropertyChanged.

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>

4
WOW - Sono contento di aver letto fino alla fine. Funziona perfettamente ed è notevolmente meno complicato, IMO questo dovrebbe essere contrassegnato come risposta.
Tod

2
Questo funziona anche per ComboBox. Come in: modo, MODO meglio di DataGridComboBoxColumn.
user1454265

2
Non quando uso la barra spaziatrice per selezionare / deselezionare e le frecce per spostarmi in un'altra cella.
Yola,

1
Ho interpretato questa risposta che devi associare "IsSelected", ma non è vero! Puoi semplicemente usarlo DataGridTemplateColumn.CellTemplatecon il tuo Binding e funzionerà !! La risposta di @ weidian-huang mi ha aiutato a capirlo, grazie!
AstralisSomnium,

62

Ho risolto questo con il seguente stile:

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

È ovviamente possibile adattarlo ulteriormente per colonne specifiche ...


8
Bello. L'ho cambiato in MultiTrigger e ho aggiunto una condizione per ReadOnly = False, ma l'approccio di base ha funzionato per il mio semplice caso in cui la navigazione da tastiera non è importante.
Marc

L'aggiunta di quello stile alla mia griglia solleva un'eccezione di Operazione non valida mentre ItemsSource è in uso. Accedi e modifica gli elementi con ItemsControl.ItemsSource.
Alkampfer,

1
Questo è il modo più pulito che ho visto finora! Bello! (per IsReadOnly = "True" un MultiTrigger farà il lavoro)
FooBarTheLittle

2
Questa soluzione presenta alcuni comportamenti imprevisti / indesiderati. Vedere stackoverflow.com/q/39004317/2881450
jHilscher

2
Perché l'associazione funzioni, è necessario un UpdateSourceTrigger = PropertyChanged
AQuirky

27

Prima di tutto, so che questa è una domanda piuttosto vecchia, ma ho ancora pensato di provare a rispondere.

Ho avuto lo stesso problema un paio di giorni fa e ho trovato una soluzione sorprendentemente breve (vedi questo blog ). Fondamentalmente, tutto ciò che devi fare è sostituire la DataGridCheckBoxColumndefinizione nel tuo XAML con il seguente:

<DataGridTemplateColumn Header="MyCheckBoxColumnHeader">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Path=MyViewModelProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Il vantaggio di questa soluzione è ovvio: è solo XAML; quindi ti impedisce efficacemente di gravare sul code-back con una logica dell'interfaccia utente aggiuntiva e ti aiuta a mantenere il tuo stato agli occhi dei fanatici di MVVM;).


1
Questo è simile alla risposta di Konstantin Salavatov e questo ha funzionato per me. +1 per includere l'esempio di codice in cui non lo era. Grazie per una buona risposta a una vecchia domanda.
Don Erode,

1
Il problema è che se lo fai con colonne combobox, il piccolo pulsante a discesa sarà visibile per tutte le celle in quella colonna, sempre. Non solo quando fai clic sulla cella.
user3690202,

18

Per rendere la risposta di Konstantin Salavatov lavoro con AutoGenerateColumns, aggiungere un gestore di eventi per le DataGrid's AutoGeneratingColumncon il seguente codice:

if (e.Column is DataGridCheckBoxColumn && !e.Column.IsReadOnly)
{
    var checkboxFactory = new FrameworkElementFactory(typeof(CheckBox));
    checkboxFactory.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center);
    checkboxFactory.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
    checkboxFactory.SetBinding(ToggleButton.IsCheckedProperty, new Binding(e.PropertyName) { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });

    e.Column = new DataGridTemplateColumn
        {
            Header = e.Column.Header,
            CellTemplate = new DataTemplate { VisualTree = checkboxFactory },
            SortMemberPath = e.Column.SortMemberPath
        };
}

Ciò renderà modificabili tutte le DataGridcolonne della casella di controllo generate automaticamente "clic singolo".


Grazie per aver compilato un approccio di colonna autogenerato, questo mi indica facilmente una direzione adatta.
el2iot2,

17

Basato sul blog a cui fa riferimento la risposta di Goblin, ma modificato per funzionare in .NET 4.0 e con la modalità di selezione riga.

Si noti che accelera anche la modifica di DataGridComboBoxColumn - accedendo alla modalità di modifica e visualizzando il menu a discesa con un singolo clic o input di testo.

XAML:

        <Style TargetType="{x:Type DataGridCell}">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown" />
            <EventSetter Event="PreviewTextInput" Handler="DataGridCell_PreviewTextInput" />
        </Style>

Code-behind:

    private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        GridColumnFastEdit(cell, e);
    }

    private void DataGridCell_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        GridColumnFastEdit(cell, e);
    }

    private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
    {
        if (cell == null || cell.IsEditing || cell.IsReadOnly)
            return;

        DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
        if (dataGrid == null)
            return;

        if (!cell.IsFocused)
        {
            cell.Focus();
        }

        if (cell.Content is CheckBox)
        {
            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;
                }
            }
        }
        else
        {
            ComboBox cb = cell.Content as ComboBox;
            if (cb != null)
            {
                //DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
                dataGrid.BeginEdit(e);
                cell.Dispatcher.Invoke(
                 DispatcherPriority.Background,
                 new Action(delegate { }));
                cb.IsDropDownOpen = true;
            }
        }
    }


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

Questa soluzione ha funzionato meglio per me. ViewModel associato non si aggiornava con le altre soluzioni.
BrokeMyLegBiking

@surfen, Devo mettere lo stile sopra e il codice in ogni pagina e il suo codice dietro, se ho molte pagine che contengono datagrid. È possibile usare lo stile e il codice in un luogo comune invece di crearlo in ogni pagina
Angel

Perché è necessario inviare un'azione vuota?
user3690202,

@ user3690202 È come DoEvents in Windows.Forms. Dopo aver chiamato BeginEdit è necessario attendere che la cella entri effettivamente nella modalità di modifica.
Jiří Skála,

@ JiříSkála - Non ricordo di aver mai avuto bisogno di farlo nelle mie soluzioni a questo problema, ma capisco cosa stai dicendo - grazie!
user3690202

10

Ho provato questi suggerimenti e molti altri ho trovato su altri siti, ma nessuno di questi ha funzionato abbastanza per me. Alla fine, ho creato la seguente soluzione.

Ho creato il mio controllo ereditato da DataGrid e ho semplicemente aggiunto questo codice:

public class DataGridWithNavigation : Microsoft.Windows.Controls.DataGrid
{
    public DataGridWithNavigation()
    {
        EventManager.RegisterClassHandler(typeof(DataGridCell), 
            DataGridCell.PreviewMouseLeftButtonDownEvent,
            new RoutedEventHandler(this.OnPreviewMouseLeftButtonDown));
    }


    private void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
        {
          DependencyObject obj = FindFirstControlInChildren(cell, "CheckBox");
            if (obj != null)
            {
                System.Windows.Controls.CheckBox cb = (System.Windows.Controls.CheckBox)obj;
                cb.Focus();
                cb.IsChecked = !cb.IsChecked;
            }
        }
    }

    public DependencyObject FindFirstControlInChildren(DependencyObject obj, string controlType)
    {
        if (obj == null)
            return null;

        // Get a list of all occurrences of a particular type of control (eg "CheckBox") 
        IEnumerable<DependencyObject> ctrls = FindInVisualTreeDown(obj, controlType);
        if (ctrls.Count() == 0)
            return null;

        return ctrls.First();
    }

    public IEnumerable<DependencyObject> FindInVisualTreeDown(DependencyObject obj, string type)
    {
        if (obj != null)
        {
            if (obj.GetType().ToString().EndsWith(type))
            {
                yield return obj;
            }

            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                foreach (var child in FindInVisualTreeDown(VisualTreeHelper.GetChild(obj, i), type))
                {
                    if (child != null)
                    {
                        yield return child;
                    }
                }
            }
        }
        yield break;
    }
}

Cosa fa tutto questo?

Bene, ogni volta che facciamo clic su qualsiasi cella nel nostro DataGrid, vediamo se la cella contiene un controllo CheckBox al suo interno. Se lo fa , allora ci concentreremo su quel CheckBox e commuteremo il suo valore .

Questo sembra funzionare per me ed è una soluzione piacevole e facilmente riutilizzabile.

E 'deludente che abbiamo bisogno per scrivere il codice per fare questo però. La spiegazione che il primo clic del mouse (su CheckBox di un DataGrid) viene "ignorata" quando WPF lo usa per mettere la riga in modalità Modifica potrebbe sembrare logica, ma nel mondo reale, questo va contro il modo in cui funziona ogni vera applicazione.

Se un utente vede una casella sullo schermo, dovrebbe essere in grado di fare clic su di essa una volta per spuntarla / deselezionarla. Fine della storia.


1
Grazie, ho provato un sacco di "soluzioni", ma questa è la prima che sembra funzionare davvero ogni volta. E si adatta perfettamente alla mia architettura applicativa.
Guge,

Questa soluzione comporta problemi nell'aggiornamento dell'associazione , mentre qui: wpf.codeplex.com/wikipage?title=Single-Click%20Editing no.
Justin Simon,

2
troppo complicato. vedi la mia risposta. :)
Konstantin Salavatov,

1
Dopo 5 anni questo codice fa ancora risparmiare tempo per la vita sociale :) Per alcuni semplici requisiti, la soluzione @KonstantinSalavatov è sufficiente. Nel mio caso ho mescolato il mio codice con la soluzione di Mike per ottenere un'associazione dinamica di eventi con i gestori, la mia griglia ha un numero dinamico di colonne, con un clic in una cella specifica è necessario memorizzare nel database le modifiche.
Fer R

8

C'è una soluzione molto più semplice qui.

          <DataGridTemplateColumn MinWidth="20" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Grid>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>

Se si utilizza DataGridCheckBoxColumnper implementare, il primo clic è quello di mettere a fuoco, il secondo clic è quello di verificare.

Ma l'utilizzo DataGridTemplateColumnper implementare richiede solo un clic.

Anche la differenza tra l'utilizzo DataGridComboboxBoxColumne l'implementazione DataGridTemplateColumnè simile.


Buona spiegazione per me e ha funzionato all'istante, grazie!
AstralisSomnium,

8

Ho risolto con questo:

<DataGridTemplateColumn>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Viewbox Height="25">
                <CheckBox IsChecked="{Binding TheProperty, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
            </Viewbox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

La casella di controllo attiva con un solo clic!


2
Non avevo bisogno di avvolgere la casella di controllo in un ViewBox, ma questa risposta ha funzionato per me.
JGeerWM,

3
Questa per me è una soluzione molto più pulita della risposta accettata. Non è necessario nemmeno Viewbox. Divertente come funziona meglio della colonna Checkbox definita.
Kenjara,

6

Basati sulla risposta di Jim Adorno e sui commenti sul suo post, questa è la soluzione con MultiTrigger:

<Style TargetType="DataGridCell">
  <Style.Triggers>
    <MultiTrigger>
      <MultiTrigger.Conditions>
    <Condition Property="IsReadOnly" Value="False" />
    <Condition Property="IsMouseOver" Value="True" />
      </MultiTrigger.Conditions>
      <Setter Property="IsEditing" Value="True" />
    </MultiTrigger>
  </Style.Triggers>
</Style>

5

Un'altra soluzione semplice è quella di aggiungere questo stile a DataGridColumn. Il corpo del tuo stile può essere vuoto.

<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>

2
Premendo la barra spaziatrice per selezionare / deselezionare si sposta il CheckBox da sinistra a metà. L'aggiunta di <Setter Property = "HorizontalAlignment" Value = "Center" /> nello stile impedirà lo spostamento di CheckBox.
Yanting, poi

1
<Style x:Key="StilCelula" TargetType="DataGridCell"> 
<Style.Triggers>
 <Trigger Property="IsMouseOver" Value="True">
   <Setter Property="IsEditing" 
     Value="{Binding RelativeSource={x:Static RelativeSource.Self}, 
     Converter={StaticResource CheckBoxColumnToEditingConvertor}}" />
 </Trigger>
</Style.Triggers>
<Style>
Imports System.Globalization
Public Class CheckBoxColumnToEditingConvertor
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert
        Try

            Return TypeOf TryCast(value, DataGridCell).Column Is DataGridCheckBoxColumn
        Catch ex As Exception
            Return Visibility.Collapsed
        End Try
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class
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.