วิธีการเลือกช่องทำเครื่องหมายคลิกเดียวใน WPF DataGrid?


143

ฉันมี DataGrid ที่มีคอลัมน์แรกเป็นคอลัมน์ข้อความและคอลัมน์ที่สองเป็นคอลัมน์กล่องกาเครื่องหมาย สิ่งที่ฉันต้องการคือถ้าฉันคลิกกล่องกาเครื่องหมาย ควรได้รับการตรวจสอบ

แต่จะใช้เวลาสองคลิกในการเลือกสำหรับการคลิกครั้งแรกเซลล์จะถูกเลือกสำหรับการคลิกครั้งที่สองกล่องกาเครื่องหมายจะได้รับการตรวจสอบ วิธีการทำเครื่องหมายในช่องเพื่อรับการตรวจสอบ / ไม่เลือกด้วยการคลิกเพียงครั้งเดียว

ฉันใช้ WPF 4.0 คอลัมน์ใน DataGrid ถูกสร้างอัตโนมัติ


4
ซ้ำของ: stackoverflow.com/questions/1225836/…แต่อันนี้มีชื่อที่ดีกว่า
surfen

คำตอบ:


189

สำหรับการคลิกเพียงครั้งเดียว DataGrid ช่องทำเครื่องหมายคุณก็สามารถใส่ในการควบคุมช่องทำเครื่องหมายประจำและการตั้งค่าDataGridTemplateColumnUpdateSourceTrigger=PropertyChanged

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

4
ว้าว - ฉันดีใจที่อ่านจนจบ มันทำงานได้อย่างสมบูรณ์และมีความซับซ้อนน้อยกว่า IMO นี้ควรถูกทำเครื่องหมายเป็นคำตอบ
ท็อด

2
ยังใช้งานได้กับ ComboBox เช่นเดียวกับในวิธีทางดีกว่า DataGridComboBoxColumn
user1454265

2
เมื่อฉันใช้ Space bar เพื่อตรวจสอบ / ไม่เลือกและลูกศรเพื่อย้ายไปยังเซลล์อื่น
Yola

1
ฉันตีความคำตอบนี้ว่าคุณต้องผูก "IsSelected" แต่นั่นไม่ใช่ความจริง! คุณสามารถใช้DataGridTemplateColumn.CellTemplateกับการผูกของคุณเองและมันจะทำงาน !! คำตอบของ @ weidian-huang ช่วยให้ฉันเข้าใจว่าขอบคุณ!
AstralisSomnium

62

ฉันแก้ไขสิ่งนี้ด้วยสไตล์ดังต่อไปนี้:

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

แน่นอนว่ามันเป็นไปได้ที่จะปรับเปลี่ยนนี้เพิ่มเติมสำหรับคอลัมน์ที่เฉพาะเจาะจง ...


8
ดี ฉันเปลี่ยนเป็น MultiTrigger และเพิ่มเงื่อนไขสำหรับ ReadOnly = False แต่วิธีการพื้นฐานทำงานได้กับกรณีง่าย ๆ ของฉันที่การนำทางคีย์บอร์ดไม่สำคัญ
MarcE

การเพิ่มสไตล์นั้นลงในกริดของฉันเป็นการเพิ่มข้อยกเว้นของการดำเนินการไม่ถูกต้องในขณะที่ ItemsSource ใช้งานอยู่ เข้าถึงและแก้ไของค์ประกอบด้วย ItemsControl.ItemsSource แทน
Alkampfer

1
นี่เป็นวิธีที่สะอาดที่สุดที่ฉันเคยเห็น! ดี! (สำหรับ IsReadOnly = "True" MultiTrigger จะทำงาน)
FooBarTheLittle

2
วิธีนี้มีพฤติกรรมที่ไม่คาดคิด / ไม่พึงประสงค์ ดูstackoverflow.com/q/39004317/2881450
jHilscher

2
สำหรับการผูกพันในการทำงานคุณจะต้อง
UpdateSourceTrigger

27

ก่อนอื่นฉันรู้ว่านี่เป็นคำถามที่ค่อนข้างเก่า แต่ฉันก็ยังคิดว่าฉันจะลองตอบ

ฉันมีปัญหาเดียวกันเมื่อสองสามวันก่อนและพบวิธีแก้ปัญหาสั้น ๆ อย่างน่าประหลาดใจ (ดูบล็อกนี้ ) โดยพื้นฐานแล้วสิ่งที่คุณต้องทำคือแทนที่DataGridCheckBoxColumnคำนิยามใน XAML ของคุณด้วยสิ่งต่อไปนี้:

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

