1

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.

K-RUSHer
  • 21
  • 5
  • The issue is that textbox doesn't know when `TextBoxEnabled` has changed, so the solution is to either make it a dependency property (read only) and set its value in the setter of `CheckBoxChecked`, or just bind to `CheckBoxChecked` and use a converter to negate its value. – Khiro Dec 01 '21 at 01:39
  • @Khiro, thanks, sounds good but I must be doing something wrong. Updated answer with what I did with your suggestion. – K-RUSHer Dec 01 '21 at 18:38
  • 1
    As a note, the `SetValue(TextBoxEnabledProperty, !value);` approach will not work, because the CLR wrapper of a dependency property `X` must not contain any other code than `SetValue(XProperty, value)`. The setter may be bypassed by the framework. – Clemens Dec 01 '21 at 18:48
  • @Clemens, I just added that breakpoints were not hit in the setter, thanks! Didn't know that. So, in all cases, if the setter isn't just the one line `SetValue(XProperty, value)` it won't work at all? – K-RUSHer Dec 01 '21 at 18:50
  • 1
    In all cases, yes. See [XAML Loading and Dependency Properties](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/xaml-loading-and-dependency-properties?view=netframeworkdesktop-4.8). – Clemens Dec 01 '21 at 18:53
  • Good catch :) I didn't know that either – Khiro Dec 01 '21 at 21:30
  • 1
    In this case I guess that moving the setting of `TextBoxEnabledProperty` to the property changed handler of `CheckBoxChecked` will workaround that issue. @K-RUSHer Please note that you didn't use read-only dependency property in the right way because it still write-able using `SetValue` from anywhere outside the class, check this [answer](https://stackoverflow.com/a/1122611/4644774) for the correct way ;) – Khiro Dec 01 '21 at 21:48

1 Answers1

1

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>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Yeah, sorry, it's not "Enabled" should be "Disabled", fixed now. Also tried your way but no dice. Updated my answer with exactly what I did, perhaps I did it wrong? – K-RUSHer Dec 01 '21 at 18:39
  • When you set the IsEnabled property of the TextBox by a DataTrigger, you must of course not bind it at the same time. Remove the `IsEnabled="{Binding TextBoxEnabled, ...}` assignment. The TextBoxEnabled property is completely redundant. – Clemens Dec 01 '21 at 18:47
  • Oh, didn't think of that but now that you say it, it is obvious. Thanks! Now it works! :) – K-RUSHer Dec 01 '21 at 18:51