WPF - Converter hides dependency on a DependencyProperty - wpf

I have a TextBlock (acutally a whole bunch of TextBlocks) where I set the Text to "" if a DependencyProperty in my ViewModel is set to Visiblity.Hidden. I do this via a converter as follows:
<TextBlock Margin="0,0,5,0">
<TextBlock.Text>
<Binding Converter="{StaticResource GetVisibilityOfColumnTitles}"
Path="Name" />
</TextBlock.Text>
</TextBlock>
The converter looks like this:
public object Convert(object value, Type targetType,
object parameter,System.Globalization.CultureInfo culture)
{
if (MainMediator.Instance.VisibilityOfWorkItemColumnTitles
== Visibility.Visible)
return value;
else
return "";
}
I admit that this is a bit convoluted way to do this, but I have my reasons (DataContext complications and spacing for the TextBlock)
The problem I have is that when VisibilityOfWorkItemColumnTitles is changed, even though it is a dependency property, TextBlock.Text does not realize there is a dependency there (because it's used in the converter).
Is there a way in the code behind (preferably in the converter) to say, this TextBlock wants to update this binding when VisibilityOfWorkItemColumnTitles changes?

Since your converter depends on both the Text property from the TextBox and the VisibilityOfWorkItemColumnTitles property on your MainMediator class, you'll probably need to use a MultiBinding and include both properties back in the XAML.
<TextBlock Margin="0,0,5,0">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource GetVisibilityOfColumnTitles}">
<Binding Path="Name" />
<Binding Path="VisibilityOfWorkItemColumnTitles" Source="{x:Static my:MainMediator.Instance}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
(I've used "my" as the XML namespace for your MainMediator class in that code sample.)
Then change your converter to an IMultiValueConverter, and reference values[0] for the text and values1 for the "visibility" property. Now the binding will know if either property changes, and call off to the converter appropriately.

Related

WPF DataBinding where the Path is recovered from the object?

I have an object with several properties. Two of these are used to control the width and height of the target text box. Here is a simple example...
<DataTemplate DataType="{x:Type proj:SourceObject}">
<TextBox Width="{Binding ObjWidth}" Height="{Binding ObjHeight}"/>
</DataTemplate>
I also want to bind the Text property of the TextBox. The actual property to bind against is not fixed but instead is named in a field of the SourceObject. So ideally I would want to do this...
<DataTemplate DataType="{x:Type proj:SourceObject}">
<TextBox Width="{Binding ObjWidth}" Height="{Binding ObjHeight}"
Text="{Binding Path={Binding ObjPath}"/>
</DataTemplate>
Here the ObjPath is a string that returns a path that would be perfectly valid for the binding. But this does not work because you cannot use a binding against the Binding.Path. Any ideas how I can achieve the same thing?
For more context I will point out that the SourceObject is user customizable and hence the ObjPath can be updated over time and hence I cannot simply put a fixed path in the data template.
You could implement an IMultiValueConverter and use this one as BindingConverter for your Text Property. But then you have the problem, that the value of the Textbox is only updated if your ObjPath property changes (the path itself), not the value where the path is pointing to. If that's, okay you can go with a BindingConverter which returns the value of your binding Path using Reflection.
class BindingPathToValue : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value[0] is string && value[1] != null)
{
// value[0] is the path
// value[1] is SourceObject
// you can use reflection to get the value and return it
return value[1].GetType().GetProperty(value.ToString()).GetValue(value[1], null).ToString();
}
return null;
}
public object[] ConvertBack(object value, Type[], object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Have the converter in your Resources:
<proj:BindingPathToValue x:Key="BindingPathToValue" />
and use it in XAML:
<DataTemplate DataType="{x:Type proj:SourceObject}">
<TextBox Width="{Binding ObjWidth}" Height="{Binding ObjHeight}">
<TextBox.Text>
<MultiBinding Mode="OneWay" Converter="{StaticResource BindingPathToValue}">
<Binding Mode="OneWay" Path="ObjPath" />
<Binding Mode="OneWay" Path="." />
</MultiBinding>
</TextBox.Text>
</TextBox>
</DataTemplate>

Binding StringFormat

I have a collection of textblocks that I'm going to be showing and I'm needing the text of each textblock to be displayed differently. I'm currently saving the format string in the tag property and I'm needing to display the text in this format. How do I bind the StringFormat section?
Something like the section below:
<TextBlock Tag="{Binding MyFormatString}" Text="{Binding MyProperty, StringFormat='{}{0:MyTag}'}" />
Since BindingBase.StringFormat is not a dependency property, I do not think that you can bind it. If the formatting string varies, I'm afraid you will have to resort to something like this
<TextBlock Text="{Binding MyFormattedProperty}" />
and do the formatting in your view model. Alternatively, you could use a MultiBinding and a converter (example code untested):
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myStringFormatter}">
<Binding Path="MyProperty" />
<Binding Path="MyFormatString" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
public class StringFormatter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format((string)values[1], values[0]);
}
...
}
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0},{1}">
<Binding Path="MyProperty" />
<Binding Path="MyFormatString" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The String Formatting is a display setting and therefore should live close to the UI layer, you can either declare it in the Xaml or have formatted string properties on a ViewModel and perform the formatting in the Get of those properties and bind the TextBlock to it the ViewModel properties. It would source its data from the original datasource.

