wpf Problems with StringFormat - wpf

I have a TextBlock with MultiBinding in the Text property, and StringFormat to concatenate the two results with some additions.
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}" >
<Binding Path="Version" />
<Binding Path="OldVersion" StringFormat="{}'({0})'" TargetNullValue=""/>
</MultiBinding>
</TextBlock.Text>
The first StringFormat works as expected, but the second isn't applied: it returns the value without parentheses. I don't want the parentheses in the first StringFormat, because sometimes the second value is Nothing.
Thanks in advance.

Inner StringFormat will be ignored when you use MultiBinding (msdn).
When you use a MultiBinding, the StringFormat property applies only
when it is set on the MultiBinding. The value of StringFormat that is
set on any child Binding objects is ignored. The number of parameters
in a composite string format cannot exceed the number of child Binding
objects in the MultiBinding.
Instead of MultiBinding you can use following code:
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Version}" />
<TextBlock Text="{Binding OldVersion, StringFormat=({0}), TargetNullValue=''}" />
</StackPanel>
Or you can create wrapper property to OldVersion property:
public string OldVersionEx
{
get
{
if (string.IsNullOrEmpty(OldVersion))
return null;
else
return "(" + OldVersion + ")";
}
}
And binding in this case is following:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}" >
<Binding Path="Version" />
<Binding Path="OldVersionEx"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>

Related

WPF) Why does the exact same binding work in one place but not another?

So i am at a complete loss why the exact same binding works for one element but not another (on the same control, code for binding is copy and pasted).
I have made a MultiValueConverter that takes in 4 values. values[0] determines which one of the values[1-3] should be returned. (Ternary logic)
This converter works great. I use this to choose which color and image a control should have based on an enum. But, when using the same converter for tooltip to choose between string, then i get a binding error.
The weird thing is that is that when i use the same converter inside a template for choosing which string for the ToolTip, then it works! The exact same code copy and pasted.
When i bind with the ToolTip (not in a template) the value[0] is "{DependencyProperty.UnsetValue}", instead of the enum that i have binded to.
Code inside a UserControl)
<v:ColoredImage Width="20" Height="20" HorizontalAlignment="Right">
<v:ColoredImage.Color> //THIS WORKS
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="{StaticResource GreenLight}"/>
<Binding Source="{StaticResource YellowLight}"/>
<Binding Source="{StaticResource RedLight}"/>
</MultiBinding>
</v:ColoredImage.Color>
<v:ColoredImage.Image> // THIS WORKS
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="{StaticResource OkIcon}"/>
<Binding Source="{StaticResource WarningIcon}"/>
<Binding Source="{StaticResource ErrorIcon}"/>
</MultiBinding>
</v:ColoredImage.Image>
<v:ColoredImage.ToolTip>
<ToolTip> //THIS PART DOES NOT WORK
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="ParamStatus" ElementName="pn"/> <-- SAME BINDING
<Binding Source="OK"/>
<Binding Source="Not Synced"/>
<Binding Source="ERROR"/>
</MultiBinding>
</ToolTip>
</v:ColoredImage.ToolTip>
</v:ColoredImage>
Code Inside a Style and ControlTemplate (this code work, even though it is the same)
<v:ColoredImage Height="24" Width="24" Margin="65,65,0,0" VerticalAlignment="Center">
<v:ColoredImage.Color>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="{StaticResource GreenLight}"/>
<Binding Source="{StaticResource YellowLight}"/>
<Binding Source="{StaticResource RedLight}"/>
</MultiBinding>
</v:ColoredImage.Color>
<v:ColoredImage.Image>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="{StaticResource OkIcon}"/>
<Binding Source="{StaticResource UnidentifiedIcon}"/>
<Binding Source="{StaticResource ErrorIcon}"/>
</MultiBinding>
</v:ColoredImage.Image>
<v:ColoredImage.ToolTip>
<ToolTip>
<MultiBinding Converter="{StaticResource TernaryConverter}">
<Binding Path="Status" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Source="OK"/>
<Binding Source="Unidentified"/>
<Binding Source="ERROR"/>
</MultiBinding>
</ToolTip>
</v:ColoredImage.ToolTip>
</v:ColoredImage>
I could fix this by doing a style/template for my first UserControl. But i feel like i shouldnt have too, and either way i wanna know why the EXACT same code works in one place but not another. I'm completely dumbfounded.
Code for the Converter, this is not where problem occurs, but i figured someone is going to ask me to post it anyway:
public class TernaryConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int index = (int)values[0];
if (index < 0 || index > 2)
{
return values[1];
}
return values[index+1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("EnumToImageConverter can only be used OneWay.");
}
}
The reason why ElementName="pn" doesn't work in a ToolTip is that a ToolTip resides in its own element tree and there is no element named "pn" in the namescope of this tree.

Applying a (non-multi) ValueConverter the output of a MultiBinding + StringFormat

Is there a way to apply a (single, not multi) ValueConverter to the output of a MultiBinding which uses StringFormat (i.e. after the string has been formatted).
It would be the equivalent of that code, in which I used an intermediary collapsed TextBlock to do the trick :
<StackPanel>
<TextBox x:Name="textBox1">TB1</TextBox>
<TextBox x:Name="textBox2">TB2</TextBox>
<TextBlock x:Name="textBlock" Visibility="Collapsed">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}{1}">
<Binding ElementName="textBox1" Path="Text"/>
<Binding ElementName="textBox2" Path="Text"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding ElementName=textBlock,
Path=Text, Converter={StaticResource SingleValueConverter}}" />
</StackPanel>
Here is a hack that does what you want:
public static class Proxy
{
public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
"Text",
typeof(string),
typeof(Proxy),
new PropertyMetadata(string.Empty));
public static void SetText(this TextBlock element, string value)
{
element.SetValue(TextProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBlock))]
public static string GetText(this TextBlock element)
{
return (string) element.GetValue(TextProperty);
}
}
<StackPanel>
<TextBox x:Name="textBox1">TB1</TextBox>
<TextBox x:Name="textBox2">TB2</TextBox>
<TextBlock Text="{Binding Path=(local:Proxy.Text),
RelativeSource={RelativeSource Self},
Converter={StaticResource SingleValueConverter}}">
<local:Proxy.Text>
<MultiBinding StringFormat="{}{0}{1}">
<Binding ElementName="textBox1" Path="Text" />
<Binding ElementName="textBox2" Path="Text" />
</MultiBinding>
</local:Proxy.Text>
</TextBlock>
</StackPanel>
If you look at the MultiBinding.Converter Property page on MSDN, you will see that you can provide a Converter for a MultiBinding. However, it is not a normal IValueConverter, instead it requires an IMultiValueConverter. It can be used like this:
<TextBlock x:Name="textBlock" Visibility="Collapsed">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}{1}" Converter="{StaticResource Converter}"
ConverterParameter="SomeValue">
<Binding ElementName="textBox1" Path="Text"/>
<Binding ElementName="textBox2" Path="Text"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
An example of an IMultiValueConverter implementation can be found in the linked pages.

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>

Resources