• 基于c#上位机制作(WPF控件)


    前言

    目前,根据前面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)
                });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    公共资源放置

    像导航菜单、上位机设计时需要读取的数据表,我建立了一个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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    初始化时集中在了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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样各个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");
    
    • 1
    • 2
    • 3

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

     <DataGrid x:Name="datashow" 
               CellEditEnding="datashow_CellEditEnding" 
               BeginningEdit="datashow_BeginningEdit"
               ItemsSource="{Binding dtrun, Mode=TwoWay}">
    
     </DataGrid>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    dtrun = CommonRes.dt1;
    
    dtset = CommonRes.dt2;
    
    dtfactor = CommonRes.dt3;
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    var x = dataset.SelectedIndex;
    
    • 1

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

    private void dataset_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        newValue = (e.EditingElement as TextBox).Text;
        CommonRes.dt2 = dtset;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

    跨线程操作

    这里,上位机的串口和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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    操作控件时,使用的就是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);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    每个页面的串口事件

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

    private void Page_Unloaded(object sender, RoutedEventArgs e)
    {
      CommonRes.mySerialPort.DataReceived -= new SerialDataReceivedEventHandler(this.mySerialPort_DataReceived);
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    VB.NET 如何将某个Excel的工作表中复制到另一个的Excel中的工作表中https://bbs.csdn.net/topics/392861034
    【贪心的商人】python实现-附ChatGPT解析
    MySQL事务
    精酿啤酒新风尚,FENDI CLUB盛宴启幕,品质生活触手可及
    非DBA人员从零到一,MySQL InnoDB数据库调优之路(四)-数据备份与迁移
    Spring【五大类注解的存储和读取Bean方法注解】
    数据结构之冒泡排序
    『手撕Vue-CLI』获取下载目录
    辅助驾驶功能开发-功能规范篇(27)-3-导航式巡航辅助NCA华为
    【深度学习】pix2pix GAN理论及代码实现
  • 原文地址:https://blog.csdn.net/finhaz/article/details/127538323