WPF StringFormat on Label Content - wpf

I want to format my string binding as Amount is X where X is a property bound to a label.
I've seen many examples but the following doesn't work:
<Label Content="{Binding Path=MaxLevelofInvestment,
StringFormat='Amount is {0}'}" />
I've also tried these combinations:
StringFormat=Amount is {0}
StringFormat='Amount is {}{0}'
StringFormat='Amount is \{0\}'
I even tried changing the binding property's datatype to int, stringand double. Nothing seems to work. This is a very common use case but doesn't seem to be supported.

The reason this doesn't work is that the Label.Content property is of type Object, and Binding.StringFormat is only used when binding to a property of type String.
What is happening is:
The Binding is boxing your MaxLevelOfInvestment value and storing it the Label.Content property as a boxed decimal value.
The Label control has a template that includes a ContentPresenter.
Since ContentTemplate is not set, ContentPresenter looks for a DataTemplate defined for the Decimal type. When it finds none, it uses a default template.
The default template used by the ContentPresenter presents strings by using the label's ContentStringFormat property.
Two solutions are possible:
Use Label.ContentStringFormat instead of Binding.StringFormat, or
Use a String property such as TextBlock.Text instead of Label.Content
Here is how to use Label.ContentStringFormat:
<Label Content="{Binding Path=MaxLevelofInvestment}" ContentStringFormat="Amount is {0}" />
Here is how to use a TextBlock:
<TextBlock Text="{Binding Path=MaxLevelofInvestment, StringFormat='Amount is {0}'}" />
Note: For simplicity I omitted one detail in the above explanation: The ContentPresenter actually uses its own Template and StringFormat properties, but during loading these are automatically template-bound to the ContentTemplate and ContentStringFormat properties of the Label, so it seems as if the ContentPresenter is actually using the Label's properties.

Make a universal StringFormatConverter : IValueConverter. Pass your format string as ConverterParameter.
Label Content="{Binding Amount, Converter={...myConverter}, ConverterParameter='Amount is {0}'"
Also, make StringFormatMultiConverter : IMultiValueConverter when you need more than one object in format string, for instance, Completed {0} tasks out of {1}.

I just checked and for some reason it doesn't work with the Label, probably because it uses a ContentPresenter for the Content property internally. You can use a TextBlock instead and that will work. You could also put the TextBlock excerpt below in the content of a Label if you need to inherit styling, behaviour etc.
<TextBlock Text="{Binding Path=MaxLevelofInvestment, StringFormat='Amount is \{0\}'} />

Try using a converter....
<myconverters:MyConverter x:Key="MyConverter"/>
<Label Content="{Binding Path=MaxLevelofInvestment, Converter={StaticResource MyConverter"} />
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return String.Format("Amount is {0}", value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}

Related

Span in Contentpresenter not working properly

I have stumbled upon a small problem and I'm not sure how to avoid it or work around it and whether it's a bug or a "feature".
When rendering a span with text in it, it seems to be disconnected from the logical tree when using a content presenter to render it. It does not bubble IsMouseOver (or probably any event) and Hyperlinks inside the span also won't fire any associated code.
<ContentPresenter>
<ContentPresenter.Content>
<!--Normally this would be a binding, but it behaves the same.-->
<Span>
Test <Hyperlink Click="Hyperlink_OnClick">Testlink</Hyperlink>
</Span>
</ContentPresenter.Content>
</ContentPresenter>
Inspecting the visual tree with Snoop indeed shows that the TextBlock used to display the span does not receive IsMouseOver-Events from it's inline elements while they themselves do indeed register them correctly (when you expand the inline property and navigate to them; they just refuse to pass them on). Also when attaching a message box to the click handler, nothing happens when you click on the link.
<TextBlock Grid.Row="1">
<Span>
Test <Hyperlink Click="Hyperlink_OnClick">Testlink</Hyperlink>
</Span>
</TextBlock>
This one on the other hand works as expected. The IsMouseOver works fine and even the Link works.
The premise of my problem is, that I want to dynamically bind the text of the TextBlock to something. But I can't bind the text-property to a span directly so I'm using a content presenter which does the job (but is broken). Is this a bug or some feature/implication that I'm unaware of? And is there another way to bind a span to something to display it with working event handling & hyperlink clicks?
You could use a converter that returns a TextBlock with the Span added to its Inlines collection:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Span span = value as Span;
TextBlock textBlock = new TextBlock();
textBlock.Inlines.Add(span);
return textBlock;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML:
<ContentPresenter Content="{Binding Span, Converter={StaticResource MyConverter}}" />

Binding templated item to Converter's Value property in DataTemplate

I'm using Silverlight 4.
I have a DataTemplate defined for a DataGrid which allows me to successfully display values to my liking. I have a Rating control inside of this DataTemplate that has a Converter on the Value property like so..
<DataTemplate>
<toolkit:Rating Value="{Binding Converter={StaticResource MyConverter}" ItemCount="5" />
</DataTemplate>
When I step through the code and get into the converter, I see that the value parameter isn't the item corresponding to the row being rendered by the template but my ViewModel that is the DataContext of the DataGrid itself!
Now, if I adjust this slightly like so,
<DataTemplate>
<toolkit:Rating Value="{Binding SomeProperty Converter={StaticResource MyConverter}" ItemCount="5" />
</DataTemplate>
The value passed to MyConverter is SomeProperty of the item rendered by the DataTemplate.
Does anyone know why this might be? How can I bind to the item the template refers to instead of the DataContext of the DataGrid?
Try "{Binding ., Converter={StaticResource MyConverter}"
I figured this out.
During the MeasureOverride stage of Silverlight's DataGrid, my converter is being invoked. It feels like a bug in the DataGrid's implementation of MeasureOverride to ignore the
<DataGrid ItemsSource="{Binding MySource}"></DataGrid>
binding expression with respect to a defined DataTemplate at this stage and use the DataContext of the DataGrid which will certainly cause a typical Converter to fail.
My band-aid solution for now is to add an if statement in my converter implementation to just make sure the type of value I get is what I expect so it passes MeasureOverride.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ExpectedType)
//do things
else
//return an instance of targetType
}
Can anyone confirm if this still happens in SL5?

