Conditional text binding XAML - c#

I have 3 properties that I'm trying to bind to a Textblock in XAML. One is a conditional and the other two are the strings that I want to display depending on that conditional.
<TextBlock Text="{Binding TrueText}" Style="{StaticResource styleSimpleText}" Visibility="{Binding ShowTrueText, Converter={StaticResource boolToVisibilityConverter}}"/>
<TextBlock Text="{Binding FalseText}" Style="{StaticResource styleSimpleText}" Visibility="{Binding ShowTrueText, Converter={StaticResource invertedBoolToVisibilityConverter}}"/>
This works, but now the textblocks have to have different names. Can I turn this into one TextBlock with the conditional inside of it?

You could achieve that with a Style and a DataTrigger:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding FalseText}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowTrueText}" Value="True">
<Setter Property="Text" Value="{Binding TrueText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
An alternative would be to use a MultiBinding with a multi-value converter:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TextConverter}">
<Binding Path="TrueText"/>
<Binding Path="FalseText"/>
<Binding Path="ShowTrueText"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The converter would look like this:
public class TextConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
var trueText = (string)values[0];
var falseText = (string)values[1];
var showTrueText = (bool)values[2];
return showTrueText ? trueText : falseText;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

The way we do this type of thing for MVVM is to create a property in your viewmodel for this. This allows for you to have unit testing for your condition on the viewmodel.
The property in your viewmodel will be the string value that the TextBlock is bound to. The viewmodel at some point will determine the value of that string based on the conditional logic that you need.

Yes, you can, just wrap them in a TextBlock as follows:
<TextBlock x:name="myTextBlock" Style="{StaticResource styleSimpleText}">
<TextBlock Text="{Binding TrueText}" Visibility="{Binding ShowTrueText, Converter={StaticResource boolToVisibilityConverter}}"/>
<TextBlock Text="{Binding FalseText}" Visibility="{Binding ShowTrueText, Converter={StaticResource invertedBoolToVisibilityConverter}}"/>
</TextBlock>
However, I think the best answer is the one provided by Clemens (using a DataTrigger).

You could set it up in your viewmodel and let it determine which text to show.
private static readonly string TRUETEXT = "This is the text to show when true";
private static readonly string FALSETEXT = "This is the text to show when false";
private bool _myBooleanProperty;
public bool MyBooleanProperty
{
get { return _myBooleanProperty; }
set
{
if (_myBooleanProperty != value)
{
_myBooleanProperty = value;
OnPropertyChanged("MyBooleanProperty");
OnPropertyChanged("ResultText");
}
}
}
public string ResultText
{
get
{
return MyBooleanProperty ? TRUETEXT : FALSETEXT;
}
}
Then you bind to it with just a single textblock. No visibility converter needed.
If there is a state where no text should show, you could work that in as well.
<TextBlock Text="{Binding ResultText}" Style="{StaticResource styleSimpleText}" />

In my opinion, the best solution to this problem would be a new string property in your view model which returns either TrueText or FalseText depending on the conditional. With such a property, you can just use a plain binding.
public string TheNewProperty
{
get
{
return ShowTrueText ? TrueText : FalseText;
}
}
<TextBlock Text="{Binding TheNewProperty}" Style="{StaticResource styleSimpleText}"/>

Related

Default DataTemplate for a control