ส่วนต่างของโซลูชันนี้ชัดเจน - เป็น XAML เท่านั้น ดังนั้นจึงเป็นการละเว้นอย่างมีประสิทธิภาพของคุณจากการภาระโค้ดกลับของคุณด้วยตรรกะ UI เพิ่มเติมและช่วยให้คุณรักษาสถานะของคุณในสายตาของ zealots MVVM;)


1
นี่คล้ายกับคำตอบของ Konstantin Salavatov และอันนี้เหมาะกับฉัน +1 สำหรับการรวมตัวอย่างโค้ดที่เขาไม่ได้ทำ ขอบคุณสำหรับคำตอบที่ดีสำหรับคำถามเก่า ๆ
Don Herod

1
ปัญหานี้คือว่าถ้าคุณทำกับคอลัมน์ combobox ปุ่มแบบเลื่อนลงเล็กน้อยจะปรากฏสำหรับทุกเซลล์ในคอลัมน์นั้นตลอดเวลา ไม่ใช่แค่เมื่อคุณคลิกที่เซลล์
3690202

18

เพื่อให้คำตอบที่คอนสแตนติ Salavatov ของการทำงานร่วมกับAutoGenerateColumnsเพิ่มตัวจัดการเหตุการณ์ไปDataGrid's AutoGeneratingColumnด้วยรหัสต่อไปนี้:

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

สิ่งนี้จะทำให้DataGridคอลัมน์ช่องทำเครื่องหมายที่สร้างขึ้นโดยอัตโนมัติทั้งหมดเป็น "คลิกเดียว" ที่สามารถแก้ไขได้


ขอบคุณสำหรับการกรอกข้อมูลในคอลัมน์ที่สร้างอัตโนมัติวิธีนี้ชี้ให้ฉันไปในทิศทางที่เหมาะสม
el2iot2

17

อ้างอิงจากบล็อกที่อ้างอิงในคำตอบของ Goblin แต่ได้รับการแก้ไขให้ทำงานใน. NET 4.0 และด้วย Row-Selection Mode

ขอให้สังเกตว่ามันยังเพิ่มความเร็วในการแก้ไข DataGridComboBoxColumn ด้วยการเข้าสู่โหมดแก้ไขและแสดงดรอปดาวน์ในการคลิกเพียงครั้งเดียวหรือการป้อนข้อความ

XAML:

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

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

วิธีนี้ใช้ได้ผลดีที่สุดสำหรับฉัน ViewModel ที่ถูกผูกไว้ของฉันไม่ได้อัปเดตด้วยโซลูชันอื่น
BrokeMyLegBiking

@Surfen ฉันต้องใส่สไตล์ข้างบนและรหัสในทุก ๆ หน้าและ codebehind หรือไม่ถ้าฉันมีหลายเพจที่มี DataGrid อยู่มันเป็นไปได้ไหมที่จะใช้สไตล์และรหัสในสถานที่ทั่วไปแทนที่จะสร้างขึ้นใน ทุกหน้า
Angel

ทำไมคุณต้องส่งการกระทำที่ว่างเปล่า
user3690202

@ user3690202 เหมือน DoEvents ใน Windows รูปแบบ หลังจากโทร BeginEdit คุณจะต้องรอให้เซลล์เข้าสู่โหมดแก้ไขจริง
JiříSkála

@ JiříSkála - ฉันจำไม่ได้ว่าต้องทำสิ่งนี้ในการแก้ปัญหา แต่ฉันเข้าใจสิ่งที่คุณพูด - ขอบคุณ!
user3690202

10

ฉันได้ลองใช้คำแนะนำเหล่านี้และอื่น ๆ อีกมากมายที่ฉันได้พบในไซต์อื่น ๆ แต่ไม่มีข้อใดที่เหมาะกับฉัน ในที่สุดฉันสร้างโซลูชันต่อไปนี้

ฉันได้สร้างตัวควบคุมที่สืบทอดมาจาก DataGrid ของฉันเองและเพียงแค่เพิ่มรหัสนี้ลงไป:

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

ทั้งหมดนี้ทำอะไร?

ทุกครั้งที่เราคลิกที่เซลล์ใด ๆ ใน DataGrid ของเราเราจะดูว่าเซลล์นั้นมีตัวควบคุมกล่องกาเครื่องหมายอยู่ภายในหรือไม่ ถ้ามันไม่ได้แล้วเราจะตั้งโฟกัสไปที่กล่องกาเครื่องหมายที่และสลับค่าของมัน

