一、简介
最近需要开发一个桌面版的工具软件,之前用得更多的是Winform,作为一个全干工程师,我们也要兼顾下WPF,趁此机会再研究下开源控件库。
MaQaQ:Winform真好用(有个HZHControls控件库,值得一看)。
二、准备工作
找了下开源控件库,诸如MaterialDesignInXAML、HandyControl、AduSkin、Adonis-UI、Panuon.WPF.UI、DMSkin等等,以上这些我们都暂时不看。
本次选用的控件库是Layui.WPF: GitHub - Layui-WPF-Team/Layui-WPF: 这是一个WPF版的Layui前端UI样式库。
选用的原因是我的渣渣网络在打开其他库的时候都加载很慢,只有它脱颖而出,这就是缘分啊。
MaQaQ:实际上MaterialDesignInXAML跟HandyControl我之前在别的项目有用过,这两个star数都挺高的,用起来也不错,HandyControl在Gitee上也有库,有兴趣的朋友可以去看看,这次我只是想试试新东西。
顺利访问到了GitHub库,我打开了里面的学习文档,他只给了我一个Hello world,真是干!得好。再往下翻,找到了使用说明:
使用说明很简洁,看起来很轻松就能用上了,但我不信。那我们就去把源码下载下来吧,感恩开发者,他还给了示例(搬砖党狂喜啊)。打开程序源码,根据提示安装了.net5跟.net7后成功加载,找到LayuiApp,这是示例项目:
运行后界面如下(PS:右上角的公告写得真好,值得一读!!!):
三、开发
作为一个wpf菜鸡+资深搬砖党,此时不忙搬运,我们先打开LayuiApp的MainWindow.xaml,仔细研究一番:
可以看到,原项目采用的是prism框架(一个用于构建复杂但组织良好的 WPF 应用程序的框架,实际上我并不认识它,这是通义千问告诉我的),总之,prism:ViewModelLocator.AutoWireViewModel="True"这一句,就是用于自动关联视图与视图模型。
创建一个新的WPF程序,这里还是用我熟悉的.net6,慎重思考1秒后决定命名为ServerControlSystem,新建的项目里面自带有MainWindow.xaml。
Nuget上引用LayUI.Wpf和Prism的相关库:
为了实现prism的自动关联,我们要新建两个文件夹,Views和ViewModels,将MainWindow.xaml移动到Views文件夹下,在ViewModels文件夹中对应新建MainWindowViewModel.cs:
当然,也可以自定义,这就需要我们自己去修改App.xaml.cs,添加配置:
protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); ViewModelLocationProvider.Register<MainWindow, MainWindowViewModel>(); }
这里我们还是用的自动关联,因为移动了MainWindow的位置,所以要对应修改下命名空间的路径(不想改你就直接删了重建吧):
MainWindow.xaml:
<Window x:Class="ServerControlSystem.Views.MainWindow"...> ... </Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
而在prism框架下,一般要将App的基类改为PrismApplication:
App.xaml:

<prism:PrismApplication x:Class="ServerControlSystem.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ServerControlSystem" xmlns:prism="http://prismlibrary.com/"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/LayUI.Wpf;component/Themes/Default.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </prism:PrismApplication>
View Code
App.xaml.cs:
public partial class App : PrismApplication
同时在prism框架下,一般是通过重写CreateShell方法来指定主窗口,且需要实现继承的RegisterTypes:

public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); DispatcherUnhandledException += App_DispatcherUnhandledException; } private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { LayMessage.Error(e.Exception.Message); //记录日志 e.Handled = true; } protected override void RegisterTypes(IContainerRegistry containerRegistry) { //注入自定义接口 LayDialog.Register(Container.Resolve<IContainerExtension>()); } }
View Code
现在,我们可以开始快乐搬运了,先小搬一下MainWindow.xaml,这里我们只是验证控件库的调用,所以就试一下Lay:LayTitleBar:

<Window x:Class="ServerControlSystem.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Lay="clr-namespace:LayUI.Wpf.Controls;assembly=LayUI.Wpf" xmlns:prism="http://prismlibrary.com/" Width="1080" Height="600" prism:ViewModelLocator.AutoWireViewModel="True" AllowsTransparency="True" WindowStartupLocation="CenterScreen" WindowStyle="None" Title="MainWindow" > <Grid> <Lay:LayTitleBar Background="{DynamicResource LighCyan}" CornerRadius="4" ResizeMode="CanResize" WindowState="{Binding WindowState, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> </Lay:LayTitleBar> </Grid> </Window>
View Code
然后再看模型,先实现基类ViewModelBase:

public abstract class ViewModelBase : BindableBase, INavigationAware, IRegionMemberLifetime, IConfirmNavigationRequest { /// <summary> /// 导航器 /// </summary> public IRegionManager Region; /// <summary> /// 弹窗服务 /// </summary> public IDialogService Dialog; /// <summary> /// 事件聚合器 /// </summary> public IEventAggregator Event; public ViewModelBase() { } public ViewModelBase(IContainerExtension container) { this.Region = container.Resolve<IRegionManager>(); this.Dialog = container.Resolve<IDialogService>(); this.Event = container.Resolve<IEventAggregator>(); } private DelegateCommand _LoadedCommand; public DelegateCommand LoadedCommand => _LoadedCommand ?? (_LoadedCommand = new DelegateCommand(ExecuteLoadedCommand)); /// <summary> ///初始化界面加载 /// </summary> public virtual void ExecuteLoadedCommand() { } /// <summary> /// 控制视图是否被缓存 /// </summary> public bool KeepAlive => false; public virtual void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) { continuationCallback(true); } /// <summary> /// 控制实例是否被缓存 /// </summary> /// <param name="navigationContext"></param> /// <returns></returns> public virtual bool IsNavigationTarget(NavigationContext navigationContext) { return false; } /// <summary> /// 导航离开当前ViewModel时被调用 /// </summary> /// <param name="navigationContext"></param> public virtual void OnNavigatedFrom(NavigationContext navigationContext) { } /// <summary> /// 导航到当前ViewModel时被调用 /// </summary> /// <param name="navigationContext"></param> public virtual void OnNavigatedTo(NavigationContext navigationContext) { } }
View Code
再实现MainWindowViewModel:

public class MainWindowViewModel : ViewModelBase, IWindowAware { private WindowState _WindowState; /// <summary> /// 窗体状态 /// </summary> public WindowState WindowState { get { return _WindowState; } set { _WindowState = value; RaisePropertyChanged(); } } public MainWindowViewModel(IContainerExtension container) : base(container) { } public override void ExecuteLoadedCommand() { base.ExecuteLoadedCommand(); } public bool CanClosing() { var res = MessageBox.Show("确定关闭窗体吗?", "提示", MessageBoxButton.OKCancel); if (res == MessageBoxResult.OK) return true; else return false; } }
View Code
到这一步基本就完成了,编译生成一下,运行后就能得到一个简陋的空界面:
之后,就可以根据需要,自己添加控件了。比如,你可以加上,标题栏就会美观一点:
<Lay:LayTitleBar.Header> <Border Height="40"> </Border> </Lay:LayTitleBar.Header>
四、总结
Winform真好写。