乐于分享
好东西不私藏

构建工业自动化软件框架:WPF界面开发(一) 主框架搭建

构建工业自动化软件框架:WPF界面开发(一) 主框架搭建

前言

界面的开发也进行了一段时间,断断续续的,所以一直没有太快的进展,当前也只完成了一小部分内容。本来想说全部写完再更新,但是考虑不能总是断更很久,所以还是想着先分享一部分出来。

在界面开发的过程中,也遇到了不少之前没有完善的部分内容,也会一起在界面部分的内容中介绍,如果有些比较大的改动,也会做成番外更新。

框架搭建

在编写界面之前,需要先确定架构,既然已经用了WPF,一定就是使用推荐的MVVM模式了,而MVVM的框架我选择了用的比较广泛的Prism,用了handy control第三方控件库做界面优化。

选择了Prism后,还需要对App.xaml进行一些改造,以保证软件启用的时候使用prism框架。

首先是需要将App.xaml里面的 StartupUri="MainWindow.xaml" 删除,这是WPF默认自带的启动项目,如果这个保留,我们在启动软件的时候会出现两个界面。

然后就是需要让App.xaml继承PrismApplication ,需要同时修改XAML和C#两部分内容

最后就是要在App.xaml.cs里面重写两个方法。CreateShell() 和RegisterTypes(),一个是决定谁是主窗体,一个是注册全局的依赖。还有一些其他需要重写的方法在用到的时候再看。

其中containerRegistry.RegisterCommon() 这部分内容,我是将各个项目的注册分布开,用扩展方法的方式完成。具体可以参考我的上一篇文章。

