如何以最简洁方式基于现有控件组合创建WPF自定义控件?
实现带按钮的TextBox的轻量化方案
方案1:附加属性+行为(无需自定义控件)
完全复用原生TextBox,通过附加属性关联按钮的命令、内容,再通过回调动态将按钮插入TextBox的可视化树,无需修改模板,自动继承TextBox的所有样式与主题支持。
代码示例
附加属性定义
public static class TextBoxButtonAttached { public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.RegisterAttached("ButtonCommand", typeof(ICommand), typeof(TextBoxButtonAttached), new PropertyMetadata(null, OnButtonCommandChanged)); public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.RegisterAttached("ButtonContent", typeof(object), typeof(TextBoxButtonAttached), new PropertyMetadata(null)); public static void SetButtonCommand(DependencyObject obj, ICommand value) { obj.SetValue(ButtonCommandProperty, value); } public static ICommand GetButtonCommand(DependencyObject obj) { return (ICommand)obj.GetValue(ButtonCommandProperty); } public static void SetButtonContent(DependencyObject obj, object value) { obj.SetValue(ButtonContentProperty, value); } public static object GetButtonContent(DependencyObject obj) { return obj.GetValue(ButtonContentProperty); } private static void OnButtonCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is TextBox textBox) { if (textBox.Template.FindName("PART_Button", textBox) is Button existingButton) { existingButton.Command = e.NewValue as ICommand; return; } var button = new Button { Name = "PART_Button", Command = e.NewValue as ICommand, Content = GetButtonContent(textBox) }; textBox.RegisterPropertyChangedCallback(ButtonContentProperty, (s, args) => { button.Content = GetButtonContent(textBox); }); if (textBox.Template.FindName("PART_ContentHost", textBox) is FrameworkElement contentHost) { if (contentHost.Parent is Panel parentPanel) { parentPanel.Children.Add(button); DockPanel.SetDock(button, Dock.Right); } } } } }
XAML使用
<TextBox local:TextBoxButtonAttached.ButtonContent="清除" local:TextBoxButtonAttached.ButtonCommand="{Binding ClearCommand}" />
方案2:自定义控件复用原有模板结构
通过模板继承仅添加按钮元素,不复制完整TextBox模板,保留原生TextBox的样式继承能力,适配深色主题等场景。
代码示例
自定义控件类
public class TextBoxWithButton : TextBox { static TextBoxWithButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxWithButton), new FrameworkPropertyMetadata(typeof(TextBoxWithButton))); } public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register(nameof(ButtonCommand), typeof(ICommand), typeof(TextBoxWithButton), new PropertyMetadata(null)); public ICommand ButtonCommand { get => (ICommand)GetValue(ButtonCommandProperty); set => SetValue(ButtonCommandProperty, value); } public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(nameof(ButtonContent), typeof(object), typeof(TextBoxWithButton), new PropertyMetadata(null)); public object ButtonContent { get => GetValue(ButtonContentProperty); set => SetValue(ButtonContentProperty, value); } }
控件模板
<Style TargetType="{x:Type local:TextBoxWithButton}" BasedOn="{StaticResource {x:Type TextBox}}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TextBoxWithButton}"> <DockPanel> <Button DockPanel.Dock="Right" Content="{TemplateBinding ButtonContent}" Command="{TemplateBinding ButtonCommand}" Margin="2" /> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ScrollViewer x:Name="PART_ContentHost" Focusable="false" /> </Border> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> </Style>
方案3:UserControl简化依赖属性开发
利用Visual Studio的propdp代码片段快速生成依赖属性,通过直观的XAML组合TextBox与Button,适合简单组合场景。
代码示例
XAML部分
<UserControl x:Class="WpfApp.TextBoxWithButtonUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <DockPanel> <Button DockPanel.Dock="Right" Content="{Binding ButtonContent, RelativeSource={RelativeSource AncestorType=UserControl}}" Command="{Binding ButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" /> <TextBox Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}" /> </DockPanel> </UserControl>
C#部分
public partial class TextBoxWithButtonUserControl : UserControl { public TextBoxWithButtonUserControl() { InitializeComponent(); } public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(TextBoxWithButtonUserControl), new PropertyMetadata(string.Empty)); public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register(nameof(ButtonCommand), typeof(ICommand), typeof(TextBoxWithButtonUserControl), new PropertyMetadata(null)); public ICommand ButtonCommand { get => (ICommand)GetValue(ButtonCommandProperty); set => SetValue(ButtonCommandProperty, value); } public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(nameof(ButtonContent), typeof(object), typeof(TextBoxWithButtonUserControl), new PropertyMetadata(null)); public object ButtonContent { get => GetValue(ButtonContentProperty); set => SetValue(ButtonContentProperty, value); } }
内容的提问来源于stack exchange,提问作者ekt
相关产品推荐
相关产品推荐

