Find the nearest Ancestor with certain property - wpf

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"

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.

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

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>

Collapse ContentControl if Content is Collapsed

I've got a ContentControl which has a style containing a border and other visual decorations. I want these decorations to disappear when the content is collapsed, so I figured I have to set the visibility of the ContentControl to collapsed in this case. I got this style for my ContentControl decoration:
<Style x:Key="DecoratedItem1" TargetType="{x:Type c:DecoratedItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:DecoratedItem}">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="2">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/file.png"/>
<ContentPresenter Name="wContent"/>
</StackPanel>
</Border>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger.Setters>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The DecoratedItem class is just a subclass of ContentControl with additional DependencyProperties which are not relevant to this issue, I just wanted to note that I already have a subclass to which I could add code, if necessary.
This works when the content of the ContentControl is a UIElement, however if the content is generated by a DataTemplate it complains about not being able to find the Visibility property.
<!-- works -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
<!-- doesn't work -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
<c:DecoratedItem.Resources>
<DataTemplate DataType="{x:Type clr:String}">
<TextBlock Text="{Binding}" Visibility="Collapsed"/>
</DataTemplate>
</c:DecoratedItem.Resources>
</c:DecoratedItem>
The error for the second case diplayed in the debug output window is:
System.Windows.Data Error: 40 : BindingExpression path error:
'Visibility' property not found on 'object' ''String' (HashCode=-885832486)'.
BindingExpression:Path=Content.Visibility;
DataItem='ContentPresenter' (Name='wContent');
target element is 'DecoratedItem' (Name='');
target property is 'NoTarget' (type 'Object')
I understand why this happens, but don't know how to fix my style to work as I want it. I don't mind adding helper code to the DecoratedItem subclass if necessary. Any idea how to fix this?
[edit 1]
Some more explanation in regard to the proposed answer:
I can't enforce that the Content is always an UIElement. This is a model-view design after all, and of course I simplified the example a lot. In the real project the content is a model selected from the DataContext, which can be of several different types, and the DataTemplate builds a presentation for that model. Some of the DataTemplates decide (depending on model-state) that there is nothing to present and switch Visibility to Collapsed. I would like to propagate that information to the decorating container. The example above really just presents the problem and not the motivation, sorry.
[edit 2]
Not sure how knowing more about the model would help the problem, but here we go. The data in the Content field doesn't have much in common since it can be a lot of things, this DecoratedItem is supposed to be reusable to give a common visual style to items shown on some forms. Content can be stuff like work items whose DataTemplate collapses them if they are disabled; other kinds of Content can be incomplete and get collapsed. Of course other kinds never may get collapsed.
But note that the data model doesn't really have much to do with the question, which still is how to bind against the Visibility of the expanded content element (after possibly exposing it through the subclass in a bindable way).
There are a couple of ways of describing what's wrong. In the first, working example:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
the Content property of the ContentControl is set to be a TextBlock, which is a UIElement with a Visibility property. (This assumes that you have not changed the ContentPropertyAttribute of your derived class DecoratedItem to be something other than Content). Thus, your DataTrigger binding can correctly evaluate:
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
Contrast the working case with the failing one:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
in which the Content property is set to an instance of a String, which does not have a Visibility property.
The other way to describe what's wrong is to note that, even though you supply a DataTemplate for the case of Content being a String, Content is still a String and still does not have a Visibility property. In other words, the statement that the Content is generated by the DataTemplate is incorrect -- your DataTemplate just told the control how to display Content of type String.
The general answer to your question is that, if you want the DataTrigger in DecoratedItem1 bound with a Path of Content.Visibility, you need to make sure the content you put in it is always a UIElement. Conversely, if you want to be able to put any sort of Content into the control, you need to trigger off of something else.
The specific answer to your question, strictly, relies on your broader intent (in particular, on how the Visibility of the Content of your control will be set/modified). A couple of possibilities:
if you really want your DataTrigger binding of the form, "Content.Visibility", make sure that the Content is always a UIElement. For instance, use the working form of the style and then bind the Text of TextBlock to something appropriate. However, this doesn't fit so well with the idea of your derived control as a ContentControl, so...
your DataTrigger could probably bind to something else. It seems like, from the way the question is formed, that there is some other property or code-behind that will control whether the various content entities are Visible or not.
finally, you could add an additional DataTrigger to the TextBlock. This DataTrigger would set the visibility of its parent based on its own visibility. Then, bind the DataTrigger in style DecoratedItem1 with Path "Visibility" instead of "Content.Visibility", essentially chaining together Visibilities manually.
Edit
Based on what you've described about how you want to use this, it sounds like you need to consider the visual tree. You might augment your DecoratedItem control to have the following functionality: if all its visual children that are UIElments have a visibility of Collapsed (or if it has no visual children), it is also Collapsed (or, whatever logic makes sense for the desired functionality in terms of the Visibility of its visual children). You'd need to use the VisualTreeHelper class from code -- in particular, the GetChildrenCount and GetChild methods. You'd also, in your DecoratedItem class, override OnVisualChildrenChanged (while still calling the base class method) so that you can get UIElement.IsVisibleChanged events for the visible children.

