Comment effectuer une sélection de case à cocher en un seul clic dans WPF DataGrid?

143

J'ai un DataGrid avec la première colonne comme colonne de texte et la deuxième colonne comme colonne CheckBox. Ce que je veux, c'est, si je clique sur la case à cocher. Il devrait être vérifié.

Mais, il faut deux clics pour être sélectionné, pour le premier clic, la cellule est sélectionnée, pour le second clic, la case à cocher est cochée. Comment faire la case à cocher pour être cochée / décochée en un seul clic.

J'utilise WPF 4.0. Les colonnes du DataGrid sont générées automatiquement.

Prince Ashitaka
la source
4
Duplicata de: stackoverflow.com/questions/1225836/… , mais celui-ci a un meilleur titre
surfez

Réponses:

189

Pour la case à cocher DataGrid en un seul clic, vous pouvez simplement mettre un contrôle de case à cocher régulier à l'intérieur DataGridTemplateColumnet définir UpdateSourceTrigger=PropertyChanged.

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Konstantin Salavatov
la source
4
WOW - Je suis content d'avoir lu jusqu'au bout. Cela fonctionne parfaitement et est considérablement moins compliqué, l'OMI cela devrait être marqué comme la réponse.
Jusqu'au
2
Cela fonctionne également pour ComboBox. Comme dans: way, WAY mieux que DataGridComboBoxColumn.
user1454265
2
Ce n'est pas le cas lorsque j'utilise la barre d'espace pour cocher / décocher et les flèches pour passer à une autre cellule.
Yola
1
J'ai interprété cette réponse que vous devez lier "IsSelected", mais ce n'est pas vrai! Vous pouvez simplement l'utiliser DataGridTemplateColumn.CellTemplateavec votre propre reliure et cela fonctionnera !! La réponse de @ weidian-huang m'a aidé à comprendre cela, merci!
AstralisSomnium
62

J'ai résolu cela avec le style suivant:

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

Il est bien sûr possible de l'adapter davantage pour des colonnes spécifiques ...

Jim Adorno
la source
8
Agréable. Je l'ai changé en MultiTrigger et ajouté une condition pour ReadOnly = False, mais l'approche de base a fonctionné pour mon cas simple où la navigation au clavier n'est pas importante.
MarcE
L'ajout de ce style à ma grille déclenche une exception d'Opération n'est pas valide lorsque ItemsSource est en cours d'utilisation. Accédez et modifiez les éléments avec ItemsControl.ItemsSource à la place.
Alkampfer
1
C'est la manière la plus propre que j'ai vue jusqu'à présent! Agréable! (pour IsReadOnly = "True", un MultiTrigger fera l'affaire)
FooBarTheLittle
2
Cette solution a un comportement inattendu / indésirable. Voir stackoverflow.com/q/39004317/2881450
jHilscher
2
Pour que la liaison fonctionne, vous aurez besoin d'un UpdateSourceTrigger = PropertyChanged
AQuirky
27

Tout d'abord, je sais que c'est une question assez ancienne, mais je pensais toujours que j'essaierais d'y répondre.

J'ai eu le même problème il y a quelques jours et je suis tombé sur une solution étonnamment courte (voir ce blog ). En gros, tout ce que vous avez à faire est de remplacer la DataGridCheckBoxColumndéfinition dans votre XAML par ce qui suit:

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

L'avantage de cette solution est évident: elle est uniquement XAML; ainsi, il vous empêche de surcharger votre code-back avec une logique d'interface utilisateur supplémentaire et vous aide à maintenir votre statut aux yeux des fanatiques de MVVM;).

Priidu Neemre
la source
1
Ceci est similaire à la réponse de Konstantin Salavatov et celle-ci a fonctionné pour moi. +1 pour inclure l'exemple de code là où il ne l'a pas fait. Merci pour une bonne réponse à une vieille question.
Don Herod
1
Le problème avec ceci est que si vous le faites avec des colonnes de combobox, le petit bouton déroulant sera visible pour toutes les cellules de cette colonne, tout le temps. Pas seulement lorsque vous cliquez sur la cellule.
user3690202
18

Pour que la réponse de Konstantin Salavatov fonctionne avec AutoGenerateColumns, ajoutez un gestionnaire d'événements aux DataGrid's AutoGeneratingColumnavec le code suivant:

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

Cela rendra toutes les DataGridcolonnes de cases à cocher générées automatiquement par "un simple clic" modifiables.

Allon Guralnek
la source
Merci d'avoir rempli une approche de colonne autogénérée, cela m'indique facilement dans une direction appropriée.
el2iot2
17

Basé sur le blog référencé dans la réponse de Goblin, mais modifié pour fonctionner dans .NET 4.0 et avec le mode de sélection de ligne.

Notez qu'il accélère également l'édition de DataGridComboBoxColumn - en entrant en mode d'édition et en affichant la liste déroulante en un seul clic ou en saisie de texte.

XAML:

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

Code derrière:

    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;
    }
surfen
la source
Cette solution a fonctionné le mieux pour moi. Mon ViewModel lié ne se mettait pas à jour avec les autres solutions.
BrokeMyLegBiking
@surfen, Dois-je mettre le style et le code ci-dessus dans chaque page et son code derrière, si j'ai de nombreuses pages contenant une grille de données, est-il possible d'utiliser le style et le code dans un endroit commun au lieu de le créer dans chaque page
Angel
Pourquoi avez-vous besoin d'envoyer une action vide?
user3690202
@ user3690202 C'est comme DoEvents dans Windows.Forms. Après avoir appelé BeginEdit, vous devez attendre que la cellule entre réellement en mode d'édition.
Jiří Skála
@ JiříSkála - Je ne me souviens pas avoir jamais eu besoin de faire cela dans mes solutions à ce problème, mais je comprends ce que vous dites - merci!
user3690202
10

J'ai essayé ces suggestions et beaucoup d'autres que j'ai trouvées sur d'autres sites, mais aucune d'elles n'a vraiment fonctionné pour moi. En fin de compte, j'ai créé la solution suivante.

J'ai créé mon propre contrôle hérité de DataGrid et j'ai simplement ajouté ce code:

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

Que fait tout cela?

Eh bien, chaque fois que nous cliquons sur une cellule de notre DataGrid, nous voyons si la cellule contient un contrôle CheckBox. Si c'est le cas , nous définirons le focus sur cette CheckBox et changerons sa valeur .

Cela semble fonctionner pour moi et c'est une solution agréable et facilement réutilisable.

Il est décevant que nous ayons besoin d'écrire du code pour ce faire. L'explication selon laquelle le premier clic de souris (sur CheckBox d'un DataGrid) est "ignoré" car WPF l'utilise pour mettre la ligne en mode Edition peut sembler logique, mais dans le monde réel, cela va à l'encontre du fonctionnement de chaque application réelle.

Si un utilisateur voit une case à cocher sur son écran, il devrait pouvoir cliquer une fois dessus pour la cocher / décocher. Fin de l'histoire.

Mike Gledhill
la source
1
Merci, j'ai essayé un tas de "solutions", mais c'est la première qui semble vraiment fonctionner à chaque fois. Et cela s'intègre parfaitement dans l'architecture de mon application.
Guge
Cette solution entraîne des problèmes de mise à jour de la liaison, contrairement à celle ici: wpf.codeplex.com/wikipage?title=Single-Click%20Editing .
Justin Simon
2
trop compliqué. voir ma réponse. :)
Konstantin Salavatov
1
Après 5 ans, ce code permet encore de gagner du temps pour la vie sociale :) Pour certaines exigences simples, la solution @KonstantinSalavatov suffit. Dans mon cas, j'ai mélangé mon code avec la solution de Mike pour obtenir une association d'événements dynamiques avec des gestionnaires, ma grille a un nombre dynamique de colonnes, avec un clic dans une cellule spécifique doit stocker dans la base de données les modifications.
Fer R
8

Il existe ici une solution beaucoup plus simple.

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

Si vous utilisez DataGridCheckBoxColumnpour mettre en œuvre, le premier clic est de se concentrer, le deuxième clic est de vérifier.

Mais utiliser DataGridTemplateColumnpour mettre en œuvre ne nécessite qu'un seul clic.

La différence d'utilisation DataGridComboboxBoxColumnet de mise en œuvre par DataGridTemplateColumnest également similaire.

Weidian Huang
la source
Bonne explication pour moi et a fonctionné instantanément, merci!
AstralisSomnium
8

J'ai résolu avec ceci:

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

La case à cocher active en un seul clic!

Darlan Dieterich
la source
2
Je n'avais pas besoin d'encapsuler la case à cocher dans une ViewBox, mais cette réponse a fonctionné pour moi.
JGeerWM
3
C'est pour moi une solution beaucoup plus propre que la réponse acceptée. Pas besoin de Viewbox non plus. C'est drôle comment cela fonctionne mieux que la colonne Checkbox définie.
kenjara
6

Basé sur la réponse de Jim Adorno et les commentaires sur son message, voici une solution avec 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>
Rafal Spacjer
la source
5

Une autre solution simple consiste à ajouter ce style à votre DataGridColumn. Le corps de votre style peut être vide.

<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
AmirHossein Rezaei
la source
2
Appuyez sur la barre d'espace pour cocher / décocher la case à cocher de la gauche vers le milieu. L'ajout de <Setter Property = "HorizontalAlignment" Value = "Center" /> dans le style empêchera le CheckBox de se déplacer.
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
TotPeRo
la source