Showing posts with label Converter. Show all posts
Showing posts with label Converter. Show all posts

Friday, February 4, 2011

WPF - Binding Converter Parameter [Including Discussion about Binding Reflector]

In this post we will be discussing how we can bind ConverterParameter used in WPF Binding. As we know that ConverterParameter is not a DependencyProperty, we can not bind it directly. We will be looking at the possible workarounds to achieve the similar results. As always, we will start with a simple window and then add necessary ingredients for this discussion. Let us create a sample Window with two text boxes for entering Gender and Name of a Student.

<Window x:Class="WpfApplication_ConverterParameterBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication_ConverterParameterBinding"
Title="Main Window" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,6,0,0" Name="textBoxGender"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="Gender" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Gender" Height="28" HorizontalAlignment="Left" Margin="12,6,0,0"
Name="labelGender" VerticalAlignment="Top" Width="58" />
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,40,0,0" Name="textBoxStudentName"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,40,0,0"
Name="labelStudentName" VerticalAlignment="Top" Width="58" />
</Grid>
</Window>

As you can see that we have used an instance of MainWindowViewModel as DataContext of the Window. MainWindowViewModel is presented as below:
namespace WpfApplication_ConverterParameterBinding
{
using System.Windows;

class MainWindowViewModel : DependencyObject
{
public static DependencyProperty GenderProperty =
DependencyProperty.Register("Gender", typeof(string),
typeof(MainWindowViewModel), new FrameworkPropertyMetadata() { AffectsRender = true });

public string Gender
{
get { return (string)GetValue(GenderProperty); }
set { SetValue(GenderProperty, value); }
}

public static DependencyProperty StudentNameProperty =
DependencyProperty.Register("StudentName", typeof(string),
typeof(MainWindowViewModel), new FrameworkPropertyMetadata() { AffectsRender = true });

public string StudentName
{
get { return (string)GetValue(StudentNameProperty); }
set { SetValue(StudentNameProperty, value); }
}
}
}

When we run this application, it appears as follows:



Now we want to display a Welcome message to the Student based on the information entered. If it is a Male student (Gender = M), the message should be read as "Welcome Mr. NAME OF STUDENT". If it is a female student (Gender = F), it should read as "Welcome Ms. NAME OF STUDENT".


In order to update the Window, let us add a TextBlock at the bottom as follows:

<Window x:Class="WpfApplication_ConverterParameterBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication_ConverterParameterBinding"
Title="Main Window" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,6,0,0" Name="textBoxGender"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="Gender" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Gender" Height="28" HorizontalAlignment="Left" Margin="12,6,0,0"
Name="labelGender" VerticalAlignment="Top" Width="58" />
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,40,0,0" Name="textBoxStudentName"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,40,0,0"
Name="labelStudentName" VerticalAlignment="Top" Width="58" />
<TextBlock Height="164" HorizontalAlignment="Left" Margin="14,135,0,0" Name="textBlockMessage"
Text="Hello Mr. Muhammad" VerticalAlignment="Top" Width="479" />
<Label Content="Message" Height="29" HorizontalAlignment="Left" Margin="11,102,0,0"
Name="label1" VerticalAlignment="Top" Width="129" />
</Grid>
</Window>

The text is hardcode "Hello Mr. Muhammad. We want it to be based on the Data entered. First let us update the Binding that it just shows "Hello NAME OF STUDENT" message in the notification box.

<TextBlock Height="164" HorizontalAlignment="Left" Margin="14,135,0,0" Name="textBlockMessage"
VerticalAlignment="Top" Width="479" >
<TextBlock.Text>
<Binding Path="StudentName" StringFormat="Hello {0}" />
</TextBlock.Text>
</TextBlock>

Now when we run the application, it shows a welcome message to the student.


Since we want to show the salutation (Mr. / Ms.) with name so we need to somehow use Gender. if it is M, we should show "Welcome Mr. NAME OF STUDENT". On the other hand if it is F, we should show "Hello Ms. NAME OF STUDENT".
Let us define an IValueConverter for this purpose:

namespace WpfApplication_ConverterParameterBinding
{
using System;
using System.Windows.Data;

class MessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string salutation = (parameter == null || parameter == "M") ? "Mr." : "Ms";

return string.Format("Hello {0} {1}", salutation, value);
}

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

Basically we are expecting that, somehow, we would be able to pass Gender as parameter of Converter. Let us instantiate the Converter in the Resources section of the window.

<Window.Resources>
<local:MessageConverter x:Key="messageConverter" />
</Window.Resources>

This is used for Binding the converter as follows:

<TextBlock.Text>
<Binding Path="StudentName" Converter="{StaticResource messageConverter}" ConverterParameter="{Binding Gender}" />
</TextBlock.Text>

