前言
界面的开发也进行了一段时间,断断续续的,所以一直没有太快的进展,当前也只完成了一小部分内容。本来想说全部写完再更新,但是考虑不能总是断更很久,所以还是想着先分享一部分出来。
在界面开发的过程中,也遇到了不少之前没有完善的部分内容,也会一起在界面部分的内容中介绍,如果有些比较大的改动,也会做成番外更新。
框架搭建
在编写界面之前,需要先确定架构,既然已经用了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<AddParamView, AddParamViewModel>();}
同时将需要的界面一一对应新建好View和ViewModel,先有界面然后再对各个界面进行需要的编辑。
主界面搭建
在完成上面的配置之后,就需要开始对界面进行划分,左侧我是用来做为界面导航使用的,上面有标题栏,下面有状态栏,中间就是界面的实际内容。
侧边、标题栏以及状态栏都是固定界面,所以直接使用即可,中间的内容需要根据侧边栏选择进行切换,所以就使用到了RegionManager来进行导航操作。
<Windowx: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 { get; private set; }//依赖注入IRegionManagerpublicSideViewModel(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
夜雨聆风