前言

目前,根据前面winform做的上位机,我研究了下wpf下设计上位机,希望把界面做的更美观,目前实现了串口助手的功能,通讯协议初步支持了modbus和原本winform里面的协议,算法调取部分目前还未迁移完成,估计无限期延后,相关的源代码公开到github和gitee上面(搜索finhaz/fruit,里面的ocean工程就是wpf对应的设计),希望给大家提供思路。这里的思路主要是界面设计方面的思路,上位机设计的思想在前面的winform设计篇(点此)讲了,而上位机所需的通讯方面,则记录于modbus协议那篇博文(点此)。
wpf做的软件,表面上至少比winform的漂亮了,而且自适应框架的能力更强,就是相对费事。
目前不想继续开发下去的主要原因是我要去搞学术研究了,时间精力有限,暂时使用mthings作为调试用上位机。

界面设计思路

之前我在winform的设计思路下,主要考虑的是利用多个窗体,来管理多个不同的输入界面,这样在刚开始设计的时候很简单。
而在换了wpf进行界面设计时,我考虑的是做一个窗体,然后在页面之间切换,来选择不同的界面,并且切换界面使用的导航栏我考虑的是使用汉堡菜单。
同时,算法开发与界面设计分离,算法另外写一个cs文件,界面设计仅考虑布局和逻辑操作。
也就是软件本身一个mainwindows,对应xaml文件如下:
在这里插入图片描述
每个页面page对应一个page:
在这里插入图片描述
用汉堡菜单切换各个页面。

页面的窗口自适应

wpf的最方面的一个地方是利用xaml可以快速的实现自适应页面,有gird等控件可以辅助使用,网上相关界面很多,这里不再赘述。
在这里插入图片描述

MahApps-汉堡菜单的实现

在wpf里面想实现汉堡菜单时,没有原生的控件,因此这时候,通过查询网上,了解到有一个设计框架库MahApps.Metro。MahApps用户UI库帮助把uwp的特性可以被移植到wpf开发上,提供了很多控件/样式,利用Nuget可以安装该类库。
这里初步的界面效果是这样的
在这里插入图片描述
左侧是导航栏,点击按钮就会跳转到对应的页面,即每个page对应的xaml文件。
修改对应的xaml文件的方法,在ShellViewModel.cs之中,修改对应的xaml文件路径和名称

            // Build the menus
            this.Menu.Add(new MenuItem()
            {
                Icon = new PackIconFontAwesome() { Kind = PackIconFontAwesomeKind.BugSolid },
                Label = "Bugs",
                NavigationType = typeof(BugsPage),
                NavigationDestination = new Uri("UI/debug_serial.xaml", UriKind.RelativeOrAbsolute)
            });

公共资源放置

像导航菜单、上位机设计时需要读取的数据表,我建立了一个CommonRes类

    public class CommonRes
    {
        public static SerialPort mySerialPort = new SerialPort();

        public static DataTable dt1 = new DataTable();
        public static DataTable dt2 = new DataTable();
        public static DataTable dt3 = new DataTable();
    }

初始化时集中在了mainwindows对应的cs代码之中。

            this.navigationServiceEx = new Navigation.NavigationServiceEx();
            this.navigationServiceEx.Navigated += this.NavigationServiceEx_OnNavigated;
            this.HamburgerMenuControl.Content = this.navigationServiceEx.Frame;

            // Navigate to the home page.
            this.Loaded += (sender, args) => this.navigationServiceEx.Navigate(new Uri("Views/MainPage.xaml", UriKind.RelativeOrAbsolute));


            CommonRes.dt1 = DB_Access.GetDBTable("PARAMETER_RUN");
            CommonRes.dt2 = DB_Access.GetDBTable("PARAMETER_SET");
            CommonRes.dt3 = DB_Access.GetDBTable("PARAMETER_FACTOR");

这样各个page读取时,就可跨越所有的页面使用。

表格控件的使用

这里对应上位机设计中的数据库部分,是比较关键的地方
读取access数据的方式和之前一样,相关的操作函数我写在database.cs文件之中。

CommonRes.dt1 = DB_Access.GetDBTable("PARAMETER_RUN");
CommonRes.dt2 = DB_Access.GetDBTable("PARAMETER_SET");
CommonRes.dt3 = DB_Access.GetDBTable("PARAMETER_FACTOR");

对应的显示控件是DataGrid,这里我是使用数据绑定的方式,让每个DataGrid绑定到一个datatable变量

 <DataGrid x:Name="datashow" 
           CellEditEnding="datashow_CellEditEnding" 
           BeginningEdit="datashow_BeginningEdit"
           ItemsSource="{Binding dtrun, Mode=TwoWay}">

 </DataGrid>

access读取的值赋值给page内的datatable,这样就把数据库内容显示到DataGrid之上。

dtrun = CommonRes.dt1;

dtset = CommonRes.dt2;

dtfactor = CommonRes.dt3;

在这里插入图片描述
DataGrid控件相比于winform的 datagridview,在数据操作上复杂,可利用datagrid变量的select_index属性来读取每个datagrid对应上你当前cell的所在行,返回值而且是int类型。

var x = dataset.SelectedIndex;

每次编辑后,利用单元格的endedit事件,将修改的结果返回给数据库读取时的datatable变量

private void dataset_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
    newValue = (e.EditingElement as TextBox).Text;
    CommonRes.dt2 = dtset;
}

另外,增加了窗口关闭的事件,将datatable变量值用于更新表

 MessageBox.Show("数据将保存!");
 //DB_Access.UpdateDBTable(CommonRes.dt1, "PARAMETER_RUN");
 DB_Access.UpdateDBTable(CommonRes.dt2, "PARAMETER_SET");
 DB_Access.UpdateDBTable(CommonRes.dt3, "PARAMETER_FACTOR");

跨线程操作

这里,上位机的串口和UI是两个线程,通常我们会把报文数据显示到textbox的控件,这时候就需要委托了。我的textbox叫show_text,每个控件有Dispatcher属性,利用这一属性编写output函数,加载ouputAction操作。

private delegate void outputDelegate(string para);
private void output(string para)
{
  this.show_text.Dispatcher.Invoke(new outputDelegate(outputAction), para);
}
private void outputAction(string para)
{
  
  show_text.Text+=para;
}

操作控件时,使用的就是output函数在串口接受事件里面,传入参数就是你想要显示的数据。

 private void mySerialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
 {
     Thread.Sleep(99);
     int n = CommonRes.mySerialPort.BytesToRead;
     byte[] buf = new byte[n];
     CommonRes.mySerialPort.Read(buf, 0, n);

     string txt = "RX:";
     for (int i = 0; i < n; i++)
     {
         if (Protocol_num == 0)
         {
             txt += Convert.ToString(buf[i], 16);
         }
         else if (Protocol_num == 1)
         {
             txt += Convert.ToString(buf[i], 16);
         }
         txt += ' ';
     }
     txt += '\r';
     txt += '\n';
     output(txt);
 }

每个页面的串口事件

由于我有调试串口和通信协议两个page都用到了串口,接受处理事件不一样,因此我在页面切换时,加了-=。

private void Page_Unloaded(object sender, RoutedEventArgs e)
{
  CommonRes.mySerialPort.DataReceived -= new SerialDataReceivedEventHandler(this.mySerialPort_DataReceived);
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