WPF binding to another property's binding in a style - wpf

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>

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.

Setting dependency properties on a static value converter from XAML

I have a value converter I wrote that allows me to bind against a property, test that property against a given (hard-coded) value, and return a brush based on if the test was true or false. The converter inherits from DependencyObject and implements IValueConverter. It exposes two dependency properties called PositiveBrush and NegativeBrush.
I declare it in XAML like this:
<UserControl.Resources>
<xyz:CBrushConverter x:Key="BrushConverter"
PositiveBrush="{DynamicResource Glyph.Resource.Brush.LightGreen}"
NegativeBrush="{DynamicResource Glyph.Resource.Brush.DarkGray}" />
</UserControl.Resources>
I can then adjust the color of a given element like this:
<TextBlock Foreground="{Binding SomeProperty, ConverterParameter='SomeValue', Converter={StaticResource BrushConverter}}" />
So in this example (making the assumption that SomeProperty returns a string) if the bound property 'SomeProperty' matches 'SomeValue' the converter will return the PositiveBrush as the Foreground (otherwise it will return the NegativeBrush).
So far so good - There may be other ways to skin this cat; but this has served me well for a long time and I don't really want to rock the boat.
What I would like to do however is declare my Positive and Negative brushes as part of my binding expression. Right now, if I wanted to use Red/Green and Blue/Yellow color combinations, I would need to declare two BrushConverters. But if I could declare the Positive/Negative brushes as part of the binding expression, I could use the same converter.
In pseudo-code, something like this (obviously this doesn't work):
<Grid Foreground="{Binding SomeProperty, ConverterParameter='SomeValue', Converter={StaticResource BrushConverter, BrushConverter.PositiveBrush='Red', BrushConverter.NegativeBrush='Green'}}" />
I did find a similar question on stack, How can I set a dependency property on a static resource? but it didn't explicitly address my question.
So... my google-foo is weak - I wasn't able to come up with the right search terms to dissect the Xaml binding syntax and work this out on my own, if it is even possible.
As always, any help is appreciated!
This should work:
<TextBlock>
<TextBlock.Foreground>
<Binding Path="SomeProperty" ConverterParameter="SomeValue">
<Binding.Converter>
<xyz:CBrushConverter PositiveBrush="Red" NegativeBrush="Green"/>
</Binding.Converter>
</Binding>
</TextBlock.Foreground>
</TextBlock>
Note however that you don't use the converter as static resource here. You would create a new converter instance for each Binding.
But if I could declare the Positive/Negative brushes as part of the binding expression, I could use the same converter.
You can't really do this. Converter is just a property of the Binding class. You still need to create an instance of the converter and set the dependency properties of this particular instance. What if you have several bindings that uses the same converter instance with different values for the PositiveBrush and NegativeBrush properties simultaneously?
You could define a converter instance inline though:
<TextBlock>
<TextBlock.Foreground>
<Binding Path="SomeProperty" ConverterParameter="SomeValue">
<Binding.Converter>
<xyz:CBrushConverter PositiveBrush="Green" NegativeBrush="Red" />
</Binding.Converter>
</Binding>
</TextBlock.Foreground>
</TextBlock>

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"

Trigger multicondition DataTrigger not working?

I'm stuck on something and hope an easy answer. First, I have a theme that has a multibinding trigger.
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource MyConverter}" >
<Binding Path=".TemplatedParent" RelativeSource="{RelativeSource Self}" />
<Binding Path="IsEnabled" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="{StaticResource NewBackgroundColor}" />
<Setter Property="BorderBrush" Value="{StaticResource NewBorderBrushColor}" />
</DataTrigger>
My data controls textboxes, comboboxes have the INotifyPropertyChanged implemented.
During the processing, under certain conditions, I want to raise the event that "IsEnabled" changed, but not actually changing its value. So, the multibinding never gets triggered. Since the first parameter to the converter is the control object itself, it never "changes", so it's just there to ride-along as a parameter to the converter to work with.
The only way to actually fire the trigger is to do something like
MyControl.IsEnabled = false;
MyControl.IsEnabled = true;
Is there some other way to force triggering a multi-binding data trigger?
Try to elaborate a little bit more on my issue. I have subclassed basic controls (textbox, combobox, buttons, etc) to add certain settings / functionality, etc. For commonality to them, they all support "IMyCommonInterface" interface. Without having to redefine the entire "Theme" (in example, simple textbox), I want to conditionally change colors like doing a data validation, but more than just simple color changing of these sample properties.
Since the basic textbox does not have any idea of the "IMyCommonInterface", nor the custom properties, I have created a converter class "MyConverter" that takes the actual control object as the first parameter. Now, within the coverter, I can do
if( values[0] is IMyCommonInterface )
I can then typecast to the interface and check all for ANY special condition I want without having to explicitly create say.. a dozen triggers each based on A+B or A+C or ( A+B NOT C) OR D, etc.
So, I didn't want to have a bunch of different themes, a bunch of triggers, etc, just a centralized element to work with. I'm looking into other alternatives, but if I ran into an instance that just throwing a PropertyChanged event doesn't force the data trigger either, I'd rather find out now while still trying to understand all the (expletive) hooks that .Net has, and that you can basically point to almost ANYTHING.
this is just an idea...
Since you already subclassed your basic controls. Why not add there yet another property. You can add a bool DependencyProperty (let's call it "IsTriggered") as well as method that toggles that property
public void ReEvaluateTrigger()
{
IsTriggered = !IsTriggered;
}
public static readonly DependencyProperty IsTriggeredProperty =
DependencyProperty.Register("IsTriggered", typeof(bool), typeof(ButtonEx), new FrameworkPropertyMetadata(false));
public bool IsTriggered
{
get { return (bool)GetValue(IsTriggeredProperty); }
set { SetValue(IsTriggeredProperty, value); }
}
then at a necessary time, under whatever your conditions may be all you would have to do in your code is call myControl.
MyControl.ReEvaluateTrigger()
which should cause the trigger to reevaluate:
...taken from your code with a change to binding to IsTriggered
<Binding Path="IsTriggered" RelativeSource="{RelativeSource Self}" />
I put this in my quick test, just to see if IsEnabled would toggle (I know it's simple, but it's just a quick test):
<Button Content="push to trigger enabled below" Click="Button_Click"/>
<Sample:ButtonEx x:Name="ButtonToBeTriggered" IsEnabled="{Binding Path=IsTriggered, RelativeSource={RelativeSource Self}}"
Content="ButtonToBeTriggered" Width="100" Height="50" Margin="50"/>
//where in code behind
private void Button_Click(object sender, RoutedEventArgs e)
{
ButtonToBeTriggered.ReEvaluateTrigger();
}
in the test, I extended
public class ButtonEx : Button
where I created ReEvaluateTrigger() as well as the trigger...
Anyway, just an idea!

Why does my IMultiBindingConverter get an array of strings when used to set TextBox.Text?

I'm trying to use a MultiBinding with a converter where the child elements also have a converter.
The XAML looks like so:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource localizedMessageConverter}" ConverterParameter="{x:Static res:Resources.RecordsFound}" >
<Binding Converter="{StaticResource localizedMessageParameterConverter}" ConverterParameter="ALIAS" Path="Alias" Mode="OneWay" />
<Binding Converter="{StaticResource localizedMessageParameterConverter}" ConverterParameter="COUNT" Path="Count" Mode="OneWay" />
</MultiBinding>
</TextBlock.Text>
The problem I'm facing here is, whenever this is used with a TextBlock to specify the Text property, my IMultiValueConverter implementation gets an object collection of strings instead of the class returned by the IValueConverter. It seems that the ToString() method is called on the result of the inner converter and passed to the IMultiValueConverter. If used to specify the Content property of Label, all is well.
It seems to me that the framework is assuming that the return type will be string, but why? I can see this for the MultiBinding since it should yield a result that is compatible with TextBlock.Text, but why would this also be the case for the Bindings inside a MultiBinding?
If I remove the converter from the inner Binding elements, the native types are returned. In my case string and int.
Probably the targetType parameter of your localizedMessageParameterConverter converter is System.String. This is because the target type of the Bindings is inherited from the MultiBinding, and the targetType of the MultiBinding is System.String because TextBlock.Text is a string property.
See the following article for a similar problem: Multi-Value Converters, Value Converters and the Case of the Wrong Target Type
According to Microsoft Connect, this has been fixed in WPF 4.0. See: Microsoft Connect
The above article also explains a workaround.

Resources