Como executar a seleção da caixa de seleção Clique único no WPF DataGrid?

143

Eu tenho um DataGrid com primeira coluna como coluna de texto e segunda coluna como coluna CheckBox. O que eu quero é se eu clicar na caixa de seleção. Deve ser verificado.

Porém, são necessários dois cliques para serem selecionados; para o primeiro clique, a célula está sendo selecionada; para os segundos cliques, a caixa de seleção está sendo marcada. Como fazer com que a caixa de seleção seja marcada / desmarcada com um único clique.

Estou usando o WPF 4.0. As colunas no DataGrid são geradas automaticamente.

Prince Ashitaka
fonte
4
Duplicado de: stackoverflow.com/questions/1225836/… , mas este possui um título melhor
surfen 20/12/11

Respostas:

189

Para a caixa de seleção DataGrid com um único clique, basta colocar o controle regular da caixa de seleção dentro DataGridTemplateColumne definir UpdateSourceTrigger=PropertyChanged.

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Konstantin Salavatov
fonte
4
WOW - Estou feliz que li até o fim. Isso funciona perfeitamente e é consideravelmente menos complicado; na IMO, isso deve ser marcado como resposta.
Tod
2
Isso também funciona para o ComboBox. Como em: way, MUITO melhor que DataGridComboBoxColumn.
user1454265
2
Não quando eu uso a barra de espaço para marcar / desmarcar e setas para mover para outra célula.
Yola
1
Eu interpretei esta resposta que você precisa vincular "IsSelected", mas isso não é verdade! Você pode simplesmente usar DataGridTemplateColumn.CellTemplatecom o seu próprio Binding e ele funcionará !! A resposta de @ weidian-huang me ajudou a entender isso, obrigado!
AstralisSomnium
62

Eu resolvi isso com o seguinte estilo:

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

É claro que é possível adaptar isso ainda mais a colunas específicas ...

Jim Adorno
fonte
8
Agradável. Mudei para um MultiTrigger e adicionei uma condição para ReadOnly = False, mas a abordagem básica funcionou no meu caso simples, em que a navegação pelo teclado não é importante.
MarcE
A adição desse estilo à minha grade gera uma exceção de Operation não é válida enquanto ItemsSource estiver em uso. Acesse e modifique elementos com ItemsControl.ItemsSource.
Alkampfer
1
Esta é a maneira mais limpa que eu já vi até agora! Agradável! (para IsReadOnly = "True" um MultiTrigger irá fazer o trabalho)
FooBarTheLittle
2
Esta solução tem algum comportamento inesperado / indesejado. Veja stackoverflow.com/q/39004317/2881450
jHilscher
2
Para que a ligação funcione, você precisará de um UpdateSourceTrigger = PropertyChanged
AQuirky
27

Primeiro, eu sei que essa é uma pergunta bastante antiga, mas ainda pensei em tentar responder.

Eu tive o mesmo problema há alguns dias e me deparei com uma solução surpreendentemente curta para isso (consulte este blog ). Basicamente, tudo que você precisa fazer é substituir a DataGridCheckBoxColumndefinição no seu XAML pelo seguinte:

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

A vantagem desta solução é óbvia - é somente XAML; assim, evita que você sobrecarregue seu código de retorno com lógica adicional da interface do usuário e ajuda a manter seu status aos olhos dos fanáticos do MVVM;).

Priidu Neemre
fonte
1
Isso é semelhante à resposta de Konstantin Salavatov e esta funcionou para mim. +1 por incluir o exemplo de código onde o dele não o fez. Obrigado por uma boa resposta a uma pergunta antiga.
Don Herod
1
O problema é que, se você fizer isso com colunas da caixa de combinação, o pequeno botão suspenso ficará visível para todas as células dessa coluna, o tempo todo. Não apenas quando você clica na célula.
user3690202
18

Para tornar a resposta de Konstantin Salavatov trabalho com AutoGenerateColumns, adicionar um manipulador de eventos para os DataGrid's AutoGeneratingColumncom o seguinte 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
        };
}

Isso fará com que todas DataGridas colunas de caixa de seleção geradas automaticamente sejam editáveis ​​com "único clique".

Allon Guralnek
fonte
Obrigado por preencher uma abordagem de coluna gerada automaticamente, isso prontamente me indica uma direção adequada.
el2iot2
17

Baseado no blog mencionado na resposta do Goblin, mas modificado para funcionar no .NET 4.0 e com o Modo de Seleção de Linha.