I am trying to make a property editor which would show different control for different data types.
For example if the data is a bool it should be a checkbox. If it is a color it should be a color picker. If it is an int it should be a numeric up down ect.
For everything else it should be a textbox <-- this is where im struggling.
for bool I can do
<DataTemplate DataType="{x:Type mscorlib:Boolean}">
<CheckBox IsChecked="{Binding Path=.}"/>
</DataTemplate>
and this works perfectly.
But I can't figure out how to make the default textbox case happen.
<DataTemplate>
<TextBlock >
</DataTemplate>
would give me IDictionary must have a Key attribute error
If I adds a key to the template it won't be used unless I explicitly do something like DataTemplate="..."
I cant seem to find a way for the given template to target more than one type either. Which is forcing me to copy paste the template over and over for each type I wish to support.
Is there a better way to do it??
Most professional way of doing this to use templateselector.
Use the IntegerUpDown control in the xtended wpf toolkit for numeric
up and down
Use the colorpicker control of xtended wpf toolkit for color picker
Include this in xaml
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
<DataTemplate x:Key="TEMPLATE">
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<DataTemplate x:Key="BOOLEANTEMPLATE">
<CheckBox IsChecked="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<DataTemplate x:Key="numericTemplate">
<xctk:IntegerUpDown Name="myUpDownControl" />
</DataTemplate>
<DataTemplate x:Key="ColorTemplate">
<xctk:ColorPicker></xctk:ColorPicker>
</DataTemplate>
<me:DynamicDataTemplateSelector x:Key="datagridDynamictemplateselector" BooleanTemplate="{StaticResource BOOLEANTEMPLATE}"
ColorTemplate ="{StaticResource ColorTemplate}"
NumericTemplate ="{StaticResource numericTemplate}"
TextBoxTemplate="{StaticResource TEMPLATE}" />
Here is the class which override datatemplateselector class
public class DynamicDataTemplateSelector : DataTemplateSelector
{
public DataTemplate TextBoxTemplate{get;set;}
public DataTemplate BooleanTemplate{get;set;}
public DataTemplate NumericTemplate { get; set; }
public DataTemplate ColorTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate dataTemplate = TextBoxTemplate;
if(item!=null)
{
Type dataTypeOfValue = item.GetType();
if(dataTypeOfValue==typeof(int))
{
dataTemplate = NumericTemplate;
}
else if(dataTypeOfValue==typeof(Color))
{
dataTemplate = ColorTemplate;
}
else if (dataTypeOfValue == typeof(Boolean) || dataTypeOfValue == typeof(bool))
{
dataTemplate = BooleanTemplate;
}
}
return dataTemplate;
}
I solved the problem by using xaml style + converter.
<ContentPresenter Content="{Binding MyValue}"
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="Dead beef"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=., Converter={StaticResource ToTypeConverter}}" Value="{x:Type mscorlib:Boolean}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=.}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
converter
public class ToTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null) ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The idea is to put the default template inside style's ContentTemplate and only modify the special ones using style trigger.

Custom trigger for length of TextBox text

