Bagaimana cara melakukan pemilihan kotak centang klik tunggal di WPF DataGrid?

143

Saya memiliki DataGrid dengan kolom pertama sebagai kolom teks dan kolom kedua sebagai kolom CheckBox. Yang saya inginkan adalah, jika saya mengklik kotak centang. Itu harus diperiksa.

Tetapi, dibutuhkan dua klik untuk dipilih, untuk klik pertama sel dipilih, untuk klik kedua kotak centang dicentang. Cara membuat kotak centang untuk dicentang / tidak dicentang dengan satu klik.

Saya menggunakan WPF 4.0. Kolom di dalam DataGrid dibuat Otomatis.

Pangeran Ashitaka
sumber
4
Duplikat dari: stackoverflow.com/questions/1225836/… , tetapi yang ini memiliki judul yang lebih baik
surfen

Jawaban:

189

Untuk kotak centang DataGrid klik tunggal, Anda bisa memasukkan kontrol kotak centang biasa ke dalam DataGridTemplateColumndan mengatur UpdateSourceTrigger=PropertyChanged.

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Konstantin Salavatov
sumber
4
WOW - Saya senang saya membaca sampai akhir. Ini berfungsi dengan baik dan tidak terlalu rumit, IMO ini harus ditandai sebagai jawabannya.
Tod
2
Ini juga berfungsi untuk ComboBox. Seperti dalam: cara, CARA lebih baik daripada DataGridComboBoxColumn.
user1454265
2
Tidak ketika saya menggunakan spasi untuk memeriksa / menghapus centang dan panah untuk pindah ke sel lain.
Yola
1
Saya telah menafsirkan jawaban ini bahwa Anda harus Mengikat "Terpilih", tetapi itu tidak benar! Anda hanya dapat menggunakan DataGridTemplateColumn.CellTemplatedengan Binding Anda sendiri dan itu akan berhasil !! Jawaban dari @ weidian-huang membantu saya untuk memahami itu, terima kasih!
AstralisSomnium
62

Saya memecahkan ini dengan Style berikut:

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

Tentu saja mungkin untuk mengadaptasi ini lebih lanjut untuk kolom tertentu ...

Jim Adorno
sumber
8
Bagus. Saya mengubahnya menjadi MultiTrigger dan menambahkan kondisi untuk ReadOnly = False tetapi pendekatan dasar bekerja untuk kasus sederhana saya di mana navigasi keyboard tidak penting.
MarcE
Menambahkan gaya itu ke kisi saya meningkatkan pengecualian Operasi tidak valid saat ItemsSource sedang digunakan. Mengakses dan memodifikasi elemen dengan ItemsControl.ItemsSource sebagai gantinya.
Alkampfer
1
Ini adalah cara terbersih yang pernah saya lihat sejauh ini! Bagus! (untuk IsReadOnly = "Benar", MultiTrigger akan melakukan pekerjaan itu)
FooBarTheLittle
2
Solusi ini memiliki beberapa perilaku tak terduga / tidak diinginkan. Lihat stackoverflow.com/q/39004317/2881450
jHilscher
2
Agar pengikatan berfungsi, Anda memerlukan UpdateSourceTrigger = PropertyChanged
AQuirky
27

Pertama, saya tahu ini adalah pertanyaan yang cukup lama tetapi saya masih berpikir saya akan mencoba dan menjawabnya.

Saya memiliki masalah yang sama beberapa hari yang lalu dan menemukan solusi singkat yang mengejutkan untuk itu (lihat blog ini ). Pada dasarnya, yang perlu Anda lakukan adalah mengganti DataGridCheckBoxColumndefinisi di XAML Anda dengan yang berikut:

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

Kelebihan dari solusi ini jelas - hanya XAML; sehingga secara efektif menahan Anda dari membebani kode-kembali Anda dengan logika UI tambahan dan membantu Anda mempertahankan status Anda di mata MVVM fanatik;).

Priidu Neemre
sumber
1
Ini mirip dengan jawaban Konstantin Salavatov dan yang ini berhasil untuk saya. +1 untuk menyertakan contoh kode di mana kode-nya tidak. Terima kasih atas jawaban yang bagus untuk pertanyaan lama.
Don Herod
1
Masalah dengan ini adalah bahwa jika Anda melakukannya dengan kolom combobox, tombol dropdown kecil akan terlihat untuk semua sel di kolom itu, sepanjang waktu. Bukan hanya ketika Anda mengklik sel.
user3690202
18

Untuk membuat jawaban Konstantin Salavatov bekerja dengan AutoGenerateColumns, tambahkan pengendali acara ke DataGrid's AutoGeneratingColumndengan kode berikut:

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

Ini akan membuat semua DataGridkolom kotak centang yang dibuat secara otomatis menjadi "satu klik" dapat diedit.

Allon Guralnek
sumber
Terima kasih telah mengisi pendekatan kolom autogenerated, ini dengan mudah menunjukkan saya ke arah yang sesuai.
el2iot2
17

Didasarkan pada blog yang dirujuk dalam jawaban Goblin, tetapi dimodifikasi agar berfungsi di .NET 4.0 dan dengan Mode Pemilihan Baris.

Perhatikan bahwa itu juga mempercepat pengeditan DataGridComboBoxColumn - dengan memasukkan mode edit dan menampilkan dropdown pada klik tunggal atau input teks.