This seems to pass Gender for ConverterParameter for Binding. But as we run the applcation, it results in an exception.



This is because of the limitation as mentioned in the beginning of this post. Basically ConverterParameter is not a dependency property so we can not bind it. Now let us discuss the alternative solutions.

Using MultiBinding:
One way to resolve the issue of Binding ConverterParameter is by using MultiBinding. Instead of Binding ConverterParameter, just have a separate binding for Target property using MultiBinding. We would be needing MultiValueConverter for this purpose. We are expecting that the following converter would be used for MultiBinding (implements IMultiValueConverter) of Gender and Name property. If the values of none of these are available then return empty string. If any of these is available then greet the person [Hello Mr. NAME_OF_PERSON for Gender = M and Hello Ms. NAME_OF_PERSON for Gender = F]

namespace WpfApplication_ConverterParameterBinding
{
using System;
using System.Windows.Data;

class MessageMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string name = (values[1] == null) ? string.Empty : values[1].ToString();
string gender = (values[0] == null) ? string.Empty : values[0].ToString();

string salutation;

switch(gender)
{
case "M":
salutation = "Mr";
break;
case "F":
salutation = "Ms";
break;
default:
salutation = string.Empty;
break;
}

string message = string.Empty;

if (!(name == string.Empty && gender == string.Empty))
{
message = string.Format("Hello {0} {1}", salutation, name);
}

return message;
}

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

Let's use this converter in a view. The only update is the usage of this new converter. We are instantiating it in the Resources section of Window. We are using the same converter with text block showing Message.

<Window x:Class="WpfApplication_ConverterParameterBinding.MainWindowMultiBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication_ConverterParameterBinding"
Title="Main Window" Height="350" Width="525">
<Window.Resources>
<local:MessageMultiConverter x:Key="messageConverter" />
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,6,0,0" Name="textBoxGender"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="Gender" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Gender" Height="28" HorizontalAlignment="Left" Margin="12,6,0,0"
Name="labelGender" VerticalAlignment="Top" Width="58" />
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,40,0,0" Name="textBoxStudentName"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,40,0,0"
Name="labelStudentName" VerticalAlignment="Top" Width="58" />
<TextBlock Height="164" HorizontalAlignment="Left" Margin="14,135,0,0" Name="textBlockMessage"
VerticalAlignment="Top" Width="479" >
<TextBlock.Text>
<MultiBinding Converter="{StaticResource messageConverter}">
<Binding Path="Gender" />
<Binding Path="StudentName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Label Content="Message" Height="29" HorizontalAlignment="Left" Margin="11,102,0,0"
Name="label1" VerticalAlignment="Top" Width="129" />
</Grid>
</Window>

When we run the project, it appears as follows:



As you can see that empty message is displayed. Let's enter F for Gender and Maria for name. It would update the message as follows:



This might or might not be a requirement but let me explain what is going on. The re-evaluation of Converter is being done when either of Gender or Name of Student is updated. Since Gender is supposed to be used just as a ConverterParameter originally this re-evaluation might not be a desired behavior. It might be desired that converter is re-evaulated just when the actual intended bound property is updated. This update should just use the current value of the property specified as ConverterParameter. In order to prove this point just update the Gender to M. This would update the message as Hello Mr. Maria. This might not be desired.



Introducing a Dependency Property in Converter and Using Binding Reflector:
ConverterParameter is a property of Binding. It is provided as a parameter to Convert and ConvertBack method of Converter whenever any update in Source or Target of Binding takes place.

Let us update define a new Converter. It is an IValueConverter like our first example. It just has an additional DependencyProperty. We name this as BindableConverterParameter.

