WPF - How to use DataTrigger without interfering with DataContext of child UIElements - wpf

I am trying to allow a user to choose one of two format types for inputting double values, and then I want to use a DataTrigger in xaml to choose one of two separate control types for the user to input these values, based on the format they have chosen.
I have a view and a viewmodel. The view model has properties like this:
public class MyViewModel
{
public string DoubleFormat {get;set;}
public double X {get;set;}
public double Y {get;set;}
public double Z {get;set;}
}
The application will set the DoubleFormat property to something like "FormatA" or "FormatB" for the DataTrigger to trigger off of.
The xaml for my view looks like this:
<DataTemplate x:Key="FormatAEditTemplate" DataType="common:MyViewModel">
<util:FormatAEditorControl Value="{Binding X}"/>
</DataTemplate>
<DataTemplate x:Key="FormatBEditTemplate" DataType="common:MyViewModel">
<wpftk:DecimalUpDown Value="{Binding X}" />
</DataTemplate>
<DataTemplate x:Key="MyEditTemplate" DataType="common:MyViewModel">
<ContentControl x:Name="MyDoubleInputControl" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding DoubleFormat}" Value="FormatA">
<Setter TargetName="MyDoubleInputControl" Property="ContentTemplate" Value="
{StaticResource FormatAEditTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding DoubleFormat}" Value="FormatB">
<Setter TargetName="MyDoubleInputControl" Property="ContentTemplate" Value="
{StaticResource FormatBEditTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
So in the trigger I'm essentially trying to set the ContentControl's ContentTemplate property to equal either of the two template values at the top, rendering either a util:FormatAEditorControl or a wpftk:DecimalUpDown for the user to edit the double value for the property X on the viewmodel.
The trigger does work and the correct control type is rendered, however the databinding of the input controls themselves does not work. The controls are rendered but with no "0.0" default value for the double property, nor does updating the property in the U/I affect the viewmodel property value. I also get the following output in the Output window when I turn on tons of WPF output logging:
System.Windows.Data Information: 41 : BindingExpression path error: 'X' property not found for 'object' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=X; DataItem=null; target element is 'DecimalUpDown' (Name='FooBar'); target property is 'Value' (type 'Nullable1')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=X; DataItem=null; target element is 'DecimalUpDown' (Name='FooBar'); target property is 'Value' (type 'Nullable1')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=X; DataItem=null; target element is 'DecimalUpDown' (Name='FooBar'); target property is 'Value' (type 'Nullable1')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=X; DataItem=null; target element is 'DecimalUpDown' (Name='FooBar'); target property is 'Value' (type 'Nullable1')
... along with several other lines which seem to state essentially the same thing.
I can simply choose one of the two Control types to "hard code" into the xaml in place of the data trigger, and the databinding works properly, which leads me to suspect that setting the Binding property of the DataTrigger is interfering with the DataContext of the "child" Elements which are chosen by the trigger.
So then my question is, since I basically want every last thing in this entire view to have MyViewModel as its DataContext, is there any good way to have my DataTrigger choose either of the double input control types without interfering with their DataContexts? Or if I am fundamentally misunderstanding this scenario, then perhaps someone could just throw me a bone.
A gracious coworker managed to work around this situation by binding like this:
{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContentControl}}, Path=DataContext.X}
but this seems like a fairly circuitous workaround.
Thank you!

You are using a ContentControl, so the templated content is inherently referring to its Content property. From what you written above, that property is not set, hence the binding fault.
Try to set the ContentControl as:
<DataTemplate x:Key="MyEditTemplate" DataType="common:MyViewModel">
<ContentControl x:Name="MyDoubleInputControl" Content="{Binding}" />
...
</DataTemplate>

Related

How may my Style Setter get at the DataContext of the of the element whose style is being set?

