-
2021-01-20 23:06:33
目录
前言
- 之前使用WPF项目使用都是VS2019,在2017版本下如何创建,做个记录。
- 记录下用户控件和自定义控件的区别
新建项目
我们可以选择所需要的框架
WPF应用是创建我们熟悉的WPF项目
windows 窗体应用就是Winform项目
还有我们熟悉的WPF 自定义控件库和用户控件
用户控件和自定义控件的区别
在WPF中,概念上来说用户自己制作的控件有两种:用户控件和自定义控件。但是这两种控件之间有什么区别?
用户控件
- 用户控件继承UserControl类,更偏向于组件的组合使用
- 可以自由组合所需的的控件,组成我们所需的组合。
- 样式和模板不能使用
- 和WPF窗口类型,由前后台文件组成。
自定义控件
- 创建类库,扩展现有控件,在现有属性方法基础上派生扩展。
- 可使用样式和模板丰富类库。
- 可构建WPF项目引用控件库。
- 由Themes文件和继承与Control的代码文件组成。
结语
将自己的理解整理下,有理解不恰当或者有误的地方,欢迎大佬们批评指点
更多相关内容 -
WPF 用户控件 Loading 效果
2019-01-24 09:33:59WPF 用户控件 Loading 效果 -
wpf用户控件添加依赖属性
2022-02-17 17:17:49VS创建用户控件项目 前台界面: 前台代码: <UserControl x:Class="WpfControlLibrary2.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x=...一。VS创建用户控件项目
前台界面:
前台代码:
<UserControl x:Class="WpfControlLibrary2.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfControlLibrary2" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Button Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:UserControl1},Path=pText}" Height="60" Margin="270,270,180,270"/> </UserControl>
后台代码:
namespace WpfControlLibrary2 { /// <summary> /// UserControl1.xaml 的交互逻辑 /// </summary> public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(pText), typeof(string), typeof(UserControl1), new PropertyMetadata(defaultValue: "666")); public string pText { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } } }
二.VS创建WPF应用项目
前台界面(左侧工具箱出现用户组件):
前台代码:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp2" xmlns:wp="clr-namespace:WpfControlLibrary2;assembly=WpfControlLibrary2" x:Class="WpfApp2.MainWindow" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <wp:UserControl1 pText="999"/> </Grid>
三.demo下载地址
https://download.csdn.net/download/sdfjasad/81152587
-
怎样封装WPF用户控件的一些实践
2020-09-08 13:47:10JHRS开发框架之公用组件WPF用户控件封装,这个系列的文章旨在记录工作中使用WPF开发新的医疗项目中,有感于必须统一掉一些规范上的事情,并且提高团队开发效率,遂折腾了这么一个半吊子的框架,这个标题WPF企业级...JHRS开发框架之公用组件WPF用户控件封装,这个系列的文章旨在记录工作中使用WPF开发新的医疗项目中,有感于必须统一掉一些规范上的事情,并且提高团队开发效率,遂折腾了这么一个半吊子的框架,这个标题WPF企业级开发框架搭建指南,2020从入门到放弃可能会唬住一些人,但看到这些零碎文字的朋友就凑和着看吧,如果能帮助到你,那也荣幸了。
继上一篇介绍了怎样封装ViewModel的基类,但随着项目大了,一个功能点一个功能点的做,真的累,很多系统里面,在局部有很多相似的功能,数据展示几乎一样的,或许不一样的只是摆放的位置,显示的样式不同罢了;这种东西,一个功能一个功能的实现,那就有点朝着996的状态发展了;因此在JHRS框架中,也体现了懒人干活的思想,那就是能封装成控件的,坚决搞成一个控件,供大家享乐。
JHRS开发框架之公用组件WPF用户控件封装
当然,用户控件可以套在用户控件里面,一个拥有复杂功能的页面,可以由众多的用户控件组成,最终你会发现,用户控件封装得越优雅,做复杂的功能页面也不会很难了,只需要像搭积木那样,把控件丢上去,数据绑定上就完事了,最后如果要调整样式,稍微调整下就OK了。
WPF用户控件封装
因为框架中引入了Prism将各子系统模块化了,所以我们在WPF用户控件封装的时候,分为两种情况,一种是封装整个系统公用的用户控件,另外一种是各子模块自己的用户控件;整个系统公用的用户控件,需要保持着高扩展性,灵活性,即使后期对该控件增加功能,也要尽量做到不影响已经使用该控件的页面(Page),还需要让使用的页面可以灵活的设置一些属性以满足不同页面(Page)的功能需求。
在框架中只封装了3个基本的用户控件,动态列的DataGrid,可调接口的Combobox,动态分页表格。
JHRS开发框架动态列的DataGrid
大部分管理系统,都离不开表格展示数据,而WPF项目中,基本上都会使用DataGrid来展示数据;熟悉WPF开发的朋友都知道,如果手工撸一个表格,那代码贼烦人,需要一个列一个列的写代码并绑定数据;而在JHRS框架中提供的思路是基于注解的方式(自定义BindDescriptionAttribute类用于描述每列)动态生成每一列数据并自动绑定数据,对于复杂的列,如某一列里面展示为下拉框(ComoboBox)或者更复杂的展示,只需要在资源(Resources)里面定义DataTemplate即可,然后动态加载就可以了。对于最每一行的操作列,也是一样的套路。
动态列的DataGrid封装思路是:编写一个DataGridEx类,继承自DataGrid类,在DataGridEx类中,需要定义一个依赖属性我们称为DataSource,用它来绑定数据,然后在DataSourceProperty的回调函数里面把DataSource赋值给原本的 ItemSource属性,最后重写OnInitialized方法,将数据源传入DataGrid的扩展方法GenerateColumns动态生成列就完成了WPF用户控件封装,详见下方代码。
DataGridEx类源码
/// <summary> /// 輕量級的DataGrid擴展 /// </summary> public class DataGridEx : DataGrid { /// <summary> /// 構造函數 /// </summary> public DataGridEx() { this.AutoGenerateColumns = false; this.Loaded += DataGridEx_Loaded; this.LoadingRow += PagingDataList_LoadingRow; } /// <summary> /// 给表格添加样式 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void DataGridEx_Loaded(object sender, RoutedEventArgs e) { this.CanUserAddRows = false; } /// <summary> /// 生成序号 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void PagingDataList_LoadingRow(object sender, DataGridRowEventArgs e) { if (EnableRowNumber) //需要分页 e.Row.Header = e.Row.GetIndex() + 1; } /// <summary> /// 操作列key /// </summary> public string OperatingKey { get; set; } = string.Empty; /// <summary> /// 操作列的宽度 /// </summary> public DataGridLength OperationWidth { get; set; } /// <summary> /// 是否启用序号 /// </summary> public bool EnableRowNumber { get; set; } = true; /// <summary> /// 禁止显示的列 /// </summary> public string DisableCloumn { get; set; } public IEnumerable<object> DataSource { get { return (IEnumerable<object>)GetValue(DataSourceProperty); } set { SetValue(DataSourceProperty, value); } } private bool IsGenerateColumns = false; // Using a DependencyProperty as the backing store for DataSource. This enables animation, styling, binding, etc... public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(IEnumerable<object>), typeof(DataGridEx), new PropertyMetadata((d, e) => { DataGridEx u = d as DataGridEx; u.ItemsSource = u.DataSource; if (u.IsGenerateColumns || u.DataSource == null || u.DataSource.Count() == 0) return; var index = 0; if (u.EnableRowNumber) { var acolumn = new DataGridTextColumn { Header = "序号", Width = new DataGridLength(50), Binding = new Binding("Header") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridRow), 1) } }; u.Columns.Insert(0, acolumn); index++; } u.GenerateColumns(index, u.ItemsSource, u.OperatingKey, u.OperationWidth); u.IsGenerateColumns = true; })); //protected override void OnInitialized(EventArgs e) //{ // if (IsGenerateColumns || ItemsSource == null) return; // var index = 0; // if (EnableRowNumber) // { // var acolumn = new DataGridTextColumn // { // Header = "序号", // Width = new DataGridLength(50), // Binding = new Binding("Header") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGridRow), 1) } // }; // this.Columns.Insert(0, acolumn); // index++; // } // this.GenerateColumns(index, ItemsSource, OperatingKey, OperationWidth); // IsGenerateColumns = true; //} }
以上的基本上是完整的源码,github是参见这里。
DataGridExtensions扩展类源码
/// <summary> /// DataGrid扩展方法 /// </summary> public static class DataGridExtensions { /// <summary> /// 动态生成列 /// </summary> /// <param name="dataGrid">DataGrid控件实例</param> /// <param name="index">列插入位置</param> /// <param name="data">数据源</param> /// <param name="operationKey">操作列资源</param> /// <param name="operationWidth">操作列宽度</param> public static void GenerateColumns(this DataGrid dataGrid, int index, object data, string operationKey, DataGridLength operationWidth) { IList<BindDescriptionAttribute> list = GetColumns(data); //Window win = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive); //Page page = win.GetChildObject<Page>("page"); //if (page == null) throw new Exception("未獲取到當前窗口名稱爲page的(Page)頁面對象,原因:沒有爲Page設置Name,且名稱必須爲【page】!"); Page page = GetParentObject<Page>(dataGrid, "page"); for (int i = 0; i < list.Count; i++) { switch (list[i].ShowAs) { case ShowScheme.普通文本: dataGrid.Columns.Insert(i + index, new DataGridTextColumn { Header = list[i].HeaderName, Binding = new Binding(list[i].PropertyName), Width = list[i].Width }); break; case ShowScheme.自定义: if (page.FindResource(list[i].ResourceKey) != null) { DataGridTemplateColumn val = new DataGridTemplateColumn(); val.Header = list[i].HeaderName; val.Width = list[i].Width; val.CellTemplate = page.FindResource(list[i].ResourceKey) as DataTemplate; dataGrid.Columns.Insert(i + index, val); } break; } } if (!string.IsNullOrWhiteSpace(operationKey) && page != null) { var resource = page.FindResource(operationKey); if (resource!=null) { var col = new DataGridTemplateColumn() { Header = "操作", Width = operationWidth }; col.CellTemplate = resource as DataTemplate; dataGrid.Columns.Add(col); } } } /// <summary> /// 获取数据源对象到列的映射关系 /// </summary> /// <param name="data"></param> /// <returns></returns> private static IList<BindDescriptionAttribute> GetColumns(object data) { List<BindDescriptionAttribute> list = new List<BindDescriptionAttribute>(); var pros = data.GetType().GenericTypeArguments[0].GetProperties(); foreach (var item in pros) { var a = item.GetCustomAttribute<BindDescriptionAttribute>(); if (a != null) { a.PropertyName = item.Name; list.Add(a); } } return list.OrderBy(x => x.DisplayIndex).ToArray(); } /// <summary> /// 查找父级控件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="obj"></param> /// <param name="name"></param> /// <returns></returns> public static T GetParentObject<T>(DependencyObject obj, string name) where T : FrameworkElement { DependencyObject parent = VisualTreeHelper.GetParent(obj); while (parent != null) { if (parent is T && (((T)parent).Name == name | string.IsNullOrEmpty(name))) { return (T)parent; } parent = VisualTreeHelper.GetParent(parent); } return null; } }
完整的参见这里。
BindDescriptionAttribute注解类
在调用web api从服务器端返回的数据中,需要在本地的WPF项目定义相关的实体类,并且在实体类的属性上标记绑定描述类(BindDescriptionAttribute),这个类直接描述了展面如何展示(一般用DataGrid,可按此思路扩展不规则表单),这也是WPF用户控件封装之前的准备工作。
/// <summary> /// DataGrid绑定数据源描述 /// </summary> public class BindDescriptionAttribute : Attribute { /// <summary> /// 列名 /// </summary> public string HeaderName { get; set; } /// <summary> /// 显示为 /// </summary> public ShowScheme ShowAs { get; set; } /// <summary> /// 显示顺序 /// </summary> public int DisplayIndex { get; set; } /// <summary> /// DataGrid列绑定属性名称 /// </summary> public string PropertyName { get; set; } /// <summary> /// 应用内的容模板Key /// </summary> public string ResourceKey { get; set; } /// <summary> /// 列宽 /// </summary> public DataGridLength Width { get; set; } /// <summary> /// 列宽ByGrid /// </summary> public GridLength CloumnWidth { get; set; } /// <summary> /// DataGrid绑定数据源描述 /// </summary> /// <param name="headerName">列名</param> /// <param name="showAs">显示为</param> /// <param name="width">宽度</param> /// <param name="displayIndex">显示顺序</param> /// <param name="resourceKey">自定义列Key</param> public BindDescriptionAttribute(string headerName, ShowScheme showAs = ShowScheme.普通文本, string width = "Auto", int displayIndex = 0, string resourceKey = "") { HeaderName = headerName; DisplayIndex = displayIndex; ResourceKey = resourceKey; ShowAs = showAs; var convert = new DataGridLengthConverter(); Width = (DataGridLength)convert.ConvertFrom(width); var gridCOnvert = new GridLengthConverter(); CloumnWidth = (GridLength)gridCOnvert.ConvertFrom(width); if (showAs == ShowScheme.自定义 && string.IsNullOrWhiteSpace(resourceKey)) throw new ArgumentException($"自定义列时需要指定{nameof(resourceKey)}参数!"); } } /// <summary> /// 展示方式 /// </summary> public enum ShowScheme { 普通文本 = 1, 自定义 = 4 }
上方的枚举ShowScheme则描述了对应的列的显示方案,是该使用普通的文本还是加载数据模板(DataTemplate)。
如何使用DataGridEx实现动态表格功能
要使用自己扩展的动态DataGrid其实是跟使用常规的DataGrid是一样的,只是区别是绑定数据是使用DataSource属性而已,下面就是WPF用户控件封装之动态表格xaml,如下代码所示:
<Page x:Class="JHRS.RegisterManagement.Views.RegisterList" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:prism="http://prismlibrary.com/" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:c="clr-namespace:JHRS.Core.Controls.Common;assembly=JHRS.Core" xmlns:b="http://schemas.microsoft.com/xaml/behaviors" prism:ViewModelLocator.AutoWireViewModel="True" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" x:Name="page" Title="RegisterList" Background="White"> <b:Interaction.Triggers> <b:EventTrigger EventName="Loaded" > <b:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=page}" /> </b:EventTrigger> </b:Interaction.Triggers> <Grid x:Name="maskContainer"> <c:DataGridEx DataSource="{Binding PageData}" IsReadOnly="True"/> </Grid> </Page>
上面代中<c:DataGridEx DataSource=”{Binding PageData}” IsReadOnly=”True”/>这行就是WPF用户控件封装的动态表格绑定数据的用法,而PageData则是来自ViewModel定义的一个IEnumerable<object>属性,只需要在当前页面(Page)的ViewModel里面调用接口获取数据给PageData赋值就可以了,如下代码所示:
/// <summary> /// 綁定分頁數據 /// </summary> [WaitComplete] protected async override Task<object> BindPagingData() { List<Account> list = new List<Account>(); for (int i = 0; i < 15; i++) { list.Add(new Account { Name = "趙佳仁" + i, RegTime = DateTime.Now.AddDays(i), RoleName = "管理員" + i, Title = "無職" + i, UserID = 100 + i }); } PageData = list; await Task.Delay(200); return true; }
而Account类则是这样定义的。
public class Account { [BindDescription("用戶ID")] public int UserID { get; set; } [BindDescription("用戶名")] public string Name { get; set; } [BindDescription("註冊時間")] public DateTime RegTime { get; set; } [BindDescription("角色名穩")] public string RoleName { get; set; } [BindDescription("職級")] public string Title { get; set; } }
完整的代码参见这里。下图是最终的效果:
WPF用户控件封装
Combobox扩展控件
原生的Combobox是WPF提供的下拉框控件,如果约定在整个系统里面,所有的下拉框控件默认项为请选择,如下图所示:
统一的给加上这个的话,在框架中是这样做的,先自定义一个BaseComboBox类,继承自ComboBox类,并在代码里面添加一个默认项,代码如下:
using JHRS.Http; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; namespace JHRS.Core.Controls.Common { /// <summary> /// 下拉框控件基类 /// </summary> public abstract class BaseComboBox : ComboBox { /// <summary> /// 下拉框数据源 /// </summary> public IList Data { get { return (IList)GetValue(DataProperty); } set { AddDefault(value); SetValue(DataProperty, value); } } /// <summary> /// 已登录获取到Token客户端对象 /// </summary> protected HttpClient AuthClient => AuthHttpClient.Instance; /// <summary> /// 服务器配置 /// </summary> protected string BaseUrl => AuthHttpClient.Instance.BaseAddress!.ToString(); // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc... public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(IList), typeof(BaseComboBox), new PropertyMetadata(null, (d, e) => { BaseComboBox c = (BaseComboBox)d; var list = e.NewValue as IList; if (list != null) c.AddDefault(list); c.Data = list; c.ItemsSource = list; })); /// <summary> /// 构造函数 /// </summary> public BaseComboBox() { this.Initialized += OnInitialized; } /// <summary> /// 下拉框初始化事件,子类实现,可以加载各自数据。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected abstract void OnInitialized(object sender, EventArgs e); private string DefaultSelectedValue = "-1"; private string DefaultSelectedText = "—請選擇—"; /// <summary> /// 添加默认项:请选择 /// </summary> /// <param name="data"></param> private void AddDefault(IList data) { if (data == null || data.Count == 0) return; var pros = data[0].GetType().GetProperties(); bool hasSelect = false; var s = pros.FirstOrDefault(x => x.Name == SelectedValuePath); var d = pros.FirstOrDefault(x => x.Name == DisplayMemberPath); if (s == null) throw new Exception("未給ComboBox指定SelectedValuePath屬性,注意:屬性區分大小寫!"); if (d == null) throw new Exception("未给ComboBox指定DisplayMemberPath屬性,注意:屬性區分大小寫!"); foreach (var item in data) { if (s == d && (s.GetValue(item, null) + "") == DefaultSelectedText) { hasSelect = true; break; } else if ((s.GetValue(item, null) + "") == DefaultSelectedValue && (d.GetValue(item, null) + "") == DefaultSelectedText) { hasSelect = true; break; } } if (hasSelect == false) { var subType = data.GetType().GenericTypeArguments[0]; if (subType.Name.StartsWith("<>f__AnonymousType")) return; var m = Activator.CreateInstance(subType); if (s != d) { s.SetValue(m, Convert.ChangeType(DefaultSelectedValue, s.PropertyType), null); d.SetValue(m, Convert.ChangeType(DefaultSelectedText, d.PropertyType), null); } else { d.SetValue(m, Convert.ChangeType(DefaultSelectedText, d.PropertyType), null); } data.Insert(0, m); } } } }
上面的就是完整代码,需要注意的是,在上面的代码中,处理匿名类型有Bug,并没有修复,强类型的绑定是会添加【—請選擇—】这个默认项的。
业务相关的ComboBox下拉框
在实际项目里面,会有很多下拉框的数据源是需要调用接口获取的,将它封装后就可以避免在很多的功能页面(Page)或者控件里面再调接口来获取数据给ComboBox绑定数据,因此封装后直接拖过去用就完事了,这是WPF用户控件封装之下拉框。
在框架里面封装了像科室,字典,通用业务状态的下拉框,这里只放一个科室的下拉框示例代码,因为科室是需要调接口获取的,下方注释掉的WPF用户控件封装代码就是真实项目中调用接口获取数据来绑定的代码。
using JHRS.Core.Controls.Common; using JHRS.Core.Models; using System; using System.Collections.Generic; namespace JHRS.Core.Controls.DropDown { /// <summary> /// 科室下拉框 /// </summary> public class DepartmentComboBox : BaseComboBox { /// <summary> /// 初始化科室數據,可調用接口獲取數據。 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected override void OnInitialized(object sender, EventArgs e) { this.DisplayMemberPath = "Name"; this.SelectedValuePath = "Id"; //var response = await RestService.For<IDepartmentApi>(AuthHttpClient.Instance).GetAll(); //if (response.Succeeded) //{ // Data = response.Data as IList; //} List<DepartmentOutputDto> list = new List<DepartmentOutputDto>(); for (int i = 0; i < 20; i++) { list.Add(new DepartmentOutputDto { Id = i + 1, Name = $"測試科室{i + 1}" }); } base.Data = list; } } }
封装后下拉框怎样使用
如下图所示一样,在演示框架中,WPF用户控件封装之后的控件是直接将其拖到用户控件相关位置,然后绑定你需要获取的值即可,使用的地方不需要关注科室的数据从哪儿来的,你只需要知道你用什么属性取接收选中的值即可。
以上就是使用的方法,接下来看看如何封装分页表格。
动态分页表格
每个系统里面分页表格是大头,或者说是比较复杂的功能,而在框架里面是将表格和分页控件封装到一起形成一个用户控件,在需要使用的地方,也是直接拖过去,并调用分页接口获取数据绑定即可;WPF用户控件封装表格的每一列展示什么数据,也是采用最上面介绍的最基础的动态表格思想解决的。
分页表格控件XAML代码
<UserControl x:Class="JHRS.Core.Controls.DataGrids.PagingDataGrid" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="http://schemas.microsoft.com/xaml/behaviors" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:local="clr-namespace:JHRS.Core.Controls.DataGrids" Loaded="UserControl_Loaded"> <Grid Name="ucDataGrid"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="5"/> <RowDefinition Height="30"/> </Grid.RowDefinitions> <DataGrid x:Name="pagingDataList" Grid.Row="0" IsReadOnly="True" ItemsSource="{Binding PageData}" CanUserAddRows="False" SelectionMode="Single" LoadingRow="pagingDataList_LoadingRow"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}" CanUserSort="False" Header="序号" IsReadOnly="True" Width="40"/> <DataGridTemplateColumn Width="40" x:Name="isCheckbox"> <DataGridTemplateColumn.Header> <CheckBox Click="CheckBox_Click"></CheckBox> </DataGridTemplateColumn.Header> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <CheckBox Click="chkItem_Click" x:Name="chkItem" VerticalAlignment="Center" HorizontalAlignment="Center"></CheckBox> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> <hc:Pagination x:Name="ucPagination" Grid.Row="2" HorizontalAlignment="Right" MaxPageCount="{Binding PagingData.MaxPageCount}" PageIndex="{Binding PagingData.PageIndex, Mode=TwoWay}"> <b:Interaction.Triggers> <b:EventTrigger EventName="PageUpdated" > <b:InvokeCommandAction Command="{Binding ChangePageIndexCommand}"/> </b:EventTrigger> </b:Interaction.Triggers> </hc:Pagination> </Grid> </UserControl>
在这个WPF用户控件封装的xaml代码中,主要放了两个控件,一个DataGrid表格控件,一个Pagination分页控件。后台的C#代码如下:
using JHRS.Core.Extensions; using JHRS.Filter; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace JHRS.Core.Controls.DataGrids { /// <summary> /// PagingDataGrid.xaml 的交互逻辑 /// </summary> public partial class PagingDataGrid : UserControl { public PagingDataGrid() { InitializeComponent(); pagingDataList.AutoGenerateColumns = false; } /// <summary> /// 生成序号 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void pagingDataList_LoadingRow(object sender, DataGridRowEventArgs e) { if (EnablePagination) //需要分页 e.Row.Header = (PagingData.PageIndex - 1) * PagingData.PageSize + e.Row.GetIndex() + 1; else //不需要分页 e.Row.Header = e.Row.GetIndex() + 1; } /// <summary> /// 表格数据源 /// </summary> public IEnumerable<object> PageData { get { return (IEnumerable<object>)GetValue(PageDataProperty); } set { SetValue(PageDataProperty, value); } } //是否已经生成了列并建立了绑定关系 private bool IsGenerateColumns = false; // Using a DependencyProperty as the backing store for PageData. This enables animation, styling, binding, etc... public static readonly DependencyProperty PageDataProperty = DependencyProperty.Register("PageData", typeof(IEnumerable<object>), typeof(PagingDataGrid), new PropertyMetadata((d, e) => { PagingDataGrid pagingDataGrid = d as PagingDataGrid; if (pagingDataGrid.IsGenerateColumns) return; int num = 0; if (pagingDataGrid.EnableRowNumber) num++; if (pagingDataGrid.EnableCheckBoxColumn) num++; pagingDataGrid.pagingDataList.GenerateColumns(num, e.NewValue, pagingDataGrid.OperatingKey, pagingDataGrid.OperatingWidth); pagingDataGrid.IsGenerateColumns = true; })); /// <summary> /// 分页控件数据源 /// </summary> public PagingData PagingData { get { return (PagingData)GetValue(PagingDataProperty); } set { SetValue(PagingDataProperty, value); } } // Using a DependencyProperty as the backing store for PagingData. This enables animation, styling, binding, etc... public static readonly DependencyProperty PagingDataProperty = DependencyProperty.Register("PagingData", typeof(PagingData), typeof(PagingDataGrid)); /// <summary> /// 是否启用分页功能 /// </summary> public bool EnablePagination { get; set; } = true; /// <summary> /// 是否启用序号 /// </summary> public bool EnableRowNumber { get; set; } = true; /// <summary> /// 是否复选框列 /// </summary> public bool EnableCheckBoxColumn { get; set; } = false; /// <summary> /// 复选框列是否启用全选功能 /// </summary> public bool EnableSelectAll { get; set; } = false; /// <summary> /// 操作列的Key /// </summary> public string OperatingKey { get; set; } /// <summary> /// 操作列宽 /// </summary> public DataGridLength OperatingWidth { get; set; } /// <summary> /// 当前选中数据 /// </summary> public IEnumerable<object> CheckedList { get { return (IEnumerable<object>)GetValue(SelectedListProperty); } set { SetValue(SelectedListProperty, value); } } // Using a DependencyProperty as the backing store for SelectedList. This enables animation, styling, binding, etc... public static readonly DependencyProperty SelectedListProperty = DependencyProperty.Register("CheckedList", typeof(IEnumerable<object>), typeof(PagingDataGrid), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); /// <summary> /// 初始化表格行为 /// </summary> private void InintBehavior() { if (!EnablePagination) ucDataGrid.Children.Remove(ucPagination); if (!EnableRowNumber) pagingDataList.Columns.Remove(pagingDataList.Columns.FirstOrDefault(x => x.Header.ToString() == "序号")); if (!EnableCheckBoxColumn) pagingDataList.Columns.Remove(isCheckbox); if (!EnableSelectAll) isCheckbox.Header = "选择"; } /// <summary> /// 用户控件初始化 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UserControl_Loaded(object sender, RoutedEventArgs e) { InintBehavior(); } /// <summary> /// 复选框全选事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void CheckBox_Click(object sender, RoutedEventArgs e) { var c = sender as CheckBox; CheckedAll(pagingDataList, c.IsChecked); CheckedList = GetSelected(); } /// <summary> /// 全选 /// </summary> /// <param name="parent"></param> /// <param name="isChecked"></param> private void CheckedAll(DependencyObject parent, bool? isChecked) { int numVisuals = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < numVisuals; i++) { DependencyObject v = VisualTreeHelper.GetChild(parent, i); CheckBox child = v as CheckBox; if (child == null) { CheckedAll(v, isChecked); } else { child.IsChecked = isChecked; break; } } } /// <summary> /// 获取所有选中项 /// </summary> /// <returns></returns> private List<object> GetSelected() { List<object> list = new List<object>(); foreach (var item in pagingDataList.ItemsSource) { var m = isCheckbox.GetCellContent(item); var c = m.GetChildObject<CheckBox>("chkItem"); if (c != null && c.IsChecked == true) { list.Add(item); } } return list; } /// <summary> /// 单击选中事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void chkItem_Click(object sender, RoutedEventArgs e) { CheckedList = GetSelected(); } } }
如何使用动态分页表格
在需要使用的页面(Page)或者控件里面,将WPF用户控件封装后的控件拖进来就可以了,完整的xaml代码如下
<Page x:Class="JHRS.OutpatientSystem.Views.Reservation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:JHRS.OutpatientSystem.Views" xmlns:prism="http://prismlibrary.com/" xmlns:b="http://schemas.microsoft.com/xaml/behaviors" prism:ViewModelLocator.AutoWireViewModel="True" xmlns:f="clr-namespace:JHRS.Core.Controls.Layouts;assembly=JHRS.Core" xmlns:p="clr-namespace:JHRS.Core.Controls.DataGrids;assembly=JHRS.Core" xmlns:c="clr-namespace:JHRS.Core.Controls.DropDown;assembly=JHRS.Core" x:Name="page" Title="Reservation" Background="#FFFFFFFF"> <b:Interaction.Triggers> <b:EventTrigger EventName="Loaded" > <b:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=page}" /> </b:EventTrigger> </b:Interaction.Triggers> <Page.Resources> <DataTemplate x:Key="Status"> <c:StatusComboBox Name="cboStatus" SelectedValue="{Binding Status, Mode=TwoWay, UpdateSourceTrigger=Explicit,Converter={StaticResource EnumToIntConverter}}"> <b:Interaction.Triggers> <b:EventTrigger EventName="SelectionChanged" > <b:InvokeCommandAction Command="{Binding DataContext.SelectionChangedCommand,RelativeSource={RelativeSource AncestorType=Page}}" CommandParameter="{Binding ElementName=cboStatus}"/> </b:EventTrigger> </b:Interaction.Triggers> </c:StatusComboBox> </DataTemplate> <DataTemplate x:Key="OperationKey"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button Name="btnEdit" Content="編輯" Background="#00FFFFFF" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=Page}}" CommandParameter="{Binding Path=DataContext, ElementName=btnEdit}" /> <Button Name="btnView" Content="詳情" Margin="10,0,0,0" ToolTip="详情" Command="{Binding DataContext.ViewDetailsCommand, RelativeSource={RelativeSource AncestorType=Page}}" CommandParameter="{Binding Path=DataContext, ElementName=btnView}" /> </StackPanel> </DataTemplate> </Page.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="50" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid VerticalAlignment="Top"> <f:FunctionArea AddButtonText="新增預約" /> </Grid> <Grid Name="maskContainer" Row="1"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="5" /> <RowDefinition Height="30" /> </Grid.RowDefinitions> <p:PagingDataGrid Name="ucDataGrid" OperatingKey="OperationKey" OperatingWidth="150*" EnableSelectAll="True" PageData="{Binding PageData}" PagingData="{Binding PagingData}" /> </Grid> </Grid> </Page>
上面代码中<p:PagingDataGrid Name=”ucDataGrid” OperatingKey=”OperationKey” OperatingWidth=”150*” EnableSelectAll=”True” PageData=”{Binding PageData}” PagingData=”{Binding PagingData}” />就是应用动态分页表格的方法,注意绑定数据是你封装的用户控件的PageData依赖属性,而里面绑定的数据源PageData则是ViewModel基类定义的分页数据属性,需要在各子类的ViewModel里面调用接口赋值,如下代码所示:
/// <summary> /// 綁定分頁數據 /// </summary> [WaitComplete] protected async override Task<object> BindPagingData() { var request = this.GetQueryRules(Query); var response = await RestService.For<IReservationApi>(AuthClient).GetPageingData(request); if (response.Succeeded) { PageData = response.Data.Rows; this.PagingData.Total = response.Data.Total; } return response; }
总结一下
合理的进行WPF用户控件封装,可以减少很多重复的代码,本文阐述了怎样封装用户控件的思想,实际项目中,可以结合团队情况和项目状况来封装,总之身处IT江湖,名正言顺的偷偷懒也是向老板多要工资的理由,因为你干活又快又好,哪个包工头不喜欢呢?
下一篇将介绍一下在团队开发中,关于目录文件遵循的一些原则,如果有更好的方式,也欢迎大家提出来。
本系列相关阅读
-
wpf用户控件值的传递
2021-04-24 21:00:49新建C#的wpf用户控件库项目 绘制界面,编写代码 private string _text; public string text { get { return _text; } set { _text = value; NotifyPropertyChanged(); } } private void ...新建C#的wpf用户控件库项目
绘制界面,编写代码
private string _text; public string text { get { return _text; } set { _text = value; NotifyPropertyChanged(); } } private void NotifyPropertyChanged() { this.buttext.Content = _text; }
生成解决方案
新建wpf项目
添加控件
代码:
MessageBox.Show(control1.text); this.control1.text = "改变控件文字";
-
WPF 用户控件的简单使用
2020-06-28 14:41:07WPF 用户控件的简单使用 效果如下图,添加了六个用户控件: 实现方式: 1.首先添加一个用户控件UserControl1,xaml代码如下: <UserControl x:Class="WPF0628.UserControl1" xmlns=... -
WPF 用户控件Loading(加载)样式
2020-12-23 09:12:46WPF 用户控件Loading(加载)样式 首先添加一个用户控件:usercontrol <UserControl x:Class="WpfApp2.LoadingControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x=... -
Powershell中的WPF用户控件
2021-04-07 07:27:35嗨,要求是在选项卡项中显示用户控件。 以下是我正在尝试的代码。Add-Type-AssemblyName PresentationFramework [xml] $ MainXAML = @ -
C# WPF用户控件与窗体之间的调用(控件之间相互调用)
2022-03-14 09:37:47有两个用户控件A,B和一个窗体C A用户控件有一个按钮 B用户控件是展示的内容 C窗体有个一个TabControl控件 A用户控件显示在C窗体上,点击A用户控件的按钮,在C窗体的TabControl中展示用户控件B 代码实现 C窗体代码... -
WPF 用户控件的使用
2020-03-11 11:30:09我们来新建一个用户控件UserControl1.xaml <UserControl x:Class="WpfApplicationDemo.Control.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="ht... -
WPF用户控件 颜色控制.zip
2019-12-03 14:55:08WPF用户控件 颜色控制 -
WPF 用户控件(UserControl)
2020-12-12 11:43:36"用户控件"继承自UserControl,而UserControl继承自ContentControl,也就是内容控件 UserControl和Window是一个层次上的,都有xaml和cs文件 流程 创建用户控件 写好用户控件 <UserControl x:Class="WpfDemo.... -
WPF用户控件的创建和使用
2020-02-29 18:27:48WPF用户控件和自定义控件的使用区别: 第一步添加用户控件,用户控件是xaml格式的,而自定义控件是generic的XAML和cs文件构成(直观区别) -
WPF 用户控件
2014-07-23 14:42:49WPF中的自定义控件往往结合 -
ControlDesign:WPF用户控件设计
2021-05-12 10:21:55ControlDesign -
WPF自定义控件和样式之自定义按钮(Button)
2020-08-27 16:32:14接触WPF也有两个多月了,有了一定的理论基础和项目经验,现在打算写一个系列,做出来一个WPF的控件库。下面这篇文章主要给大家介绍了关于WPF自定义控件和样式之自定义按钮(Button)的相关资料,需要的朋友可以参考... -
wpf 界面控件随着界面大小进行缩放
2018-11-04 21:24:20WPF 界面 实现控件随着界面大小进行缩放,按住Ctrl按钮,然后混动混轮,就可以实现缩放了,控件比例和布局不变,会产生滚动条。 -
WPF 用户控件UserControl 依赖属性绑定
2020-08-06 11:42:56用户控件里面的元素也是继承用户控件的DataContext,所以是没办法用简单的Binding Path 来绑定到依赖属性的。 如果不喜欢写那么一大串的绑定,可以用模板来。用TemplateBinding 是直接绑定到控件的依赖属性上的。 PS:... -
WPF用户控件怎么显示在窗体上
2019-10-06 13:03:28在目标窗体中拖进一个ElementHost控件,然后在后台代码中通过代码的方式将定制的WPF用户控件添加到elementHost控件中, ex: 1 UserControl1 wpf = new UserControl(); 2 elementHost1.Child = wpf; 方法2... -
C# 如何在winform中使用wpf用户控件
2018-08-23 18:14:51使用elementhost, 然后在elementhost上引用wpf的用户控件ElementHost 类命名空间:Windows.System.Forms.Integration程序集:WindowsFormIntegration 转载于:https://blog.51cto.com/13935635/2163534... -
WPF带属性的用户控件
2014-12-05 10:34:00WPF带属性的用户控件,在前台可以直接利用属性进行后台绑定数据 -
Winform用户控件添加WPF用户控件方法
2016-11-17 10:14:43概述:本文主要介绍Winform用户控件添加WPF用户控件方法,希望对大家有帮助。 1.在UserControl控件中加入一个ElementHost控件。 2.添加后台代码: protected override void OnLoad(EventArgs e) {... -
wpf自定义用户按钮控件
2013-01-17 10:15:46wpf 用户控件 按钮.如果你不会用,我无法可说 -
WPF 用户控件和窗体的区别
2022-02-26 09:26:31问题遇到的现象和发生背景 使用VS2019 WPF ,使用Windows窗体,新建了一个MQTT客户端,可以正常运行 又新建了一个程序,在窗体中,新建了一个用户组件,把MQTT客户端写到,用户组件中,再通过窗体显示出来,MQTT... -
WPF用户控件入门
2016-10-12 15:55:32WPF 用户控件 UserControl -
wpf 任意控件拖动改变大小
2015-11-25 18:49:11wpf中任意控件可以拖动和改变大小 -
WPF用户控件与自定义控件(1)用户控件
2020-07-17 14:16:371 -
WinForm-用户控件添加WPF用户控件方法
2019-09-23 11:34:321.在UserControl控件中加入一个ElementHost控件。 2.添加后台代码: protected override void OnLoad(EventArgs e) { base.OnLoad(e); if(!DesignMode) { _WPF... -
wpf 用户控件实现分页
2014-09-08 12:01:29wpf下使用用户控件实现分页的简单例子,相信一定会对大家有很大的参考意义 -
C# WPF ListView控件的实例详解
2020-12-31 11:00:33C# WPF ListView控件的实例详解 C#的WPF作为现在微软主流的桌面程序开发平台,相比过去的MFC时代,有了非常多的不同。本人刚从MFC平台转过来,以为... WPF的代码分为前端和后端两部分,前端为UI,负责与用户进行交互