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");

}

}

}

相关推荐
FuckPatience3 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白3 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu4 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu4 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野5 天前
WPF——效果和可视化对象
wpf
He BianGu5 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
时光追逐者5 天前
一款专门为 WPF 打造的开源 Office 风格用户界面控件库
ui·开源·c#·.net·wpf
He BianGu5 天前
【笔记】介绍 WPF XAML 中 Binding 的 StringFormat详细功能
笔记·wpf
Rotion_深6 天前
C# WPF使用线程池运行Action方法
c#·wpf·线程池
攻城狮CSU6 天前
WPF 深入系列.2.布局系统.尺寸属性
wpf