0

So I'm trying to learn how to dynamically apply style changes to controls. I have not been able to get a user control to change its borderbrush and background based off a radio button in the main window and the usercontrol's text property. Basing it just off the usercontrol's text property does seem to work. So it appears that I'm doing something wrong with getting the radio button's isCheck property.

I've simplified from the original code but this still shows the issue.

MainWindow.xaml

    <Window x:Class="UserControlTest.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:UserControlTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <RadioButton x:Name="calcPace" TabIndex="1" Content="Pace" HorizontalAlignment="Left" Margin="34,50,0,0" VerticalAlignment="Top" GroupName="CalculationType"
                     Height="16" Width="41"/>
        <RadioButton x:Name="calcDistance" TabIndex="2" Content="Distance" HorizontalAlignment="Left" Margin="80,50,0,0" VerticalAlignment="Top" GroupName="CalculationType"
                     Height="16" Width="61"/>
        <RadioButton x:Name="calcTime" TabIndex="3" Content="Time" HorizontalAlignment="Left" Margin="146,50,0,0" VerticalAlignment="Top" GroupName="CalculationType"
                     Height="16" Width="42"/>
        <local:TextBoxTime/>
    </Grid>
</Window>

TextBoxTime.xaml (usercontrol):

<UserControl x:Class="UserControlTest.TextBoxTime"
         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:local="clr-namespace:UserControlTest"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <TextBox x:Name="timeString" TabIndex="4" HorizontalAlignment="Left" Height="23" Margin="68,130,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Setter Property="BorderBrush" Value="PaleGreen"/>
                <Setter Property="Background" Value="White"/>
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding ElementName=calcTime, Path=IsChecked}" Value="False"/>
                            <Condition Binding="{Binding ElementName=timeString, Path=Text}" Value=""/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="BorderBrush" Value="Red"/>
                        <Setter Property="Background" Value="Snow"/>
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
</Grid>

Currently I've added no code behind for either.

Thanks

Daniel Ash
  • 33
  • 3
  • `calcTime` is out of scope. That's for the best; you wouldn't want a UserControl to have dependencies on the names of sibling controls in the parent. – 15ee8f99-57ff-4f92-890c-b56153 Jun 01 '17 at 19:21
  • So what would be the proper way of getting the functionality here. – Daniel Ash Jun 01 '17 at 19:22
  • Add a dependency property to the usercontrol. I'm slapping together a quick example. – 15ee8f99-57ff-4f92-890c-b56153 Jun 01 '17 at 19:22
  • What does `calTime` mean? – 15ee8f99-57ff-4f92-890c-b56153 Jun 01 '17 at 19:25
  • calcTime is just the name for a radio button. If you mean in the larger context of the actual code and not just this sample then the three radio buttons are used to determine what type of calculation you are wanting. Either calculate the time (based off the pace and distance) or calculate the distance (based off the time and pace) or calculate the pace (based off the distance and time). Hope that helps. So if we are setting a property it would need to say which of the three calculation types are wanted. – Daniel Ash Jun 01 '17 at 19:29
  • Thanks, that's what I meant. I'm wondering what to name the dependency property on the usercontrol. Are you going to have three of these, one for time, one for pace, and one for distance? It almost seems like you don't really want to break this part out into a usercontrol at all. – 15ee8f99-57ff-4f92-890c-b56153 Jun 01 '17 at 19:31
  • I'd rather have one usercontrol that can work for all three. The usercontrol is the input for each of those fields. When an input is required I'd like to have the borderbrush show red. So when calcTime is checked the borderbrush would be green - meaning it's being calculated. When calcTime is not checked the borderbrush would be red meaning a value must be present. – Daniel Ash Jun 01 '17 at 19:35
  • I've thought about leaving it as is (first implementation was without converting it to a usercontrol) but this little sample application is really just a learning tool so I thought "let's try to do this" just for a learning lesson. I failed and could not figure out what I was doing wrong. – Daniel Ash Jun 01 '17 at 19:36

1 Answers1

1

Here's how I might do it:

public partial class RequireableTextBox : UserControl
{
    public RequireableTextBox()
    {
        InitializeComponent();
    }

    #region IsRequired Property
    public bool IsRequired
    {
        get { return (bool)GetValue(IsRequiredProperty); }
        set { SetValue(IsRequiredProperty, value); }
    }

    public static readonly DependencyProperty IsRequiredProperty =
        DependencyProperty.Register(nameof(IsRequired), typeof(bool), typeof(RequireableTextBox),
            new PropertyMetadata(false));
    #endregion IsRequired Property

    #region Text Property
    public String Text
    {
        get { return (String)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(nameof(Text), typeof(String), typeof(RequireableTextBox),
            //  Default must be "" not null, for the trigger to understand
            new PropertyMetadata(""));
    #endregion Text Property
}

XAML

<UserControl 
    x:Class="UserControlTest.RequireableTextBox"
    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:local="clr-namespace:UserControlTest"
    mc:Ignorable="d" 
    d:DesignHeight="300" d:DesignWidth="300"
    IsTabStop="False"
    >
    <Grid>
        <TextBox 
            x:Name="timeString" 
            HorizontalAlignment="Left" 
            TextWrapping="Wrap" 
            VerticalAlignment="Top" 
            Width="120"
            Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged}"
            >
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Setter Property="BorderBrush" Value="PaleGreen"/>
                    <Setter Property="Background" Value="White"/>

                    <Style.Triggers>
                        <!-- 
                        Seemed right to disable when unneeded; delete this trigger 
                        if you'd rather not.
                        -->
                        <DataTrigger 
                            Binding="{Binding IsRequired, RelativeSource={RelativeSource AncestorType=UserControl}}"
                            Value="False"
                            >
                            <Setter Property="IsEnabled" Value="False" />
                        </DataTrigger>

                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition 
                                    Binding="{Binding IsRequired, RelativeSource={RelativeSource AncestorType=UserControl}}" 
                                    Value="True"
                                    />
                                <Condition 
                                    Binding="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}" 
                                    Value=""
                                    />
                            </MultiDataTrigger.Conditions>
                            <Setter Property="BorderBrush" Value="Red"/>
                            <Setter Property="Background" Value="Snow"/>
                        </MultiDataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>
    </Grid>
</UserControl>

Usage:

<StackPanel>
    <RadioButton x:Name="calcTime" GroupName="CalculationType">Calculate Time</RadioButton>
    <RadioButton x:Name="calcDistance" GroupName="CalculationType">Calculate Distance</RadioButton>
    <local:RequireableTextBox
        IsRequired="{Binding IsChecked, ElementName=calcTime}"
        />
    <local:RequireableTextBox
        x:Name="DistanceValue"
        IsRequired="{Binding IsChecked, ElementName=calcDistance}"
        />
    <!-- Just tossed this in to demonstrate the Text property -->
    <Label Content="{Binding Text, ElementName=DistanceValue}" Foreground="Gray" />
</StackPanel>