How does FallbackValue work with a MultiBinding?

I ask because it doesn't seem to work.
Assume we're binding to the following object:
public class HurrDurr
{
public string Hurr {get{return null;}}
public string Durr {get{return null;}}
}
Well, it would appear that if we used a MultiBinding against this the fallback value would be shown, right?
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} to the {1}"
FallbackValue="Not set! It works as expected!)">
<Binding Path="Hurr"/>
<Binding Path="Durr"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
However the result is, in fact, " to the ".
Even forcing the bindings to return DependencyProperty.UnsetValue doesn't work:
<TextBlock xmnlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} to the {1}"
FallbackValue="Not set! It works as expected!)">
<Binding Path="Hurr"
FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
<Binding Path="Durr"
FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Tried the same with TargetNullValue, which was also a bust all the way around.
So it appears that MultiBinding will never ever use FallbackValue. Is this true, or am I missing something?
A little more messing around and I found that a converter can return the UnsetValue I need:
class MultiValueFailConverter : IMultiValueConverter
{
public object Convert(
object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
if (values == null ||
values.Length != 2 ||
values.Any(x=>x == null))
return System.Windows.DependencyProperty.UnsetValue;
return values;
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("Too complex hurt brain.");
}
}
However, this seems like a dirty filthy hack. I'd think a scenario like this would be accounted for in the framework. I can't find anything in Reflector, however.
This is a bit of an old question, but it could use some explanation.
From the FallbackValue documentation:
A binding returns a value successfully if:
The path to the binding source resolves successfully.
The value converter, if any, is able to convert the resulting value.
The resulting value is valid for the binding target (target) property.
If 1 and 2 return DependencyProperty.UnsetValue, the target property
is set to the value of the FallbackValue, if one is available. If
there is no FallbackValue, the default value of the target property is
used.
In the example provided, the binding successfully resolves to the Hurr and Durr properties. Null is valid value for a string which means the binding is valid.
In other words, the FallbackValue is used when the binding is unable to return a value and in the example provided, the binding does provide a valid value.
Take for example each of the following snippets that are based off the original example:
Example 1
The Hurr and Durr properties are bound correctly; null is a valid value and the FallbackValue will never be seen.
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is valid. I will never be seen." StringFormat="{}{0} to the {1}">
<Binding Path="Hurr" />
<Binding Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 2
The Hurr and Durr properties are not bound correctly; the FallbackValue will be seen.
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="Binding paths are invalid. Look at me." StringFormat="{}{0} to the {1}">
<Binding Path="xHurr" />
<Binding Path="xDurr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 3
If one binding path is invalid, then the FallbackValue will be seen.
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="One binding path is invalid. Look at me." StringFormat="{}{0} to the {1}">
<Binding Path="xHurr" />
<Binding Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 4
As with previous examples, the binding is correct, so the FallbackValue will not be used. Further, the FallbackValue for each of the child Binding properties of the MultiBinding parent should refer to a FallbackValue to be used for the target property of the MultiBinding, not for the child Bindings.
<TextBlock xmlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is valid. I will never be seen." StringFormat="{}{0} to the {1}">
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" Path="Hurr" />
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 5
The binding is still valid even though a path is not provided in Binding properties since the binding will use whatever object it is bound to.
<TextBlock xmlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is still valid. I will never be seen." StringFormat="{}{0} to the {1}">
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
<Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example 6
Finally, if a converter is added to any of the Binding properties to force an UnsetValue, then the MultiBinding FallbackValue will be seen:
Converter
internal class ForceUnsetValueConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
return DependencyProperty.UnsetValue;
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
{
throw new NotImplementedException();
}
#endregion
}
XAML
<TextBlock>
<TextBlock.Text>
<MultiBinding FallbackValue="Binding is valid, but look at me. I'm an UnsetValue." StringFormat="{}{0} to the {1}">
<Binding Converter="{StaticResource ForceUnset}" Path="Hurr" />
<Binding Path="Durr" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>

Converters on child bindings in a MultiBinding

