WPF 表格控件 ReoGrid 的简单使用
目录
一、概述
二、安装
三、添加控件
四、加载 Excel
五、属性设置
六、支持触摸滚动
七、其它操作
1、显示和隐藏列
2、显示特定字体
八、资源链接
独立观察员 2021 年 7 月 9 日
一、概述
ReoGrid 是一个开源的表格控件库,支持 Winform 和 WPF。本文演示在 WPF 中的使用,用的是直接加载 Excel 的方式,另外解决了触摸滑动的问题。
二、安装
新建好 WPF 项目后,我们使用 NuGet 安装 ReoGrid。直接搜索 “ReoGrid”,选择 unvell.ReoGridWPF,当前最新版是 3.0.0,但是这个版本有点问题,所以我们安装 2.2.0 版本,如下图:
那么 3.0.0 版本有什么问题呢?参见 GitHub 上的一个 issue:https://github.com/unvell/ReoGrid/issues/410 ,简单来说就是拖动滚动条到最边上,3.0.0 版本会出现多余的空白,如果固定了表头,还会看到突出的表头线(这个是 Excel 机制决定的,主要还是空白的问题),如下图:
三、添加控件
在 Xaml 中引入命名空间,然后添加 ReoGridControl:
xmlns:reoGrid="clr-namespace:unvell.ReoGrid;assembly=unvell.ReoGrid" | |
<reoGrid:ReoGridControl x:Name="reoGridControl" Width="Auto" Height="Auto" Margin="0" Readonly="True" | |
SheetTabNewButtonVisible="False" ShowScrollEndSpacing="False" SheetTabVisible="False"/> |
设计界面就自动出现预览效果了:
如果取消只读设置,可以直接进行编辑:
四、加载 Excel
我们先在项目中添加一个 Excel,生成操作设置为 Resource(资源):
然后使用如下方法载入 Excel 内容到控件中(在构造函数中调用该方法):
/// <summary> | |
/// 载入数据 | |
/// </summary> | |
private void LoadData() | |
{ | |
Dispatcher.BeginInvoke(new Action(() => | |
{ | |
var workbook = reoGridControl; | |
try | |
{ | |
using (Stream stream = Application.GetResourceStream(new Uri("/WPFPractice;component/Docs/ 合并中英文对照并换行显示.xlsx", UriKind.Relative)).Stream) | |
{ | |
workbook.Load(stream, FileFormat.Excel2007); | |
} | |
} | |
catch (Exception ex) | |
{ | |
_VM.ShowInfo($" 载入用户权限表异常:{ex}"); | |
} | |
})); | |
} |
在 ReoGrid 中,ReoGridControl 控件对象就代表了一个 WorkBook,和 Excel 对应。workbook 通过加载 Excel 的文件资源流来呈现内容。
五、属性设置
/// <summary> | |
/// 设置控件 | |
/// </summary> | |
private void SetReoGridControl() | |
{ | |
Dispatcher.BeginInvoke(new Action(() => | |
{ | |
// 滚动条设置; | |
//var workbookSettingsDisable = WorkbookSettings.View_ShowScrolls; | |
//reoGridControl.DisableSettings(workbookSettingsDisable); | |
var worksheet = reoGridControl.CurrentWorksheet; | |
worksheet.SelectionStyle = WorksheetSelectionStyle.None; // 选择范围样式; | |
// 冻结行和列; | |
worksheet.FreezeToCell(2, 1, FreezeArea.LeftTop); | |
// 禁用显示行和列头; | |
var worksheetSettingsDisable = WorksheetSettings.View_ShowRowHeader | WorksheetSettings.View_ShowColumnHeader; | |
worksheet.DisableSettings(worksheetSettingsDisable); | |
// 设置只读; | |
var worksheetSettingsEnable = WorksheetSettings.Edit_Readonly; | |
worksheet.EnableSettings(worksheetSettingsEnable); | |
// 设置显示的行数和列数; | |
worksheet.SetCols(3); | |
worksheet.SetRows(13); | |
})); | |
} |
属性设置通常是针对 Worksheet 来进行,可参考官方文档:https://reogrid.net/document/settings/ 。以上方法依次进行了如下操作:去除了选择的样式,冻结了前两行和第一列(固定表头),去除了行和列的序号,设置只读,设置需要显示的行和列范围。效果如下:
可以看到固定了表头后还是会有表头线突出,这是因为窗体(或者说显示范围)比表格内容区域大,实际使用时可通过设置合适的显示大小,ReoGrid 控件会自动出现滚动条,即可解决这个问题(3.0.0 版本不行),如下所示:
可以看到,滚动条滑到最边上,并没有空白出现,也就看不到突出的表头线了。
至于其它的表格样式调整,直接在 Excel 中调整即可(效果和源文件略有差异):
六、支持触摸滚动
到目前为止,在触摸屏下,是只支持触摸滚动条进行内容滚动的,直接在表格内容区进行触摸滚动是没有效果的。这个应该是 WPF 的 ScrollViewer 本身的问题,之前碰到过,网上有人提供过一个方法来解决(通过附加属性)。这里如果要支持表头固定,就要用控件自己添加的 ScrollViewer,也就不能直接用那个方法,所以我改了个专用的方法。各种情况如下:
// 命名空间 | |
xmlns:rg="clr-namespace:unvell.ReoGrid;assembly=unvell.ReoGrid" | |
xmlns:utils="clr-namespace:WPFPractice.Utils" | |
// 普通使用 | |
<rg:ReoGridControl x:Name="reoGridControl" SheetTabNewButtonVisible="False" ShowScrollEndSpacing="False" SheetTabVisible="False" Readonly="True"/> | |
// 支持触摸滚动(WpfTouchScrollHelper 见 https://gitee.com/dlgcy/WPFTemplate) | |
<ScrollViewer wpfHelpers:WpfTouchScrollHelper.IsEnabled="True" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> | |
<Grid Width="Auto" Height="Auto"> | |
<rg:ReoGridControl Width="1150" Height="2100" x:Name="reoGridControl" SheetTabNewButtonVisible="False" ShowScrollEndSpacing="False" SheetTabVisible="False" Readonly="True"/> | |
</Grid> | |
</ScrollViewer> | |
// 支持触摸滚动 2(ReoGridTouchScrollHelper),兼容固定表头(冻结) | |
<rg:ReoGridControl Width="Auto" Height="Auto" x:Name="reoGridControl" SheetTabNewButtonVisible="False" ShowScrollEndSpacing="False" SheetTabVisible="False" Readonly="True" Margin="0" utils:ReoGridTouchScrollHelper.IsEnabled="True"/> |
其中 ReoGridTouchScrollHelper 的代码如下:
using System; | |
using System.Collections.Generic; | |
using System.Windows; | |
using System.Windows.Input; | |
using unvell.ReoGrid; | |
namespace WPFPractice.Utils | |
{ | |
/// <summary> | |
/// (参考 WpfTouchScrollHelper 见 https://gitee.com/dlgcy/WPFTemplate) | |
/// 用法:引入命名空间 (比如 util) 后,在 ReoGrid 上写上 util:ReoGridTouchScrollHelper.IsEnabled="True" | |
/// </summary> | |
public class ReoGridTouchScrollHelper : DependencyObject | |
{ | |
public static bool GetIsEnabled(DependencyObject obj) | |
{ | |
return (bool)obj.GetValue(IsEnabledProperty); | |
} | |
public static void SetIsEnabled(DependencyObject obj, bool value) | |
{ | |
obj.SetValue(IsEnabledProperty, value); | |
} | |
public bool IsEnabled | |
{ | |
get { return (bool)GetValue(IsEnabledProperty); } | |
set { SetValue(IsEnabledProperty, value); } | |
} | |
public static readonly DependencyProperty IsEnabledProperty = | |
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ReoGridTouchScrollHelper), new UIPropertyMetadata(false, IsEnabledChanged)); | |
public static Dictionary<object, MouseCapture> _captures = new Dictionary<object, MouseCapture>(); | |
/// <summary> | |
/// 开关触发事件 | |
/// </summary> | |
public static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
var target = d as ReoGridControl; | |
if (target == null) return; | |
if ((bool)e.NewValue) | |
{ | |
target.Loaded += Target_Loaded; | |
} | |
else | |
{ | |
target.Loaded -= Target_Loaded; | |
Target_Unloaded(target, new RoutedEventArgs()); | |
} | |
} | |
/// <summary> | |
/// 启用 | |
/// </summary> | |
public static void Target_Loaded(object sender, RoutedEventArgs e) | |
{ | |
var target = sender as ReoGridControl; | |
if (target == null) return; | |
System.Diagnostics.Debug.WriteLine("Target Loaded"); | |
target.Unloaded += Target_Unloaded; | |
target.PreviewMouseLeftButtonDown += Target_PreviewMouseLeftButtonDown; | |
target.PreviewMouseMove += Target_PreviewMouseMove; | |
target.PreviewMouseLeftButtonUp += Target_PreviewMouseLeftButtonUp; | |
} | |
/// <summary> | |
/// 禁用 | |
/// </summary> | |
public static void Target_Unloaded(object sender, RoutedEventArgs e) | |
{ | |
System.Diagnostics.Debug.WriteLine("Target Unloaded"); | |
var target = sender as ReoGridControl; | |
if (target == null) return; | |
_captures.Remove(sender); | |
target.Unloaded -= Target_Unloaded; | |
target.PreviewMouseLeftButtonDown -= Target_PreviewMouseLeftButtonDown; | |
target.PreviewMouseMove -= Target_PreviewMouseMove; | |
target.PreviewMouseLeftButtonUp -= Target_PreviewMouseLeftButtonUp; | |
} | |
/// <summary> | |
/// 鼠标左键按下 | |
/// </summary> | |
public static void Target_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) | |
{ | |
var target = sender as ReoGridControl; | |
if (target == null) return; | |
_captures[sender] = new MouseCapture | |
{ | |
HorticalOffset = e.GetPosition(target).X, | |
VerticalOffset = e.GetPosition(target).Y, | |
Point = e.GetPosition(target), | |
}; | |
} | |
/// <summary> | |
/// 鼠标左键抬起 | |
/// </summary> | |
public static void Target_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) | |
{ | |
var target = sender as ReoGridControl; | |
if (target == null) return; | |
target.ReleaseMouseCapture(); | |
} | |
/// <summary> | |
/// 鼠标移动 | |
/// </summary> | |
public static void Target_PreviewMouseMove(object sender, MouseEventArgs e) | |
{ | |
if (!_captures.ContainsKey(sender)) return; | |
if (e.LeftButton != MouseButtonState.Pressed) | |
{ | |
_captures.Remove(sender); | |
return; | |
} | |
var target = sender as ReoGridControl; | |
if (target == null) return; | |
var capture = _captures[sender]; | |
var point = e.GetPosition(target); | |
var dy = point.Y - capture.Point.Y; | |
var dx = point.X - capture.Point.X; | |
if (Math.Abs(dy) > 5) | |
{ | |
target.CaptureMouse(); | |
} | |
if (Math.Abs(dx) > 5) | |
{ | |
target.CaptureMouse(); | |
} | |
//target.ScrollCurrentWorksheet(capture.HorticalOffset - dx,capture.VerticalOffset - dy); | |
//target.ScrollCurrentWorksheet(dx,dy); | |
target.ScrollCurrentWorksheet(-dx,-dy); | |
} | |
/// <summary> | |
/// 鼠标快照 | |
/// </summary> | |
public class MouseCapture | |
{ | |
public double VerticalOffset { get; set; } | |
public double HorticalOffset { get; set; } | |
public Point Point { get; set; } | |
} | |
} | |
} |
这样就能通过触摸内容区来进行滚动了。不过,也有缺点,因为滚动条也是在控件范围内,所以有点受影响,不知道大家有没有什么好方法。
七、其它操作
1、显示和隐藏列
比如可以根据用户权限来显示和隐藏列,主要是使用 Worksheet 的 ShowColumns () 和 HideColumns () 方法来设置:
2、显示特定字体
官方文档(https://reogrid.net/document/style/)指明了设置字体的方法:
另外一种方法依然是直接在 Excel 中设置字体。
当然,无论用哪种方法,如果电脑里没有安装该字体,则还是没有效果的,可考虑通过代码自动安装字体(可参考:https://gitee.com/dlgcy/dotnetcodes/blob/cd6d091d3c082ec1f7f450b0e4aec61c6a1ea5cd/DotNet.Utilities/FontHelper.cs)。
八、资源链接
官网文档:https://reogrid.net/document/
GitHub:https://github.com/unvell/ReoGrid
Gitee 克隆:https://gitee.com/DLGCY_Clone/ReoGrid
本文示例:https://gitee.com/dlgcy/Practice/tree/Blog20210709/WPFPractice