XAML:

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

Kode di belakang:

    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;
    }
berselancar
sumber
Solusi ini bekerja paling baik untuk saya. ViewModel terikat saya tidak memperbarui dengan solusi lain.
BrokeMyLegBiking
@surfen, Apakah saya harus meletakkan style di atas dan kode di setiap halaman dan codebehind-nya, jika saya memiliki banyak halaman yang memiliki datagrid di dalamnya. Apakah mungkin menggunakan style dan kode di tempat yang umum daripada membuatnya di setiap halaman
Malaikat
Mengapa Anda perlu mengirim Tindakan kosong?
user3690202
@ user3690202 Ini seperti DoEvents di Windows.Forms. Setelah memanggil BeginEdit Anda harus menunggu sel untuk benar-benar memasuki mode edit.
Jiří Skála
@ JiříSkála - Saya tidak ingat pernah perlu melakukan ini dalam solusi saya untuk masalah ini, tapi saya mengerti apa yang Anda katakan - terima kasih!
user3690202
10

Saya sudah mencoba saran-saran ini, dan banyak yang lain yang saya temukan di situs lain, tetapi tidak ada yang bekerja untuk saya. Pada akhirnya, saya menciptakan solusi berikut.

Saya telah membuat kontrol yang diwarisi DataGrid saya sendiri, dan cukup menambahkan kode ini ke dalamnya:

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

Apa yang dilakukan semua ini?

Nah, setiap kali kita mengklik sel mana pun di DataGrid kita, kita melihat apakah sel itu berisi kontrol kotak centang di dalamnya. Jika tidak , maka kita akan mengatur fokus ke yang kotak centang dan beralih nilai itu .

Ini sepertinya bekerja untuk saya, dan merupakan solusi yang bagus dan mudah digunakan kembali.

Sangat mengecewakan bahwa kita perlu menulis kode untuk melakukan ini. Penjelasan bahwa klik mouse pertama (pada kotak centang DataGrid) "diabaikan" karena WPF menggunakannya untuk memasukkan baris ke mode Edit mungkin terdengar logis, tetapi di dunia nyata, ini bertentangan dengan cara setiap aplikasi nyata bekerja.

Jika pengguna melihat kotak centang di layar mereka, mereka harus dapat mengkliknya sekali untuk mencentang / menghapusnya. Akhir dari cerita.

Mike Gledhill
sumber
1
Terima kasih, saya sudah mencoba banyak "solusi", tetapi ini adalah yang pertama yang tampaknya benar-benar berfungsi setiap saat. Dan sangat cocok dengan arsitektur aplikasi saya.
Guge
Solusi ini menghasilkan masalah memperbarui ikatan, sedangkan yang di sini: wpf.codeplex.com/wikipage?title=Single-Click%20Editing tidak.
Justin Simon
2
terlalu rumit. lihat jawaban saya. :)
Konstantin Salavatov
1
Setelah 5 tahun kode ini masih menghemat waktu untuk kehidupan sosial :) Untuk beberapa persyaratan sederhana, solusi @KonstantinSalavatov sudah cukup. Dalam kasus saya, saya mencampur kode saya dengan solusi Mike untuk mencapai asosiasi acara yang dinamis dengan penangan, kisi saya memiliki jumlah kolom yang dinamis, dengan satu klik di sel tertentu harus menyimpan perubahan dalam database.
Fer R
8

Ada solusi yang lebih sederhana di sini.

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

Jika Anda gunakan DataGridCheckBoxColumnuntuk mengimplementasikan, klik pertama adalah untuk fokus, klik kedua adalah untuk memeriksa.

Tetapi menggunakan DataGridTemplateColumnuntuk mengimplementasikan perlu satu klik saja.

Perbedaan penggunaan DataGridComboboxBoxColumndan implementasi oleh DataGridTemplateColumnjuga serupa.

Weidian Huang
sumber
Penjelasan yang bagus untuk saya dan bekerja secara instan, terima kasih!
AstralisSomnium
8

Saya menyelesaikannya dengan ini:

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

Kotak centang aktif dengan sekali klik!

Darlan Dieterich
sumber
2
Saya tidak perlu membungkus Kotak Centang dalam ViewBox, tetapi jawaban ini berhasil bagi saya.
JGeerWM
3
Bagi saya ini adalah solusi yang jauh lebih bersih daripada jawaban yang diterima. Tidak perlu untuk Kotak Suara juga. Lucu bagaimana ini bekerja lebih baik daripada kolom Kotak centang yang ditentukan.
kenjara
6

Berdasarkan jawaban Jim Adorno dan komentar di posnya, ini adalah solusi dengan 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
sumber
5

Namun solusi sederhana lainnya adalah menambahkan gaya ini ke DataGridColumn Anda. Badan gaya Anda bisa kosong.

<DataGridCheckBoxColumn>
     <DataGridCheckBoxColumn.ElementStyle>
          <Style TargetType="CheckBox">
           </Style>
     </DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
AmirHossein Rezaei
sumber
2
Menekan bilah spasi untuk mencentang / menghapus centang akan memindahkan Kotak centang dari kiri ke tengah. Menambahkan <Setter Property = "HorizontalAlignment" Value = "Center" /> dalam style akan mencegah Kotak Centang bergerak.
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
sumber