I have a usercontrol hosted in an office Taskpane, for a Word addin.
I have tried following the answer to DataTrigger to make WPF Button inactive until TextBox has value and the answer to Cleanest way to bind a Button's visibility to the contents of two textboxes, in order to enable my button when exactly two textboxes have non-null contents.
The converter:
using System.Windows.Data;
using System.Globalization;
namespace RetrofitDocumentTool.Converter
{
[ValueConversion(typeof(String), typeof(Boolean))]
class StringToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string val = (string)value;
bool result = !string.IsNullOrEmpty(val);
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("This converter is oneway only.");
}
}
}
The XAML:
<UserControl
...>
<UserControl.Resources>
<Style x:Key="ErrorLabel" TargetType="Label">
...
</Style>
<Style x:Key="StandardLabel" TargetType="Label">
...
</Style>
<Style TargetType="TextBox">
...
</Style>
<conv:StringToBooleanConverter x:Key="StringToBoolean"/>
</UserControl.Resources>
<DockPanel LastChildFill="True">
<Border CornerRadius="8">
<Grid
...
...
...
<TextBox
Name="Serial_Number"
...>
</TextBox>
<TextBox
Name="Job_Number"
...>
</TextBox>
<Button
...>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Text, ElementName=Serial_Number, Converter={StaticResource StringToBoolean}}" Value="True"/>
<Condition Binding="{Binding Text, ElementName=Job_Number, Converter={StaticResource StringToBoolean}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Border>
</DockPanel>
</UserControl>
The Problem:
The converters return true when there is something in the textbox, and if both textboxes have something in them both converters fire and the setter should be applied. I know this happens because I can set the IsEnabled
property to False in the setter and it will deactivate the button, where the initial state was activated.
The reverse does not happen: setting the IsEnabled
property to True does not enable the button, where the initial state was deactivated (which I did by setting the IsEnabled property to False directly on the button). I figured this would act as the default state, and the Setter would override it. This does not seem to be the case.
Some oddities I've noticed:
The second condition does not seem to fire until the first one does. Ie, if I enter some data into the second textbox before touching the first textbox, the converter does not fire at all. If I think enter data into the first textbox, both converters fire.
Is there a way to do enable the button only when data exists in the textboxes? Do I actually need to write out every combination of cases: (True/True = Enabled, True/False = Disabled, False/True = Disabled, False/False = Disabled) ? Is this a case of a typo or some silly logic error on my part? I really can't look at this with unbiased eyes at the moment.
Try updating your Style
to
<Style TargetType="{x:Type Button}">
<!-- New Bit -->
<Setter Property="IsEnabled" Value="False"/>
<!-- End of New Bit -->
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Text, ElementName=Serial_Number, Converter={StaticResource StringToBoolean}}" Value="True"/>
<Condition Binding="{Binding Text, ElementName=Job_Number, Converter={StaticResource StringToBoolean}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
which I did by setting the IsEnabled property to False directly on the button
If you set a property on the element, then WPF precedence will make any Trigger
's or default setters for that property pretty much inactive.
Finally it's better to always assign a default value for a property if you're using a Trigger
to modify it and make sure you don't have that property mentioned on the element itself.
In your specific case, you could even do this without using a converter.
<StackPanel xmlns:sys="clr-namespace:System;assembly=mscorlib">
<TextBox x:Name="tbOne" />
<TextBox x:Name="tbTwo" />
<Button Content="Some Button">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled"
Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=tbOne,
Path=Text}"
Value="{x:Static sys:String.Empty}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=tbTwo,
Path=Text}"
Value="{x:Static sys:String.Empty}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>