WPF【09】WPF基础入门 (三层架构与MVC架构)

9-2 【操作】WPF 基础入门

新建一项目

Create a new project - WPF Application (A project for creating a .NET Core WPF Application) - Next - .NET 5.0 (Current) - Create

项目创建完成,VS自动打开 GUI用户界面,格式是 .xaml文件,跟xml差不多,也属于xml文件的类别。

虽然xaml是WPF用户的底层标准,但是VS已帮我们全部封装好了,可以直接拖拽布局界面。

9-3 【理论】XAML页面剖析

MainWindow.xaml代码示例

<Window x:Class="WpfApp.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:local="clr-namespace:WpfApp"

mc:Ignorable="d"

Title="MainWindow" Height="450" Width="800">

<Grid>

<Button Height="50" Width="100" Content="CLick me 4" />

<d:Button>

<Button.Height>50</Button.Height>

<Button.Width>100</Button.Width>

<Button.Content>

<TextBlock>Click me 4</TextBlock>

</Button.Content>

</d:Button>

</Grid>

</Window>

上面标红的<Button> 相当于下面的7行代码(当然不能有 d:),因为<Button ......> 的内部属性可以通过"元素嵌套"的方式声明。

<Window ......> 根元素介绍

xmlns 是 xml-namespace 的缩写,用于定义命名空间的。上面看到的是MS系统默认的命名空间,可以使用这个命名空间下的所有UI控件。

比如这里的 Button TextBlock,另外当来源不同的类重名时,也可以通过命名空间区分不同的UI控件。

xmlns 是默认的命名空间,这种不带任何映射参数的命名空间,整个页面只能有一个,一般使用元素最频繁使用的命名空间。

比如这里的 Window Grid Button TextBlock,都来自 xmlns 。

接着,还能看到带有映射前缀的:x 、:d 等的命名空间,这些命名空间都与解析 xaml语言相关,

比如这里的 x:Class 来自 xmlns:x 这个命名空间,它将会指定当前页面所对应的 cs 文件,以及当前页面所对应的类的名称。

比如这里的 xmlns:local代表当前应用的命名空间,即项目名称:WpfApp,而

Title="MainWindow" Height="450" Width="800" 分别定义了视窗的标题、高度和宽度。

mc:Ignorable="d"这个命名空间稍微有点费解,它的名称叫 Ignorable,即可以忽略的,表示在运行过程中可以被忽略的控件,而这些可忽略的控件使用 d: 来引导。即运行时忽略,上面布局中有两个 Button ,运行时,下面的Button不会显示的。

MainWindow.xaml.cs(UI页面逻辑代码)代码示例

......

namespace WpfApp

{

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

var grid = (Grid)this.Content;

Button button = new Button();

button.Height = 50;

button.Width = 100;

Margin = new Thickness(0, 0, 700, 385);

button.Content = "Button !!!";

grid.Children.Add(button);

}

}

}

x:Class="WpfApp.MainWindow" 对应上面的 namespace WpfApp 和 class MainWindow。

构造函数 public MainWindow() 在程序启动时调用。InitializeComponent();帮助完成页面初始化工作(比如初始化标题、字体大小等)。

<Grid>就是页面的内容,cs中 this.Content 访问的<Grid>元素,cs中 var grid = (Grid)this.Content; 下面就是用 代码的方式添加Button按钮。

纯粹使用 cs 来完成用户界面的做法已经非常过时了,主流的界面开发工作更加倾向于MVC(Model View Controller),即:数据模型-视图-控制逻辑 三者分离的模式。

建议使用 xaml 来进行页面设计,而通过 cs 来控制页面和数据的动态变化。

9-4 【拓展】MVC 架构

什么是MVC

·软件工程的架构方式

·模型(Model)、视图(View)和控制器(Controller)

·分离业务操作、数据显示、逻辑控制

从而使用同一个程序可以使用不同的表现形式!

请问:Model就是用来访问数据库的吗?

对于整个MVC架构,最难理解的就是Model模型了。

很多程序员在最开始使用MVC框架时,都会误认为Model就是用来访问数据库索取数据的,而最重要的业务逻辑却放在控制器(Controller)里,最终写出来的系统结构,就变成了:View用来显示界面、Controller做核心业务、Model仅仅用来访问数据库,做数据变化而已。很明显,这是混淆了另一种软件开发模式:三层架构。

三层架构 和 MVC 有些类似,也是分为三个部分。

三层架构

·UI层,表示用户界面

·BLL(Business Logic Layer)业务逻辑层,处理核心业务以及数据封装

·DAL(Data Access Layer)层,表示数据访问