In the trigger below, if TextBox Text value is empty, Border color will be Red.
<Style TargetType="TextBox" >
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="BorderBrush" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
How can I (with trigger) do that when the length on the text is under 4 chars the border will be red?
Thanks!!
I'm pretty sure you want to implement some kind of validation.
Instead of using a trigger I would use the built in validation features of WPF.
One way of doing this is to implement the IDataErrorInfo interface in your view model (or model).
public class MainWindowViewModel : INotifyPropertyChanged, IDataErrorInfo
Error property implementation:
public string Error { get { return null; } }
The indexer's implementation:
public string this[string columnName]
{
get
{
if(columnName == "SomeRandomText")
{
if(string.IsNullOrEmpty(SomeRandomText) || SomeRandomText.Length < 4)
{
return "Text should be at least four characters long";
}
}
return null;
}
}
In XAML:
<TextBox Text="{Binding SomeRandomText, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
Result:
If you don't want to implement the IDataErrorInfo interface you can use ValidationRules.
Same thing achieved with a ValidationRule:
<Binding Path="SomeRandomText" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:LengthValidationRule RequiredLength="4" />
</Binding.ValidationRules>
</Binding>
And the ValidationRule:
public class LengthValidationRule : ValidationRule
{
public int RequiredLength { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var text = (string)value;
if(string.IsNullOrEmpty(text) || text.Length < RequiredLength)
{
return new ValidationResult(false, "Text should be at least four characters long");
}
return ValidationResult.ValidResult;
}
}
If you want to have a different border you can look into the Validation.ErrorTemplate attached property.
If you really want to do this with a trigger, as others mentioned you can do it with a converter named for example LessThanConverter. It would take the Length of the Text and the other number you want to compare it with as the ConverterParameter and would return a bool.
If you really want a Style.Trigger based solution you need a converter:
The converter converts the String input to a Boolean, weather the input meets your criteria. In this example the text length must be bigger than 4.
Converter Class:
public class LengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var text = value as string;
if (text != null) {
return text.Length > 4;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Window.Resources>
<conv:LengthConverter x:Key="converter" />
<Style TargetType="TextBox" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text,
RelativeSource={RelativeSource Self},
Converter={StaticResource converter}}"
Value="False">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
But a proper validation (like in Szabolcs Dézsi's answer) is probably the better approach.
Reference assemblies :
Microsoft.Expression.Interactions.dll, and
System.Windows.Interactivity.dll .
On my machine they are in :
C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries
<TextBox x:Name="textBox" HorizontalAlignment="Left" Margin="96,0,0,164.04" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Bottom">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:Interaction.Behaviors>
<ei:ConditionBehavior>
<ei:ConditionalExpression>
<ei:ComparisonCondition Operator="LessThan" RightOperand="4" LeftOperand="{Binding Text.Length, ElementName=textBox}"/>
</ei:ConditionalExpression>
</ei:ConditionBehavior>
</i:Interaction.Behaviors>
<ei:ChangePropertyAction PropertyName="BorderBrush">
<ei:ChangePropertyAction.Value>
<SolidColorBrush Color="#FFD41717"/>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>

CheckBox Show/Hide TextBox WPF

As the title says, Iam trying to show/hide a TextBox in WPF without writing code in MainWindow.xaml.cs file.
Model:
public class Person
{
public string Comment { get; set; }
}
View:
<Window x:Class="PiedPiper.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="WPF" Height="400" Width="400">
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden" Name="CommentTextBox"></TextBox>
</Grid>
ViewModel:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel(Person person)
{
Comment = person.Comment;
}
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value; OnPropertyChanged("Comment"); }
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So the TextBox should be hidden at start, but visible when checkbox is checked. Please help!
Thanks.
You can bind TextBox.Visiblity to CheckBox.IsChecked. If you want to toggle between Hidden and Visible then you need to either write custom IValueConverter or create simple Style.Trigger
<StackPanel>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Name="CommentTextBox">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=CommentCheckBox, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
if you want to toggle between Collapsed and Visible there is an easier way and you can use build in BooleanToVisibilityConverter
<StackPanel>
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>
The simplest way is to write a custom "BooleanToHiddenVisibilityConverter" and use it (like dkozl said).
It's a really simple converter and it comes in handy in many situations. I think that every descent WPF application should have one.
public sealed class BooleanToHiddenVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = false;
if (value is bool)
{
bValue = (bool)value;
}
return (bValue) ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Visibility)
{
return (Visibility)value == Visibility.Visible;
}
return false;
}
}
And use it like dkozl said:
<StackPanel>
<StackPanel.Resources>
<BooleanToHiddenVisibilityConverter x:Key="BooleanToHiddenVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked,
Converter={StaticResource BooleanToHiddenVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>

WPF ListView with group of RadioButtons and select default value

Today I have a problem with selected default CheckBox. But First i show my code:
<ScrollViewer>
<ListView ItemsSource="{Binding itemsSource, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<Label Content="{Binding AttrName, Mode=OneWay}" />
</Expander.Header>
<ListView Margin="20, 0, 0, 0" ItemsSource="{Binding subItemSource}" BorderBrush="Transparent" >
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding DataContext.AttrName, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Content="{Binding}"
<!-- What should I bind to to get item checked? -->
IsChecked={}/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
So i have a nested binding where in CheckBoxes I bind GroupName to parent data context. My itemsSource contains the following properties:
int DefaultValue { get; set; }
List<int> subItemSource { get; set; }
And all I want now is to mark RadioButton when actual binding value is equal to DefaultValue. How should I do this? Should I write validator?
I'll start by writing a converter class
class ElementComparer : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
then declare the converter as a resource where l: is your namespace to converter
<l:ElementComparer x:Key="ElementComparer"/>
then in your data template
<DataTemplate>
<RadioButton GroupName="{Binding DataContext.AttrName, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Content="{Binding}"
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource ElementComparer}" Mode="OneWay">
<Binding Path="DataContext.DefaultValue" RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding />
</MultiBinding>
</RadioButton.IsChecked>
provided the datacontext of the ItemsControl is containing the property for default value to compare with, the trick is to compare the selected item of the list to the current item to detect if it is default item, and will return true from converter and hence radio will be checked

WPF binding from parent to child element

