将一个控件模板内部的某个元素(如按钮、复选框)的 Command 属性,绑定到该控件本身所定义的 Command 属性上。

为什么需要模板命令?
想象一下一个自定义控件,比如一个 CustomButton,这个控件在视觉上可能是一个复杂的组合,包含一个图标(Image)和一段文字(TextBlock),从功能上讲,用户点击图标或文字都应该触发按钮的 Click 事件或 Command。
如果不在模板中使用命令绑定,你可能会在代码中处理鼠标点击事件,然后判断点击的是哪个元素,再手动调用命令,这非常繁琐且不符合 WPF 的数据绑定思想。
使用“模板命令”模式,你可以:
- 将命令逻辑与 UI 解耦:控件的命令逻辑由控件自身定义(通常通过依赖属性),而模板只负责如何触发它。
- 简化模板设计:模板设计者不需要关心命令的具体实现,只需要将内部的元素与控件的
Command属性绑定即可。 - 提供一致的交互:无论用户点击模板内的哪个部分,都能触发相同的命令,提供统一的用户体验。
核心实现步骤
要实现模板命令,通常需要以下几个关键部分:

- 定义命令依赖属性:在自定义控件的代码后端(Code-behind)中,定义一个
ICommand类型的依赖属性,Command。 - 定义命令参数依赖属性:通常还需要一个
CommandParameter属性,用于传递给命令的数据。 - 在模板中使用
ContentPresenter:ContentPresenter是一个神奇的关键元素,当控件被使用时(<CustomButton>Click Me</CustomButton>),ContentPresenter会自动将 "Click Me" 这个内容呈现出来。 - 绑定
Command:在控件模板中,为需要触发命令的元素(如Button或ContentPresenter本身)设置Command属性,并绑定到控件的Command依赖属性上。
完整示例:一个可点击的图标按钮
我们将创建一个 IconButton 控件,它内部包含一个图标和文字,点击任意部分都会触发绑定的命令。
步骤 1:创建自定义控件 IconButton
我们创建一个继承自 Control 的自定义控件。
IconButton.xaml (定义控件样式和默认模板)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTemplateCommandDemo">
<!-- 定义默认样式和模板 -->
<Style TargetType="{x:Type local:IconButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:IconButton}">
<!--
这是模板的核心部分。
1. Border 作为整个控件的根容器,提供背景和边框。
2. Grid 用来布局内部的图标和文字。
3. ContentPresenter 负责显示控件的 Content(即文字)。
4. 最关键的是:我们将整个 Border 的 Command 绑定到控件的 Command。
5. CommandParameter 绑定到控件的 CommandParameter。
-->
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 图标部分 -->
<Image x:Name="PART_Icon"
Source="{TemplateBinding Icon}"
Width="20"
Height="20"
Margin="5,0"
Stretch="Uniform"
VerticalAlignment="Center"/>
<!-- 文字部分,由 ContentPresenter 呈现 -->
<ContentPresenter Grid.Column="1"
Margin="5,0"
VerticalAlignment="Center"
TextElement.Foreground="{TemplateBinding Foreground}"
Content="{TemplateBinding Content}"/>
</Grid>
</Border>
<!-- 添加视觉状态,例如鼠标悬停效果 -->
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="border" Property="Background" Value="#FFBEE6FD"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="border" Property="Background" Value="#FFC4E0FC"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
IconButton.cs (代码后端,定义依赖属性)

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfTemplateCommandDemo
{
public class IconButton : Control
{
static IconButton()
{
// 静态构造函数中注册依赖属性
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButton), new FrameworkPropertyMetadata(typeof(IconButton)));
}
// 1. 定义 Command 依赖属性
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(IconButton), new PropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// 2. 定义 CommandParameter 依赖属性
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(IconButton), new PropertyMetadata(null));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
// 3. 定义一个额外的 Icon 依赖属性,用于设置图标
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register(nameof(Icon), typeof(ImageSource), typeof(IconButton), new PropertyMetadata(null));
public ImageSource Icon
{
get { return (ImageSource)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
}
}
步骤 2:在主窗口中使用 IconButton
我们可以在主窗口中像使用普通按钮一样使用我们的 IconButton,并给它绑定一个命令。
MainWindow.xaml
<Window x:Class="WpfTemplateCommandDemo.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:WpfTemplateCommandDemo"
mc:Ignorable="d"
Title="Template Command Demo" Height="450" Width="800">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 定义一个资源,包含一个示例图标 -->
<Grid.Resources>
<BitmapImage x:Key="SampleIcon" UriSource="https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Windows_10_Settings_app_icon.png/240px-Windows_10_Settings_app_icon.png" />
</Grid.Resources>
<!-- 使用我们的自定义 IconButton -->
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<!--
将 Icon 控件的 Icon 属性绑定到资源
将 Content 绑定到显示的文字
将 Command 绑定到 ViewModel 中的 SaveCommand
将 CommandParameter 绑定到一个字符串
-->
<local:IconButton Icon="{StaticResource SampleIcon}"
Content="Save Settings"
Command="{Binding SaveCommand}"
CommandParameter="SettingsData"
Margin="0,0,0,10"
Width="200"/>
<local:IconButton Icon="{StaticResource SampleIcon}"
Content="Load Profile"
Command="{Binding LoadCommand}"
CommandParameter="UserProfile"
Width="200"/>
</StackPanel>
<!-- 显示命令执行结果 -->
<TextBlock Grid.Row="1"
Text="{Binding CommandResult}"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold"/>
</Grid>
</Window>
MainWindow.xaml.cs (设置数据上下文)
using System.Windows;
namespace WpfTemplateCommandDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// 将当前窗口的 DataContext 设置为 MainViewModel 实例
this.DataContext = new MainViewModel();
}
}
}
步骤 3:创建 ViewModel 来处理命令
这是命令逻辑的所在地。
MainViewModel.cs
using System.ComponentModel;
using System.Windows.Input;
namespace WpfTemplateCommandDemo
{
public class MainViewModel : INotifyPropertyChanged
{
private string _commandResult;
public string CommandResult
{
get => _commandResult;
set
{
_commandResult = value;
OnPropertyChanged(nameof(CommandResult));
}
}
// 保存命令
public ICommand SaveCommand { get; }
// 加载命令
public ICommand LoadCommand { get; }
public MainViewModel()
{
// 使用 RelayCommand (通常来自 MVVM Light 或 CommunityToolkit.Mvvm) 或自己实现
SaveCommand = new RelayCommand<string>(param =>
{
CommandResult = $"Save command executed with parameter: {param}";
// 这里执行实际的保存逻辑...
});
LoadCommand = new RelayCommand<string>(param =>
{
CommandResult = $"Load command executed with parameter: {param}";
// 这里执行实际的加载逻辑...
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
// 一个简单的 RelayCommand 实现
public class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Predicate<T> _canExecute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canCanExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
运行结果
当你运行这个程序时,你会看到一个带有图标和文字的按钮,当你点击按钮的图标部分或文字部分时,MainViewModel 中的 SaveCommand 会被执行,TextBlock 会显示相应的结果。
总结与关键点
- 核心概念:模板命令就是将模板内部元素的
Command属性,绑定到控件自身暴露的Command依赖属性上。 - 必备元素:
- 控件后端:定义
Command和CommandParameter依赖属性。 - 控件模板:使用
Command属性绑定,将模板内的元素(如Border或Button)与控件的命令连接起来。
- 控件后端:定义
ContentPresenter的作用:它不是命令模式的一部分,但在构建复合控件时至关重要,它负责将控件的Content属性动态地渲染到模板中。- 优点:实现了逻辑与 UI 的完美分离,使得控件既灵活又易于使用,是构建高质量自定义控件的基石。
通过这种模式,你可以创建出功能强大且易于集成的自定义控件,其使用方式与 WPF 内置控件(如 Button)完全一致。