这种三层架构曾经红极一时,不过现在逐渐被 MVC、DDD(领域驱动)、Microservices(微服务)等架构取代了。

简单来说,MVC 与 三层架构 除了都分为三个部分以外,也没什么共同点了!

MVC 与 三层架构

·三层架构面向接口编程,而三个层级之间的完全解耦、完全可替换

·MVC的每个部分都是紧密结合的,它的核心并不是解耦,而是重用

也就是说,MVC中同样的Model可以适配于不同的控制器,搭配不同的视图用来显示不同的内容,然而系统最核心的逻辑始终包含在模型(Model)中,可以被重复使用。

三层架构所描述的是自下而上,具有明显层级的架构。首先,得有数据库,根据数据库来创建DAL(Data Access Layer) 来获取和映射数据;得到原始数据后传递给BLL(Business Logic Layer)业务逻辑层,进行数据验证、数据转换、对象封装;最后封装好的数据传递给UI层,显示给用户。

MVC架构各个组成部分是水平架构的,只有调用关系,没有层级关系。所有的数据流动和显示,都是通过数据绑定事件驱动所处理的。

首先,应该确定核心业务模型(Model),通过Model来创建数据库;

第二,用户发起请求,将请求传递给控制器;

第三,控制器调用模型;

第四,模型获取数据,对数据做出验证,并将转换好的数据交还给Controller,业务逻辑也发生在这里;

第五,Controller把数据交给视图,视图向用户展示数据。

所以,MVC架构中一定要知道自己的核心业务是什么,所有业务都要围绕它展开,不要混淆三层架构设计理念。

为什么要使用MVC呢?因为有各种好处:

MVC的优点

第一、耦合性低

视图层和业务层可以分离,这样就允许修改视图层代码,而不需要重新编译模型和控制器的代码;比如,改写 jsp、html、css 或者 js 代码,并不需要重启服务器。同样,一个业务流程或业务规则发生改变,只需要改变MVC的模型,因为模型与控制器、视图相分离,所以很容易改变应用程序的数据层 和 业务规则。

第二、可复用性高

MVC的各种组件都具有高可复用性,随着技术的不断进步需要越来越多的方式来访问应用程序,MVC模式允许各种各样不同样式的视图来访问同一个服务端的代码,多个视图可以共享一个模型。

比如用户可以通过 Web应用,也可以通过手机App来订购某样产品,虽然订购的方式不一样,但处理订购产品的业务是一样的,由于模型访问数据并没有发生改变,所以同样的功能可以被不同的界面使用。

第三、高可维护性

MVC也具有高可维护性,分离视图和业务逻辑,可以使得Web应用更容易维护和修改,比如,如果想更改业务逻辑,只需要更改业务逻辑,......,这样的好处就是后期维护成本降低,新功能的增加、代码的扩展也非常方便。

MVC的缺点

第一、定义不明确,学习曲线陡

大家都是依照自己经验来解释和使用MVC,而MVC内部原理比较复杂,组合多种设计模式,所以完全理解MVC并不是很容易,需要花一些时间去思考,尤其对新手来说,有一定的学习曲线

第二、结构复杂

MVC并不适合小型,甚至中等规模的应用程序,对于简单的页面,严格遵循MVC,反而会增加结构的复杂性,可能产生过多的数据操作,导致运行效率低下

第三、数据流动效率低

依据模型操作接口的不同,视图可能需要多次调用才能获得足够的数据显示,对于未变化的数据频繁地访问模型,也可能会造成操作性能的下降,所以从数据操作角度来说,过多地频繁访问会导致数据流动的效率下降。

9-5 【理论】逻辑树与视觉树

StackPanel 是纵向排列的空间,可以在里面添加各种各样的控件。

<StackPanel>

<TextBlock HorizontalAlignment="Center" Margin="20">Hello World</TextBlock>

<ListBox Height="100" Width="100">

<ListBoxItem Content="item 1"></ListBoxItem>

<ListBoxItem Content="item 2"></ListBoxItem>

<ListBoxItem Content="item 3"></ListBoxItem>

<ListBoxItem Content="item 4"></ListBoxItem>

</ListBox>

<Button Margin="20" Width="100" Click="Button_Click">随便</Button>

</StackPanel>

cs中代码

private void Button_Click(object sender, RoutedEventArgs e)

{

MessageBox.Show("按钮被点击了!"); //这里加入断点

}

给上面点击事件,加入断点。启动调试,在"Autos"中,找到"Name"中 sender 对应的"Value"中,鼠标放到放大镜上,提示"WPF Tree Visualizer",点击【放大镜】进入视觉树查看页面。

