Is possible bind with IF ELSE statement - c#

<TextBlock Text="{Binding Name}" />
if Name is empty or null, bind with NameOpt
Something like this:
<TextBlock Text="{Binding if Name ? Name : NameOpt}" />

You can use DataTriger to achieve the behavior
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Name}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="{x:Null}">
<Setter Property="Text" Value="{Binding NameOpt}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Well you could with a DataTrigger and there is nothing stopping you, but that's arguably a bad way to do binding, particularly from a MVVM point of view. Generally triggers are for changing a property based on conditions either in the XAML itself or a particular property in the VM. It's kind of weird to do dynamic binding to a property depending upon the state of whether a VM property is null or not.
To do so purely from the VM try, your view should bind to a single property like Name:
<TextBlock Text="{Binding Name}" />
...and in the processing in your VM do something like:
public string Name { get ; set; } // TODO: add usual property changed stuff
void UpdateStuff()
{
// perhaps update Name and NameOpt here
// ...
// Now update the exposed property
Name ? Name : NameOpt
}

Related

How to remove a dictionary resource reference from a TextBlock?

I have this element:
<TextBlock Style="{StaticResource textReplyMessageStyle}">
<Run x:Name="answerMessage" Text="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
</TextBlock>
And I have the need to print in answerMessage either the content of the var Message (thanks to the binding) or an element from the dictionary (thanks to resource reference done by the code below).
answerMessage.SetResourceReference(Run.TextProperty, "Answer_Message_Not_Selected");
The binding is fully working as well as the dictionary reference, but after setting the resource reference once I can not find a way to make the binding work again.
I tried to re-do the binding programmatically but is not working...
The only working workaround I found is to set programmatically the text of answerMessage.
How can I remove the resource reference from the Run element and make the binding work again?
Just to give a bit of context the variable Message contains a number and the resource Answer_Message_Not_Selected
contains the text "Not selected" or a translation in a different language depending on the dictionary active on the program. I have to use dynamic resource reference because the language of the program can be changed on the fly.
Thanks!
A simple way to work this around is to make the output of the TextBlock depends mainly on the Message property.
For example, you can bind the value of Text to Message only if the value of Message is not null, otherwise set it to your resource:
<TextBlock Style="{StaticResource textReplyMessageStyle}">
<Run x:Name="answerMessage" >
<Run.Style>
<Style TargetType="{x:Type Run}">
<Setter Property="Text" Value="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Message}" Value="{x:Null}">
<Setter Property="Text" Value="{StaticResource Answer_Message_Not_Selected}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
</TextBlock>
Notice that you should set the starting value of Text inside the style because properties set in the control initializer will override any style setters!
Another thing you can do instead of setting the value of Message to null is to add a property AnswerMessageSelected in the view model, to be more explicit when the TextBlock should change its target value:
<TextBlock Style="{StaticResource textReplyMessageStyle}">
<Run x:Name="answerMessage" >
<Run.Style>
<Style TargetType="{x:Type Run}">
<Setter Property="Text" Value="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
<Style.Triggers>
<DataTrigger Binding="{Binding AnswerMessageSelected}" Value="False">
<Setter Property="Text" Value="{StaticResource Answer_Message_Not_Selected}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
</TextBlock>
And generally after using this method, it might not be necessary to use x:Name to refer to in the code behind, since it's a better practice to let the ViewModel do all the work and not the code behind the View:
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource textReplyMessageStyle}">
<Setter Property="Text" Value="{Binding Message, Mode = OneWay, FallbackValue = ''}" />
<Style.Triggers>
<DataTrigger Binding="{Binding AnswerMessageSelected}" Value="False">
<Setter Property="Text" Value="{StaticResource Answer_Message_Not_Selected}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
My initial suspicions were correct, thanks to #Jason Tyler #o_w for confirming that.
When you set a reference to a Run (and also for similar elements) you lose the binding property.
The solution to my problem is simply to do the binding again.
Binding b = new Binding();
b.Path = new PropertyPath("Message");
b.Source = this;
BindingOperations.SetBinding(answerMessage, Run.TextProperty, b);
This solution fixes the problem, but it is possible to fix the cause by setting the Answer_Message_Not_Selected resource to the bonded Message element.
Message = Application.Current.Resources["Answer_Message_Not_Selected "].ToString()
If the resource is dynamic, like in my case, you will need some sort of OnChange event observing the resource (or dictionary) to keep the content of Message always updated.

