Wie führe ich die Auswahl eines Kontrollkästchens mit einem Klick in WPF DataGrid durch?


143

Ich habe ein DataGrid mit der ersten Spalte als Textspalte und der zweiten Spalte als CheckBox-Spalte. Was ich will ist, wenn ich das Kontrollkästchen anklicke. Es sollte überprüft werden.

Die Auswahl dauert jedoch zwei Klicks. Beim ersten Klick wird die Zelle ausgewählt, beim zweiten Klick wird das Kontrollkästchen aktiviert. So aktivieren Sie das Kontrollkästchen, um mit einem einzigen Klick aktiviert / deaktiviert zu werden.

Ich benutze WPF 4.0. Spalten im DataGrid werden automatisch generiert.


4
Duplikat von: stackoverflow.com/questions/1225836/… , aber dieser hat einen besseren Titel
surfen

Antworten:


189

Für das Single-Click-Kontrollkästchen DataGrid können Sie einfach das reguläre Kontrollkästchen steuern DataGridTemplateColumnund festlegen UpdateSourceTrigger=PropertyChanged.

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

4
WOW - ich bin froh, dass ich bis zum Ende gelesen habe. Dies funktioniert perfekt und ist wesentlich unkomplizierter. IMO sollte dies als Antwort markiert werden.
Tod

2
Dies funktioniert auch für ComboBox. Wie in: Weise, viel besser als DataGridComboBoxColumn.
user1454265

2
Es funktioniert nicht, wenn ich die Leertaste zum Aktivieren / Deaktivieren und die Pfeile zum Verschieben in eine andere Zelle verwende.
Yola

1
Ich habe diese Antwort so interpretiert, dass Sie "IsSelected" binden müssen, aber das ist nicht wahr! Sie können es einfach DataGridTemplateColumn.CellTemplatemit Ihrer eigenen Bindung verwenden und es wird funktionieren !! Die Antwort von @ weidian-huang hat mir geholfen, das zu verstehen, danke!
AstralisSomnium

62

Ich habe dies mit folgendem Stil gelöst:

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

Es ist natürlich möglich, dies für bestimmte Spalten weiter anzupassen ...


8
Nett. Ich habe es in einen MultiTrigger geändert und eine Bedingung für ReadOnly = False hinzugefügt, aber der grundlegende Ansatz funktionierte für meinen einfachen Fall, in dem die Tastaturnavigation nicht wichtig ist.
MarcE

Das Hinzufügen dieses Stils zu meinem Raster löst eine Ausnahme von Operation aus, die nicht gültig ist, während ItemsSource verwendet wird. Greifen Sie stattdessen mit ItemsControl.ItemsSource auf Elemente zu und ändern Sie sie.
Alkampfer

1
Dies ist der sauberste Weg, den ich bisher gesehen habe! Nett! (für IsReadOnly = "True" erledigt ein MultiTrigger die Arbeit)
FooBarTheLittle

2
Diese Lösung weist ein unerwartetes / unerwünschtes Verhalten auf. Siehe stackoverflow.com/q/39004317/2881450
jHilscher

2
Damit die Bindung funktioniert, benötigen Sie einen UpdateSourceTrigger = PropertyChanged
AQuirky

27

Zunächst weiß ich, dass dies eine ziemlich alte Frage ist, aber ich dachte immer noch, ich würde versuchen, sie zu beantworten.

Ich hatte vor ein paar Tagen das gleiche Problem und fand eine überraschend kurze Lösung dafür (siehe diesen Blog ). Grundsätzlich müssen Sie lediglich die DataGridCheckBoxColumnDefinition in Ihrer XAML durch Folgendes ersetzen :

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

Der Vorteil dieser Lösung liegt auf der Hand - sie ist nur für XAML verfügbar. Auf diese Weise wird verhindert, dass Sie Ihren Code-Back mit zusätzlicher UI-Logik belasten, und Sie können Ihren Status in den Augen von MVVM-Eiferern beibehalten;).


1
Dies ähnelt der Antwort von Konstantin Salavatov und diese hat für mich funktioniert. +1 für das Einfügen des Codebeispiels, wo dies nicht der Fall war. Vielen Dank für eine gute Antwort auf eine alte Frage.
Don Herod

1
Das Problem dabei ist, dass, wenn Sie dies mit Combobox-Spalten tun, die kleine Dropdown-Schaltfläche jederzeit für alle Zellen in dieser Spalte sichtbar ist. Nicht nur, wenn Sie auf die Zelle klicken.
user3690202

18

Um Konstantin Salavatov Antwort mit der Arbeit AutoGenerateColumns, fügen Sie einen Event - Handler auf den DataGrid‚s AutoGeneratingColumnmit dem folgenden Code:

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

Dadurch können alle DataGridautomatisch generierten Kontrollkästchenspalten mit einem Klick bearbeitet werden.


Vielen Dank, dass Sie einen automatisch generierten Säulenansatz ausgefüllt haben. Dies weist mich leicht in eine geeignete Richtung.
el2iot2

17

Basierend auf einem Blog, auf das in Goblins Antwort verwiesen wird, das jedoch so geändert wurde, dass es in .NET 4.0 und im Zeilenauswahlmodus funktioniert.

