WPF:创建多视图复用控件的最优方案——通用网格结构复用需求
嘿,这个需求太常见啦!在WPF里复用这种固定的三行Grid布局,有几个非常靠谱的方案,我给你拆解一下,你可以根据自己的项目场景选最合适的:
这是最直观、上手最快的方式,把重复的Grid结构打包成一个可复用的UserControl,再通过依赖属性暴露三个区域的内容入口,让各个页面可以灵活填充不同内容。
步骤1:创建UserControl的XAML结构
新建一个名为ThreeRowGridContainer的UserControl,写入你的Grid布局,并给三个区域添加ContentPresenter作为内容容器:
<UserControl x:Class="YourProjectNamespace.ThreeRowGridContainer" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!-- 顶部内容容器 --> <ContentPresenter Grid.Row="0" Content="{Binding TopContent, RelativeSource={RelativeSource AncestorType=UserControl}}" /> <!-- 中间内容容器(占满剩余空间) --> <ContentPresenter Grid.Row="1" Content="{Binding MiddleContent, RelativeSource={RelativeSource AncestorType=UserControl}}" /> <!-- 底部内容容器 --> <ContentPresenter Grid.Row="2" Content="{Binding BottomContent, RelativeSource={RelativeSource AncestorType=UserControl}}" /> </Grid> </UserControl>
步骤2:添加依赖属性
在UserControl的后台代码中,定义三个依赖属性,让外部可以设置对应区域的内容:
using System.Windows; using System.Windows.Controls; namespace YourProjectNamespace { public partial class ThreeRowGridContainer : UserControl { public ThreeRowGridContainer() { InitializeComponent(); } // 顶部内容依赖属性 public static readonly DependencyProperty TopContentProperty = DependencyProperty.Register(nameof(TopContent), typeof(object), typeof(ThreeRowGridContainer)); public object TopContent { get => GetValue(TopContentProperty); set => SetValue(TopContentProperty, value); } // 中间内容依赖属性 public static readonly DependencyProperty MiddleContentProperty = DependencyProperty.Register(nameof(MiddleContent), typeof(object), typeof(ThreeRowGridContainer)); public object MiddleContent { get => GetValue(MiddleContentProperty); set => SetValue(MiddleContentProperty, value); } // 底部内容依赖属性 public static readonly DependencyProperty BottomContentProperty = DependencyProperty.Register(nameof(BottomContent), typeof(object), typeof(ThreeRowGridContainer)); public object BottomContent { get => GetValue(BottomContentProperty); set => SetValue(BottomContentProperty, value); } } }
步骤3:在页面中使用
现在你可以在任意页面里直接引用这个控件,填充自定义内容:
<Window xmlns:local="clr-namespace:YourProjectNamespace"> <local:ThreeRowGridContainer> <local:ThreeRowGridContainer.TopContent> <!-- 这里放顶部内容,比如导航栏 --> <StackPanel Orientation="Horizontal" Padding="10"> <Button Content="首页" Margin="0 0 10 0"/> <Button Content="设置"/> </StackPanel> </local:ThreeRowGridContainer.TopContent> <local:ThreeRowGridContainer.MiddleContent> <!-- 这里放页面主内容,比如数据表格或文本 --> <TextBlock Text="这是页面的核心内容区域" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="18"/> </local:ThreeRowGridContainer.MiddleContent> <local:ThreeRowGridContainer.BottomContent> <!-- 这里放底部内容,比如版权信息 --> <TextBlock Text="© 2024 你的项目名称" HorizontalAlignment="Center" Padding="10"/> </local:ThreeRowGridContainer.BottomContent> </local:ThreeRowGridContainer> </Window>
如果你的项目需要支持换肤,或者希望布局能应用到不同控件上,ControlTemplate是更灵活的选择。你可以给ContentControl定义一个模板,把三行Grid结构嵌入其中:
<Window.Resources> <Style x:Key="ThreeRowGridTemplateStyle" TargetType="ContentControl"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ContentControl"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <!-- 顶部区域:可通过附加属性或自定义属性绑定 --> <ContentPresenter Grid.Row="0" Content="{Binding TopContent, RelativeSource={RelativeSource TemplatedParent}}"/> <!-- 中间区域:绑定ContentControl的默认Content --> <ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}"/> <!-- 底部区域:同样通过自定义属性绑定 --> <ContentPresenter Grid.Row="2" Content="{Binding BottomContent, RelativeSource={RelativeSource TemplatedParent}}"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources>
使用时需要给ContentControl添加对应的自定义属性(和UserControl的依赖属性逻辑类似),适合需要高度样式定制的场景。
如果你的项目采用MVVM架构,可以通过定义基类ViewModel和对应的DataTemplate来实现布局复用:
步骤1:定义基类ViewModel
using System.ComponentModel; using System.Runtime.CompilerServices; public class ThreeRowBaseViewModel : INotifyPropertyChanged { private object _topContentVM; public object TopContentVM { get => _topContentVM; set { _topContentVM = value; OnPropertyChanged(); } } private object _middleContentVM; public object MiddleContentVM { get => _middleContentVM; set { _middleContentVM = value; OnPropertyChanged(); } } private object _bottomContentVM; public object BottomContentVM { get => _bottomContentVM; set { _bottomContentVM = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:定义DataTemplate
<Window.Resources> <DataTemplate DataType="{x:Type local:ThreeRowBaseViewModel}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <ContentControl Grid.Row="0" Content="{Binding TopContentVM}"/> <ContentControl Grid.Row="1" Content="{Binding MiddleContentVM}"/> <ContentControl Grid.Row="2" Content="{Binding BottomContentVM}"/> </Grid> </DataTemplate> </Window.Resources>
步骤3:页面ViewModel继承基类
每个页面的ViewModel继承ThreeRowBaseViewModel,设置对应的子ViewModel,然后在View中用ContentControl绑定即可,实现布局和业务逻辑的完全解耦。
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 自定义UserControl | 简单布局复用、非MVVM/轻MVVM | 上手快、代码直观、易维护 | 样式定制灵活性稍弱 |
| ControlTemplate | 需要换肤、多控件共享布局 | 样式高度可定制 | 需要额外处理属性绑定 |
| DataTemplate+MVVM | 复杂MVVM架构项目 | 完全解耦布局与业务逻辑 | 学习成本较高、代码量略大 |
我个人最推荐自定义UserControl方案,对于你这种5个页面复用的场景来说,它的实现成本最低,代码最清晰,维护起来也最省心。
内容的提问来源于stack exchange,提问作者Werner

