当前位置: 代码迷 >> 综合 >> 工业上位机开发实战 WPF + MVVM 框架
  详细解决方案

工业上位机开发实战 WPF + MVVM 框架

热度:80   发布时间:2023-10-25 14:58:59.0

上一篇博客介绍了上位机实现MVVM 框架的步骤 MVVMtoolkit 学习_叮当说的博客-CSDN博客

下面我们继续来讲解下实现上位机中会遇到的一些小问题:

回顾:

之前的程序中我们已经知道了 ,当数据改变的时候,可以使用通知的方法来来告知其他的模块这样便可以实现多个界面的通讯,但是ContentControl中存放的page 如何实现自适应?

1、ContentControl 中的内容自适应

因为这个项目中ContentControl 控件内部存放的是 page,所以其实只要在改变窗口大小的时候将对应的page 尺寸进行更改就可以实现效果上面的自适应;

这里采用的方法是使用window 的sizechange 事件,在win的sizechange 的事件发生时,就会调用一个函数“resizeWindow” 该函数用来调整 当前page 的尺寸

注意: 由于这里使用的是MVVM 框架,正常事件触发需要将 该事件绑定到ViewModel 中,但是事件绑定 需要使用委托有兴趣的可以参考:MVVM设计模式和WPF中的实现(四)事件绑定 - durow - 博客园

该方法介绍:

事件绑定

但是由于上文中提到的插件 已经更新换代了,所以目前使用的是新的框架,该框架可以参考如下连接:

Open Sourcing XAML Behaviors for WPF - .NET Blog

解释下步骤,

Steps to migrate:

  1. Remove reference to “Microsoft.Expression.Interactions” and “System.Windows.Interactivity”
  2. Install the “Microsoft.Xaml.Behaviors.Wpf” NuGet package.
  3. XAML files – replace the xmlns namespaces “http://schemas.microsoft.com/expression/2010/interactivity” and “http://schemas.microsoft.com/expression/2010/interactions“with “http://schemas.microsoft.com/xaml/behaviors“
  4. C# files – replace the usings in c# files “Microsoft.Xaml.Interactivity” and “Microsoft.Xaml.Interactions” with “Microsoft.Xaml.Behaviors”

步骤如上所示;

1、将原有的旧版本引用去除

2、在nuget 管理中增加 Microsoft.Xaml.Interactions 工具

3、 然后再xmal 中引用该命名空间

      xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
 

然后就可以在按钮的下面增加事件的绑定了;绑定的代码如下:

                            <Button Content="xxx"><i:Interaction.Triggers><i:EventTrigger EventName="Click"><i:InvokeCommandAction Command="{Binding DebugFunction}"  CommandParameter="holdcnc" /></i:EventTrigger></i:Interaction.Triggers></Button>

需要注意一个细节:

在绑定事件得时候需要注意一个事情 就是 关于MouseLeftButtonDown事件失效的问题

 所以如果需要使用 按钮按下或者弹起的事件时,不能使用冒泡类的路由类型,而是要使用隧道的类型,否则会失效,所以这里我们要用Preview 的事件类型

参考连接:

WPF高级教程(八)专题:事件_白话屋-CSDN博客

wpf:关于MouseLeftButtonDown事件失效的问题_卐兜兜飞卍的专栏-CSDN博客

     

另外需要注意一点就是 但按钮按下如果触发了MessageBox 就会导致

PreviewMouseLeftButtonUp 事件无法触发,这个时候可以增加一个LostMouseCapture

的事件作为补充,另外为了避免因为触发瞬间要触发LostMouseCapture 事件,导致系统无法识别,建议在按钮按下需要执行的指令 增加Task 另外开一个现程;

            var myTask = new Task(() =>{MessageBox.Show("press");});myTask.Start();

           

因为这个实现比较复杂,我使用的方法依然是消息发送类型:

我这面使用的是将消息 发送成 string 然后让对应的模块接收后进行调整;当然这部分代码是写在了主界面的逻辑层
 

       private void Window_SizeChanged(object sender, SizeChangedEventArgs e){WeakReferenceMessenger.Default.Send<String>("resizeWindow");}

实现效果后如下:

2、如何增加软件得图标 ico?

首先,需要先建立一个ico 的图标文件

转换的网站为:ICO图标在线生成

然后将文件保存到 对应的img 文件夹中

右击项目的名字,然后选择属性

 然后选择自己的图标文件

 最后运行就可以了;

3、点击图中作者栏的头像如何跳转到对应的网页

如:

 因为作者图片使用的是Image 控件

在前台写一个事件:

                <Image Style="{DynamicResource ImageStyle1}" Source="Img/3.jpg" Width="50" Height="50" Margin="0,0,0,0"MouseLeftButtonDown="Image_MouseLeftButtonDown">

 然后事件可以直接在逻辑层写吧,因为比较简单,使用发送消息反而有点浪费了:

访问对应的网址:

System.Diagnostics.Process.Start("explorer.exe", "https://blog.csdn.net/qq_34995963");

如何实现次级子界面:

下面以数据库为例,创建如上图所示的子界面

