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: PostList

Tags: , , , ,

Utilities | WPF (.net)

WPF: WindowHelpers - how to retrieve a window-reference by the window's name and others

by Olaf Rabbachin 16. February 2010 15:56

Today I have a quickie that I thought would be worth sharing. Note that, even though I only present C# code in this article, the sample solution (see the bottom for the download) again contains both a project for C# and VB.

 

The problem

In the past 6 months, I've spent quite a while or two in the WPF forums, crawling over other peoples' questions and learning by answering them. A very common approach to answering is to simply post a sample window that i.e. demonstrates the solution to the OP's question. In the very beginning, I tended to have a sample app with a Window1 which got loaded upon startup, replacing the XAML and/or code-behind everytime I created a sample window. That meant that, everytime I tried something new, the earlier version was either lost or had to be copied into another window. Alternatively, I sometimes named windows and then changed App.xaml to show the window I wanted.

Any of the aforementioned approaches includes either a) the loss of previous work, or b) additional work everytime you add something or need to show a different (older) window. I hence thought it should be fairly easy to simply make a main window, provide a list of windows found throughout the application and just open them on a click (or double-click).

 

Reflection to the rescue

I must say I just love Reflection - it provides a convenient (well, most of all times) approach to obtain information that just wasn't possible (or only with many hacks) in pre-.Net-times!

Here goes.

 

Getting a list of all windows

Here's all you need to do in order to get all windows that are part of the executing assemlby (aka the exe):

 

public static IEnumerable<string> WindowNames
{
   get
   {
      IEnumerable<string> ieWindowNames = null;
      Assembly asm = Assembly.GetExecutingAssembly();

      ieWindowNames =
         from types in asm.GetTypes()
         where types.BaseType.Name == "Window"
         orderby types.Name
         select types.Name;

      return ieWindowNames;
   }
}

 

The above returns a list (IEnumerable) that would allow for binding and, thanks to Linq, sorted alphabetically.

 

Displaying windows using their name

How about providing a convenient way of displaying a window when all you have is its name (i.e., taken from the previously introduced list)?

Again, this can be done with just a few lines:

 

public static bool? ShowWindowByName(string strWindowName, bool fShowDialog)
{
   if (string.IsNullOrEmpty(strWindowName)) return null;

   Assembly asm = Assembly.GetExecutingAssembly();
   string strFullyQualifiedName = asm.GetName().Name + "." + strWindowName;
   object obj = asm.CreateInstance(strFullyQualifiedName);

   Window win = obj as Window;
   if (win == null) return null;

   if (fShowDialog)
   {
      win.ShowDialog();
      return win.DialogResult;
   }
   else
   {
      win.Show();
      return null;
   }
}

 

The above method also allows you to show the window modally in which case the method will return the DialogResult after the window was closed.

 

WindowByName

The ShowWindowByName() method might actually do more than desired. If you only need a reference to a window by its name, here's another little helper method:

 

public static Window WindowByName(string strWindowName)
{
   if (string.IsNullOrEmpty(strWindowName)) return null;

   Assembly asm = Assembly.GetExecutingAssembly();
   string strFullyQualifiedName = asm.GetName().Name + "." + strWindowName;
   object obj = asm.CreateInstance(strFullyQualifiedName);

   if (obj != null)
      return obj as Window;
   else
      return null;
}

 

Closing all windows

If you're using VB, one way to close all open windows when your application exits is to specify this in the project's settings:

However, C# doesn't have a respective counterpart. And, anyway, I tend to rather have my own method for dealing with this, so here's another simple helper:

public static void CloseAllWindows()
{
   Application app = Application.Current;
   for (int intCounter = app.Windows.Count - 1; intCounter >= 0; intCounter--)
      app.Windows[intCounter].Close();
}
Due to the simplicity, it's not really all too worth sharing (must say, I feeling a bit ashamed to post something like this), but I thought the method simply belongs to the class. Embarassed

 

The sample solution

I've placed all of the above (basic documentation included - skipped in the article) into a simple class that you'll find in the solution (see the bottom for the download link). However, since I don't want to provide a bunch of windows just to allow to test things out, the projects' sample window only contains - besides the main window - a dummy second window to have at least two of them. Here's a screenshot:

The code-behind for the above is pretty short:

using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls;

namespace CS
{
   public partial class Main : Window
   {
      public Main()
      {
         InitializeComponent();
         this.DataContext = this;
         this.Unloaded += new RoutedEventHandler(MainWindow_Unloaded);
      }

      //Allows to bind the ListBox to the window names in this assembly
      public IEnumerable<string> WindowNames { get { return Utils.WindowHelpers.WindowNames; } }

      //Show a window after an item in the ListBox has been double-clicked.
      private void lbWindows_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
      {
         if (lbWindows.SelectedItem != null)
         {
            Utils.WindowHelpers.ShowWindowByName(
                  lbWindows.SelectedItem.ToString(),
                  (bool)chkShowDialog.IsChecked
               );
            e.Handled = true;
         }
      }

      void MainWindow_Unloaded(object sender, RoutedEventArgs e)
      {
         Utils.WindowHelpers.CloseAllWindows();
      }
   }
}

But wait!

In the code above, you may (or may not) have noted that the selected window is shown when double-clicking a window rather than after a single click has been performed. I have no idea as to why there isn't any DoubleClick-event for the ListBox control, but luckily, it's not all too hard to provide a dummy one. I can't take credit for that as this is something I stumbled over in this StackOverflow thread, so thanks go out to Bob King. All you'll have to remember when going that way though is to set e.Handled = true to avoid the message from bubbling up.

 

