Tuesday, January 11, 2011

WPF - StringFormat when View's Non-String Properties use Binding

StringFormat was a useful addition to WPF Binding system. It provided fast and easy means of formatting the bound properties. There are still some issues with using StringFormat. I have discussed one issue and its workaround here:

StringFormat avoiding Current UI culture by default:

http://shujaatsiddiqi.blogspot.com/2010/08/wpf-localization-stringformat-for.html

In this post, we will be discussing the issue with StringFormat when bound with a property which is not a String type property. We will be binding the properties of our DataContext to the view and see different alternatives for formatting the data in shown in the Label control. You can get an idea of the issues from this image.



The view model has properties Id and Name which we will be binding to the view. To supports change notification, it is implementing INotifyPropertyChanged. The definition of the view model is as follows:

class StudentViewModel : INotifyPropertyChanged
{
private string _name;
private int _id;

public StudentViewModel()
{
Name = "Muhammad";
Id = 1;
}

public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}

public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}

#region INotifyPropertyChanged Implementation

public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

#endregion INotifyPropertyChanged Implementation
}

In the constructor, we are assigning some definite values to Id and Name properties of the view model so that we don't have to worry about creating a Model.

The view using the above view model is as follows. This is the exact definition of image you have seen above. We have used various ways to format the string bound with the Content property of Label.

<Window x:Class="WpfApplication_MultiBinding_Object.StudentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication_MultiBinding_Object"
Title="Student View" Height="384" Width="300">
<Window.Resources>
<local:StudentConverter x:Key="studentConverter" />
<local:StudentSingleBindingConverter x:Key="studentSingleBindingConverter" />
</Window.Resources>
<Window.DataContext>
<local:StudentViewModel />
</Window.DataContext>
<Grid>
<!--Single Binding with StringFormat-->
<Label Background="BlanchedAlmond"
Height="34" HorizontalAlignment="Left" Margin="13,18,0,0" Name="labelBoundStringFormat"
VerticalAlignment="Top" Width="246" >
<Label.Content>
<Binding Path="Name" StringFormat="test: {0}" />
</Label.Content>
</Label>
<!--Single Binding with ContentStringFormat-->
<Label Height="34" HorizontalAlignment="Left" Margin="12,58,0,0" Background="BlanchedAlmond"
Name="labelBoundContentStringFormat" VerticalAlignment="Top" Width="246"
ContentStringFormat="test: {0}" >
<Label.Content>
<Binding Path="Name" />
</Label.Content>
</Label>
<!--Single Binding with Converter-->
<Label Background="BlanchedAlmond"
Height="34" HorizontalAlignment="Left"
Margin="14,96,0,0" Name="label3" VerticalAlignment="Top" Width="246" >
<Label.Content>
<Binding Path="Name" Converter="{StaticResource studentSingleBindingConverter}" />
</Label.Content>
</Label>
<!--MultiBinding with StringFormat-->
<Label Background="BlanchedAlmond"
Height="34" HorizontalAlignment="Left" Margin="12,136,0,0" Name="label1"
VerticalAlignment="Top" Width="246" >
<Label.Content>
<MultiBinding StringFormat="{}{0} : {1}">
<Binding Path="Id" />
<Binding Path="Name" />
</MultiBinding>
</Label.Content>
</Label>
<!--MultiBinding with StringFormat and ContentStringFormat-->
<Label Background="BlanchedAlmond" Height="34" HorizontalAlignment="Left" Margin="12,174,0,0"
Name="label2" VerticalAlignment="Top" Width="246" ContentStringFormat="{}{0} : {1}">
<Label.Content>
<MultiBinding StringFormat="{}{0} : {1}">
<Binding Path="Id" />
<Binding Path="Name" />
</MultiBinding>
</Label.Content>
</Label>
<!--MultiBinding with Converter-->
<Label Height="33" HorizontalAlignment="Left" Margin="12,214,0,0" Background="Turquoise"
Name="labelBoundUsingConverter" VerticalAlignment="Top" Width="250" >
<Label.Content>
<MultiBinding Converter="{StaticResource studentConverter}">
<Binding Path="Id" />
<Binding Path="Name" />
</MultiBinding>
</Label.Content>
</Label>
<!--TextBlock MultiBinding with StringFormat-->
<TextBlock Height="33" HorizontalAlignment="Left" Margin="13,253,0,0" Name="textBlock1"
VerticalAlignment="Top" Width="247" Background="Azure" >
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} : {1}">
<Binding Path="Id" />
<Binding Path="Name" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!--<Button Content="Button" Height="31" HorizontalAlignment="Left" Margin="11,296,0,0" Name="button1" VerticalAlignment="Top" Width="122" Click="button1_Click" />-->
</Grid>
</Window>

You can see that StringFormat has not fulfilled its responsibility of formatting the bound data. As a workaround we can use ContentStringFormat property of Label control and we can specify the format. This works perfectly fine but it never works with MultiBinding. If you see the image, you can verify that StringFormat is working perfectly fine when bound with Text property of TextBlock (property of type string).

The code behind of the above view is as follows:

public partial class StudentView : Window
{
public StudentView()
{
InitializeComponent();
}
}

As an alternate, you can see that the data is correctly formatted when converters are used. The definition of the XAML above has used two such converters. IMultiValueConverter is used for MultiBinding. The definition of IMultiValueConverterbased converter is as follows:

class StudentConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
string Id = values[0].ToString();
string Name = (string) values[1];
return string.Format("{0} : {1}", Id, Name);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

The definition of IValueConverter based converter used in the XAML definition of the view is as follows:

class StudentSingleBindingConverter : IValueConverter
{

public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return string.Format("test: {0}", (string) value);
}

public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

As you can see that it is just formatting the data in the same fashion as StringFormat is supposed to work but hasn't worked.

There is one more example you can look at for the same issue. In this example, I have to use a Converter to bind to CommandParameter when it is being Multiple bound with values in the form.

http://shujaatsiddiqi.blogspot.com/2011/01/updatesourcetrigger-explicit-for-mvvm.html

Download:

No comments: