WPF: Concatenating multiple fields (bindings)

by Olaf Rabbachin 12. April 2010 17:21

I'm pretty busy at present, hence I haven't had much time to write new blog entries. However, today I came about a thread in the WPF forum where the OP asks for a way to concatenate multiple fields for a binding. Now, since I (IIRC) have written about this several times in the forums (I guess everybody at one time will stumble over that - I did) and this is actually something that sure qualifies for a less-than-30-minutes-blog-entry (edit: ... or so I thought; I recently updated to BlogEngine 1.6 and the code-formatter was giving me one hell of a hard time!), I thought I'd write another quickie, so here's what I'm using as a sort of generic approach for stuff like this.

Note that, even though I only present C# code in this article, the sample solution (see the bottom for the download) contains both a project for C# and VB.

 

Edit (April 21st 2010)

Please note that I had to find out that the Converter I'm introducing here isn't even required to do the job outlined in this article. See the last paragraph (But wait!) for more information.
I'm leaving the blog-post here anyway (might as well help to teach somebody else the same lesson Embarassed). And, as of April 30th 2010, I'm again turning around 180°, stating that the Converter is useful (again, see the bottom of this post).

 

The problem

The class that provides the data for your window has i.e two fields that are to be shown in a single column of a ListBox (or ListView, GridView, you name it). The classical fields are the LastName/FirstName combos.

 

The sample data

For the remainder of the blog, I've created a simplistic class Person, that will only provide the two properties LastName and FirstName. Here it is:

   public class Person
   {
      private string _LastName;
      public string LastName
      {
         get { return _LastName; }
         set { _LastName = value; }
      }

      private string _FirstName;
      public string FirstName
      {
         get { return _FirstName; }
         set { _FirstName = value; }
      }

      public Person(string LastName, string FirstName)
      {
         this.LastName = LastName;
         this.FirstName = FirstName;
      }
   }

 

 

Possible approaches

There's basically three approaches to working out a solution:

  1. Addding another property to the class that provides the data; for our Person class, this could be a LastFirstName prop, returning this.LastName + " " + this.FirstName
  2. If you're using a database (with or without ORM), either have the SELECT return the concatenated field or, if your DB is capable of it, add a computed column (i.e. as feasible with SQL Server for ages)
  3. Adding a converter that concatenates the fields

So, what approach would win a prize for simplest, best, nicest? It depends, of course. Smile
FWIW, I tend to find generic approaches whereever possible. That having been said, I dislike the another property in my data-class approach - simply because I'll have to provide such a property for every class where I need to concatenate fields. The DB-based approach is not even close to practical either - what if I don't have control over the database (this has happened quite a few times).

Alright, the title of the article suggested it - I'll show you how to create a pretty simple converter to do the trick. But why? The converter is a stand-alone class that I can drop into any project and, from there on, use it with whatever fields I need to concatenate. It also allows me to concatenate a bunch of fields (as opposed to only two of them - think of a delimited list).

 

Enter the IMultiValueConverter interface

