- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
滴咚,大家好久不见💖。好就没写东西了,鸽着鸽着就无了😭。。.
回到正题, 上篇文章说完命令 提了一嘴 MVVM 模式直接就上 MVVMLight 这些程序的框架了,虽然也没说多少,但还是有点不好过渡,这篇对 MVVM 做一些系统学习,同时复习一下前几篇博文中的内容,完成一个采用 MVVM 模式的示例 Demo .
给 N 早之前文章中的可能混淆的内容做下说明 - 关于 WPF 入门笔记 - 04 - 数据绑定 - OneWayToSource 文本框爆红现象:
OneWayToSource
是将数据从绑定目标传递到绑定源,所以不给初值一定会出现绑定失败的问题 string
类型的字符串,它是空的话是不会爆红的,因为空会转化成 ""
,但如果绑定的数据类型是不能为空的 int
类型,就会在文本框没有内容时出现爆红现象,同时会出现绑定失败 应该是这样,不对的话记得在评论区实现 INotifyPropertyChanged 接口教教我噢🛫 。
MVVM 是 Model-View-ViewModel 的缩写,是一种用于构建用户界面的设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。它在 WPF 和其他 XAML-based 技术中广泛应用,比如 UWP 、采用 XAML 的 MAUI APP ,在前端开发也比较常见。 MVVM 的目标是实现用户界面和业务逻辑之间的彻底分离,以便更好地管理和维护应用程序,并提供更好的可测试性和可扩展性.
MVVM 模式包含以下三个核心组件:
Model(模型)
: Model
代表应用程序的数据和业务逻辑。它负责存储和管理数据,并且通常不包含任何与用户界面相关的代码。 Model
可以是数据实体、数据库访问类、服务类等。 View(视图)
: View
是用户界面的可视化表示,通常由 XAML
文件定义。它负责展示数据给用户,并捕获用户输入。 View
并不处理业务逻辑,而是通过绑定和命令将用户操作传递给 ViewModel
。 ViewModel(视图模型)
: ViewModel
是 View
和 Model
之间的桥梁,负责处理 View
与 Model
之间的交互。它将 Model
中的数据转换为 View
所需的数据,并处理用户输入、命令和其他交互逻辑。 ViewModel
通常实现了 INotifyPropertyChanged
接口,用于通知 View
当数据发生变化时进行更新。
MVVM 的基本原则是让 View 和 Model 完全解耦,它们之间通过 ViewModel 进行通信。 View 通过数据绑定将 ViewModel 中的数据展示给用户,通过命令绑定将用户的操作传递给 ViewModel 。 ViewModel 则通过数据绑定和命令绑定将用户输入转换为对 Model 的操作,从而实现业务逻辑的处理.
回到 WPF 里, View 就是用 xaml 描绘的页面,负责与用户交互,响应用户操作,展示数据给用户; ViewModel 负责收集需要绑定的数据和命令,通过 View 的 DataContext 属性绑定到 View ,同时处理 UI 逻辑; Model 就作为系统中的对象,包含数据对象.
一个 View 对应一个 ViewModel ,一个 ViewModel 可以聚合多个 Model , ViewModel 可以对应多个 View ,在 MVVM 下,所有事件和动作都转换成命令,如按钮的点击操作,不在是触发点击事件,而是绑定到命令,由命令去执行对应的逻辑.
MVVM 模式的优势包括:
MVVM
将用户界面逻辑和业务逻辑彻底分开,使代码结构更清晰,易于理解和维护。 View
和 ViewModel
之间的解耦,可以更轻松地对 ViewModel
进行单元测试,而无需涉及到用户界面的操作。 MVVM
支持模块化开发,不同的 View
和 ViewModel
可以独立开发和组合,使应用程序更易于扩展。 ViewModel
可以与不同的 View
进行重用,从而减少了重复编写代码的工作。 学习一下 MSDN - .NET MAUI 数据绑定和 MVVM 中的 简单MVVM 章节,是一个 .NET MAUI 的 ContentPage ,用来显示时间, Maui 和 WPF 在命名空间上是有点不一样的:
<Window x:Class="OneShotDateTimeDemo.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:local="clr-namespace:OneShotDateTimeDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="One-Shot DateTime Page"
Width="800"
Height="450"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
DataContext="{x:Static sys:DateTime.Now}">
<TextBlock Text="{Binding Year, StringFormat='The year is {0}'}" />
<TextBlock Text="{Binding StringFormat='The month is {0:MMMM}'}" />
<TextBlock Text="{Binding Day, StringFormat='The day is {0}'}" />
<TextBlock Text="{Binding StringFormat='The time is {0:T}'}" />
</StackPanel>
</Grid>
</Window>
这段 Xaml 在 StackPanel 的 DataContext 中设置了一个绑定到当前日期和时间的 DateTime 对象,然后通过数据绑定将 DateTime 对象的各个属性显示在不同的子元素 TextBlock 中,同时对显示内容进行了格式化.
运行程序,文本中的日期和时间只会在构造和初始化的时候设置一次,不会随时间的变化而变化.
示例中是 Label ,我这里换成了 TextBlock ,因为 Label 在 Binding 中使用 StringFormat 不生效【哦,我用的方法不对, Label 不能用 StringFormat 😅】:
上面的页面写出来显然是没有意义的,我们想让它实时显示当前时间,这就需要有个东西在时间发生变化时一直更新页面上的时间,这个过程一般会通过后台代码完成,当然可以通过一个 Timer 搞定,但就和这篇文章的内容没关系了,用 MVVM 的模式怎么实现呢?
首先从 MVVM 三部分来看, View 就是标题为 One-Shot DateTime Page 的窗体, Model 是 DateTime 对象,需要 ViewModel 来处理时间数据同时通知页面响应更改.
创建一个 ClockViewModel.cs 类:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
namespace OneShotDateTimeDemo
{
public class ClockViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private DateTime _dateTime;
private Timer _timer;
public DateTime DateTime
{
get => _dateTime;
set
{
if (_dateTime != value)
{
_dateTime = value;
OnPropertyChanged();
}
}
}
public ClockViewModel()
{
DateTime = DateTime.Now;
_timer = new Timer(new TimerCallback((s) => DateTime = DateTime.Now),
null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}
~ClockViewModel() =>
_timer.Dispose();
public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
ClockViewModel 类实现了 INotifyPropertyChanged 接口,并通过 Timer 定时更新时间并触发属性更改通知,提供实时的系统时间。完成 ViewModel 部分以后,将 ViewModel 绑定到视图 View - MainWindow 上
<Window x:Class="OneShotDateTimeDemo.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:local="clr-namespace:OneShotDateTimeDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="One-Shot DateTime Page"
Width="800"
Height="450"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Window.DataContext>
<local:ClockViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
DataContext="{x:Static sys:DateTime.Now}">
<TextBlock Text="{Binding Year, StringFormat='The year is {0}'}" />
<TextBlock Text="{Binding StringFormat='The month is {0:MMMM}'}" />
<TextBlock Text="{Binding Day, StringFormat='The day is {0}'}" />
<TextBlock Text="{Binding StringFormat='The time is {0:T}'}" />
</StackPanel>
<StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding DateTime, StringFormat='The time is {0:T}'}" />
</StackPanel>
</Grid>
</Window>
这样就可以得到一个 MVVM 模式下的时钟了:
以上就是一个简单的 MVVM 的示例,功能比较简单,只涉及到了属性变动,无关乎用户操作。【时间更新肉眼可见的有问题👻】 。
接下来完成一个完整的 MVVM 示例,内容参考自博主 丑萌气质狗 在B站发布的WPF入门视频,大家可以取搜一下找来看看。内容比较基础,但同时讲解也非常细腻,碎碎念教学,哈哈哈。有些基础但不多的小伙伴可以去看一下😇 。
回到正题,示例是一个简单的登录页面的 MVVM 实现,用界面部分来回顾下之前学习的内容:
MainView.xaml -为了方便区分每部分扮演的角色, 给MainWindow.xaml 改了个名:
登录页面是这样设计的,窗体取消了默认的标题栏,调整为带阴影的圆角窗体,左侧放一张登录背景图,右边自绘了一个关闭按钮,文本框和按钮也做了美化。快速来看一下如何实现的.
窗口样式调整为 None ,不允许调整窗口大小,用 Border 包裹整个窗体实现圆角以及阴影效果,整体分两部分 - 左侧图片、右侧背景图:
<Window x:Class="MVVMDemo.MainView"
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:local="clr-namespace:MVVMDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainView"
Width="800"
Height="450"
AllowsTransparency="True"
Background="{x:Null}"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
mc:Ignorable="d">
<Border Margin="5" Background="AntiqueWhite" CornerRadius="8">
<Border.Effect>
<DropShadowEffect BlurRadius="5"
Direction="0"
Opacity="0.3"
ShadowDepth="0"
Color="Gray" />
</Border.Effect>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Border CornerRadius="8,0,0,8">
<Border.Background>
<ImageBrush ImageSource="./Assets/Images/LoginBackground.png" Stretch="Fill" />
</Border.Background>
</Border>
<Border Grid.Column="1" CornerRadius="0,8,0,8">
... // 内容部分
</Border>
</Grid>
</Border>
</Window>
之前笔记中有说,这里复习以下。去 阿里巴巴矢量图标库 选好要用的图标,添加入库,然后从库里添加至项目:
下载到本地:
主要用上面勾选的两个文件, html 文件是下载的图标演示, tff 是程序中用到的字体文件了,把它放到新建的 Assets 资源文件夹下边:
之后就可以用了, Text 内容可以从演示的 html 中找到,也可以在矢量图项目里面复制代码:
<TextBox Text="" FontFamily="./Assets/Fonts/#iconfont"/> 。
模板内容因为只在这一个登录窗体上使用,就都定义在车 Window.Resources 窗体资源里了,所以看上去页面内容有点多,大家也可以单独定义资源文件。先说按钮,两个按钮都是通过自定义控件模板来完成的,关闭按钮主要是定义了一个 Path 控件,用于绘制一个 "X" 形状,类似于关闭按钮的图标,等率按钮就直接用 TextBlock 代替的:
<ControlTemplate x:Key="CloseButton" TargetType="Button">
<Border Name="back" Background="Transparent" CornerRadius="0,8,0,0">
<Path HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M0 0 12 12M0 12 12 0"
Stroke="Black"
StrokeThickness="1" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="back" Property="Background" Value="red" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="LoginButton" TargetType="Button">
<Border Name="back" Background="{TemplateBinding Background}" CornerRadius="8">
<TextBlock Text="登录" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="back" Property="Background" Value="red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
文本框也是通过自定义控件模板来完成的,只不过自己写东西有点多,所以是通过编辑模板给文本库框加的图标:
<Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" CornerRadius="8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="8*"/>
</Grid.ColumnDefinitions>
<TextBlock FontFamily="/Assets/Fonts/#iconfont" Text="{TemplateBinding Tag}" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center" />
<ScrollViewer Grid.Column="1" x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalAlignment="Center"/>
</Grid>
</Border>
图标属性因为 Text 属性要获取输入的文本,所以改成了通过文本框的 Tag 属性绑定内容.
而后的控件布局就不解释了吧,直接看代码吧:
<Window x:Class="MVVMDemo.MainView"
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:local="clr-namespace:MVVMDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainView"
Width="800"
Height="450"
AllowsTransparency="True"
Background="{x:Null}"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
mc:Ignorable="d">
<Window.Resources>
<ControlTemplate x:Key="CloseButton" TargetType="Button">
<Border Name="back" Background="Transparent" CornerRadius="0,8,0,0">
<Path HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M0 0 12 12M0 12 12 0"
Stroke="Black"
StrokeThickness="1" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="back" Property="Background" Value="red" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="LoginButton" TargetType="Button">
<Border Name="back" Background="{TemplateBinding Background}" CornerRadius="8">
<TextBlock Text="登录" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="back" Property="Background" Value="red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<SolidColorBrush x:Key="TextBox.Static.Border" Color="#FFABAdB3"/>
<SolidColorBrush x:Key="TextBox.MouseOver.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="TextBox.Focus.Border" Color="#FF569DE5"/>
<Style x:Key="IconTextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True" CornerRadius="8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="8*"/>
</Grid.ColumnDefinitions>
<TextBlock FontFamily="/Assets/Fonts/#iconfont" Text="{TemplateBinding Tag}" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center" />
<ScrollViewer Grid.Column="1" x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalAlignment="Center"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="border" Value="0.56"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.Focus.Border}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsInactiveSelectionHighlightEnabled" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
</MultiTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Border Margin="5" Background="AntiqueWhite" CornerRadius="8">
<Border.Effect>
<DropShadowEffect BlurRadius="5"
Direction="0"
Opacity="0.3"
ShadowDepth="0"
Color="Gray" />
</Border.Effect>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Border CornerRadius="8,0,0,8">
<Border.Background>
<ImageBrush ImageSource="./Assets/Images/LoginBackground.png" Stretch="Fill" />
</Border.Background>
</Border>
<Border Grid.Column="1" CornerRadius="0,8,0,8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*" />
<RowDefinition Height="7*" />
</Grid.RowDefinitions>
<StackPanel>
<Button Width="30" Height="30" HorizontalAlignment="Right" Template="{StaticResource ResourceKey=CloseButton}" />
<TextBlock Margin="15"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="26"
Foreground="Black"
Text="图书管理系统" />
<TextBlock Margin="5"
HorizontalAlignment="Center"
FontSize="16"
Text="MVVM示例Demo" />
</StackPanel>
<Grid Grid.Row="1" Margin="20,20">
<Grid.RowDefinitions>
<RowDefinition Height="3*" />
<RowDefinition Height="3*" />
<RowDefinition Height="3*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TextBox Style="{DynamicResource IconTextBoxStyle}" Tag="" Height="60" FontSize="15"/>
<TextBox Grid.Row="1" Style="{DynamicResource IconTextBoxStyle}" Tag="" Height="60" FontSize="15" />
<Button Grid.Row="2" Width="200" Height="50" Template="{StaticResource LoginButton}" Background="Blue" Foreground="White" FontSize="20"/>
</Grid>
</Grid>
</Border>
</Grid>
</Border>
</Window>
这是登录页面,登录成功后的页面随便写个吧: handyControlView ,尝鲜一下 HandyControl :
先安装 HandyControl
在 App.xaml 中添加 HandyControl 资源字典:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
引入命名空间: xmlns:hc="https://handyorg.github.io/handycontrol" ,之后使用引用对应样式就可以了:
HandyControl 分原生控件和拓展控件,原生控件是在 WPF 原生控件的基础上做了美化,本质功能是一样的,拓展控件则是原生控件中没有的,比如可以播放 Gif 动图的 GifImage ,我就看看原生控件了,扩展控件等专门用的时候再研究:
<Window x:Class="MVVMDemo.handyControlView"
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:controls="https://handyorg.github.io/handycontrol"
xmlns:local="clr-namespace:MVVMDemo"
mc:Ignorable="d"
Title="handyControlView" Height="450" Width="800">
<Grid>
<StackPanel>
<Label Content="LabelDanger样式" Margin="10" Style="{DynamicResource LabelDanger}"></Label>
<Button Content="Button" Style="{StaticResource ButtonPrimary}"/>
<Button Content="这是一个按钮" Margin="10,0,0,0" controls:BorderElement.CornerRadius="15"/>
<CheckBox Content="CheckBox" IsChecked="True"/>
<CheckBox Margin="0,16,0,0" Content="CheckBox" IsChecked="True" IsEnabled="False"/>
<CheckBox Margin="0,16,0,0" Content="CheckBox" IsChecked="{x:Null}"/>
<Slider VerticalAlignment="Center" Width="400" Value="40" Maximum="100"/>
<TextBox Width="200" VerticalAlignment="Center"/>
<RadioButton Content="默认样式"/>
<RadioButton Margin="0,16,0,0" Content="不可编辑" IsChecked="True" IsEnabled="False"/>
<StackPanel Margin="20">
<TextBlock Text="ProgressBarSuccess"></TextBlock>
<ProgressBar Style="{DynamicResource ProgressBarSuccess}" Value="40"></ProgressBar>
</StackPanel>
<StackPanel Margin="20">
<TextBlock Text="ProgressBarInfo"></TextBlock>
<ProgressBar Style="{DynamicResource ProgressBarInfo}" Value="40"></ProgressBar>
</StackPanel>
</StackPanel>
</Grid>
</Window>
在不使用 MVVM 情况下实现点击登录:点击登录按钮获取输入的用户名密码,验证是否正确,正确则创建页面隐藏当前页:
private void btnLogiin_Click(object sender, RoutedEventArgs e)
{
if (txtUserName.Text == "Cass" && txtPassword.Text == "12136")
{
handyControlView handyControlView = new handyControlView();
handyControlView.Show();
this.Close();
}
else
{
MessageBox.Show("用户名或密码错误。");
}
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
现在所有东西是耦合在一起的,虽然这样看起来实现起来比较快,但是在后期维护起来是非常麻烦的,尤其是💩⛰️工程,假如其中任何一个环节发生了变化,程序就会出问题,简而言之就是牵一发而动全身.
接下来看一下MVVM模式下的实现:
这部分主要是登录页面的数据部分,涉及两个字段,用户名和密码:
LoginModel.cs :
namespace MVVMDemo
{
public class LoginModel
{
private string _UserName;
public string UserName
{
get { return _UserName; }
set { _UserName = value; }
}
private string _Password;
public string Password
{
get { return _Password; }
set { _Password = value; }
}
}
}
视图部分主要做数据的呈现,通过绑定和命令解耦图形界面和数据以及执行动作之间的关系,数据之间的交互在用户名和密码文本框:
动作有两个,一个是关闭按钮,另一个是登录按钮。关闭按钮只是关闭当前窗体,不涉及业务交互,所以保留之前的 Click 事件即可,登录按钮就涉及到视图、数据以及业务逻辑了,这就需要通过命令来完成.
在这之前需要完成命令接口的定义,新建一个文件: RelayCommand.cs 实现 ICommand 接口:
using System;
using System.Windows.Input;
namespace MVVMDemo
{
public class RelayCommand : ICommand
{
private readonly Action _execute; // 需要执行的操作(命令体)
private readonly Func<bool> _canExecute; // 命令是否可以执行的逻辑
public RelayCommand(Action action, Func<bool> canExecute)
{
_execute = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) // ICommand接口方法之一,用于判断命令是否可以执行
{
if (_canExecute == null)
{
return true; // 命令始终可以执行
}
else
{
return _canExecute(); // 调用 _canExecute() 获取判断结果
}
}
public void Execute(object parameter) // ICommand接口方法之一 用于执行命令体,调用 _execute 所存储的操作
{
_execute?.Invoke();
}
public event EventHandler CanExecuteChanged // ICommad接口中的事件,当命令的可执行状态发生变化时,触发此事件来通知界面元素更新
{
add
{
if (_canExecute != null) { CommandManager.RequerySuggested += value; }
}
remove
{
if (_canExecute != null) { CommandManager.RequerySuggested -= value; }
}
}
}
}
这样就完成了命令接口的实现,接着来看 ViewModel 视图模型,视图模型作为沟通数据 Model 和视图 View 之间的桥梁,就得多干一些活了。首先是数据部分,为了更好的解耦,不让图形界面与数据产生直接交互,在 ViewModel 里嵌套一层在获取 Model 以及 View 部分的数据并完成验证处理、数据更新还有命令操作:
LoginViewModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace MVVMDemo
{
public class LoginViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; // INotifyPropertyChanged接口事件,用于在属性更改时发出通知
private LoginModel _loginModel;
private MainView _mainView;
public LoginViewModel(MainView mainView)
{
_loginModel = new LoginModel();
_mainView = mainView;
}
// 绑定到登录界面文本框的属性,用于获取和设置用户名和密码
public string UserName
{
get { return _loginModel.UserName; }
set
{
_loginModel.UserName = value;
OnPropertyChanged(UserName);
}
}
public string Password
{
get { return _loginModel.Password; }
set
{
_loginModel.Password = value;
OnPropertyChanged(Password);
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) // 触发属性更改通知的方法
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void LoginFunc() // 处理登录操作
{
if (UserName == "Cass" && Password == "12136")
{
handyControlView handyControlView = new handyControlView();
handyControlView.Show();
_mainView.Close();
}
else
{
MessageBox.Show("用户名或密码错误");
UserName = "";
Password = "";
}
}
private bool CanLoginExecute()
{
return true;
}
public ICommand LoginAction // 绑定到登录按钮的命令属性
{
get
{
return new RelayCommand(LoginFunc, CanLoginExecute); //(执行体,判断条件)
}
}
}
}
完成了沟通部分的桥梁怎样,和其他两部分取得练习呢?数据部分,通过属性更新已经通知到了,视图部分就需要通过绑定数据上下文来完成了:
Mainview.xaml.cs 。
using System.Windows;
namespace MVVMDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
this.DataContext = new LoginViewModel(this);
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}
以上一个简单的 MVVM 示例就完成了,怎么说呢,虽然看着很简单,代码也没多少,但是对初学者来说绝非易事。还有一件事想要告诉大家,学习过程中一定不要只看不写,在学习过程中,实践是非常重要的。阅读和理解代码的同时,动手编写代码是巩固知识的关键部分。即使在完成这篇博文的过程中,我仍有新的收获在里面,所以大家千万不要只看,纸上谈兵永远都是空谈😉.
希望可以给大家一点参考,有错误的部分还请在评论区指出.
最后此篇关于WPF入门笔记-07-MVVM示例的文章就讲到这里了,如果你想了解更多关于WPF入门笔记-07-MVVM示例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
有没有办法在另一个 WPF 窗口内托管 WPF 窗口。我有几个有点复杂的表格。但现在为了简化事情,我试图将其中一些合并为一个“仪表板”表单中的标签页。 请注意,我不是要托管 Windows 窗体,而是
WPF 特有的哪些方面和实践在非 WPF GUI 编程中最有用(并且并非难以实现)? 最佳答案 通过学习 WPF 命令,我了解了命令模式。它构成了 UI - 代码分离的基础,我认为应该在其他应用程序中
WinRT/Metro 正在获得一个新的 SemanticZoom控制,但我很难为 WPF 找到任何东西。 我不想为我的特定项目切换到 Metro,因为它不允许我制作窗口应用程序或跨多个显示器的多个实
我很难解决我的问题,我快要疯了。 想法是这样的:我有两个 ListView 元素,当一个元素从第一个列表掉落到第二个列表时,我需要打开一个对话,但我需要被掉落的元素的信息以及被添加以填充对话的元素。
如果我遵循TabControl,并且一切正常,当我切换到第二个Tabitem时,它显示就没有问题。 //datagrid //datagrid2 但是如果我有这个xaml,当我
在 Windows 窗体应用程序中,我们的数据 GridView 有很多事件,如行鼠标双击或行单击以及额外的...... 但是在 WPF 中我找不到这些事件。我如何将行鼠标双击添加到其中包含数据网格的
在这个项目中,代码 正确编译和执行 ;但是,我需要帮助解决两个问题: VS2012 WPF 设计器不适用于此 XAML 文件。它显示消息设计 View 对于 x64 和 ARM 目标平台不可用。 我收
目前我正在设计 WPF ScrollViewer,我发现了这个 Content="M 0 0 L 4 4 L 0 8 Z" 阅读 MSDN examples .现在我真的很想知道这意味着什么,但我无法
在 WPF 中,元素的可见性可以为“可见”,但实际上在屏幕上不可见,因为它的父元素(或父元素的父元素)具有折叠的可见性。 我希望能够知道一个元素是否实际呈现在屏幕上,而不必遍历可视化树检查父元素。 有
我应该使用 ApplicationCommands.Close用于关闭模式对话框还是该命令被认为是为关闭应用程序保留的?如果是后者,请大家创建Close每个命令 Dialog盒子或只是一个 Close
WPF 是否有任何可用的 piemenu 控件? 最佳答案 我在我的最爱中找到了这个,你可以看看: This 祝你今天过得愉快。 关于wpf - WPF 的菜单,我们在Stack Overflow上找
我正在尝试使用 WrapPanel 和两个 TextBlock 将星号 (*) 附加到某些文本的左侧,允许文本换行,并强制文本右对齐。通过创建一个 FlowDirection 设置为 RightToL
这里是场景(简化):我在Window上有一个控件(比如说一个Rectangle)。我迷上了MouseMove事件,以使其启动拖放操作。然后在MouseDown事件中进行动画处理,向右移动50个像素。但
我有一个 ListView ,它的项目来源是一个列表。我希望用户只选择一项。当我将 listview 的 SelectionMode 设置为 single 时,用户仍然可以选择多个项目,并且似乎 li
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the he
INotifyPropertyChanged 的目的是什么。我知道每当更改属性时都会触发此事件,但是 View /用户界面如何知道触发了此事件: 这是实现 INotifyPropertyChang
我正在查看工具箱中的 WPF 组件,但找不到 2005/2008 中存在的错误提供程序。 被移除了吗? 最佳答案 ErrorProvider是一个 Winforms 控件。 WPF 中没有等效项。但是
我试图在单击和双击 wpf Image 控件时有不同的行为。不幸的是,单击首先被触发,因此双击被忽略。 最佳答案 如果您改用 MouseDown 事件,则它在 EventArgs 中为 ClickCo
这可能吗? 我使用了一个框架控件并且:显示(例如:showwindow.xaml) 但是我得到这个错误: root element is not valid for navigation 最佳答案 确
我在蓝色背景的窗口上放置了一个扩展器,我想让扩展器的按钮与默认颜色不同(蓝色,它是从窗口接收的)。当我修改扩展器的背景属性时,它会将整个扩展器、标题和全部更改为新颜色。但是,我只想更改按钮本身。谁能指
我是一名优秀的程序员,十分优秀!