This seems like it should be simple as pie but I can't figure it out.
I am trying to change a working Style Setter for a "Fill" property on an element to actually declare a SolidColorBrush object in XAML in the Setter.Value. Unfortunately, in my brush declaration, I cannot seem to "get at" the DataContext of the very object whose Fill I am attempting to set. How may do I do this?
Below is the Style with setter. It uses a converter that takes an enum value ("Usage") and returns a Color value. The converter is fine but the binding fails because it cannot find the object.
<core:UsageToColorConverter x:Key="CvtUsageToColor"/>
<Style x:Key="RegionBandStyle"
TargetType="{x:Type tk:CartesianPlotBandAnnotation}">
<!-- Help Intellisense show us the correct bindings when coding -->
<d:Style.DataContext>
<x:Type Type="gci:IProfileRegion" />
</d:Style.DataContext>
<Setter Property="Fill">
<Setter.Value>
<SolidColorBrush >
<SolidColorBrush.Color>
<!-- THIS BINDING FAILS: It cannot find the "ancestor" CartesianPlotBandAnnotation
in order to get at its DataContext
-->
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type tk:CartesianPlotBandAnnotation}}"
Path="DataContext.(gci:IProfileRegion.Usage)"
Converter="{StaticResource CvtUsageToColor}"/>
</SolidColorBrush.Color>
</SolidColorBrush>
</Setter.Value>
</Setter>
</Style>
The binding fails with this message:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Telerik.Windows.Controls.ChartView.CartesianPlotBandAnnotation', AncestorLevel='1''. BindingExpression:Path=DataContext.Usage; DataItem=null; target element is 'SolidColorBrush' (HashCode=39731869); target property is 'Color' (type 'Color')
I suppose this makes sense. My dim understanding is that Brush is not in the visual (or logical) tree, right? So it can't find an ancestor? Is that the reason?
The original version of this Setter worked but it was much simpler. It just used a similar converter that returns a SolidColorBrush instead of a color:
<Setter Property="Fill" Value="{Binding Usage, Converter={StaticResource CvtUsageToBrush}}"/>
This worked fine but unfortunately (for unrelated reasons), I need to do things the other way; I need to declare the Brush explicitly myself using Property Element Syntax
Can someone tell me what binding voodoo I need here to get at the DataContext of the CartesianPlotBandAnnotation in my SolidColorBrush color binding?
(This is the sort of Binding issue that makes my head spin no matter how many times I read up on the Logical and Visual trees. My searches keep bringing up related topics but not the one I want. )
The FindAncesstor mode of the RelativeSource may be used to find a parent item in the Visual Tree. However, this is not the case. The brush color should inherit the element data context. Try to remove the RelativeSource setter at all. Also, are you sure that the Usage is the attached property? If not, simply set "Usage" in the Path.
If this does not help, at least you will get a new error message saying which object is actually provide in the DataContext of CartesianPlotBandAnnotation.
Update:
In case of interface binding, it should be enough to set Path to "(gci:IProfileRegion.Usage)". I have just tested this code and confirm that it works correctly: https://github.com/Drreamer/ColorInterfaceBinding
If it does not work in your project, please clarify what exception is raised in this case. It helps to find the exact cause of the issue.

Why do I need to specify ElementName and DataContext in a binding?

To familiarize myself with WPF and MVVM concepts I built a visual representation of a Sudoku board.
My (simplified) setup looks like this (no custom code-behind in views anywhere):
I have a MainWindow.xaml:
<Window x:Class="Sudoku.WPF.MainWindow">
<Window.DataContext>
<models:MainWindowViewModel/>
</Window.DataContext>
<ctrl:SudokuBoard DataContext="{Binding Path=GameViewModel}"/>
</Window>
My MainWindowViewModel:
class MainWindowViewModel
{
public MainWindowViewModel()
{
IGame g = new Game(4);
this.GameViewModel = new GameViewModel(g);
}
public IGameViewModel GameViewModel { get; private set; }
}
SudokuBoard is a UserControl. Its DataContext is set to GameViewModel as per above.
Relevant parts of GameViewModel, Elements is populated in the ctor, Possibilities is set via a command:
public IList<CellViewModel> Elements { get; private set; }
private bool _showPossibilities;
public bool ShowPossibilities
{
get { return _showPossibilities; }
set
{
_showPossibilities = value;
OnPropertyChanged();
}
}
In SudokuBoard.xaml I have:
<ItemsControl x:Name="SudokuGrid" ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource ToggleContentStyle}"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Elements is a collection of CellViewModels generated in the constructor of GameViewModel.
Now to the question: my ToggleContentStyle as defined in <UserControl.Resources>:
<Style x:Key="ToggleContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource valueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource possibilityTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
(both ContentTemplates just show other properties of a single CellViewModel in different representations)
Question 1: I have to explicitly reference DataContext in order to get to the ShowPossibilities property. If I leave it out, so that Path=ShowPossibilities, I get a UniformGrid with the ToString() representation of CellViewModel. My assumption is that that is because the style is referenced from the ItemTemplate, with it's binding set to a single CellViewModel. Is that assumption valid?
Question 2: When I omit the ElementName part, I also get the ToString() representation of CellViewModel. Now I'm really confused. Why is it needed?
Datacontext is a dependency property which is marked as inherits. That means its inherited down the visual tree.
When you bind the default place it's going to look for a source is in the datacontext.
This is the simple situation.
Say you have a window and that has datacontext set to WindowViewmodel and stick a textbox in that Window. You bind it's Text to FooText. This means the textbox goes and looks for a FooText property in that instance of WindowViewmodel.
All pretty simple so far.
Next...
You use elementname.
What that does is says go and take a look at this element. Look for a property on that. If you did that with our textbox above then it would expect a dependency property FooText on whatever you point it to.
Datacontext is a dependency property.
And when you do:
"{Binding FooProperty
This is shorthand for:
"{Binding Path=FooProperty
Where FooProperty is a property path, not =just the name of a property.
Which is maybe worth googling but means you can use "dot notation" to walk down the object graph and grab a property on an object ( on an object.... ).
Hence DataContext.Foo or Tag.Whatever ( since tag is another dependency property a control will have ).
Let's move on to some other complications.
The datcontext is inherited down the visual tree but there's a few of gotchas here. Since
some things look like they're controls but are not ( like datagridtextcolumn ). Templated things can be tricky. Itemscontrols are a kind of obvious and relevent special case.
For an itemscontrol, the datacontext of anything in each row is whichever item it's presented to from the itemssource. Usually you're binding an observablecollection of rowviewmodel to that itemssource. Hence ( kind of obviously ) a listbox or datagrid shows you the data from each rowviewmodel you gave it in each row.
If you then want to go get a property is not in that rowviewmodel you need to tell it to look somewhere else.
When you specify an element in Binding (eg ElementName=SudokuGrid), the Path has to refer to any property of that element. Because this element is a wpf control, DataContext is one of it's properties but ShowPossibilities isn't. So if you do just Path=ShowPossibilities it will not be able to find that path at all.
If you don't specify element in Binding at all then it defaults to the DataContext associated with the control. If the associated DataContext doesn't have the property ShowPossibilities it will not be able to find it.
PS: If you want to debug wpf UI to see what the DataContext is at run-time you could use utility like Snoop.