Can the Setter of a DataTrigger modify a property within the DataContext?

I have a check box which I plan to implement a 'Select All' feature on.
I figured the easiest implementation was to modify my DataTemplate with a DataTrigger to change the IsChecked property to true/false.
<UserControl.Resources>
<DataTemplate x:Key="MyTemplate">
<Grid>
<CheckBox x:Name="Selection" IsChecked="{Binding Selected}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=SelectAll, Path=IsChecked}" Value="true">
<Setter TargetName="Selection" Property="DataContext.Selected" Value="true" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SelectAll, Path=IsChecked}" Value="false">
<Setter TargetName="Selection" Property="IsChecked" Value="false" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
<Grid>
<CheckBox Name="SelectAll />
<ListView Name="lvSteps" ItemTemplate="{StaticResource MyTemplate}" ItemsSource="{Binding MyList}" />
</Grid>
However, this overwrites the binding of the CheckBox which is set to the DataContext property 'Selected' to just the bool values set in the DataTrigger.
Which thinking about it makes perfect sense.
So my question is, can I change the value of DataContext property within a DataTrigger via Setter?
So I can keep my CheckBox bound to 'Selected', and change the 'Selected' value within the Setter of the DataTrigger?
you maybe think about it in the wrong way.
SelectAll is surly part of your container Element (lets say ListVM) which intern holds your objects (ListItemVM) which contains Selected so if you check SelectAll you can and should iterate over your Elements and set Selected to true and this items use there INotifyPropertyChanged to inform your CheckBox
Edit for Comment
there is no need to hook up the event of SelectAll just bind to it and do your thing in the Set-Part
public bool SelectAll
{
get { return _selectAll;}
set
{
_selectAll = value;
RaisPropertyChanged();
foreach( var item in MyList)
{
item.Select = _selectAll;
}
}
}
Also why would you like to do this in xaml?
Because you could use interfaces and factor it out in a way that you can us it at many places without code redundancy also you will be able to do it in code from where ever you are able to access the "ListVM"

Textblock won't recieve trigger in HierachicalDataTemplate

I'm trying to get a TreeView to display items as a TextBlock, and then based on a boolean inside the data-bound object to either make the FontWeight Normal or Bold, pretty much the following:
<TreeView x:Name="TreeView" ItemsSource="{Binding Layers}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModels:Layer}" ItemsSource="{Binding Path=Layers}">
<TextBlock Text="{Binding Path=Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowInPreview}">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The Setter outside the trigger actually works, when I set that one to "Bold", everything goes Bold right away. It's just the DataTrigger that never, well... triggers :P
The ItemSource implements INotifyPropertyChanged, and so does the Layer object on all properties (including the ShowInPreview).
I've tried all kinds of different setups I could find on the web (using Window.Resources, putting it in TreeView.ItemContainerStyle, etc. etc), so I'm completely at a loss right now!
Set the Value on your data trigger.
I dont know exactly where is your property, try something like this. I think, issue in binding:
<DataTrigger Binding="{Binding Path=DataContext.ShowInPreview, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}">

How to label content set using triggers/( etc) when bind value null or empty in wpf

I want to bind label content dynamically at run time. When the binding property null or empty I want to show bind value(name) as content. I have try as below but it not works:
when binding property has a value it works fine.
XAML code as follows.
<Label Content="{DynamicResource name}">
<Label.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding name}" Value="{x:NULL}">
<Setter Property="Label.Content" Value="name" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
Help me. Thanks!
Instead of using DataTrigger you can use TargetNullValue property (msdn).
Gets or sets the value that is used in the target when the value of
the source is null.
Example:
<Label Content="{Binding LabelContent, TargetNullValue=LabelContent}" />
Solution in code-behind:
You don't have to write trigger. You can check value before adding it to Resources:
...
foreach (var item in resourceList)
{
if(!string.IsNullOrEmpty(item.Value))
window.Resources.Add(item.Key, item.Value);
else
window.Resources.Add(item.Key, item.Key);
}
...