How Binding Converter to a control's property can access other properties of the control

I am trying to bind a value "MaxLines" to the TextBlock's Height property in WP7 app. There is a converter to the binding which is supposed to multiple the LineHeight with the MaxLines and return the expected height. What I am trying to say is I want to control the number of lines being shown in the TextBlock. How will I be able to access the TextBlock's LineHeight property from the converter.
To make this generic I did not want maintain the LineHeights separately or access them from viewModel
Check out this article, Silverlight data binding and value converters, where he explains how to Databind in Silverlight. In the example he uses a ValueConverter with parametervalue.
I think that is what you need, just bind your LineHeight to the parameter. (You can use Blend for that)
You can use the ConverterParameter:
<TextBlock x:Name="MyTextBlock" Height="{Binding ConverterParameter=Height, ElementName=MyTextBlock, Converter={StaticResource SomeConverter}}" Text="{Binding SomeLongText}" />
or pass the whole textblock:
<TextBlock x:Name="MyTextBlock" Height="{Binding Converter={StaticResource ImageFileConverter}, ElementName=DropdownImage}" Text="{Binding SomeLongText}" />
Then inside the controller:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var image = value as TextBlock;
/*do your magic here*/
}

How to get a symmetrical TwoWay binding?

While debugging a problem in a WPF application, I have noticed that the TwoWay data binding does not seem to be symmetrical. Here is an example:
<Window x:Class="ConverterTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ConverterTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:TextConverter x:Key="textConverter"/>
</Window.Resources>
<Grid>
<TextBox x:Name="txt1" Height="24" Margin="0" VerticalAlignment="Top"/>
<TextBox x:Name="txt2" Height="24" Margin="0,40,0,0" VerticalAlignment="Top"
Text="{Binding ElementName=txt1, Path=Text,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource textConverter}}"/>
</Grid>
</Window>
The converter looks like this:
public class TextConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().ToUpper();
}
public object ConvertBack(object value, System.Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().ToLower();
}
#endregion
}
When editing inside txt1, only Convert() is called; but when editing inside txt2, first ConvertBack() and then Convert() is called.
In other words, TwoWay binding seems to work like this:
When the source property is updated, the target property is updated.
When the target property is updated, first the source property is updated, and then the target property is updated again.
The MSDN documentation merely says:
Causes changes to either the source property or the target property to automatically update the other.
From that sentence, I would have expected the following behavior:
When the source property is updated, the target property is updated.
When the target property is updated, the source property is updated.
Is there any way to achieve this symmetrical behavior (purely in XAML, without C# programming)?
EDIT: I just have discovered that in WPF 3.5, TwoWay bindings work symmetrical. In WPF 4.0, however, the behavior has changed (as described above).
Well, entirely symmetrical cannot be achieved because:
When the source is updated, the control just has to show the new value, period. Nothing fancy happens.
When the target is updated, before it is written to the source, the change of value can be canceled at a ridiculous amount of places.
For example: validation.
When the source is updated, say, from code, it passed all validation (unless you forgot to check, but that's another problem). The control just displays the new value.
When the target is updated, the value is first converted back in the converter to be manipulable, then coerced, validated and then at last, the source is set.
But as you see, the value could have been modified between the target and the source, so the value is converted again, and finally displayed.
Bonus: to understand where and in which order the value can change, see Dependency Property Value Precedence.

WPF : Conditional templating of textblock

I have a bunch of textblocks in an itemscontrol... I need to know how can I underline the text in the textblock based on whether the text is available in a list in the data model..
Sounds very simple to me...but I have been googling since the past 8 hrs...
Can I use datatriggers and valueconverters for this purpose? If yes, then how can I execute the method which lies in the viewModel (the method which helps me to check whther a given a text exists in the data model list)...
Even if I go for conditional templating....how do I access the list which lies in my model (the viewmodel can fetch it...but then how do i access the viewmodel?)..
This should be a fairly easy thing to do...Am I really missing something very simple here?? :)
I am following the MVVM pattern for my application..
One way is to use a multivalueconverter which is a class that implements IMultiValueConverter. A multivalueconverter allows you to bind to several values which means that you can get a reference to both your viewmodel and the text of your TextBlock in your valueconverter.
Assuming that your viewmodel has a method called GetIsUnderlined that returns true or false indicating whether or not the text should be underlined your valueconverter can be implemented along these lines:
class UnderlineValueConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var viewmodel = values[0] as Window1ViewModel;
var text = values[1] as string;
return viewmodel.GetIsUnderlined(text) ? TextDecorations.Underline : null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
You can use this valueconverter in the following way for a TextBlock:
<Grid x:Name="grid1" >
<Grid.Resources>
<local:UnderlineValueConverter x:Key="underlineValueConverter" />
</Grid.Resources>
<TextBlock Text="Blahblah">
<TextBlock.TextDecorations>
<MultiBinding Converter="{StaticResource underlineValueConverter}">
<Binding /> <!-- Pass in the DataContext (the viewmodel) as the first parameter -->
<Binding Path="Text" RelativeSource="{RelativeSource Mode=Self}" /> <!-- Pass in the text of the TextBlock as the second parameter -->
</MultiBinding>
</TextBlock.TextDecorations>
</TextBlock>
</Grid>

Resources