The last word

I presently don't really see much "real world" usage scenarios for the class, but my forums-life has gotten a hell of a lot easier. Using the class, I can simply add windows to my test-solution as they come and then access/run them by selecting them from a list of my pre-defined main window. One problem that this might impose on you (it does on me) - the time it takes to build your solution will increase rapidly. My present solution has short of 100 windows ...

 

The sample solution

I’ve created a sample solution that contains everything discussed here.

Download: WindowByName.zip (27.38 kb)


Location: PostList

Tags: , , , , , ,

Utilities | Utilities | WPF (.net) | WPF (.net)

WPF: ColorHelper - how to retrieve the name of a given color or to retrieve a color by its name

by Olaf Rabbachin 5. February 2010 19:47

Today I stumbled over a thread in the WPF forum in which the OP was looking for a way to retrieve the (known) name for a given color.
That is, given a color such as #FFB22222 (or ARGB :: 255, 178, 32, 32), to retrieve "Firebrick". What I thought was darn simple actually isn't, because System.Windows.Media.Colors is a class and thus exposes all (known) colors as properties (as opposed to System.Drawing.KnownColor which is an enum). So there really isn't any other way than to use reflection to obtain a list of all colors. Here's a little helper class that returns the name of a color passed to it:

Retrieving the name of a given Color object

/// <summary>
/// Returns the known name of the color passed (if found), or an empty string.
/// </summary>
/// <param name="clr">The color whose name is to be returned.</param>
/// <returns>
/// The name of the passed color resp. an empty string if 
/// no matching known color could be found.
/// </returns>
public static string GetKnownColorName(Color clr)
{
   Color clrKnownColor;

   //Use reflection to get all known colors
   Type ColorType = typeof(System.Windows.Media.Colors);
   PropertyInfo[] arrPiColors = ColorType.GetProperties(BindingFlags.Public | BindingFlags.Static);

   //Iterate over all known colors, convert each to a <Color> and then compare
   //that color to the passed color.
   foreach (PropertyInfo pi in arrPiColors)
   {
      clrKnownColor = (Color)pi.GetValue(null, null);
      if (clrKnownColor == clr) return pi.Name;
   }

   return string.Empty;
}

If you call the above method with i.e. ...

string strColorName = GetKnownColorName(Color.FromArgb(255, 178, 32, 32));
MessageBox.Show(strColorName == "" ? "(None found)" : strColorName);

... the MessageBox will show "Firebrick".

 

Update to the above (Feb 13 2010)

I just updated the code for the method above after it became obvious that the GetHashCode() method is not returning unique values. I did a bit of searching in order to find out more about that, but couldn't find anything; the docs are no help at all with this respect or even when it comes to the reason for the existance of this method in the first place.

Actually, I don't have the slightest idea as to how the hashes couldn't be unique - colors resp. their ARGB values are uniqe in itself after all. However, the hashes for Colors.White and Colors.Blue are equal. Now if that isn't plain stupid! Lesson learned: never assume anything ...

Thanks to Pedro for catching this (see the comments).

 

Retrieving a list with all known colors (names + Color objects)

If you actually need a list with all color values and their name-counterparts (might make sense if you need this frequently), here's another little helper-method that will return all known colors along with the name of each color:

/// <summary>
/// Returns a list containing all known colors, each as a KeyValuePair with the name
/// as the key and the Color as the value.
/// </summary>
public static List<KeyValuePair<string, Color>> GetKnownColors()
{
   List<KeyValuePair<string, Color>> lst = new List<KeyValuePair<string, Color>>();
   Type ColorType = typeof(System.Windows.Media.Colors);
   PropertyInfo[] arrPiColors = ColorType.GetProperties(BindingFlags.Public | BindingFlags.Static);

   foreach (PropertyInfo pi in arrPiColors)
      lst.Add(new KeyValuePair<string, Color>(pi.Name, (Color)pi.GetValue(null, null)));
   return lst;
}

You could use the above in various ways, here's just one sample:

Color clr = Color.FromArgb(255, 178, 32, 32);
List> lstKnownColors = GetKnownColors();
string strColorName = (
   from c in lstKnownColors
   where
      c.Value.A == clr.A &&
      c.Value.R == clr.R &&
      c.Value.G == clr.G &&
      c.Value.B == clr.B
   select c.Key
   ).FirstOrDefault();

 

Retrieving a color via its name

Last but not least, if you need to go the opposite way, how about another simple helper method:

 

/// <summary>
/// Returns the Color which is represented by the name passed.
/// </summary>
/// <param name="strColorName">The name of the Color to retrieve.</param>
/// <returns>Nullable color - either the found Color or null.</returns>
public static Color? GetColorByName(string strColorName)
{
   Color? clrResult = null;
   try
   {
      object value = ColorConverter.ConvertFromString(strColorName);
      if (null != value) clrResult = (Color)value; 
   }
   catch (Exception) { }

   return clrResult;
}

 

Update to the above (Feb 15 2010)

I've just updated the GetColorByName method after Richard pointed out that it was prone to fail if an invalid name or an empty string were passed (see the comments). I've only resolved for a general purpose handler rather than just to catch FormatExceptions.

 

Wrapping it all up

To simplify things, I dropped the above into a class that you can simply drop into your projects.
Download it here:

C# version: ColorHelpers.cs (3.43 kb)

VB version: ColorHelpers.vb (3.48 kb)

 

Happy coding!


Location: PostList

Tags: , , , , , ,

Utilities | WPF (.net)

About

Hi and welcome to my blog!

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

More about me ...