Observe que ele também acelera a edição do DataGridComboBoxColumn - entrando no modo de edição e exibindo a lista suspensa com um único clique ou entrada de texto.

XAML:

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

Código por trá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
fonte
Esta solução funcionou melhor para mim. Meu ViewModel vinculado não estava sendo atualizado com as outras soluções.
BrokeMyLegBiking
@surfen, Preciso colocar o estilo acima e o código em todas as páginas e seu código por trás, se houver muitas páginas que contenham dados.É possível usar o estilo e o código em um local comum em vez de criá-lo em todas as páginas
Angel
Por que você precisa despachar uma ação vazia?
user3690202
@ user3690202 É como DoEvents no Windows.Forms. Depois de chamar o BeginEdit, é necessário aguardar a célula realmente entrar no modo de edição.
Jiří Skála
@ JiříSkála - Não me lembro de precisar fazer isso nas minhas soluções para esse problema, mas entendo o que você está dizendo - obrigado!
user3690202
10

Eu tentei essas sugestões e muitas outras que encontrei em outros sites, mas nenhuma delas funcionou para mim. No final, criei a seguinte solução.

Eu criei meu próprio controle herdado do DataGrid e simplesmente adicionei esse código a ele:

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

O que tudo isso faz?

Bem, cada vez que clicamos em qualquer célula do nosso DataGrid, vemos se a célula contém um controle CheckBox dentro dela. Se isso acontecer , definiremos o foco para essa caixa de seleção e alternaremos seu valor .

Isso parece funcionar para mim e é uma solução agradável e facilmente reutilizável.

É decepcionante que precisamos escrever código para fazer isso. A explicação de que o primeiro clique do mouse (na caixa de seleção de um DataGrid) é "ignorada", pois o WPF a utiliza para colocar a linha no modo de edição pode parecer lógica, mas no mundo real, isso contraria o modo como todos os aplicativos reais funcionam.

Se um usuário vir uma caixa de seleção na tela, ele poderá clicar nela uma vez para marcar / desmarcar. Fim da história.

Mike Gledhill
fonte
1
Obrigado, tentei várias "soluções", mas esta é a primeira que parece realmente funcionar sempre. E se encaixa perfeitamente na minha arquitetura de aplicativos.
Guge
Essa solução resulta em problemas na atualização da ligação, enquanto a solução aqui: wpf.codeplex.com/wikipage?title=Single-Click%20Editing não.
Justin Simon
2
muito complicado. veja minha resposta. :)
Konstantin Salavatov 4/11
1
Após 5 anos, esse código ainda economiza tempo para a vida social :) Para alguns requisitos simples, a solução @KonstantinSalavatov é suficiente. No meu caso, misturei meu código com a solução de Mike para obter associação dinâmica de eventos com manipuladores, minha grade possui um número dinâmico de colunas, com um clique em uma célula específica para armazenar no banco de dados as alterações.
Fer R
8

Existe uma solução muito mais simples aqui.

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

Se você costuma DataGridCheckBoxColumnimplementar, o primeiro clique é o foco, o segundo clique é o de verificar.

Mas usar DataGridTemplateColumnpara implementar precisa apenas de um clique.

A diferença de uso DataGridComboboxBoxColumne implementação DataGridTemplateColumntambém é semelhante.

Weidian Huang
fonte
Boa explicação para mim e funcionou instantaneamente, obrigado!
AstralisSomnium
8

Eu resolvi com isso:

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

A caixa de seleção ativa em um único clique!

Darlan Dieterich
fonte
2
Não precisei envolver a caixa de seleção em um ViewBox, mas essa resposta funcionou para mim.
JGeerWM 28/03
3
Isso para mim é uma solução muito mais limpa do que a resposta aceita. Também não é necessário o Viewbox. Engraçado como isso funciona melhor do que a coluna da caixa de seleção definida.
Kenjara
6

Com base na resposta de Jim Adorno e comentários em seu post, esta é a solução com 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
fonte
5

Outra solução simples é adicionar esse estilo ao seu DataGridColumn. O corpo do seu estilo pode estar vazio.

<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
AmirHossein Rezaei
fonte
2
Pressionar a barra de espaço para marcar / desmarcar moverá a CheckBox da esquerda para o meio. Adicionar <Setter Property = "HorizontalAlignment" Value = "Center" /> no estilo impedirá que o CheckBox se mova.
precisa saber é o seguinte
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
fonte