构建工业自动化软件框架:番外(一) 从Autofac到DryIoc,WPF上位机依赖注入的“去繁就简”

 public partial class App : PrismApplication {     protected override Window CreateShell()     {	     //确定主窗体         return Container.Resolve<MainWindow>();     }     protected override void RegisterTypes(IContainerRegistry containerRegistry)     {        //注册其他项目中的内容		containerRegistry.RegisterCommon();	    containerRegistry.RegisterInfrastructure();        containerRegistry.RegisterCore();        containerRegistry.RegisterApplication();        //注册需要显示的界面        containerRegistry.RegisterForNavigation<UserView>("UserView");        containerRegistry.RegisterForNavigation<AxisInfrastructureView>("AxisInfrastructureView");        containerRegistry.RegisterForNavigation<IOInfrastructureView>("IOInfrastructureView");        containerRegistry.RegisterForNavigation<ProtocolInfrastructureView>("ProtocolInfrastructureView");        containerRegistry.RegisterForNavigation<ParamView>("ParamView");        containerRegistry.RegisterForNavigation<HomeView>("HomeView");        containerRegistry.RegisterForNavigation<StationView>("StationView");        containerRegistry.RegisterForNavigation<AlarmRegisterView>("AlarmRegisterView");        containerRegistry.RegisterForNavigation<AlarmHistoryView>("AlarmHistoryView");//注册弹窗        containerRegistry.RegisterDialog<AddParamViewAddParamViewModel>();     }

同时将需要的界面一一对应新建好View和ViewModel,先有界面然后再对各个界面进行需要的编辑。

主界面搭建

在完成上面的配置之后,就需要开始对界面进行划分,左侧我是用来做为界面导航使用的,上面有标题栏,下面有状态栏,中间就是界面的实际内容。

侧边、标题栏以及状态栏都是固定界面,所以直接使用即可,中间的内容需要根据侧边栏选择进行切换,所以就使用到了RegionManager来进行导航操作。

<Window    x:Class="Sophon.UI.Views.MainWindow"    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:prism="http://prismlibrary.com/"    xmlns:views="clr-namespace:Sophon.UI.Views"    Title="Sophon"    Width="1440"    Height="850"    prism:ViewModelLocator.AutoWireViewModel="True"    ResizeMode="NoResize"    WindowStyle="None"    mc:Ignorable="d">    <Grid>        <Grid.ColumnDefinitions>            <ColumnDefinitionWidth="200" />            <ColumnDefinitionWidth="*" />        </Grid.ColumnDefinitions>        <views:SideView />        <GridGrid.Column="1">            <Grid.RowDefinitions>                <RowDefinitionHeight="100" />                <RowDefinitionHeight="*" />                <RowDefinitionHeight="40" />            </Grid.RowDefinitions>            <views:HeaderView />            <ContentControlGrid.Row="1"		            prism:RegionManager.RegionName="ContentRegion" />            <views:BottomViewGrid.Row="2"VerticalAlignment="Center" />        </Grid>    </Grid></Window>

整体的界面如下图所示。

侧边栏

侧边栏采用了handycontrol的SideMenu控件和Prism的RegionManager功能来实现界面的切换功能。我下面就只展示ViewModel部分的内容,xaml我就不展示了,可以去Github上面查看我的完整项目内容。

public class SideViewModel : BindableBase{    private readonly IRegionManager _regionManager;	//绑定的命令    public DelegateCommand<object> NavigateCommand { getprivate set; }	//依赖注入IRegionManager    publicSideViewModel(IRegionManager regionManager)    {        _regionManager = regionManager;        NavigateCommand = new DelegateCommand<object>(ExcuteNavigate);    }	//执行命令    publicvoidExcuteNavigate(object param)    {        string viewName = (string)param;//导航region        _regionManager.RequestNavigate("ContentRegion", viewName);    }}

标题栏

标题栏比其他部分多几个事件,包括最大化最小化关闭按钮,还有拖拽移动,以及双击最大化最小化。这部分内容都属于界面内部的工作,不包含任何业务逻辑,所以就直接放在了View本身里面,没有绑定到ViewModel中。

private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e){    var window = Window.GetWindow(this);    if (window != null)    {        if (e.ButtonState == MouseButtonState.Pressed)        {            window.DragMove();        }        if (e.ClickCount == 2)        {            ToggleMaximize(window);        }    }}private void ToggleMaximize(Window window){    if (window.WindowState == WindowState.Maximized)    {        window.WindowState = WindowState.Normal;    }    else    {        window.WindowState = WindowState.Maximized;    }}private void MinButton_Click(object sender, RoutedEventArgs e){    var window = Window.GetWindow(this);    if (window != null)    {        window.WindowState = WindowState.Minimized;    }}private void MaxButton_Click(object sender, RoutedEventArgs e){    var window = Window.GetWindow(this);    if (window != null)    {        ToggleMaximize(window);    }}private void CloseButton_Click(object sender, RoutedEventArgs e){    var window = Window.GetWindow(this);    if (window != null)    {        window.Close();        PrismApplication.Current.Shutdown();    }}

状态栏

状态栏当前只包含了当前用户和当前时间,后续如果有添加的部分到时候再做。

其中订阅事件中我使用了ThreadOption.UIThread 将这部分内容强制在UI线程执行,这样就可以确保不影响其他正常线程。

//构造函数 依赖注入IEventAggregator和IUserRepositorypublicBottomViewModel(IEventAggregator eventAggregator, IUserRepository userRepository){    _eventAggregator = eventAggregator;    _userRepository = userRepository;//用户登录事件显示当前用户    _eventAggregator.GetEvent<UserChangeEvent>().Subscribe(async userName =>    {        CurrentUser = userName;        User user = await _userRepository.GetUserByName(CurrentUser);        IsNotLogin = user == null;    }, ThreadOption.UIThread);    StartTimer();}//定时器显示当前事件privatevoidStartTimer(){    DispatcherTimer timer = new DispatcherTimer    {        Interval = TimeSpan.FromSeconds(1)    };    timer.Tick += (s, e) => UpdateClock();    timer.Start();}privatevoidUpdateClock(){    CurrentTime = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");}

后记

以上就是界面主框架相关的部分内容,后面会继续介绍用户管理、报警管理 、硬件管理、流程管理以及主界面的相关内容。后面的分享还是遵循写一部分,分享一部分的原则继续分享。欢迎大家继续关注。

所有的代码都存放再Github中,欢迎大家前往Star、Fork、提Issue。所有文章都发布在专栏里,有需要看前期内容和后续更新的欢迎点击订阅。

https://github.com/JeffreyXXL/Sophon

构建工业自动化软件框架
设计模式在C#开发工控软件中的应用