Làm cách nào để thực hiện lựa chọn hộp kiểm nhấp chuột trong WPF DataGrid?


143

Tôi có một DataGrid với cột đầu tiên là cột văn bản và cột thứ hai là cột CheckBox. Những gì tôi muốn là, nếu tôi nhấp vào hộp kiểm. Nó sẽ được kiểm tra.

Nhưng, phải mất hai lần nhấp để được chọn, lần nhấp đầu tiên, ô sẽ được chọn, đối với lần nhấp thứ hai, hộp kiểm sẽ được chọn. Cách làm hộp kiểm để được kiểm tra / bỏ chọn chỉ bằng một cú nhấp chuột.

Tôi đang sử dụng WPF 4.0. Các cột trong DataGrid được AutoGenerated.


4
Bản sao của: stackoverflow.com/questions/1225836/ , nhưng cái này có tiêu đề tốt hơn
lướt

Câu trả lời:


189

Đối với một lần bấm hộp kiểm DataGrid, bạn chỉ cần đặt điều khiển hộp kiểm thông thường vào bên trong DataGridTemplateColumnvà đặt UpdateSourceTrigger=PropertyChanged.

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

4
WOW - Tôi rất vui vì tôi đã đọc đến cuối. Điều này hoạt động hoàn hảo và ít phức tạp hơn đáng kể, IMO này nên được đánh dấu là câu trả lời.
Tod

2
Điều này cũng hoạt động cho ComboBox. Như trong: cách, CÁCH tốt hơn DataGridComboBoxColumn.
dùng1454265

2
Nó không phải khi tôi sử dụng thanh không gian để kiểm tra / bỏ chọn và mũi tên để di chuyển đến một ô khác.
Yola

1
Tôi đã giải thích câu trả lời này rằng bạn phải Bind "IsSelected", nhưng điều đó không đúng! Bạn chỉ có thể sử dụng DataGridTemplateColumn.CellTemplatevới Binding của riêng bạn và nó sẽ hoạt động !! Câu trả lời của @ weidian-huang đã giúp tôi hiểu điều đó, cảm ơn!
AstralisSomnium

62

Tôi đã giải quyết điều này với Phong cách sau:

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

Tất nhiên có thể điều chỉnh điều này hơn nữa cho các cột cụ thể ...


8
Đẹp. Tôi đã thay đổi nó thành MultiTrigger và thêm một điều kiện cho ReadOnly = false nhưng cách tiếp cận cơ bản hoạt động cho trường hợp đơn giản của tôi khi điều hướng bàn phím không quan trọng.
MarcE

Việc thêm kiểu đó vào lưới của tôi làm tăng ngoại lệ Hoạt động không hợp lệ trong khi ItemSource đang được sử dụng. Thay vào đó, truy cập và sửa đổi các phần tử với ItemControl.ItemsSource.
Alkampfer

1
Đây là cách sạch nhất tôi từng thấy cho đến nay! Đẹp! (đối với IsReadOnly = "Đúng", MultiTrigger sẽ thực hiện công việc)
FooBarTheLittle

2
Giải pháp này có một số hành vi bất ngờ / không mong muốn. Xem stackoverflow.com/q/39004317/2881450
jHilscher

2
Để liên kết hoạt động, bạn sẽ cần UpdateSourceTrigger = PropertyChanged
AQuirky

27

Đầu tiên, tôi biết đây là một câu hỏi khá cũ nhưng tôi vẫn nghĩ mình sẽ thử và trả lời nó.

Tôi đã có cùng một vấn đề một vài ngày trước và đã tìm thấy một giải pháp ngắn đáng ngạc nhiên cho nó (xem blog này ). Về cơ bản, tất cả những gì bạn cần làm là thay thế DataGridCheckBoxColumnđịnh nghĩa trong XAML của bạn bằng cách sau:

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

Mặt trái của giải pháp này là rõ ràng - chỉ dành cho XAML; do đó, nó có hiệu quả kiềm chế bạn khỏi gánh nặng mã trở lại với logic UI bổ sung và giúp bạn duy trì trạng thái của mình trong mắt những người quá khích MVVM;).


1
Điều này tương tự như câu trả lời của Konstantin Salavatov và điều này có hiệu quả với tôi. +1 để bao gồm các mẫu mã mà không. Cảm ơn cho một câu trả lời tốt cho một câu hỏi cũ.
Don Herod

1
Vấn đề với điều này là nếu bạn làm điều đó với các cột combobox, nút thả xuống nhỏ sẽ hiển thị cho tất cả các ô trong cột đó mọi lúc. Không chỉ khi bạn nhấp vào ô.
dùng3690202

18

Để làm cho câu trả lời Konstantin Salavatov của làm việc với AutoGenerateColumns, thêm một event handler cho DataGrid's AutoGeneratingColumnvới đoạn mã sau:

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

Điều này sẽ làm cho tất cả các DataGridcột hộp kiểm được tạo tự động có thể chỉnh sửa "một lần nhấp".


Cảm ơn bạn đã điền vào một cách tiếp cận cột tự phát, điều này khéo léo chỉ cho tôi một hướng phù hợp.
el2iot2

17

Dựa trên blog được tham chiếu trong câu trả lời của Goblin, nhưng được sửa đổi để hoạt động trong .NET 4.0 và với Chế độ chọn hàng.

Lưu ý rằng nó cũng tăng tốc độ chỉnh sửa DataGridComboBoxColumn - bằng cách vào chế độ chỉnh sửa và hiển thị thả xuống trên một lần nhấp hoặc nhập văn bản.