ดูเหมือนว่าจะใช้งานได้สำหรับฉันและเป็นโซลูชันที่นำกลับมาใช้ใหม่ได้อย่างง่ายดาย

มันน่าผิดหวังที่เราต้องเขียนโค้ดเพื่อทำสิ่งนี้ คำอธิบายว่าการคลิกเมาส์ครั้งแรก (บนกล่องกาเครื่องหมายของ DataGrid) คือ "ละเว้น" เนื่องจาก WPF ใช้เพื่อทำให้แถวเข้าสู่โหมดแก้ไขอาจฟังดูมีเหตุผล แต่ในโลกแห่งความเป็นจริง

หากผู้ใช้เห็นช่องทำเครื่องหมายบนหน้าจอผู้ใช้ควรคลิกเลือกหนึ่งครั้งเพื่อทำเครื่องหมาย / ยกเลิกการเลือก ตอนจบของเรื่อง.


1
ขอบคุณฉันได้ลองใช้ "วิธีแก้ปัญหา" มากมาย แต่นี่เป็นครั้งแรกที่ดูเหมือนว่าจะใช้ได้จริงทุกครั้ง และมันก็เหมาะสมกับสถาปัตยกรรมแอปพลิเคชันของฉันอย่างสวยงาม
Guge

วิธีแก้ปัญหานี้ส่งผลให้เกิดปัญหาในการอัปเดตการเชื่อมโยงในขณะที่อีกวิธีหนึ่งที่นี่: wpf.codeplex.com/wikipage?title=Single-Click%20Editingไม่ได้
Justin Simon Simon

2
ซับซ้อนเกินไป. เห็นคำตอบของฉัน :)
Konstantin Salavatov

1
หลังจาก 5 ปีที่รหัสนี้ยังคงประหยัดเวลาสำหรับชีวิตทางสังคม :) สำหรับความต้องการง่ายๆ, @KonstantinSalavatov ทางออกก็เพียงพอแล้ว ในกรณีของฉันฉันผสมรหัสของฉันกับโซลูชันของ Mike เพื่อให้เกิดการเชื่อมโยงเหตุการณ์แบบไดนามิกกับตัวจัดการตารางของฉันมีจำนวนคอลัมน์แบบไดนามิกด้วยการคลิกเพียงครั้งเดียวในเซลล์เฉพาะต้องเก็บไว้ในฐานข้อมูลการเปลี่ยนแปลง
Fer R

8

มีทางออกที่ง่ายกว่ามากที่นี่

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

หากคุณใช้DataGridCheckBoxColumnในการดำเนินการคลิกแรกคือการมุ่งเน้นคลิกที่สองคือการตรวจสอบ

แต่การใช้DataGridTemplateColumnงานเพื่อสร้างความต้องการเพียงคลิกเดียวเท่านั้น

ความแตกต่างของการใช้DataGridComboboxBoxColumnและการนำไปใช้โดยDataGridTemplateColumnยังคล้ายกัน


คำอธิบายที่ดีสำหรับฉันและทำงานได้ทันทีขอบคุณ!
AstralisSomnium

8

ฉันแก้ไขด้วยสิ่งนี้:

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

ช่องทำเครื่องหมายที่ใช้งานในคลิกเดียว!


2
ฉันไม่จำเป็นต้องห่อกล่องกาเครื่องหมายไว้ใน ViewBox แต่คำตอบนี้ใช้ได้สำหรับฉัน
JGeerWM

3
สำหรับฉันนี้เป็นทางออกที่สะอาดกว่าคำตอบที่ยอมรับ ไม่จำเป็นต้องใช้ Viewbox เช่นกัน ตลกวิธีนี้ทำงานได้ดีกว่าคอลัมน์ช่องทำเครื่องหมายที่กำหนดไว้
kenjara

6

จากคำตอบของJim Adornoและความคิดเห็นเกี่ยวกับโพสต์ของเขานี่เป็นวิธีแก้ปัญหาด้วย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

อีกวิธีง่ายๆคือการเพิ่มสไตล์นี้ลงใน DataGridColumn ของคุณร่างกายของสไตล์ของคุณจะว่างเปล่า

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

2
การกดแป้นเว้นวรรคเพื่อตรวจสอบ / ยกเลิกการเลือกจะย้ายช่องทำเครื่องหมายจากด้านซ้ายไปยังตรงกลาง การเพิ่ม <Setter Property = "HorizontalAlignment" Value = "Center" /> ในสไตล์จะป้องกันไม่ให้กล่องกาเครื่องหมายย้าย
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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.