首先在Views 中新键一个page 比如 叫做: Page3.xmal

然后对前台的代码写法如下:

 

    <Page.Resources><Style x:Key="RadioButtonStyle" TargetType="{x:Type RadioButton}"><Setter Property="FocusVisualStyle"><Setter.Value><Style><Setter Property="Control.Template"><Setter.Value><ControlTemplate><Rectangle Margin="2" SnapsToDevicePixels="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/></ControlTemplate></Setter.Value></Setter></Style></Setter.Value></Setter><Setter Property="FontSize" Value="26"/><Setter Property="VerticalContentAlignment" Value="Center"/><Setter Property="HorizontalContentAlignment" Value="Left"/><Setter Property="BorderBrush" Value="Transparent"/><Setter Property="Foreground" Value="Black"/><Setter Property="BorderThickness" Value="0"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type RadioButton}"><Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True"><Border x:Name="border" BorderBrush="Red" BorderThickness="0" CornerRadius="10"  SnapsToDevicePixels="True"/><Border x:Name="bd2"/><ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/></Grid><ControlTemplate.Triggers><Trigger Property="HasContent" Value="True"><Setter Property="FocusVisualStyle"><Setter.Value><Style><Setter Property="Control.Template"><Setter.Value><ControlTemplate><Rectangle Margin="14,0,0,0" SnapsToDevicePixels="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/></ControlTemplate></Setter.Value></Setter></Style></Setter.Value></Setter><Setter Property="Padding" Value="4,-1,0,0"/></Trigger><Trigger Property="IsMouseOver" Value="True"><Setter Property="Background" Value="Transparent" TargetName="border"/></Trigger><Trigger Property="IsChecked" Value="true"><Setter Property="Background" Value="#ffffff" TargetName="border"/><Setter Property="Opacity" Value="0.15" TargetName="border"/></Trigger><Trigger Property="IsChecked" Value="{x:Null}"/></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter><Setter Property="MinHeight" Value="50"/><Setter Property="HorizontalAlignment" Value="Stretch"/></Style></Page.Resources><Grid><!--关于界面--><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="220"/><ColumnDefinition Width="4*"/></Grid.ColumnDefinitions><Border Background="#ffffff" Margin="10 10 10 10" CornerRadius="10" Opacity="0.1"/><Grid><Grid.RowDefinitions><RowDefinition Height="100"/><RowDefinition/></Grid.RowDefinitions><TextBlock Text="数据库" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="30" /><!--左侧的工具列--><ListBox Grid.Row="1" ItemsSource="{Binding MenuModels}"  Background="Transparent"  BorderThickness="0" Width="220" HorizontalAlignment="Left"><ListBox.ItemContainerStyle><Style TargetType="ListBoxItem"><Setter Property="HorizontalContentAlignment" Value="Stretch"/><Setter Property="BorderThickness" Value="0"/><Setter Property="Background" Value="Transparent"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type ListBoxItem}"><Grid><Border Name="border"/><ContentPresenter/></Grid><ControlTemplate.Triggers><Trigger Property="IsSelected" Value="True"><Setter Property="Background" TargetName="border" Value="Transparent"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style></ListBox.ItemContainerStyle><ListBox.ItemTemplate><DataTemplate><RadioButton Margin="30 10 30 0"VerticalContentAlignment="Center" Command="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:Page3}},Path=DataContext.SelectedCommand,Mode=TwoWay}" Style="{StaticResource RadioButtonStyle}"CommandParameter="{Binding Index}"><RadioButton.IsChecked><Binding Path="IsSelected" RelativeSource="{RelativeSource AncestorType=ListBoxItem}" Mode="TwoWay"/></RadioButton.IsChecked><StackPanel  Orientation="Horizontal"><TextBlock Grid.Column="0" Text="{Binding IconFont}" FontFamily="../Fonts/#iconfont" FontSize="28" Margin="10 0 0 0" Foreground="White"/><TextBlock Margin="15 0 0 0" Text="{Binding Title}"  FontSize="18" VerticalAlignment="Center" Foreground="White"/></StackPanel></RadioButton></DataTemplate></ListBox.ItemTemplate></ListBox></Grid><!--右侧的装子控件的容器--><ContentControl Grid.Row="1" Grid.Column="1" x:Name="PageContent" HorizontalAlignment="Left"><Border ><Grid  Width=" 600"><Border Background="#ffffff"   Margin="10" CornerRadius="15" Opacity="0.1"/><StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 30 0 0"><TextBlock Text="Kohli tells India fans not to boo Smith" Foreground="White" FontSize="18" FontWeight="Bold" Width="230" TextWrapping="Wrap"/><TextBlock Text="India captain makes intervention from the middle after 'Cheater!' chants directed at Steven Smith" FontSize="12" Margin="0 5 0 0" Foreground="White" Width="230" TextWrapping="Wrap"/><TextBlock Foreground="White" Margin="0 20 0 0"><Hyperlink  NavigateUri="https://www.baidu.com" Foreground="#E0D1EA" FontWeight="Bold" FontSize="18">READ FULL STORY</Hyperlink></TextBlock></StackPanel></Grid></Border></ContentControl></Grid></Grid>
