C# 从零开始使用Layui.Wpf库开发WPF客户端

一、简介

最近需要开发一个桌面版的工具软件,之前用得更多的是Winform,作为一个全干工程师,我们也要兼顾下WPF,趁此机会再研究下开源控件库。

MaQaQ:Winform真好用(有个HZHControls控件库,值得一看)。

二、准备工作

找了下开源控件库,诸如MaterialDesignInXAMLHandyControlAduSkinAdonis-UIPanuon.WPF.UIDMSkin等等,以上这些我们都暂时不看。

本次选用的控件库是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真好写。

相关推荐
wangnaisheng1 天前
【WPF】Opacity 属性的使用
wpf
姬激薄1 天前
配置Hadoop集群-集群配置
wpf
python算法(魔法师版)1 天前
.NET 在鸿蒙系统上的适配现状
华为od·华为·华为云·.net·wpf·harmonyos
大道随心2 天前
【wpf】11 在WPF中实现父窗口蒙版效果:原理详解与进阶优化
wpf
zizisuo2 天前
9.1.领域驱动设计
wpf
大道随心2 天前
【wpf】10 C#树形控件高效实现:递归构建与路径查找优化详解
开发语言·c#·wpf
离歌漠2 天前
WPF内嵌其他进程的窗口
c#·wpf
沉到海底去吧Go3 天前
【身份证识别表格】批量识别身份证扫描件或照片保存为Excel表格,怎么大批量将身份证图片转为excel表格?基于WPF和腾讯OCR的识别方案
ocr·wpf·excel·身份证识别表格·批量扫描件身份证转表格·图片识别表格·图片识别excel表格
csdn_aspnet3 天前
WPF 性能 UI 虚拟化 软件开发人员的思考
ui·wpf
冰茶_3 天前
WPF之绑定模式深入
学习·microsoft·微软·c#·wpf·绑定模式