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 () 方法,完整代码如下:
代码语言:javascript复制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,并在选中项改变时调用(参数为选中行的索引):
代码语言:javascript复制/// <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 方法,代码如下:
代码语言:javascript复制_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