Here is my XAML
<ListView x:Name="missingVariablesListView" ScrollViewer.CanContentScroll="True" HorizontalAlignment="Left" Height="320" Margin="81,28,0,0" VerticalAlignment="Top" Width="641" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="ComponentID: " FontWeight="Bold" Foreground="Brown" />
<TextBlock Text="{Binding Name}"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Variable Name: " Foreground="Green"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text=" "/>
<TextBlock Text="Variable Value: " Foreground="Blue"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style >
<Style.Triggers>
<DataTrigger Binding="{Binding IsMissing}" Value="false">
<Setter Property="UIElement.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<TextBlock Text="-----------------------------------------------------------------"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is the CodeBehind
this.missingVariablesListView.DataContext = //Code to fill in the list View
Classes Involved
public class Component
{
private ObservableCollection<ComponentParameter> parameters = new ObservableCollection<ComponentParameter>();
public string Name
{
get;
set;
}
public ObservableCollection<ComponentParameter> Parameters
{
get{return parameters;}
set{parameters = value;}
}
}
public class ComponentParameter
{
public string Name
{
get;set;
}
public string Value
{
get;set;
}
public bool HasErrors
{
get;
set;
}
public bool IsMissing
{
get;set;
}
Sample Output (at the moment)
ComponentID: Component1
--------------------------
ComponentID: Component2
VariableName:Var1 Variable Value:Val1
VariableName:Var2 Variable Value:Val2
-----------------------
ComponentID: Component3
-----------------------
ComponentID: Component4
-----------------------
What i want to do is that whenever the boolean IsMissing is true for the inner element within the itemsControl i want to make sure that the ComponentID and the Name properties within the StackPanel (that has the orientation as horizontal) are not shown in the window including the child elements. Basically i am trying to find a way to exclude the whole description for that particular ComponentID whose isMissing variable is set to true. Any suggestions on this?
I would consider to add a property IsAnyParameterMissing to the Component class:
public class Component
{
private ObservableCollection<ComponentParameter> parameters = new ObservableCollection<ComponentParameter>();
public string Name
{
get;
set;
}
public ObservableCollection<ComponentParameter> Parameters
{
get{return parameters;}
set{parameters = value;}
}
public bool IsAnyParameterMissing
{
get { return this.Parameters.Any(param => param.IsMissing); }
}
}
And then bind the visibility to this property:
<StackPanel Orientation="Horizontal" Visibility="{Binding IsAnyParameterMissing, Converter={BooleanToVisibilityConverter}}">
<TextBlock Text="ComponentID: " FontWeight="Bold" Foreground="Brown" />
<TextBlock Text="{Binding Name}"/>
</StackPanel>
This will only display the StackPanel if any of the Parameters in the collection has its IsMissing property equal to true. Note that this will not change the visibility if the IsMissing property is changed for any of the items! This would need some additional work.
EDIT: Note that the {BooleanToVisibilityConverter} might need to be adjusted, depending on your available converters. Should be a simple task though.
One thing i noticed is that you don't implement the properties (such as "isMissing") as DependencyProperty. Neither do you use property change notifications via INotifyPropertyChanged.
You have to implement either of the two, otherwise changes of the properties will not be propagated through the bindings. That means, the trigger wouldn't trigger...
You could use a BooleanToVisibilityConverter to hide those items for you, although you may need to change your property or add a new IsPresent property for this:
In Resources:
<Converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"
IsInverted="True" />
In your ListView.ItemTemplate:
<StackPanel Orientation="Horizontal" Visibility="{Binding IsMissing, Converter={
StaticResource BoolToVisibilityConverter}}">
<TextBlock Text="ComponentID: " FontWeight="Bold" Foreground="Brown" />
<TextBlock Text="{Binding Name}"/>
</StackPanel>
Custom BoolToVisibilityConverter with IsInverted property:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolToVisibilityConverter : IValueConverter
{
public bool IsInverted { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value.GetType() != typeof(bool)) return null;
bool boolValue = IsInverted ? !(bool)value : (bool)value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || value.GetType() != typeof(Visibility)) return null;
if (IsInverted) return (Visibility)value != Visibility.Visible;
return (Visibility)value == Visibility.Visible;
}
}
Now it will just take you one minute to implement it. I have updated the XAML example above to use it.

Categories