</Page>

Page 后台的代码为:

    public partial class Page3 : Page{public static Page3 page;public Page3(){InitializeComponent();this.DataContext = new Page3ViewModel();page = this;}}

另外这个位置用到了MVVM 的模型所以,需要建立一个名称为

Page3ViewModel  viewModel的类;

需要添加的命名空间如下:

using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using Microsoft.Toolkit.Mvvm.Messaging;
using Mvvmtoolkit.Models;
using Mvvmtoolkit.Views;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

Page3ViewModel 的代码为:

using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using Microsoft.Toolkit.Mvvm.Messaging;
using Mvvmtoolkit.Models;
using Mvvmtoolkit.Views;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;namespace Mvvmtoolkit.ViewModels
{class Page3ViewModel : ObservableRecipient, IRecipient<MessengerA>, IRecipient<MessengerB>, IRecipient<string>{static Views.PageDatabaseSubs.Page1 subPage1 = new Views.PageDatabaseSubs.Page1();static Views.PageDatabaseSubs.Page2 subPage2 = new Views.PageDatabaseSubs.Page2();static Views.PageDatabaseSubs.Page4 subPage4 = new Views.PageDatabaseSubs.Page4();// 如果需要绑定到 mainViewmodel 上面的话 需要在这里Frame frame1 = new Frame() { Content = subPage1 };Frame frame2 = new Frame() { Content = subPage2 };Frame frame4 = new Frame() { Content = subPage4 };//Frame frame1 = new Frame() { Content = new Views.PageDatabaseSubs.Page1() };//Frame frame2 = new Frame() { Content = new Views.PageDatabaseSubs.Page2() };Frame frame3 = new Frame() { Content = new Views.PageDatabaseSubs.Page3() };//Frame frame4 = new Frame() { Content = new Views.PageDatabaseSubs.Page4() };Frame frame5 = new Frame() { Content = new Views.PageDatabaseSubs.Page5() };Frame nowFrame = new Frame();private ObservableCollection<MenuModel> menuModels;public ObservableCollection<MenuModel> MenuModels{get { return menuModels; }set { menuModels = value; }}private int _value;public int Value{get { return _value; }set{// 这种方式只要变更就会通知,不管数值是否有变化_value = value;this.OnPropertyChanged();//SetProperty<int>(ref _value, value);// 数据有变化才会通知}}public void resizeWindow(){// 赋值小于0 会报错nowFrame.Height = Math.Max(MainWindow.win.ActualHeight - 140, 0);nowFrame.Width = Math.Max(MainWindow.win.ActualWidth - 350, 0);}public ICommand SelectedCommand { get; set; }public void changePage(object obj){if (obj != null){//MessageBox.Show(obj.ToString());}switch (obj.ToString()){case "1":subPage1.DataContext = MainWindow.win.DataContext;nowFrame = frame1;//nowFrame.DataContext= MainWindow.win.DataContext;break;case "2":// 在这个位置进行数据上下文的绑定 在后台绑定会报错subPage2.DataContext = MainWindow.win.DataContext;nowFrame = frame2;break;case "3":nowFrame = frame3;break;case "4":// 绑定数据上下文subPage4.DataContext = MainWindow.win.DataContext;nowFrame = frame4;break;case "5":nowFrame = frame5;break;default:break;}Page3.page.PageContent.Content = nowFrame;resizeWindow();}public Page3ViewModel(){this.IsActive = true;MenuModels = new ObservableCollection<MenuModel>();MenuModels.Add(new MenuModel() { IconFont = "\xe617", Title = "ini", Index = "1", });MenuModels.Add(new MenuModel() { IconFont = "\xec5d", Title = "Json", Index = "2", });MenuModels.Add(new MenuModel() { IconFont = "\xe62b", Title = "cyc", Index = "3", });MenuModels.Add(new MenuModel() { IconFont = "\xe6f6", Title = "xml", Index = "4", });MenuModels.Add(new MenuModel() { IconFont = "\xe647", Title = "SQL", Index = "5", });//MenuModels.Add(new MenuModel() { IconFont = "\xe790", Title = "Stats", Index = "6", });//MenuModels.Add(new MenuModel() { IconFont = "\xe672", Title = "World cu", Index = "7", });//初始话commandSelectedCommand = new RelayCommand<object>(changePage);}public void Receive(MessengerA message){System.Windows.MessageBox.Show("我收到了收到MessengerA");}public void Receive(MessengerB message){MessageBox.Show("收到MessengerB");}public void Receive(string message){if (message.ToString() == "resizeWindow"){resizeWindow();//nowFrame.Refresh();}}}}

注意这个位置使用了一个名称为:MenuModel的类,所以需要在Model中声明该类

该类的写法为;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace Mvvmtoolkit.Models
{public class MenuModel{public string IconFont { get; set; }public string Title { get; set; }public string Index { get; set; }}
}

最后需要在Views 中建立如下的sub界面

文件夹为PageDatabaseSubs