DataTemplate in ControlTemplate not updating Binding

I've created a control with 3 PART_s, one PART_ changes depending on the type bound to it, however values changed within the Control do not update the Binding, it seems to work as OneWay Binding.
Here's part of the code I beleive is relevant:
<DataTemplate x:Key="BooleanDAView" DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="DateTimeDAView" DataType="{x:Type sys:DateTime}">
<extToolkit:DateTimePicker Value="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="Int32DAView" DataType="{x:Type sys:Int32}">
<extToolkit:IntegerUpDown Value="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="StringDAView" DataType="{x:Type sys:String}">
<TextBox Text="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
....
<ContentControl x:Name="PART_Content"
Grid.Row="0" Grid.Column="1"
Margin="{TemplateBinding Padding}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
>
<ContentControl.ContentTemplateSelector>
<controls:TypeBasedDataTemplateSelector>
<controls:TypeBasedDataTemplateSelector.Templates>
<controls:TypedDictionary>
<sys:String x:Key="{x:Type sys:Boolean}">BooleanDAView</sys:String>
<sys:String x:Key="{x:Type sys:DateTime}">DateTimeDAView</sys:String>
<sys:String x:Key="{x:Type sys:Int32}">Int32DAView</sys:String>
<sys:String x:Key="{x:Type sys:String}">StringDAView</sys:String>
</controls:TypedDictionary>
</controls:TypeBasedDataTemplateSelector.Templates>
</controls:TypeBasedDataTemplateSelector>
</ContentControl.ContentTemplateSelector>
</ContentControl>
For Content I've also tried ... RelativeSource={RelativeSource AncestorType=local:DABaseControl} but no change.
If the DataTemplate Binding use "{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" the template doesn't change once set.
Or is there a better way to do this?
Thanks
I just encountered the same problem, I wanted to create a DataTemplate with DataType="{x:Type sys:Boolean} that just had a checkbox. But there were many warning signs along the way telling me this isn't the way it should be done.
At first, the simple binding of {Binding} would throw an exception "Two-way binding requires path or xpath", which was the first warning sign. I changed the binding to {Binding .} which worked (even though this MSDN article clearly states that they're equivalent). That fact that voodoo was helping was the second warning sign. It then displayed correctly and the checked state was according to the boolean value, but when clicking the checkbox (even with UpdateSourceTrigger=PropertyChanged), it refused to update the binding source, no matter what I tried. Using diagnostics:PresentationTraceSources.TraceLevel=High showed that it didn't even try to bind back (third warning sign).
I went ahead and created a simple "box" for the bool value - a class with a single bool property named Value with anINotifyPropertyChanged implementation. I changed the binding to {Binding Value} and now everything worked, including two way binding.
Conclusion: It seems a binding can't update the bound object itself, but only properties of that object (which is why {Binding} throws an exception, but the more explicit {Binding .} suppresses that exception, according to H.B.'s answer). In any case, the approach of creating a ViewModel and creating templates that target it appears to be more than a mere design guideline, but an actual technical requirement.
I've actually never worked with a ContentTemplateSelector, but if I had to hazard a guess I would say either it's not responding to PropertyChanged events on your ContentControl.Content property, or your Content binding is incorrect.
You can easily check if your binding is correct or not by removing the ContentTemplateSelector and seeing if data shows up at all. If it does, your binding is correct. If it doesn't, it's incorrect and you need to fix it.
If the problem is the ContentTemplateSelector, then I would suggest switching to a DataTrigger which determines which ContentTemplate to use based on the Content. This is what I usually do, and it uses a Converter which simply returns typeof(value)
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource StringDAView}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type sys:Boolean">
<Setter Property="ContentTemplate" Value="{StaticResource BooleanDAView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type DateTime">
<Setter Property="ContentTemplate" Value="{StaticResource DateTimeDAView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type sys:Int32">
<Setter Property="ContentTemplate" Value="{StaticResource Int32DAView}" />
</DataTrigger>
</Style.Triggers>
</Style>

Categories