Find the nearest Ancestor with certain property

Is there a way in a Style to bind a property setter value to the nearest parent that has provided a value for that property? For example if I have the following hierarchy:
Window > Grid > GroupBox > Grid > TextBox
and I write the following Style:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Visibility" >
<Setter.Value>
<Binding Converter="{StaticResource TagToVisibilityConverter}"
RelativeSource="{RelativeSource AncestorType=Window}" Path="Tag" />
</Binding>
</Setter.Value>
</Setter>
</Style>
This will simply go to the top-level parent (Window) and fetch the Tag property. What I want it is to search for the nearest parent that has used Tag property, For instance, in the above hierarchy, if UserControl specifies a Tag and so does the GroupBox, it should fetch the value from GroupBox. I was thinking of some clever usage of AncestorLevel, but it looks it won't be that straight. Any ideas?
I think that Property Value Inheritance is what you need. According the article,
To make a property participate in value inheritance, create a custom attached property, as described in How to: Register an Attached Property. Register the property with metadata (FrameworkPropertyMetadata) and specify the "Inherits" option in the options settings within that metadata. Also make sure that the property has an established default value, because that value will now inherit.
If you want something more special, you can write your own markup extention which will use the VisualTreeHelper class to walk through the WPF Visual Tree and look up the element you need.
You could just check for the first FrameworkElement since FrameworkElement is the class that contains the Tag property.
RelativeSource="{RelativeSource AncestorType=FrameworkElement}" Path="Tag"

Exposing read-only dependency properties on a control who's values come from children controls in the ControlTemplate

I have a Control, FooControl. It needs to expose a read-only DependencyProperty called HasError. The value of this property is actually just the value from a control in FooControl's ControlTemplate.
The following code accomplishes exactly what I want, except it forces me to declare FooControl.HasError as a read-write DependencyProperty (the Binding cannot set the value otherwise.)
<ControlTemplate TargetType="FooControl">
<ChildControl HasError="{Binding HasError, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
I'm trying to get around having to do annoying stuff like using PART_'s to find the child control, attach to its HasError ValueChanged event, and copy the value. Because that's obnoxious, and I have quite a few properties like this.
My best guess is that the dependency property ChildControl.HasError defines the framework property metadata option BindsTwoWayByDefault. This means that the mode of {Binding HasError, RelativeSource={RelativeSource TemplatedParent}} is TwoWay, which does not work when the source property is read-only.
Therefore, change the binding to
{Binding HasError, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}
This overrides BindsTwoWayByDefault and the binding should work.

Trigger when class field changed

I have created a DataGridCellTemplate where I have an Image control. By default it's Source property is X. I fill DataGrid with objects of my own class (have also implemented INotifyPropertyChanged interface).
I would like to change Source property of Image control when some boolean variable changes from False to True.
Should I use a trigger? If yes, how? Or maybe it should be done in c# code?
I could make 2 image controls, bind and control their Visible property, but it's lame solution I think.
Would appreciate any help.
In your DataTemplate try the following:
<DataTemplate>
<Image Name="Image" Source="X"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding BooleanProperty}" Value="True">
<Setter Property="Source" TargetName="Image" Value="Y" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Where BooleanProperty is the property that triggers the source-shift. Note that the Image must have a name - and that should be used in the Setter-tag. In the example - I change the source from 'X' to 'Y'
Hope this helps!
You should see if a Converter will do what you're wanting. You write one in code by creating a class which implements the IValueConverter interface (MSDN has an example on their site).
You would then declare the ValueConverter as a StaticResource like the following (you'll have to declare the local namespace if you don't already have it):
<local:BoolToImageConverter x:Key="imageConverter" />
To use it, you then bind the ImageControl's Source property to the Boolean property and specify the converter in the binding. An example follows:
<Image Source={Binding Path=IsImageShown, Converter={StaticResource imageConverter}} />
One more thing to be aware of is that the converter cannot just return a string containing a URI to an image location. It should return an ImageSource such as a BitmapImage.

Resources