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>
Related
I am trying to access a property of a sibling element that comes directly BEFORE the target element. This is something I will have to duplicate several times in my app so I'd rather use something reproduceable (not elementName) if possible.
Any Ideas?
<Groupbox x:Name="GB1">
<Checkbox x:Name="CB1" IsChecked="True"/>
<TextBlock>
<Style TargetType="TextBlock">
<Style.Triggers>
<Data.Trigger Binding RelativeSource={??? (I want this to access the Checkbox CB1 above), Path=IsChecked}" Value="True>
<Setter Property="*Do a Thing if IsChecked=True*" Value="..."/>
</Style.Triggers>
</Style>
</TextBlock>
</Groupbox>
Maybe this example will give you an idea.
<StackPanel>
<TextBlock x:Name="textBlock1" Text="Text" />
<TextBlock x:Name="textBlock2" Text="Text">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}}, Path=Children[0].Text}"/>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
Path=Children[0].Text it will look for the first child element of this ancestor and access its Text property.
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.
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}}}">
In XAML I can set TwoWay binding to the local settings using the following
<TextBox
Name="TextXYZ"
Text="{Binding Source={x:Static local:Settings.Default},
Path=TextXYZ,
Mode=TwoWay}" />
<CheckBox Content=""
Name="checkBox1"
IsChecked="{Binding Source={x:Static local:Settings.Default},
Path=checkBox1,
Mode=TwoWay}" />
<CheckBox Content=""
Name="checkBoxSaveSettings"
IsChecked="{Binding Source={x:Static local:Settings.Default},
Path=checkBoxSaveSettings, Mode=TwoWay}" />
Is it possible to introduce rules to the binding in XAML so that if checkBoxSaveSettings.IsChecked=true then controls will have twoway binding but if checkBoxSaveSettings.IsChecked=false then the binding mode is another option?
You can achieve what you want with DataTrigger like so:
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=OneWay}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static local:Settings.Default}, Path=checkBoxSaveSettings, Mode=OneWay}" Value="True">
<Setter Property="Text" Value="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=TwoWay}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Your method however sounds somewhat confusing for the user as you can change control value but it won't take effect until some other CheckBox it ticked. I would recommend binding IsEnabled to checkBoxSaveSettings.IsChecked like so:
<TextBox
Text="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=TwoWay}"
IsEnabled="{Binding ElementName=checkBoxSaveSettings, Path=IsChecked}"/>
Not directly, but there are options for this. Here's just one. Create a converter on your binding. For the converter parameter, pass in the checkbox checked value.
<TextBox
Name="TextXYZ"
Text="{Binding Source={x:Static local:Settings.Default},
Path=TextXYZ,
Converter={StaticResource foo},
ConverterParameter = {Binding ElementName="checkBoxSaveSettings", Path="IsChecked",
Mode=TwoWay}" />
Then create a converter called "foo" (whatever you want). Inside it, if the parameter is true, you return the value passed in. If the parameter is false, you can return whatever you want, including the Settings.Default.TextXYZ value so nothing changes.
Another possible option is to incorporate a setter on TextXYZ but only apply the passed value to the private _TextXYZ if some other condition is true. That other condition would be bound to the checkbox IsChecked. That's something that should be done in a ViewModel and not an object class, but it would work in either.
I've got the following TabItem template where the TabItem contains an image whose source depends on the IsSelected property. I accomplish this by binding the Image.Source to the TabItem.Header, with a Converter that converts the header text to a full filename. (i.e. a header of "awb" will become "images/awb-white.png" or "images/awb-black.png" depending on the converter).
This code works, but it seems to have some unnecessary redundancy. All I really need to change is the Image.Source's binding's converter; not the whole binding itself. (The RelativeSource and Path both stay the same). Is there any way to accomplish this without the redundancy?
<Window.Resources>
<local:UnselectedImageFilenameConverter x:Key="UnselectedImageFilenameConverter" />
<local:SelectedImageFilenameConverter x:Key="SelectedImageFilenameConverter" />
<ControlTemplate TargetType="TabItem" x:Key="TabItemTemplate">
<Image x:Name="TabImage" Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header, Converter={StaticResource UnselectedImageFilenameConverter}}" Stretch="None"/>
<ControlTemplate.Triggers>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter TargetName="TabImage" Property="Source" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header, Converter={StaticResource SelectedImageFilenameConverter}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<TabControl>
<TabItem Header="awb" Template="{StaticResource TabItemTemplate}">
<Grid/>
</TabItem>
<TabItem Header="av" Template="{StaticResource TabItemTemplate}">
<Grid/>
</TabItem>
</TabControl>
To my knowledge that is not possible.
But you could change the way you handle this by creating only one converter but passing both the original value and the selection status via a MultiBinding. (The converter would need to be an IMultiValueConverter). Whether that is a good idea is of course questionable...