WPF UserControl如何在CheckBox状态变更时更新自身属性控制TextBox启停
问题描述
我在WPF应用中开发了一个由CheckBox和TextBox组成的UserControl,希望TextBox的启用/禁用状态跟随CheckBox选中状态动态变化,且状态支持数据绑定。
初始代码
UserControl XAML代码
<UserControl x:Class="WpfApp.Views.Elements.TextCheckBox" 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> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <CheckBox Content="Disabled" IsChecked="{Binding CheckBoxChecked, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" Margin="5,5,8,5"/> <TextBox Text="{Binding TextBoxText, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" IsEnabled="{Binding TextBoxEnabled, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" Grid.Row="1" Grid.Column="0" Height="20"/> </Grid> </UserControl>
UserControl后台代码
using System.Windows; using System.Windows.Controls; namespace WpfApp.Views.Elements { /// <summary> /// Interaction logic for TextCheckBox.xaml /// </summary> public partial class TextCheckBox : UserControl { public bool TextBoxEnabled => !CheckBoxChecked; public string TextBoxText { get => (string)GetValue(TextBoxTextProperty); set => SetValue(TextBoxTextProperty, value); } public bool CheckBoxChecked { get => (bool)GetValue(CheckBoxCheckedProperty); set => SetValue(CheckBoxCheckedProperty, value); } public event DependencyPropertyChangedEventHandler TextPropertyChanged; public static readonly DependencyProperty TextBoxTextProperty = DependencyProperty.Register(nameof(TextBoxText), typeof(string), typeof(TextCheckBox), new PropertyMetadata(OnAnyPropertyChanged)); public static readonly DependencyProperty CheckBoxCheckedProperty = DependencyProperty.Register(nameof(CheckBoxChecked), typeof(bool), typeof(CheckBoxInput), new PropertyMetadata(OnAnyPropertyChanged)); public TextCheckBox() => InitializeComponent(); static void OnAnyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => (obj as TextCheckBox).OnAnyPropertyChanged(args); void OnAnyPropertyChanged(DependencyPropertyChangedEventArgs args) => TextPropertyChanged?.Invoke(this, args); } }
引用UserControl的页面代码
<UserControl x:Class="WpfApp.Views.MyView" 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" xmlns:other="clr-namespace:WpfApp.Other" xmlns:elements="clr-namespace:WpfApp.Views.Elements" mc:Ignorable="d" other:ViewModelLocator.AutoWireViewModel="True" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <elements:TextCheckBox CheckBoxChecked="{Binding IsChecked, Mode=OneWayToSource}" TextBoxText="{Binding TextContent, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/> </Grid> </UserControl>
已尝试方案及问题
- 尝试将TextBoxEnabled定义为依赖属性,在CheckBoxChecked的setter中更新它的值,出现两个问题:点击CheckBox时TextBox状态无变化,且CheckBoxChecked的setter中断点不会被命中。
- 尝试给TextBox加样式触发器绑定CheckBox的IsChecked属性,依然没有效果。
根因分析
你代码里存在3个核心错误导致所有方案失效:
- 依赖属性注册的所有者类型错误:注册
CheckBoxCheckedProperty和TextBoxEnabledProperty时,OwnerType参数填的是typeof(CheckBoxInput),但你的控件类是TextCheckBox,直接导致依赖属性系统找不到对应属性,绑定完全失效。 - 依赖属性CLR包装器逻辑不生效:WPF依赖属性系统更新属性值时会直接调用
SetValue方法,不会走你定义的CLR属性setter,所以你在setter里写的更新逻辑永远不会执行,这也是断点不命中的原因。 - 初始版本的
TextBoxEnabled不是依赖属性,没有属性变更通知,就算值变化了界面也收不到更新通知。
解决方案
两种方案任选其一即可:
方案1:通过依赖属性关联状态(适合需要向外暴露TextBoxEnabled属性的场景)
仅修改后台代码即可:
using System.Windows; using System.Windows.Controls; namespace WpfApp.Views.Elements { public partial class TextCheckBox : UserControl { public bool TextBoxEnabled { get => (bool)GetValue(TextBoxEnabledProperty); set => SetValue(TextBoxEnabledProperty, value); } public string TextBoxText { get => (string)GetValue(TextBoxTextProperty); set => SetValue(TextBoxTextProperty, value); } public bool CheckBoxChecked { get => (bool)GetValue(CheckBoxCheckedProperty); set => SetValue(CheckBoxCheckedProperty, value); } public event DependencyPropertyChangedEventHandler TextPropertyChanged; public static readonly DependencyProperty TextBoxEnabledProperty = DependencyProperty.Register(nameof(TextBoxEnabled), typeof(bool), typeof(TextCheckBox), new PropertyMetadata(OnAnyPropertyChanged)); public static readonly DependencyProperty TextBoxTextProperty = DependencyProperty.Register(nameof(TextBoxText), typeof(string), typeof(TextCheckBox), new PropertyMetadata(OnAnyPropertyChanged)); public static readonly DependencyProperty CheckBoxCheckedProperty = DependencyProperty.Register(nameof(CheckBoxChecked), typeof(bool), typeof(TextCheckBox), new PropertyMetadata(OnCheckBoxCheckedChanged)); public TextCheckBox() => InitializeComponent(); private static void OnCheckBoxCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control = (TextCheckBox)d; control.TextBoxEnabled = !(bool)e.NewValue; OnAnyPropertyChanged(d, e); } static void OnAnyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => (obj as TextCheckBox)?.OnAnyPropertyChanged(args); void OnAnyPropertyChanged(DependencyPropertyChangedEventArgs args) => TextPropertyChanged?.Invoke(this, args); } }
方案2:直接在XAML绑定(逻辑最简单,不需要额外依赖属性)
先修复后台代码的依赖属性所有者类型错误,去掉TextBoxEnabled相关逻辑:
using System.Windows; using System.Windows.Controls; namespace WpfApp.Views.Elements { public partial class TextCheckBox : UserControl { public string TextBoxText { get => (string)GetValue(TextBoxTextProperty); set => SetValue(TextBoxTextProperty, value); } public bool CheckBoxChecked { get => (bool)GetValue(CheckBoxCheckedProperty); set => SetValue(CheckBoxCheckedProperty, value); } public event DependencyPropertyChangedEventHandler TextPropertyChanged; public static readonly DependencyProperty TextBoxTextProperty = DependencyProperty.Register(nameof(TextBoxText), typeof(string), typeof(TextCheckBox), new PropertyMetadata(OnAnyPropertyChanged)); public static readonly DependencyProperty CheckBoxCheckedProperty = DependencyProperty.Register(nameof(CheckBoxChecked), typeof(bool), typeof(TextCheckBox), new PropertyMetadata(OnAnyPropertyChanged)); public TextCheckBox() => InitializeComponent(); static void OnAnyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => (obj as TextCheckBox)?.OnAnyPropertyChanged(args); void OnAnyPropertyChanged(DependencyPropertyChangedEventArgs args) => TextPropertyChanged?.Invoke(this, args); } }
修改XAML代码,直接用触发器关联状态:
<UserControl x:Class="WpfApp.Views.Elements.TextCheckBox" 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> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <CheckBox x:Name="checkBox" Content="Disabled" IsChecked="{Binding CheckBoxChecked, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" Margin="5,5,8,5"/> <TextBox Text="{Binding TextBoxText, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" Grid.Row="1" Grid.Column="0" Height="20"> <TextBox.Style> <Style TargetType="TextBox"> <Setter Property="IsEnabled" Value="True"/> <Style.Triggers> <DataTrigger Binding="{Binding IsChecked, ElementName=checkBox}" Value="True"> <Setter Property="IsEnabled" Value="False"/> </DataTrigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox> </Grid> </UserControl>
内容的提问来源于stack exchange,提问作者K-RUSHer
相关产品推荐
相关产品推荐

