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.
Related
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}}}">
I want to have a StackPanel who's visibility should be depending on a Combobox selection. Unfortunatly the XAML below does not work.
I found a solution with a new property which will be set on the PropertyChanged event of the Combobox selection, though I would prefer a strict XAML solution for this.
Any hints on how to solve this?
<StackPanel>
<Label>Picture in Picture function</Label>
<ComboBox Name="cbPictureInPicture" ItemsSource="{Binding Path=PictureInPictureCodeList, Mode=OneWay}" DisplayMemberPath="CodeText"
SelectedValuePath="CodeID" SelectedValue="{Binding Path=PictureInPicture, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbPictureInPicture, Path=IsSelected.CodeText}" Value="Yes">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label>Picture in Picture is used</Label>
(...)
</StackPanel>
you may perhaps rewrite the same as
<DataTrigger Binding="{Binding ElementName=cbPictureInPicture, Path=SelectedItem.CodeText}" Value="Yes">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
assuming the combobox is bound to a collection whose item has CodeText property. so SelectedItem.CodeText will point to the same.
additionally it may not be required to set <Setter Property="Visibility" Value="Visible" /> as it is the default value. it does not have any effect in this case just some extra line of code which can be removed.
You can also use a converter and bind directly to the PictureInPicture property:
<StackPanel Visibility="{Binding PictureInPicture, Converter={StaticResource myVisibilityConverter}}"/>
<Label>Picture in Picture is used</Label>
(...)
</StackPanel>
Create flags and pass this flag in stackpanel visibility converter.
On the basis of flag in converter make decision stackpanel visible/hide whatever
Set this flat in comboBox selection change event if selected value as per your requirement.
So, I'm building an order tracking app with different user accounts, some of whom have less need-to-know than others. This means that certain controls are displayed for some accounts, and hidden for others.
The datacontext for the Window is set to my Order class, and the data binding within the text fields works perfectly in regards to displaying properties from the specific Order. However, the DataTemplates and Triggers I've made don't seem to be doing anything at all, and I'm not entirely sure why. I've looked all over the web and I can't seem to find why it's not working. Here's the XAML:
<Label Name="StatusLabelText" Content="Status:" FontSize="15" DockPanel.Dock="Top">
<Label.Resources>
<DataTemplate DataType="x:Type local:Order">
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=selectedAccount}" Value="Color Correct">
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Label.Resources>
</Label>
I suspect you want to hide label in case selectedAccount value is Color Correct.
You need Style to do that and not a template if my assumption is correct which can be done like this:
<Label Name="StatusLabelText" Content="Status:" FontSize="15"
DockPanel.Dock="Top">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=selectedAccount}"
Value="Color Correct">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
On a side note, you should use Collapsed instead of Hidden to set visibility of control in case you don't want the label to take the size even when it's not visible on GUI. Read more about it here.
I would like to make a WPF usercontrol that shows a string when and only when the datacontext == null. I'm using the TargetNullValue attribute in binding to display a custom string when the datacontext is null, and that's working as intended. But when the datacontext is non-null, it just shows the ToString value, which I would like to get rid of.
Of course I could solve this easily by using a valueconverter, but I'd like to find a way to solve this with xaml only. Does anyone know a way to do it?
In case you want TextBlock to be shown only in case binding value is null, you can have trigger in place and set Visibility to Visible when binding value is null and otherwise Collapsed always.
<TextBlock Text="{Binding TargetNullValue=NullValue}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Use a data trigger on {x:Null}. There are many options using styles, data templates etc., depending on taste and needs. For instance:
<DataTemplate x:Key="ShowOnNull">
<TextBlock x:Name="text"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter TargetName="text" Property="Text" Value="your custom string"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
...
<ContentPresenter ContentTemplate="{StaticResource ShowOnNull}"
Content="{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>