Suppose I have this MultiBinding:
<MultiBinding Converter="{StaticResource FooBarConverter}>
<Binding Path="Foo" Converter="{StaticResource FooConverter}" />
<Binding Path="Bar" Converter="{StaticResource BarConverter}" />
</MultiBinding>
This doesn't seem to work: the values array passed to FooBarConverter contains DependencyProperty.UnsetValue for each value (two, in this case). Removing the converters on the child bindings (FooConverter and BarConverter) gives me the actual values. By the way: those converters are properly invoked, it just looks like their result is discarded.
Is this intended behavior? I want to bind 2 properties by I need to convert at least one of them before throwing them into the MultiValueConverter...
The developers in Kishore's shared link came to the conclusion that to make such a MultiBinding, the child Bindings must return the same type of result as the parent MultiBinding. So, in my case, if I wanted the parent MultiBinding to return a value of type Visibility, the child Bindings must also return Visibility values. Not doing so will pass UnsetValues to your converter method, likely giving you undesireable results.
Here is a snippet of code that works for me. Note that Converters "VisibleIfTrue" and "EnumToVisibility" both return type Visibility values:
<Grid.Visibility>
<MultiBinding Converter="{StaticResource MultiVisibilityConverter}">
<Binding Path="JobHasData" Converter="{StaticResource VisibleIfTrue}" />
<Binding Path="CurrentMode" Converter="{StaticResource EnumToVisibility}" ConverterParameter="{x:Static Mode.Setup}" />
</MultiBinding>
</Grid.Visibility>
It is annoying that you can't pass it different value types to process and give you the result you want. (I initially tried to pass bools to the converter.)
Hope this helps anyone who waited the seven years for the answer. ;)
you have mention converter in the Multibinding tag like this
<TextBlock Grid.Row="3" Grid.Column="1" Padding="5">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource sumConverter}">
<Binding Path="FirstNum" />
<Binding Path="SecondNum" />
<Binding Path="ThirdNum" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
If WPF was prior to 4.0 then it is a known and fixed bug that have workaround.
Here located a sample implementation of the workaround for poor man that are forced to work with elder versions.
To say shortly, old wpf versions are trying to convert values from multibinding child bindings that have converters directly into type of target dependency property. Workaround is to create hidden label, move multibinding or its child binding converter to label.content as it expects object and then bind desired property to it.
public class DataClass
{
public string FirstName { get; set; }
public string Surname { get; set; }
}
public class NameMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return String.Format("{0} {1}", values[0], values[1]);
}
}
The XAML looks basically like this
<Window xmlns:local="clr-namespace:BlogIMultiValueConverter">
<Window.Resources>
<local:NameMultiValueConverter x:Key="NameMultiValueConverter" />
</Window.Resources>
<Grid>
<TextBox Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Path=Surname, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiValueConverter}">
<Binding Path="FirstName" />
<Binding Path="Surname" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>

Bind an element to two sources

I currently have two text boxes which accept any number. I have a text block that takes the two numbers entered and calculates the average.
I was wondering if there was a way I could bind this text block to both text boxes and utilize a custom converter to calculate the average? I currently am catching the text changed events on both text boxes and calculating the average that way, but I am under the assumption data binding would be more efficient and easier.
You're looking for MultiBinding.
Your XAML will look something like this:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myConverter}">
<Binding Path="myFirst.Value" />
<Binding Path="mySecond.Value" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
With reasonable replacements for myConverter, myFirst.Value, and mySecond.Value.
Create a converter that implements IMultiValueConverter. It might look something like this:
class AverageConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int total = 0;
int number = 0;
foreach (object o in values)
{
int i;
bool parsed = int.TryParse(o.ToString(), out i);
if (parsed)
{
total += i;
number++;
}
}
if (number == 0) return 0;
return (total/number).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
A multivalue converter receives an object array, one for each of the bindings. You can process these however you need, depending on whether you're intending it for double or int or whatever.
If the two textboxes are databound, you can use the same bindings in the multibinding for your textblock (remembering to notify when the property changes so that your average is updated), or you can get the text value by referring to the textboxes by ElementName.
<TextBox Text="{Binding Value1}" x:Name="TextBox1" />
<TextBox Text="{Binding Value2}" x:Name="TextBox2" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AverageConverter}">
<Binding ElementName="TextBox1" Path="Text" />
<Binding ElementName="TextBox2" Path="Text" />
<!-- OR -->
<!-- <Binding Path="Value1" /> -->
<!-- <Binding Path="Value2" /> -->
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Or, you could make a property in code behind, and bind the TextBlock to that ... I do that all the time, and it's a little simpler than making a converter, then doing that same code there.
Example: (in your code behind of the xaml):
public double AvgValue
{
get { return (valueA + valueB) / 2.0; }
}
And then, in your XAML:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=AvgValue}" />
That's a LOT simpler than a custom converter.
Just to add step-by-step procedure to Timothy's answer:
Setup the View.TextBlock.Text property to bind to the ViewModel.AvgValue property.
Catch the TextChanged event of the TextBox control, then set the AvgValue in the handler of that TextChanged event.
As part of that handler in step 2, make sure to raise a property change so that the TextBlock is updated.

Resources