WPF DataGrid 如何将被选中行带到视野中

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF DataGrid 如何将被选中行带到视野中相关的知识,希望对你有一定的参考价值。

WPF DataGrid 如何将被选中行带到视野中

目录

前言

准备工作

方法一

方法二

总结

独立观察员 2021 年 12 月 11 日

前言

在 WPF 开发中,显示表格一般使用 DataGrid 控件,而且我们一般会依据用户的选中行的操作来执行一些逻辑,这种情况,选中了哪一行,用户是心知肚明的。而还有一种情况,我们可能在业务逻辑中,由程序自己选中了某一行,如果这一行当前不在用户界面的可视区(换句话说也就是滚动条没有滚到那个位置),那么我们如何将其带到用户的视野中呢?

准备工作

今天准备介绍两个方法。正所谓,工欲善其事必先利其器,所以在开始之前,我们先来构建一个可以模拟后台选中行的功能。

使用的还是之前用过的 DataGrid 的 Demo 程序(在《WPF DataGrid 通过自定义表头模拟首行固定》和《WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题》中用过),加了一个可以填写要选中的行号的文本框,以及一个执行选中操作的按钮:

下面来演示一下没有自动将选中行带到视野中的情况。我们先将数据添加到 10 条,然后缩小程序的窗口,这样有些数据就在滚动区外面了,也就是不在视野中。然后我们通过程序来选中行,可以看到选中功能是正常的,但是对于视野外的数据,用户看不到其是否选中,需要手动滚动来寻找,如下图(动图):

好,那接下来就介绍怎么解决吧。

方法一

这个方法是参考《【翻译】WPF 中附加行为的介绍 Introduction to Attached Behaviors in WPF》文章中的将 TreeViewItem(树状列表项)带到视野中的方法,我稍微改造了一下,使其同时支持 DataGridRow 和 TreeViewItem,并且之后如果有其它受支持的类型也可以方便地扩展。BringIntoViewBehavior 类提供了一个 IsBroughtIntoViewWhenSelected 附加属性,给每个列表项的 Selected 事件指定了处理方法,处理方法中调用 BringIntoView () 方法,完整代码如下:

using System.Windows;
using System.Windows.Controls;
/*
 * 源码已托管:https://gitee.com/dlgcy/WPFTemplateLib
 */
namespace WPFTemplateLib.WpfHelpers

    /// <summary>
    /// 功能:列表项被选中时带到视野中
    /// 参考:http://dlgcy.com/introduction-to-attached-behaviors-in-wpf/
    /// 说明:用于 DataGrid 时需要设置 EnableRowVirtualization="False"
    /// </summary>
    /// <example>
    /// <code>
    /// Setter Property="wpfHelpers:BringIntoViewBehavior.IsBroughtIntoViewWhenSelected" Value="True"/>
    /// </code>
    /// </example>
    public class BringIntoViewBehavior
    
        #region IsBroughtIntoViewWhenSelected
        public static bool GetIsBroughtIntoViewWhenSelected(FrameworkElement item)
        
            return (bool)item.GetValue(IsBroughtIntoViewWhenSelectedProperty);
        
        public static void SetIsBroughtIntoViewWhenSelected(FrameworkElement item, bool value)
        
            item.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
        


        /// <summary>
        /// 是否在选中时带到视野中
        /// </summary>
        public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
            DependencyProperty.RegisterAttached(
                "IsBroughtIntoViewWhenSelected",
                typeof(bool),
                typeof(BringIntoViewBehavior),
                new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));


        static void OnIsBroughtIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        
            FrameworkElement item = depObj as FrameworkElement;


            if (item == null)
                return;


            if (e.NewValue is bool == false)
                return;


            switch (depObj)
            
                case DataGridRow row:
                
                    if ((bool)e.NewValue)
                        row.Selected += OnItemSelected;
                    else
                        row.Selected -= OnItemSelected;
                    break;
                
                case TreeViewItem treeViewItem:
                
                    if ((bool)e.NewValue)
                        treeViewItem.Selected += OnItemSelected;
                    else
                        treeViewItem.Selected -= OnItemSelected;
                    break;
                
                default:
                    break;
            
        


        static void OnItemSelected(object sender, RoutedEventArgs e)
        
            // 忽略所有只是报告子孙的 Selected 被触发的祖先。
            if (!ReferenceEquals(sender, e.OriginalSource))
                return;


            if (e.OriginalSource is FrameworkElement item)
                item.BringIntoView();
        


        #endregion
    