namespace WpfApplication_ConverterParameterBinding
{
using System;
using System.Windows.Data;
using System.Windows;

class EnhancedMessageConverter : DependencyObject, IValueConverter
{
public static DependencyProperty BindableConverterParameterProperty =
DependencyProperty.Register("BindableConverterParameter", typeof(string),
typeof(EnhancedMessageConverter));

public string BindableConverterParameter
{
get { return (string)GetValue(BindableConverterParameterProperty); }
set { SetValue(BindableConverterParameterProperty, value); }
}

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string gender = BindableConverterParameter ?? string.Empty;
var name = value ?? string.Empty;

string salutation;

switch (gender)
{
case "M":
salutation = "Mr";
break;
case "F":
salutation = "Ms";
break;
default:
salutation = string.Empty;
break;
}

string message = string.Empty;

if (!(name == string.Empty && gender == string.Empty))
{
message = string.Format("Hello {0} {1}", salutation, name);
}

return message;

}

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

Let's use this converter in the view.

<Window x:Class="WpfApplication_ConverterParameterBinding.MainWindowEnhanced"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication_ConverterParameterBinding"
Title="MainWindowEnhanced" Height="300" Width="300">
<Window.Resources>
<local:EnhancedMessageConverter x:Key="messageConverter"
BindableConverterParameter="{Binding Gender}" />
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,6,0,0" Name="textBoxGender"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="Gender" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Gender" Height="28" HorizontalAlignment="Left" Margin="12,6,0,0"
Name="labelGender" VerticalAlignment="Top" Width="58" />
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,40,0,0" Name="textBoxStudentName"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,40,0,0"
Name="labelStudentName" VerticalAlignment="Top" Width="58" />
<TextBlock Height="164" HorizontalAlignment="Left" Margin="14,135,0,0" Name="textBlockMessage"
VerticalAlignment="Top" Width="479" >
<TextBlock.Text>
<Binding Path="StudentName" Converter="{StaticResource messageConverter}" />
</TextBlock.Text>
</TextBlock>
<Label Content="Message" Height="29" HorizontalAlignment="Left" Margin="11,102,0,0"
Name="label1" VerticalAlignment="Top" Width="129" />
</Grid>
</Window>

Just look at the instantiation of the Converter in Window.Resources section. BindableConverterParameter in initialized to be bound to Gender property of the object. From our knowledge of Data Binding in WPF, we know that if we don't provide any Binding Source then it would be considered as DataContext.



Now keep changing the Gender and Name of Student in the view. No matter how many times we change the values entered, salutation title (Mr. / Ms.) never appears in the message text. As we see the definition of converter we know that this is only possible when BindableConverterParameter is null / Empty. Let's insert a break-point in the Convert method keep changing the data on the view.



This is as per the expectation. Basically BindableConverterParameter is always null. This is basically due to perceived over-simplification of the problem. We are binding to the DataContext in the Resources section. It must be remembered that DataContext is a DependencyProperty inherited through the Visual Tree. Since Resources section is not in the Visual Tree, it would not have the DataContext inherited resulting in no Binding at all. Because of same reason Relative Source Binding would also not work if we attempt to do it like this:

<Window.Resources>
<local:EnhancedMessageConverter x:Key="messageConverter"
BindableConverterParameter="{Binding RelativeSource={RelativeSource Self}, Path=DataContext.Gender}" />
</Window.Resources>

As we have discussed that Resources can not use DataContext property of the view. But we know that DataContext can utilize any view it desires. Let us try to Bind the BindableConverterParameter to Gender property of DataContext in such a way that Binding is defined on DataContext in reverse direction. This can be done as follows:

<Window.Resources>
<local:EnhancedMessageConverter x:Key="messageConverter" />
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel>
<local:MainWindowViewModel.Gender>
<Binding Source="{StaticResource messageConverter}" Path="BindableConverterParameter" Mode="OneWayToSource" />
</local:MainWindowViewModel.Gender>
</local:MainWindowViewModel>
</Window.DataContext>

Now let us run the application. If we enter data in the text boxes, we realize that it results in update of the message to user. The greatest thing is that updates in Gender (used as ConverterParameter) is not causing re-evaluation of message. Message is re-evaluated only when the actual bound value to the message text block (StudentName) is updated. When we run the application and enter data [Gender=F and Name = Maria], the message is displayed as follows:



Updating the Gender to M does not result in updating the display of message as follows:



As we update the name of student, the updated value of BindableConverterParameter is considered and view is updated with new message as follows:



Using Binding Reflector:
This is still not perfect solution because this is based on the assumption that view model is always instantiated by view. This is also possible that view model is instantiate separately and some other code assigns it to the DataContext property of view. Our logic would find limitation under this condition. In this case we are still instantiating the Converter in Resources section which is generally the norm.

The best solution could be to bind the Text property of Gender text box to BindableConverterParameter property of the Converter in OneWayToSource mode. But again, this is already bound to the Gender property of DataContext.

In WPF, a DependencyProperty might be the source for a number of Binding but it might be the Target of only one Binding. Although because of availability of various Binding modes, the difference between Source and Target of Binding in the real sense is really vague but Target is referred to the Property on which the binding is defined and Source is considered as the property which is actually being used for Binding to Target.

Based on the above discussion, this seems to be the end of world. But never lose hope! Let us invent a new tool. We name this tool as Binding Reflector. We will be using this to pass the updates in one dependency property to another Dependency Property. In our example we will be using it to Bind between Gender property of DataContext to BindableConverterParameter property of Converter instantiated in Resources section of Window. In order to use the DataContext, it must be an element in the Visual Tree of view. Let's define it as a FrameworkElement.

namespace WpfApplication_ConverterParameterBinding
{
using System.Windows;
using System.Windows.Data;

class BindingReflector : FrameworkElement
{
public static DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(object), typeof(BindingReflector),
new FrameworkPropertyMetadata()
{
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
PropertyChangedCallback = OnSourceChanged
});

public object Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}

public static DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(object), typeof(BindingReflector),
new FrameworkPropertyMetadata() { DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });

