¿Cómo realizar la selección de la casilla de verificación de un solo clic en WPF DataGrid?

143

Tengo un DataGrid con la primera columna como columna de texto y la segunda columna como columna CheckBox. Lo que quiero es, si hago clic en la casilla de verificación. Debería ser revisado.

Sin embargo, se requieren dos clics para ser seleccionado, para el primer clic se selecciona la celda, para los segundos clics se marca la casilla de verificación. Cómo hacer que la casilla de verificación se marque / desmarque con un solo clic.

Estoy usando WPF 4.0. Las columnas en la cuadrícula de datos se autogeneran.

Príncipe Ashitaka
fuente
44
Duplicado de: stackoverflow.com/questions/1225836/… , pero este tiene un mejor título
surfen

Respuestas:

189

Para la casilla de verificación DataGrid de un solo clic, simplemente puede colocar el control de casilla de verificación regular dentro DataGridTemplateColumny configurar UpdateSourceTrigger=PropertyChanged.

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Konstantin Salavatov
fuente
44
WOW - Me alegro de haber leído hasta el final. Esto funciona perfectamente y es considerablemente menos complicado, en mi opinión, esto debería marcarse como la respuesta.
Tod
2
Esto también funciona para ComboBox. Como en: way, MUCHO mejor que DataGridComboBoxColumn.
user1454265
2
No lo hace cuando uso la barra espaciadora para marcar / desmarcar y las flechas para moverme a otra celda.
Yola
1
He interpretado esta respuesta de que debes vincular "IsSelected", ¡pero eso no es cierto! ¡Puede usarlo DataGridTemplateColumn.CellTemplatecon su propio enlace y funcionará! La respuesta de @ weidian-huang me ayudó a entender eso, ¡gracias!
AstralisSomnium
62

Resolví esto con el siguiente estilo:

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

Por supuesto, es posible adaptar esto aún más para columnas específicas ...

Jim Adorno
fuente
8
Agradable. Lo cambié a MultiTrigger y agregué una condición para ReadOnly = False, pero el enfoque básico funcionó para mi caso simple donde la navegación por teclado no es importante.
MarcE
Agregar ese estilo a mi grilla genera una excepción de Operación no válida mientras ItemsSource está en uso. Acceda y modifique elementos con ItemsControl.ItemsSource en su lugar.
Alkampfer
1
¡Esta es la forma más limpia que he visto hasta ahora! ¡Agradable! (para IsReadOnly = "True" un MultiTrigger hará el trabajo)
FooBarTheLittle
2
Esta solución tiene un comportamiento inesperado / no deseado. Ver stackoverflow.com/q/39004317/2881450
jHilscher
2
Para que el enlace funcione, necesitará un UpdateSourceTrigger = PropertyChanged
AQuirky el
27

En primer lugar, sé que esta es una pregunta bastante antigua, pero todavía pensé que intentaría responderla.

Tuve el mismo problema hace un par de días y encontré una solución sorprendentemente corta (ver este blog ). Básicamente, todo lo que necesita hacer es reemplazar la DataGridCheckBoxColumndefinición en su XAML con lo siguiente:

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

La ventaja de esta solución es obvia: es solo XAML; por lo tanto, evita que cargue su código con lógica UI adicional y lo ayuda a mantener su estado ante los fanáticos de MVVM;).

Priidu Neemre
fuente
1
Esto es similar a la respuesta de Konstantin Salavatov y esta funcionó para mí. +1 por incluir el ejemplo de código donde el suyo no lo hizo. Gracias por una buena respuesta a una vieja pregunta.
Don Herodes
1
El problema con esto es que si lo hace con columnas de cuadro combinado, el pequeño botón desplegable estará visible para todas las celdas de esa columna, todo el tiempo. No solo cuando haces clic en la celda.
user3690202
18

Para que la respuesta de Konstantin Salavatov trabajo con AutoGenerateColumns, agregar un controlador de eventos para los DataGrid's AutoGeneratingColumncon el siguiente código:

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

Esto hará que todas DataGridlas columnas de casillas de verificación autogeneradas sean editables con un solo clic.

Allon Guralnek
fuente
Gracias por completar un enfoque de columna autogenerado, esto me señala fácilmente en una dirección adecuada.
el2iot2
17

Basado en el blog al que se hace referencia en la respuesta de Goblin, pero modificado para funcionar en .NET 4.0 y con el modo de selección de filas.