在这个窗口中,左边是视觉树,右边是当前视觉树(选中的)节点所有的属性。

从页面来说整个像是一棵树被倒过来一样。从树根出发,一层一层开枝散叶,树根 MainWindow ,而 MainWindow 连接的是视觉节点 Border(边框),继续连接后续的视觉节点等等。

不过,在视觉树中,也可以找到逻辑节点。比如 StackPanel、TextBlock、ListBox 等等。

从原理来说,逻辑树是视觉树的子集

逻辑树是视觉树的子集示例图

9-6 【操作】Grid 网格系统

Grid网格是WPF中最基本的行列布局工具。

示例:3行3列的网格布局

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="3*"></ColumnDefinition>

<ColumnDefinition Width="2*"></ColumnDefinition>

<ColumnDefinition Width="*"></ColumnDefinition>

</Grid.ColumnDefinitions>

<Grid.RowDefinitions>

<RowDefinition Height="*"></RowDefinition>

<RowDefinition Height="*"></RowDefinition>

<RowDefinition Height="*"></RowDefinition>

</Grid.RowDefinitions>

<Button Grid.Row="0" Grid.Column="0">click me dddd </Button>

<Button Grid.Row="0" Grid.Column="1">click me 2</Button>

<Button Grid.Row="1" Grid.Column="0">click me 3</Button>

<Button Grid.Row="1" Grid.Column="1">click me 2</Button>

<TextBlock Grid.Row="2" Grid.Column="0">Hello World</TextBlock>

</Grid>

Width值中:* 是权重操作,值还可以是 auto,或 数字(表示像素) 等

9-7 【操作】依赖属性与数据处理

<Grid>

<Button Height="100" Width="200" Content="ClickMe">

<Button.Style>

<Style TargetType="Button">

<Style.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="FontSize" Value="25"></Setter>

<Setter Property="Foreground" Value="White"></Setter>

</Trigger>

</Style.Triggers>

</Style>

</Button.Style>

</Button>

</Grid>

在xaml中到处都是属性,像这边Button中的属性Height等,所有这些属性都依赖于 Button.Style 中所定义的 property,也就是说,这些UI上的属性 与 事件触发属性是绑定在一起的,从数据流动的角度来说,这就是数据绑定最基本的逻辑。属性依赖也可以让相关的UI控件知道,当前事件的处理逻辑

public class Button : ButtonBase {

public static readonly DependencyProperty IsCancelProperty;

public static readonly DependencyProperty IsDefaultedProperty;

public static readonly DependencyProperty IsDefaultProperty;

public Button();

public bool IsCancel{ get; set; }

public bool IsDefault { get; set; }

public bool IsDefaulted { get; }

......

}

这里可以看到3个readonly的DependencyProperty,也就是属性依赖,分别用来判断默认属性、属性取消、以及是否回到默认属性。同时对应这三个依赖属性,还有3个基础属性,在程序正常运行时,这3个基础属性会与依赖属性互相连接,而这3组属性则可以控制UI控件中的动态变化。比如,样式数据的绑定、动画效果,甚至样式的继承。

而在这个 ButtonBase 中,继承于ContentControl,在它内部除了有可以处理事件的 RoutedEvent 以外,所有其他的都是依赖属性。

public abstract class ButtonBase : ContentControl, ICommandSource {

public static readonly RoutedEvent ClickEvent;

public static readonly DependencyProperty ClickModeProperty;

public static readonly DependencyProperty CommandParameterProperty;

public static readonly DependencyProperty CommandProperty;

public static readonly DependencyProperty CommandTargetProperty;

public static readonly DependencyProperty IsPressedProperty;

......

}

public class ContentControl : Control, IAddChild {

......

}

让我们更深入一点,进入ContentControl,再进入 Control,同样可以看到类似的属性依赖,可以看到刚刚使用过的 FontSizeProperty 字体大小、ForegroundProperty 前景,等各种各样其他的属性,对于WPF的UI控件来说,属性依赖是最基础的数据和最基础的事件传递机制。

public class Control : FrameworkElement {

public static readonly DependencyProperty BackgroundProperty;

public static readonly DependencyProperty TemplateProperty;

public static readonly DependencyProperty TabIndexProperty;

public static readonly RoutedEvent PreviewMouseDoubleClickEvent;

public static readonly DependencyProperty PaddingProperty;

public static readonly RoutedEvent MouseDoubleClickEvent;

public static readonly DependencyProperty IsTabStopProperty;

............

public static readonly DependencyProperty FontSizeProperty;

............

public static readonly DependencyProperty ForegroundProperty;

}