public object Target
{
get { return GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}

private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var reflector = (BindingReflector)d;
if (reflector.Source != reflector.Target)
{
reflector.Target = reflector.Source;
}
}
}
}

As you can see that it is a FrameworkElement with just two Dependency properties i.e. Source and Target. Source is registered with a PropertyChangedCallBack. In the callback handler, we are just copying the value of Source Property to Target property. This Binding Reflector will be used in the view. With appropriate Binding, we will be obtaining the actual desired result. Let us define a new view as follows:

<Window x:Class="WpfApplication_ConverterParameterBinding.MainWindowEnhancedReflector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication_ConverterParameterBinding"
Title="MainWindowEnhancedReflector" Height="300" Width="585">
<Window.Resources>
<local:EnhancedMessageConverter x:Key="messageConverter" />
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModelEnhanced />
</Window.DataContext>
<Grid>
<local:BindingReflector Target="{Binding Mode=OneWayToSource, Source = {StaticResource messageConverter}, Path=BindableConverterParameter}"
Source="{Binding Path=Gender, Mode=OneWay}" />
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,6,0,0" Name="textBoxGender"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="Gender" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Gender" Height="28" HorizontalAlignment="Left" Margin="12,6,0,0"
Name="labelGender" VerticalAlignment="Top" Width="58" />
<TextBox Height="30" HorizontalAlignment="Left" Margin="84,40,0,0" Name="textBoxStudentName"
VerticalAlignment="Top" Width="412" >
<TextBox.Text>
<Binding Path="StudentName" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
</TextBox.Text>
</TextBox>
<Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,40,0,0"
Name="labelStudentName" VerticalAlignment="Top" Width="58" />
<TextBlock Height="164" HorizontalAlignment="Left" Margin="14,135,0,0" Name="textBlockMessage"
VerticalAlignment="Top" Width="479" >
<TextBlock.Text>
<Binding Path="StudentName" Converter="{StaticResource messageConverter}" />
</TextBlock.Text>
</TextBlock>
<Label Content="Message" Height="29" HorizontalAlignment="Left" Margin="11,102,0,0"
Name="label1" VerticalAlignment="Top" Width="129" />
</Grid>
</Window>

Just have a look at the first child of Grid. It is the same BindingReflector that we have created. You can see that we have used specific Binding modes for Source and Target Binding. For Source, it is One Way Binding so that it could receive value updates from Source. For Target property, it is set as OneWayToSource so that value changes as assigned in SourceChanged call back could be passed to the Target.

One important point is that we have bound Target property before Source property here. This is deliberate action. Basically as we know that using a FrameworkElement like this causes the default constructor of FramworkElement to be executed. It then assigns the specified properties to the values as specified. If we specify Source before Target here then the initial values of Source will be copied to the Target but they wouldn't be assigned to the Binding defined later. But if we first define binding for Target then as we would update the value of Target it would be available in BindableConverterParameter.

Now we run the application. The behavior is similar to our previous example. It is also not causing the Binding to re-evaluate when just BindableConverterParameter is updated. It is only updated when the actual Binding Source i.e. StudentName is updated. Let's run this to see this behavior:


Now update Gender followed by Name and see the message updated as follows:



Note:
In this post, we have introduced Binding Reflector. We can also use it to bind between dependency properties of two different Resources.

Download:

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:

Saturday, August 28, 2010

WPF - Using Converter and StringFormat together for same Binding

We are so used to using Converters. Sometimes it is easier to use StringFormat then defining a converter (the whole class implementing IValueConverter or IMultiConverter. The unexpected happen when we use them together. Well it should not be unexpected because it is well documented. When we use StringFormat together with Converter then Converter is applied first then StringFormat is applied on the bound data.

http://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.stringformat.aspx

For example, if we apply like this:
<TextBlock Text="{Binding Path=CurrentDate, StringFormat='\{0:MMMM\}', Converter={StaticResource DefaultLowerCaseConverter}}"/>

It would be better if we format within the Converter. In this example, we are converting a date to string in a particular format. We are then formatting the string to lower case.

class DefaultLowerCaseConverter: System.Windows.Data.IValueConverter
{

#region IValueConverter Members

public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return ((DateTime) value).ToString("MMMM").ToLower();
}

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

#endregion
}

We can use this converter like this:
<TextBlock Text="{Binding Path=CurrentDate, Converter={StaticResource DefaultLowerCaseConverter}}"/>