WPF桌面端开发-组件化 自定义UserControl及Window、Page、UserControl的区别

前言

WPF 窗口 页 用户控件的区别

在 WPF 中,窗口(Window)、页面(Page)和用户控件(User Control)是三种常见的可视化元素,它们在应用程序中扮演不同的角色。

  1. 窗口(Window):窗口是一个独立的顶级容器,通常表示应用程序的主界面或者一个模式对话框。

    窗口可以包含其他的 WPF 控件,如按钮、文本框等,并且可以具有标题栏、最大化、最小化和关闭按钮等标准窗口功能。

  2. 页面(Page):页面是一种特殊的控件,用于创建具有导航功能的应用程序。

    页面通常用于创建多个相关内容的视图,并且可以通过导航服务在这些视图之间进行切换。

    在 WPF 应用程序中,通常使用 Frame 控件来承载页面,并且可以通过 Frame 的导航功能在不同的页面之间导航。

  3. 用户控件(UserControl):用户控件是一种自定义的可重用控件,它可以包含其他的 WPF 控件,并且可以定义自己的布局和行为。

    用户控件通常用于封装一些常用的 UI 元素或者功能,以便在应用程序中多次重用。

    与页面不同,用户控件通常不具有导航功能,而是被设计为嵌入到其他容器中使用。

总的来说,窗口用于表示应用程序的顶级界面,页面用于创建具有导航功能的视图,而用户控件用于封装和重用 UI 元素或功能。

Page

以下是一个简单的示例,演示如何在 WPF 中创建和使用页面:

MainWindow.xaml 包含一个 Frame 控件,用于承载页面。

MainWindow.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>

<Button Content="Go to Page 1" Click="GoToPage1_Click" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>
<Button Content="Go to Page 2" Click="GoToPage2_Click" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>

<Frame x:Name="MainFrame" Grid.ColumnSpan="2" NavigationUIVisibility="Hidden"/>
</Grid>
</Window>

MainWindow.xaml.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.Windows;

namespace WpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void GoToPage1_Click(object sender, RoutedEventArgs e)
{
MainFrame.NavigationService.Navigate(new Page1());
}

private void GoToPage2_Click(object sender, RoutedEventArgs e)
{
MainFrame.NavigationService.Navigate(new Page2());
}
}
}

在这个示例中,MainWindow 中有两个按钮,分别用于导航到 Page1Page2

点击不同的按钮会切换到对应的页面。

Page1.xaml

1
2
3
4
5
6
7
8
9
<Page x:Class="WpfApp.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page 1">
<Grid>
<TextBlock Text="This is Page 1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24"/>
<Button Content="Go to Page 2" Click="GoToPage2_Click" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,50"/>
</Grid>
</Page>

Page1.xaml.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Windows;
using System.Windows.Controls;

namespace WpfApp
{
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
}

private void GoToPage2_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Page2());
}
}
}

Page2.xaml

1
2
3
4
5
6
7
8
<Page x:Class="WpfApp.Page2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page 2">
<Grid>
<TextBlock Text="This is Page 2" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24"/>
</Grid>
</Page>

Page2.xaml.cs

1
2
3
4
5
6
7
8
9
10
11
12
using System.Windows.Controls;

namespace WpfApp
{
public partial class Page2 : Page
{
public Page2()
{
InitializeComponent();
}
}
}

当点击 Page1 中的按钮时,会导航到 Page2

UserControl

组件化我们都用UserControl实现。

以下是一个简单的示例,演示如何创建和使用用户控件(UserControl):

MyUserControl.xaml

1
2
3
4
5
6
7
8
9
10
11
<UserControl x:Class="WpfApp.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Text="This is my user control!" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24"/>
</Grid>
</UserControl>

MyUserControl.xaml.cs

1
2
3
4
5
6
7
8
9
10
11
12
using System.Windows.Controls;

namespace WpfApp
{
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
}
}

MainWindow.xaml

1
2
3
4
5
6
7
8
9
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="Main Window" Height="450" Width="800">
<Grid>
<local:MyUserControl/>
</Grid>
</Window>

在这个示例中,MyUserControl.xaml 包含一个简单的 TextBlock,用于显示文本。

然后在 MainWindow.xaml 中将这个用户控件嵌入到了 Grid 中。

组件传值

自定义组件

假如我的自定义UserControl的类是UcBlackbord

添加

1
2
3
4
5
6
7
8
9
10
11
12
13
//UserControl中接收值的属性
public static readonly DependencyProperty ShowToolbarProperty = DependencyProperty.Register(
nameof(ShowToolbar),
typeof(bool),
typeof(UcBlackbord),
new PropertyMetadata(false)
);

public bool ShowToolbar
{
get => (bool)GetValue(ShowToolbarProperty);
set => SetValue(ShowToolbarProperty, value);
}

UcBlackbord的xaml中绑定值的方式

1
Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UcBlackbord}}, Path=ShowToolbar, Converter={StaticResource BooleanToVisibilityConverter}}"

引用

在引用组件的地方传值

1
2
3
4
5
6
7
8
9
10
11
<DataTemplate x:Key="BlackbordTpl" DataType="page:SubmitUserItem">
<Border
Name="OuterBorder"
BorderBrush="{Binding Path=Selected, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='true:#339DFF|other:#00ffffff'}"
BorderThickness="2"
Focusable="True"
PreviewMouseDown="UIElement_OnMouseDown"
Tag="{Binding}">
<uc:UcBlackbord ShowToolbar="{Binding DataContext.Selected, ElementName=OuterBorder}" />
</Border>
</DataTemplate>

注意下面的写法是有问题的,他会在组件内查找属性

1
<uc:UcBlackbord ShowToolbar="{Binding Selected}" />

组件绑定事件

要在 WPF 自定义 UserControl 中绑定事件并传值,可以通过以下步骤实现:

创建自定义组件

首先,创建一个自定义的 UserControl,并在其中定义事件以及需要传递的值。

1
2
3
4
5
6
7
8
<!-- MyUserControl.xaml -->
<UserControl x:Class="YourNamespace.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
x:Name="root">
<Button Content="Click Me" Click="Button_Click"/>
</UserControl>

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// MyUserControl.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;

namespace YourNamespace
{
public partial class MyUserControl : UserControl
{
public event EventHandler<string> ValueSelected;

public MyUserControl()
{
InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
// 传递值
ValueSelected?.Invoke(this, "Hello from UserControl!");
}
}
}

使用组件并订阅事件

在主窗口中使用自定义 UserControl 并订阅事件

在主窗口中使用自定义的 UserControl,并订阅其事件,接收传递的值:

1
2
3
4
5
6
7
8
9
10
11
<!-- MainWindow.xaml -->
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
Title="MainWindow" Height="450" Width="800">

<Grid>
<local:MyUserControl ValueSelected="MyUserControl_ValueSelected"/>
</Grid>
</Window>

页面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// MainWindow.xaml.cs
using System.Windows;

namespace YourNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void MyUserControl_ValueSelected(object sender, string e)
{
string value = e;
MessageBox.Show(value);
}
}
}

在这个示例中,当用户点击 UserControl 中的按钮时,触发了 ValueSelected 事件,并传递了一个字符串值。

在主窗口中订阅了这个事件,可以收到传递的值并进行相应处理。

这样就实现了在自定义 UserControl 中绑定事件并传值的功能。