Beachten Sie, dass es auch die Bearbeitung von DataGridComboBoxColumn beschleunigt - indem Sie in den Bearbeitungsmodus wechseln und Dropdown-Listen bei Einzelklick oder Texteingabe anzeigen.

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

Diese Lösung hat bei mir am besten funktioniert. Mein gebundenes ViewModel wurde nicht mit den anderen Lösungen aktualisiert.
BrokeMyLegBiking

@surfen, Muss ich den obigen Stil und den Code in jede Seite und ihren Code einfügen, wenn ich viele Seiten habe, die Datagrid enthalten. Ist es möglich, den Stil und den Code an einem gemeinsamen Ort zu verwenden, anstatt ihn zu erstellen? jede Seite
Angel

Warum müssen Sie eine leere Aktion versenden?
user3690202

@ user3690202 Es ist wie DoEvents in Windows.Forms. Nach dem Aufruf von BeginEdit müssen Sie warten, bis die Zelle tatsächlich in den Bearbeitungsmodus wechselt.
Jiří Skála

@ JiříSkála - Ich erinnere mich nicht, dass ich dies jemals in meinen Lösungen für dieses Problem tun musste, aber ich verstehe, was Sie sagen - danke!
user3690202

10

Ich habe diese Vorschläge ausprobiert und viele andere, die ich auf anderen Websites gefunden habe, aber keiner von ihnen hat für mich ganz funktioniert. Am Ende habe ich die folgende Lösung erstellt.

Ich habe mein eigenes DataGrid-geerbtes Steuerelement erstellt und einfach diesen Code hinzugefügt:

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

Was macht das alles?

Nun, jedes Mal, wenn wir auf eine Zelle in unserem DataGrid klicken, sehen wir, ob die Zelle ein CheckBox-Steuerelement enthält. Wenn dies der Fall ist , setzen wir den Fokus auf diese CheckBox und schalten ihren Wert um .

Dies scheint für mich zu funktionieren und ist eine schöne, leicht wiederverwendbare Lösung.

Es ist enttäuschend , dass wir brauchen , um Code schreiben dies aber zu tun. Die Erklärung, dass der erste Mausklick (auf das Kontrollkästchen eines DataGrid) "ignoriert" wird, da WPF die Zeile damit in den Bearbeitungsmodus versetzt, mag logisch klingen, aber in der realen Welt widerspricht dies der Funktionsweise jeder realen Anwendung.

Wenn ein Benutzer ein Kontrollkästchen auf seinem Bildschirm sieht, sollte er einmal darauf klicken können, um es anzukreuzen / zu deaktivieren. Ende der Geschichte.


1
Danke, ich habe eine Reihe von "Lösungen" ausprobiert, aber dies ist die erste, die jedes Mal wirklich zu funktionieren scheint. Und es passt wunderbar in meine Anwendungsarchitektur.
Guge

Diese Lösung führt zu Problemen beim Aktualisieren der Bindung, während dies hier nicht der Fall ist : wpf.codeplex.com/wikipage?title=Single-Click%20Editing .
Justin Simon

2
zu kompliziert. siehe meine Antwort. :)
Konstantin Salavatov

1
Nach 5 Jahren spart dieser Code immer noch Zeit für das soziale Leben :) Für einige einfache Anforderungen reicht die Lösung @KonstantinSalavatov aus. In meinem Fall habe ich meinen Code mit Mikes Lösung gemischt, um eine dynamische Ereigniszuordnung mit Handlern zu erreichen. Mein Raster hat eine dynamische Anzahl von Spalten. Mit einem Klick in einer bestimmten Zelle müssen die Änderungen in der Datenbank gespeichert werden.
Fer R

8

Hier gibt es eine viel einfachere Lösung.

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

Wenn Sie DataGridCheckBoxColumnzum Implementieren verwenden, dient der erste Klick zum Fokussieren und der zweite Klick zum Überprüfen.

Für die DataGridTemplateColumnImplementierung ist jedoch nur ein Klick erforderlich.

Der Unterschied zwischen Verwendung DataGridComboboxBoxColumnund Implementierung durch DataGridTemplateColumnist ebenfalls ähnlich.


Gute Erklärung für mich und hat sofort funktioniert, danke!
AstralisSomnium

8

Ich habe damit gelöst:

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

Das Kontrollkästchen ist mit einem Klick aktiv!


2
Ich musste das Kontrollkästchen nicht in eine ViewBox einbinden, aber diese Antwort funktionierte für mich.
JGeerWM

3
Dies ist für mich eine viel sauberere Lösung als die akzeptierte Antwort. Viewbox ist ebenfalls nicht erforderlich. Komisch, wie das besser funktioniert als die definierte Checkbox-Spalte.
Kenjara

6

Basierend auf Jim Adornos Antwort und Kommentaren zu seinem Beitrag ist dies eine Lösung mit 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

Eine weitere einfache Lösung besteht darin, diesen Stil zu Ihrer DataGridColumn hinzuzufügen. Der Textkörper Ihres Stils kann leer sein.

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

2
Durch Drücken der Leertaste zum Aktivieren / Deaktivieren wird das Kontrollkästchen von links in die Mitte verschoben. Durch Hinzufügen von <Setter Property = "HorizontalAlignment" Value = "Center" /> im Stil wird verhindert, dass sich das Kontrollkästchen bewegt.
YantingChen

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.