XAML:

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

Mã ẩn:

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

Giải pháp này làm việc tốt nhất cho tôi. ViewModel bị ràng buộc của tôi không cập nhật với các giải pháp khác.
BrokeMyLegBiking

@surfen, Tôi có cần đặt kiểu trên và mã ở mỗi trang và cơ sở mã của nó không, nếu tôi có nhiều trang có datagrid trong đó. Tôi có thể sử dụng kiểu và mã ở một vị trí chung thay vì tạo nó không mỗi trang
Angel

Tại sao bạn cần phải gửi một hành động trống?
dùng3690202

@ user3690202 Giống như DoEvents trong Windows.Forms. Sau khi gọi BeginEdit, bạn cần đợi ô thực sự vào chế độ chỉnh sửa.
Jiří Skála

@ JiříSkála - Tôi không nhớ mình cần phải làm điều này trong các giải pháp của mình cho vấn đề này, nhưng tôi hiểu những gì bạn đang nói - cảm ơn!
dùng3690202

10

Tôi đã thử các đề xuất này và nhiều đề xuất khác mà tôi đã tìm thấy trên các trang web khác, nhưng không có đề xuất nào phù hợp với tôi. Cuối cùng, tôi đã tạo ra giải pháp sau đây.

Tôi đã tạo điều khiển được kế thừa DataGrid của riêng mình và chỉ cần thêm mã này vào nó:

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

Tất cả những điều này làm gì?

Chà, mỗi lần chúng tôi nhấp vào bất kỳ ô nào trong DataGrid, chúng tôi sẽ xem liệu ô đó có chứa điều khiển CheckBox bên trong nó không. Nếu đúng như vậy , thì chúng ta sẽ đặt trọng tâm vào CheckBox đó và chuyển đổi giá trị của nó .

Điều này dường như làm việc cho tôi, và là một giải pháp tốt đẹp, dễ dàng tái sử dụng.

Thật đáng thất vọng khi chúng ta cần phải viết mã để làm điều này mặc dù. Giải thích rằng lần nhấp chuột đầu tiên (trên CheckBox của DataGrid) bị "bỏ qua" vì WPF sử dụng nó để đưa hàng vào chế độ Chỉnh sửa nghe có vẻ hợp lý, nhưng trong thế giới thực, điều này đi ngược lại cách mọi ứng dụng thực hoạt động.

Nếu người dùng nhìn thấy một hộp kiểm trên màn hình của họ, họ sẽ có thể nhấp vào nó một lần để đánh dấu / bỏ chọn nó. Kết thúc câu chuyện.


1
Cảm ơn, tôi đã thử một loạt các "giải pháp", nhưng đây là lần đầu tiên dường như thực sự hoạt động mọi lúc. Và nó rất phù hợp với kiến ​​trúc ứng dụng của tôi.
Guge

Giải pháp này dẫn đến các vấn đề cập nhật liên kết, trong khi giải pháp tại đây: wpf.codeplex.com/wikipage?title=Single-Click%20Editing thì không.
Justin Simon

2
quá phức tạp. xem câu trả lời của tôi :)
Konstantin Salavatov

1
Sau 5 năm, mã này vẫn tiết kiệm thời gian cho đời sống xã hội :) Đối với một số yêu cầu đơn giản, giải pháp @KonstantinSalavatov là đủ. Trong trường hợp của tôi, tôi đã trộn mã của mình với giải pháp của Mike để đạt được liên kết sự kiện động với các trình xử lý, lưới của tôi có số cột động, với một lần nhấp trong ô cụ thể phải lưu trữ trong cơ sở dữ liệu các thay đổi.
Fer R

8

Có một giải pháp đơn giản hơn nhiều ở đây.

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

Nếu bạn sử dụng DataGridCheckBoxColumnđể thực hiện, nhấp chuột đầu tiên là để tập trung, nhấp chuột thứ hai là để kiểm tra.

Nhưng sử dụng DataGridTemplateColumnđể thực hiện chỉ cần một cú nhấp chuột.

Sự khác biệt của việc sử dụng DataGridComboboxBoxColumnvà thực hiện bởi DataGridTemplateColumncũng tương tự.


Giải thích tốt cho tôi và làm việc ngay lập tức, cảm ơn!
AstralisSomnium

8

Tôi đã giải quyết điều này:

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

Các hộp kiểm hoạt động trên một nhấp chuột!


2
Tôi không cần phải bọc Hộp kiểm trong ViewBox, nhưng câu trả lời này có hiệu quả với tôi.
JGeerWM

3
Điều này với tôi là một giải pháp sạch hơn nhiều so với câu trả lời được chấp nhận. Không cần Viewbox. Thật buồn cười là cách này hoạt động tốt hơn cột Checkbox được xác định.
kenjara

6

Dựa trên câu trả lời và nhận xét về bài đăng của Jim Adorno , đây là giải pháp với 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

Tuy nhiên, một giải pháp đơn giản khác là thêm kiểu này vào DataGridColumn. Phần thân theo kiểu của bạn có thể trống.

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

2
Nhấn vào thanh dấu cách để kiểm tra / bỏ chọn sẽ di chuyển CheckBox từ bên trái sang giữa. Thêm <Setter property = "verticalAlocation" Value = "Center" /> theo kiểu sẽ ngăn CheckBox di chuyển.
Y niệmChen

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.