Home > Enterprise >  How do I make a usercontrol update its own property?
How do I make a usercontrol update its own property?

Time:12-06

I have a usercontrol in my WPF app that is basically a checkbox and textbox. I want the textbox to be disabled or enabled based on the checkbox state and the state be data bindable.

This is my 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>

Codebehind

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);
    }
}

I have no idea how to tell the TextCheckBox to update its "IsEnabled" property when the checkbox is checked.

This is the XAML where I'm using TextCheckBox and the bindings are working correctly:

<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>

EDIT:

As Khiro suggested, I tried using a dependency property whose value is being set in the setter of "CheckBoxChecked".

This is the codebehind after the change (changed code annotated with comments "Change here" on the previous line):

using System.Windows;
using System.Windows.Controls;

namespace WpfApp.Views.Elements
{
    /// <summary>
    /// Interaction logic for TextCheckBox.xaml
    /// </summary>
    public partial class TextCheckBox : UserControl
    {
        // Change here.
        public bool TextBoxEnabled => (bool)GetValue(TextBoxEnabledProperty);

        public string TextBoxText
        {
            get => (string)GetValue(TextBoxTextProperty);
            set => SetValue(TextBoxTextProperty, value);
        }

        public bool CheckBoxChecked
        {
            get => (bool)GetValue(CheckBoxCheckedProperty);
            set
            {
                SetValue(CheckBoxCheckedProperty, value);
                // Change here.
                SetValue(TextBoxEnabledProperty, !value);
            }
        }

        public event DependencyPropertyChangedEventHandler TextPropertyChanged;

        // Change here.
        public static readonly DependencyProperty TextBoxEnabledProperty =
            DependencyProperty.Register(nameof(TextBoxEnabled), typeof(bool), typeof(CheckBoxInput), 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(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);
    }
}

But nothing changed. Clicking the checkbox does not disable the TextBox. Also, breakpoints are not hit in the setter of "CheckBoxChecked".

Then I tried the answer, provided by Clemens and the XAML of my usercontrol became (only altered part posted here):

<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">
    <TextBox.Style>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsChecked, ElementName=checkBox}" Value="True">
                    <Setter Property="IsEnabled" Value="False"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

    <CheckBox x:Name="checkBox"
            Content="Newline"
            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"/>

But, again, nothing happens. :(
I undid all changes before trying any suggestion, so there wasn't a case where both suggestions were in effect.

CodePudding user response:

It seems odd that the TextBox is supposed to be enabled when a CheckBox with Content "Enabled" is not checked.

You probably just wanted to do it the other way round. Give the CheckBox a name and bind the TextBox's IsEnabled property to the IsChecked property of the CheckBox:

<CheckBox x:Name="checkBox" .../>
<TextBox IsEnabled="{Binding IsChecked, ElementName=checkBox}" .../>

If you really need to invert the logic, use an appropriate Binding Converter, or use a DataTrigger:

<TextBox ...>
    <TextBox.Style>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger
                    Binding="{Binding IsChecked, ElementName=checkBox}"
                    Value="True">
                    <Setter Property="IsEnabled" Value="False"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>
  • Related