I was just about to paste a link to the IMultiValueConverter Interface docs on MSDN, so I looked it up and flew over it. Duh - the article contains quite a few bits that I was to describe here! Besides some error-handling (which of course should be in place!), there's really two tiny bits my converter has that the MSDN samples don't - a) I'm utilizing the parameter argument, allowing to specify the delimiter to be used when concatenating and b) my converter doesn't restrict you to just two fields. Here's the converter's code (note that I stripped off all the comments and the sample XAML - you'll find that in the sample solution (see the bottom of this post for the download-link).

   [ValueConversion(typeof(object), typeof(string))]
   public class ConcatenateFieldsMultiValueConverter : IMultiValueConverter
   {
      public object Convert(
                  object[] values, 
                  Type targetType, 
                  object parameter, 
                  System.Globalization.CultureInfo culture
               )
      {
         string strDelimiter;
         StringBuilder sb = new StringBuilder();

         if (parameter != null)
         {
            //Use the passed delimiter.
            strDelimiter = parameter.ToString();
         }
         else
         {
            //Use the default delimiter.
            strDelimiter = ", ";
         }

         //Concatenate all fields
         foreach (object value in values)
         {
            if (value != null && value.ToString().Trim().Length > 0)
            {
               if (sb.Length > 0) sb.Append(strDelimiter);
               sb.Append(value.ToString());
            }
         }

         return sb.ToString();
      }

      public object[] ConvertBack(
                  object value, 
                  Type[] targetTypes, 
                  object parameter, 
                  System.Globalization.CultureInfo culture
            )
      {
         throw new NotImplementedException("ConcatenateFieldsMultiValueConverter cannot convert back (bug)!");
      }

   }

 

Using the converter with your XAML

Let's see how to actually make use of the converter in XAML. First of all, since we reference a class within the project from XAML, we need to add the underlying namespace to the window:

<Window x:Class="CS.DemoCS"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CS"
        Title="DemoVB" Height="300" Width="300">
   (...)

From here on, we can now refer to classes in the project with the <local:[class]> syntax. The converter will furthermore need to be added as a resource. While this resource could be placed into the <[Control].Resources> section, I prefer to define my resources on the window-level, this way I only need to reference them once, so here goes:

   <Window.Resources>
      <local:ConcatenateFieldsMultiValueConverter x:Key="mvc"/>
   </Window.Resources>

From this point on, we can refer to the converter by its key mvc. Here's the complete XAML of the window you'll find in the sample solution:

<Window x:Class="CS.DemoCS"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CS"
        Title="DemoVB" Height="300" Width="300">
   <Window.Resources>
      <local:ConcatenateFieldsMultiValueConverter x:Key="mvc"/>
   </Window.Resources>
   <ListBox ItemsSource="{Binding PersonList}">
      <ListBox.ItemTemplate>
         <DataTemplate>
            <TextBlock>
               <TextBlock.Text>
                  <MultiBinding Converter="{StaticResource mvc}"
                                ConverterParameter=", ">
                     <Binding Path="LastName"/>
                     <Binding Path="FirstName"/>
                  </MultiBinding>
               </TextBlock.Text>
            </TextBlock>
         </DataTemplate>

      </ListBox.ItemTemplate>
   </ListBox>
</Window>

As you can see in the XAML above, the TextBlock I'm using as the ListBox'es DataTemplate uses a MultiBinding in order to pass the field-bindings as well as the delimiter to the converter which, in turn, will return the concatenated result.
If you wanted the ListBox to show its items as in "FirstName LastName", you'd only have to exchange the two bindings (having FirstName appear first) and change the ConverterParameter from ", " to " ".

For the sake of completeness, here's the code-behind of the window, so you can see the source of the bindings:

using System.Collections.Generic;
namespace CS
{
   public partial class DemoCS
   {

      //The source for the ListBox
      private List<Person> _lstPersons;
      public List<Person> PersonList
      {
         get { return _lstPersons; }
         set { _lstPersons = value; }
      }


      public DemoCS()
      {
         InitializeComponent();

         //Create the list of persons and add some entries for display
         _lstPersons = new List<Person>();

         _lstPersons.Add(new Person("Davolio", "Nancy"));
         _lstPersons.Add(new Person("Fuller", "Andrew"));
         _lstPersons.Add(new Person("Leverling", "Janet"));
         _lstPersons.Add(new Person("Peacock", "Margaret"));
         _lstPersons.Add(new Person("Buchanan", "Steven"));
         _lstPersons.Add(new Person("Suyama", "Michael"));
         _lstPersons.Add(new Person("King", "Robert"));
         _lstPersons.Add(new Person("Callahan", "Laura"));
         _lstPersons.Add(new Person("Dodsworth", "Anne"));

         this.DataContext = this;
      }

   }
}   

 

And here's the result of the sample window


The converter bonus

Another major advantage of the converter over any other alternative I can think of is the fact that, using a converter, we'll make use of the terrific data-binding possibilities WPF has to offer! That is, as a result, the converter's result can easily be used in scenarios where the fields that are being concatenated are changed by means of a different control on the same window. For instance, in my data-centric apps I often have windows in which I'm presenting a list of records, the selected item being the one loaded into detail-controls on the same window. Suppose we had a window with the ListBox control (containing list of Person-classes) on the left and one TextBox for each the LastName and FirstName property on the right - when the user selects an entry in the ListBox, the TextBoxes will allow to edit that entry. If you're using the converter, you'll see the data-binding magic kick in - changing i.e. the LastName in the TextBox would thus change it in the ListBox as well (provided INotifyPropertyChanged was properly implemented).

 

But wait!

Today (April 21st 2010), Richard posted a comment that was a real eye-opener. As it seems, the Converter actually isn't required at all. That is, utilizing the MultiBinding's StringFormat really makes the Converter obsolete:

<MultiBinding StringFormat="{}{0}, {1}">
   <Binding Path="LastName"/>
   <Binding Path="FirstName"/>
</MultiBinding>

Although I haven't tested this with lists that are being changed by other means, I'm pretty certain that the data-binding would work equally well for this. Another lesson learned!

Make sure you visit Lester's blog about the WPF 3.5 SP1 feature: StringFormat - wish I had come over that post earlier (again, thanks go out to Richard for mentioning this - see the comments).

Edit (April 30th 2010)

I came back to revisit this today, in an application I'm working on at present. I must admit I still feel stupid for not knowing about the StringFormat mentioned above. However, there actually still is the need for the Converter. That is, while the StringFormat does the job for the data presented in this post, it won't if your persons' names do not necessarily require users to enter a first name. In this case, the StringFormat will always print the comma (or whatever delimiter is being used) if you print your name-fields as in LastName, FirstName, even if there is no value for the FirstName field. That being said, I'm still favoring the Converter-approach (does that save my neck?). Innocent

 

The sample solution

If you still want to download the code, I’ve created a sample solution that contains everything discussed here with one project for each C# and VB.

Download: ConcatenateConverter.zip (26.30 kb)


Location: SinglePost

Tags: , , , ,

Utilities | WPF (.net)

Comments


April 19. 2010 07:51
trackback
Windows Client Developer Roundup for 4/19/2010

This is Windows Client Developer roundup #20. The Windows Client Developer Roundup aggregates information


April 19. 2010 07:51
trackback
Windows Client Developer Roundup for 4/19/2010

This is Windows Client Developer roundup #20. The Windows Client Developer Roundup aggregates information


United Kingdom Richard 
April 21. 2010 20:19
Richard
Can't you just use the StringFormat property on the MultiBinding and ignore the converter?

<TextBlock.Text>
    <MultiBinding StringFormat="{}{0}, {1}">
        <Binding Path="LastName"/>
        <Binding Path="FirstName"/>
    </MultiBinding>
</TextBlock.Text>


April 21. 2010 21:21
Olaf Rabbachin
Hey Richard,

duh! This works perfectly - thanks for posting this!

Where did you get that syntax from (I've never seen this before)!?

Cheers,
Olaf


United Kingdom Richard 
April 21. 2010 21:30
Richard
It was on Lester's WPF Blog a couple of years ago:
blogs.msdn.com/.../...p1-feature-stringformat.aspx


April 21. 2010 21:40
Olaf Rabbachin
I updated the post. Cheers, mate!

Add comment


(Will show your Gravatar icon)

  Country flag

Click to change captcha   

biuquote
  • Comment
  • Preview
Loading



About

Hi and welcome to my blog!

I'm a developer from Germany, currently focusing on .Net and WPF.

More about me ...