How to set TextAlignment property on a TextBox that is part of a Content of other element?

I have an element witch has TextBlock and TextBox in its Content property. I need to set TextAlignment for TextBox without interfering with the current style of the control, so I should make it work somehow with Setter Property and xaml.
Is this possible and how? I don't want to recreate Content with my own controls, or setting it through style, since style can be changed in runtime, and I want to preserve the styles as they are.
Edit: an example is DataForm. DataForm consists of several DataField elements. Each DataField consists of a Label and input control. Now, normally I could do this
<DataField Label="{Binding ...}">
<TextBox Text="{Binding ...}" TextAlignment="Right" />
</DataField>
But if I do that, I will loose the style of the DataField, which I want to avoid. So, is there a way I can access this TextAlignment property of DataField, so I can set it to Right, but not for all of them, just the ones that I want (ex numeric ones). This is why it should be done on a particular instance of DataField.
So the task: set DataField Text to be aligned right, without interfering with its style, and do this for a particular DataField.
I hope now is more clear.
What about create a specific TextBox Style?
<Style x:Key="AlignedTextBoxStyle" TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Right"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
Doing this way you can set it on xaml by the property "Style"
<TextBox Style="AlignedTextBoxStyle"></TextBox>
And if you are adding it dynamically you can also do this :
TextBox myTb = new TextBox();
myTb.Style = (Style)Application.Current.Resources["AlignedTextBoxStyle"];
I think this is the best way to do that without interfering on your "ListBoxItem" style
Hope it helps

WPF binding to another property's binding in a style

I'm not sure the best way to ask this question (sorry for the ambiguous question title), but essentially I'd like to set the MaxLength property on a TextBox using a value converter that is passed in a property from the data context, and the property on the passed-in property as the converter parameter. I'd like to do all this in a style, as opposed to on a control-by-control basis. Here's an example of doing this in a non-styled manner:
<TextBox Text="{Binding MyPropertyName.TheirPropertyName}" MaxLength="{Binding MyPropertyName, Converter={StatocRespirceMyCoolConverter}, ConverterParameter=TheirPropertyName}" />
(In case you're wondering, TheirPropertyName represents a property on the type of MyPropertyName that has an attribute like [StringMaxLength(15)], which I'd be able to get to and return inside the value converter.)
Additionally, is there any way to pass in the type of MyPropertyName as opposed to the instance? I only need the type to do the StringMaxLength attribute lookup.
Anyway, how could I go about doing something like this in a style? I've gotten as far as:
<Setter Property="MaxLength">
<Setter.Value>
<Binding Converter="{StaticResource textFieldMaxLengthConverter}" />
</Setter.Value>
</Setter>
But that passes the overall datacontext in to the value converter, as opposed to the MyPropertyName object, and I really have no clue if I can have it parse the MyPropertyName.TheirPropertyName part of the binding to pass TheirPropertyName in on the ConverterParameter attribute of the binding.
Any guidance would be really appreciated!
Ok, after some more digging, I've figured this out to my satisfaction. I'm binding to RelativeSource Self and then parsing the binding expression on the Text property (since this is a TextFieldMaxLength converter, I am presuming I'm working against a TextBox.
The styling up in the resource dictionary:
<Style TargetType="TextBox">
<Setter Property="MaxLength">
<Setter.Value>
<Binding Converter="{StaticResource textFieldMaxLengthConverter}" RelativeSource="{RelativeSource Self}" />
</Setter.Value>
</Setter>
</Style>
The usage (basically showing nothing special needs to be done since it's all in the style):
<TextBox Text="{Binding MyPropertyName.TheirPropertyName}" />
The Convert Method for the textFieldMaxLengthConverter:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Control control = value as Control;
BindingExpression be = control.GetBindingExpression(TextBox.TextProperty);
if (be != null)
{
string boundPropertyName = be.ParentBinding.Path.Path;
// .. boundPropertyName here is MyPropertyName.TheirPropertyname, do some parsing and return a value based on that
}
}
(Obviously my actual implementation is a bit more complex/handles unexpected input/uses reflection as per my original question's statement).
Anyway, thought I would post this solution in case anyone else tries to do something similar, or if there might be a better way to do this than I am using.
you can pass in lutiple properties to your converter by using a multi binding, this allows you to do a binding on as may properties as you want, and if any of the properties change (i.e. implent INotifyPropertyChanged) the binding will be reevaluated. for what you are doing you would have to use reflection to find a property on the passed in object with a particular property name that matches your converter parameter. i dont think you will end up using the code below, but it shows you can have multiple parameters to your binding in xaml. including the path, converter, converter parameter. Im not sure about the relative source but however, but i think you might need it to do what you want. have a look at debugging Data Bindings for a good way to debug. this technique is essential. i use it continually.
<Setter
Property="MaxLength">
<Setter.Value>
<Binding
Converter="{StaticResource textFieldMaxLengthConverter}"
RelativeSource="{RelativeSource TemplatedParent}"
Path="MyPropertyName"
ConverterParameter="TheirPropertyName" />
</Setter.Value>
</Setter>

Resources