9-8 【操作】Data Binding 数据绑定

  1. 单向绑定 one way bining : Source -> Target

  2. 双向绑定 two way bining : Source <-> Target

  3. 指定方向单向绑定 oneWayToSource Target -> Source

  4. 单次绑定 One Time -> 构造方法中单次执行

<StackPanel>

<TextBox Name="myTextBox" Width="100" Margin="50" Text="{Binding ElementName=mySlider, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>

<Slider Name="mySlider" Minimum="0" Maximum="100" IsSnapToTickEnabled="True"></Slider>

</StackPanel>

指定绑定的数据内容是什么?指定数据需要使用 Path 属性,这里的 Path 其实是 Slider 的数据,也就是 Value,最后指定绑定方式。

IsSnapToTickEnabled="True" 则显示的数字,拖动不会显示小数了。

有什么办法,不需要按下 Tab键,就进行数据绑定呢?

有,加上 UpdateSourceTrigger=PropertyChanged

One Time绑定,就是一次性的绑定。

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

mySlider.Value = 35;

myTextBox.Text = mySlider.Value.ToString();

}

}

9-9 【操作】INotifyPropertyChanged 事件处理

<StackPanel>

<Label Content="number 1"></Label>

<TextBox Width="200" Margin="30" Text="{Binding Path=Num1, Mode=TwoWay}"></TextBox>

<Label Content="number 2"></Label>

<TextBox Width="200" Margin="30" Text="{Binding Path=Num2, Mode=TwoWay}"></TextBox>

<Label Content="Resule"></Label>

<TextBox Width="200" Margin="30" Text="{Binding Path=Result, Mode=TwoWay}"></TextBox>

</StackPanel>

cs中代码:

public partial class MainWindow : Window

{

public Sum Sum { get; set; }

public MainWindow()

{

InitializeComponent();

Sum = new Sum() { Num1 = "1", Num2 = "2" };

this.DataContext = Sum;

}

}

新建一个Sum.cs类文件。Sum实现INotifyPropertyChanged接口(在 System.ComponentModel 命名空间下),让VS自动实现接口:public event PropertyChangedEventHandler PropertyChanged;

这样就多了一个方法 PropertyChangedEventHandler,它是一个典型的事件处理委托。

接着来添加事件的触发处理 OnPropertyChanged 方法,因为 TextBox的输入都是字符串,所以监听数据是 string property。

为了能够处理这三个TextBox的联动,还需要在 Num1和Num2 的Set中发送 OnPropertyChanged("Result"); 计算Result这个事件。

public class Sum : INotifyPropertyChanged

{

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(string property)

{

if (PropertyChanged == null) //防御性编程

{

return;

}

PropertyChanged(this, new PropertyChangedEventArgs(property)); //事件处理机制

}

private string _num1;

private string _num2;

private string _result;

public string Num1

{

get

{

return _num1;

}

set

{

int number;

bool result = int.TryParse(value, out number);

if(result)

{

_num1 = value;

OnPropertyChanged("Num1");

OnPropertyChanged("Result");

}

}

}

public string Num2

{

get

{

return _num2;

}

set

{

int number;

bool result = int.TryParse(value, out number);

if (result)

{

_num2 = value;

OnPropertyChanged("Num2");

OnPropertyChanged("Result");

}

}

}

public string Result

{

get

{

int result = int.Parse(_num1) + int.Parse(_num2);

return result.ToString();

}

set

{

int result = int.Parse(_num1) + int.Parse(_num2);

_result = result.ToString();

OnPropertyChanged("Result");

}

}

}

相关推荐
TA远方2 小时前
【C#】一个简单的http服务器项目开发过程详解
服务器·http·c#·wpf·web·winform·console
陈奕昆9 小时前
2.1HarmonyOS NEXT开发工具链进阶:DevEco Studio深度实践
华为·wpf·harmonyos
Dr.多喝热水11 小时前
WPF prism
windows·wpf
Hare_bai1 天前
WPF响应式UI的基础:INotifyPropertyChanged
ui·c#·wpf·xaml
上元星如雨1 天前
WPF 全局加载界面、多界面实现渐变过渡效果
wpf
Hare_bai1 天前
WPF的布局核心:网格布局(Grid)
ui·c#·wpf·交互·xaml
Hare_bai1 天前
WPF的基础控件:布局控件(StackPanel & DockPanel)
ui·c#·wpf·交互·xaml·visual studio
上元星如雨1 天前
WPF 按钮悬停动画效果实现
wpf
菜长江1 天前
WPF-Prism学习笔记之 “导航功能和依赖注入“
笔记·wpf