此方法用于 DataGrid 时需要设置 EnableRowVirtualization="False"(默认为 true):

使用时只要在行样式中应用这个附加属性即可:

注意引入命名空间:

效果如下(动图):

方法二

如果开了行虚拟化(EnableRowVirtualization="True"),离可视区较远的行的 Selected 事件就不会被触发,以上方法就不行了。

类似于这个帖子的情况《WPF 开启行虚拟化的时候,行选择功能不正常,求解决方案》(https://bbs.csdn.net/topics/392666509):

所以如果因为数据量比较大必须开启行虚拟化时,可以使用下面的方法。

先给 DataGrid 命个名(如 x:Name="Dg" )方便后台使用,然后在 ViewModel 中添加一个选中项改变事件 SelectedItemChanged,并在选中项改变时调用(参数为选中行的索引):

/// <summary>
/// 选中项改变事件
/// </summary>
public event Action<int> SelectedItemChanged;


private User _SelectedItem;
/// <summary>
/// 选中项
/// </summary>
public User SelectedItem

    get => _SelectedItem;
    set
    
        SetProperty(ref _SelectedItem, value);


        SelectedItemChanged?.Invoke(Datas.IndexOf(_SelectedItem));
    

接着在后台事件中注册事件处理方法,处理方法中调用了 DataGrid 的 ScrollIntoView 方法,代码如下:

_vm.SelectedItemChanged += OnSelectedItemChanged;


/// <summary>
/// 选中项改变事件执行方法
/// </summary>
/// <param name="index">选中行索引</param>
private void OnSelectedItemChanged(int index)

    Dg.ScrollIntoView(Dg.Items.GetItemAt(index));

效果和方法一的一样,就不再赘述了。

总结

关于将 DataGrid 选中行带到视野中的需求,本文介绍了两种方法。方法一提供了一个附加属性,可以方便地实现该需求,不过要求不能开启行虚拟化。方法二则是需要在 ViewModel 和页面后台编写代码,通过事件来触发相关操作,不过可以支持行虚拟化。大家可以依据实际情况选择使用,如果有更好的方法,欢迎交流。 

源代码地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20211211 

WPF

WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题

WPF DataGrid 通过自定义表头模拟首行固定

WPF ComboBox 使用 ResourceBinding 动态绑定资源键并支持语言切换

【翻译】WPF 中附加行为的介绍 Introduction to Attached Behaviors in WPF

WPF 使用 Expression Design 画图导出及使用 Path 画图

WPF MVVM 弹框之等待框

解决 WPF 绑定集合后数据变动界面却不更新的问题(使用 ObservableCollection)

WPF 消息框 TextBox 绑定新数据时让光标和滚动条跳到最下面

真・WPF 按钮拖动和调整大小

WPF MVVM 模式下的弹窗

WPF 让一组 Button 实现 RadioButton 的当前样式效果

WPF 原生绑定和命令功能使用指南

WPF 用户控件的自定义依赖属性在 MVVM 模式下的使用备忘

在WPF的MVVM模式中使用OCX组件

以上是关于WPF DataGrid 如何将被选中行带到视野中的主要内容,如果未能解决你的问题,请参考以下文章

wpf datagrid中如何获取选中行中模板如textbox,combobox的内容

wpf datagrid绑定了数据 如果选中多行中怎么获取选中行的某列的值

WPF关闭datagrid插入行

WPF中对datagrid删除选中的某一行。

wpf datagrid 单元格如何默认单击一次点中

WPF DataGrid获取选择行的数据