Tenga en cuenta que también acelera la edición de DataGridComboBoxColumn, al ingresar al modo de edición y mostrar el menú desplegable con un solo clic o entrada de texto.

XAML:

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

Código detrás:

    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
fuente
Esta solución funcionó mejor para mí. Mi ViewModel vinculado no se estaba actualizando con las otras soluciones.
BrokeMyLegBiking
@surfen, ¿tengo que poner el estilo anterior y el código en cada página y su código detrás, si tengo muchas páginas que tienen una cuadrícula de datos? ¿Es posible usar el estilo y el código en un lugar común en lugar de crearlo en cada página
Angel
¿Por qué necesita enviar una acción vacía?
user3690202
@ user3690202 Es como DoEvents en Windows.Forms. Después de llamar a BeginEdit, debe esperar a que la celda ingrese realmente al modo de edición.
Jiří Skála
@ JiříSkála: no recuerdo haber tenido que hacer esto en mis soluciones a este problema, pero entiendo lo que dices, ¡gracias!
user3690202
10

He intentado estas sugerencias y muchas otras que he encontrado en otros sitios, pero ninguna de ellas me funcionó. Al final, creé la siguiente solución.

Creé mi propio control heredado de DataGrid y simplemente le agregué este código:

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

¿Qué hace todo esto?

Bueno, cada vez que hacemos clic en cualquier celda de nuestro DataGrid, vemos si la celda contiene un control CheckBox dentro de ella. Si lo hace , a continuación, vamos a establecer el foco a esa CheckBox y alternar su valor .

Esto parece funcionar para mí, y es una buena solución, fácilmente reutilizable.

Sin embargo, es decepcionante que necesitemos escribir código para hacer esto. La explicación de que el primer clic del mouse (en un CheckBox de DataGrid) se "ignora" a medida que WPF lo usa para poner la fila en modo Edición puede sonar lógico, pero en el mundo real, esto va en contra de la forma en que funciona cada aplicación real.

Si un usuario ve una casilla de verificación en su pantalla, debería poder hacer clic en ella una vez para marcarla o desmarcarla. Fin de la historia.

Mike Gledhill
fuente
1
Gracias, he probado un montón de "soluciones", pero esta es la primera que parece funcionar realmente cada vez. Y encaja perfectamente en la arquitectura de mi aplicación.
Guge
Esta solución genera problemas al actualizar el enlace, mientras que el que está aquí: wpf.codeplex.com/wikipage?title=Single-Click%20Editing no lo hace.
Justin Simon
2
demasiado complicado. mira mi respuesta :)
Konstantin Salavatov
1
Después de 5 años, este código sigue ahorrando tiempo para la vida social :) Para algunos requisitos simples, la solución @KonstantinSalavatov es suficiente. En mi caso mezclé mi código con la solución de Mike para lograr una asociación dinámica de eventos con controladores, mi cuadrícula tiene un número dinámico de columnas, con un clic en la celda específica debe almacenar en la base de datos los cambios.
Fer R
8

Hay una solución mucho más simple aquí.

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

Si utiliza DataGridCheckBoxColumnpara implementar, el primer clic es para enfocar, el segundo clic es para verificar.

Pero usar DataGridTemplateColumnpara implementar solo necesita un clic.

La diferencia de uso DataGridComboboxBoxColumne implementación por DataGridTemplateColumntambién es similar.

Weidian Huang
fuente
Buena explicación para mí y funcionó al instante, ¡gracias!
AstralisSomnium
8

Resolví con esto:

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

¡La casilla de verificación activa con un solo clic!

Darlan Dieterich
fuente
2
No necesitaba envolver la casilla de verificación en un ViewBox, pero esta respuesta funcionó para mí.
JGeerWM
3
Esto para mí es una solución mucho más limpia que la respuesta aceptada. No hay necesidad de Viewbox tampoco. Es curioso cómo funciona esto mejor que la columna de casilla de verificación definida.
kenjara
6

Base en la respuesta de Jim Adorno y comentarios en su publicación, esta es la solución 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>
Rafal Spacjer
fuente
5

Otra solución simple es agregar este estilo a su DataGridColumn. El cuerpo de su estilo puede estar vacío.

<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
AmirHossein Rezaei
fuente
2
Al presionar la barra espaciadora para marcar / desmarcar, la casilla de verificación se moverá de la izquierda al centro. Agregar <Setter Property = "HorizontalAlignment" Value = "Center" /> en el estilo evitará que el CheckBox se mueva.
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
fuente