Click here to Skip to main content
Click here to Skip to main content

Building an ObjectsToStringAdapter

, 18 Sep 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
An article on creating a class used to create a useful display of one or more objects in a listbox.

Introduction

When I found out that a ListBox is only able to select one property (through DisplayMember) from an object or use the ToString() method, I thought I would create a general class that solves the problem.

Background

When adding items to a ListBox, you usually want them displayed to the user. However, there are only two options for selecting what to display:

  • Select a public property from the item (through DisplayMember)
  • Let the ListBox call ToString() in the item

To have the item display nicely in the ListBox, we can simply implement the ToString() method on the corresponding class.

At times, we might want to use a domain object in several different GUIs and have it displayed in different ways according to the GUI. Now, we could add several properties to the domain class to have it suit the different GUIs. But it is probably already clear to most readers that amending the domain code to have the GUI display correctly is not good design.

So, what do we do? What do we do in order to avoid amending the domain model in order to display the correct information in the GUI.

Well, I implemented a ObjectsToStringAdapter class.

This class has an internal List<> to which objects can be added. When the objects have been added, you need to give a format string. After this is done, you (or a ListBox) can call ToString() and the class will return a string formatted according to your format string and the objects in the internal List<>.

So, the formatting string is of the format:

!sequence!.PropertyName, !sequence!.PropertyName ... !sequence!.PropertyName

where sequence is the (zero based) sequence of the object in the List, i.e., the sequence in which it was added to the class. An example could be:

Firstname: {0}.FirstName, Lastname: {0}.Lastname {1}.ToString {2}.ToString {0}.Email

where a Person object, two value types, or then strings has been added to the object. So, if a Person object with data from me, an int with value '31', and a string object with the value "Testing" was added to the object, the output would be:

Firstname: Klaus, Lastname: Hebsgaard 31 Testing spam@spam.com

This is the code for the class:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace Krifa.UI.Util {

    //syntax:
    //{sequence}.PropertyName, {sequence}.PropertyName ... {sequence}.PropertyName
    //Where sequence is the index in the internal!?!(the sequence
    //at which the object was added) list of the object
    //And Name is the name of the property to append.
    //Must be seperated by comma, semikolon or space
    //For instance Fornavn: {1}.Fornavn, Efternavn: {1}.Efternavn
    //Please notice:
    //For reference types one reflection is done per property
    public class ObjectsToStringAdapter {

        List<SYSTEM.OBJECT> listedObjects;
        List<SYSTEM.TYPE> typeOfListedObjects;
        List<STRING> formattedValue;
        List<STRING> propertyNames;

        private string _format = string.Empty;
        private string output = string.Empty;

        public ObjectsToStringAdapter() {
            listedObjects = new List<SYSTEM.OBJECT>();
            typeOfListedObjects = new List<TYPE>();
            formattedValue = new List<STRING>();
            propertyNames = new List<STRING>();
        }

        public List<STRING> PropertyNames {
            get {
                return propertyNames;
            }
        }

        public string Format {
            get {
                return _format;
            }
            set {
                _format = value;
            }
        }

        public void Add<T>(T objectToBeListed) {
            Debug.Assert(objectToBeListed != null);
            listedObjects.Add(objectToBeListed);
            typeOfListedObjects.Add(objectToBeListed.GetType());
        }

        public override string ToString() {
            Parse();

            return output;
        }

        public string[] ToStringArray() {
            Parse();

            return (string[])formattedValue.ToArray();
        }

        private void Parse() {
            Debug.Assert(listedObjects.Count > 0);
            Debug.Assert(typeOfListedObjects.Count > 0);

            formattedValue.Clear();
            output = Format;

            string pattern = @"{(?<objectSEQUENCE>.*)}.(?<PROPERTYNAME>.*)";

            string[] splittedFormats = Format.Split(',', ' ', ';');
            Debug.Assert(splittedFormats.Length > 0);

            foreach (string splittedFormat in splittedFormats) {
                Match parsed = Regex.Match(splittedFormat, pattern);

                if (parsed.Success) {
                    int objectSequence;
                    Int32.TryParse(parsed.Groups["objectSequence"].Value, 
                                   out objectSequence);
                    string propertyName = parsed.Groups["propertyName"].Value;
                    string replString = "!" + objectSequence.ToString() + "!." + 
                                        propertyName;
                    Debug.Assert(objectSequence <= listedObjects.Count);
                    propertyNames.Add(propertyName);
                    //for value types and strings

                    if (typeOfListedObjects[objectSequence].IsValueType || 
                            typeOfListedObjects[objectSequence].Name == "String") {
                        ParseValueTypeAndString(objectSequence, propertyName, replString);
                    }
                    //for reference types
                    else {
                        ParseReferenceType(objectSequence, propertyName, replString);
                    }
                }
            }
        }

        private void ParseReferenceType(int objectSequence, 
                     string propertyName, string replString) {
            try {
                //Get the property by using reflection
                string propValue = typeOfListedObjects[objectSequence].GetProperty(
                   propertyName).GetValue(listedObjects[objectSequence], 
                                          null).ToString();
                formattedValue.Add(propValue);
                output = output.Replace(replString, propValue);
            }
            catch (Exception) {
                try {
                    FieldInfo field = typeOfListedObjects[objectSequence].GetField(
                                      propertyName, 
                                      BindingFlags.Instance | 
                                      BindingFlags.NonPublic | BindingFlags.Public);
                    string propValue = 
                      field.GetValue(listedObjects[objectSequence]).ToString();
                    formattedValue.Add(propValue);
                    output = output.Replace(replString, propValue);
                }
                catch (Exception) {
                    FormatError(replString);
                }
            }
        }

        private void ParseValueTypeAndString(int objectSequence, 
                     string propertyName, string replString) {
            //only valid "property" on value types and strings is "ToString"
            if (propertyName == "ToString") {
                formattedValue.Add(listedObjects[objectSequence].ToString());
                output = output.Replace(replString, 
                               listedObjects[objectSequence].ToString());
            }
            else {
                FormatError(replString);
            }
        }

        private void FormatError(string replString) {
            string errMessage = "Error in parsing";
            formattedValue.Add(errMessage);
            output = output.Replace(replString, errMessage);
        }

        public System.Object this[int index] {
            get {
                return listedObjects[index];
            }
            set {
                listedObjects[index] = value;
            }
        }

    }

}

Why didn't I inherit the ListBox class and implement this functionality on the ListBox? Well, I wanted this functionality to be very general, so by making it a class of its own, it can be used for multiple purposes. The class can be extended to other uses where objects need to be formatted as strings.

I have already added a function ToStringArray, which can be used for adding the formatted code to a DataGridView.

Please note, however, that when adding to a DataGridView, you do not actually add an object, so by using this method, you need to do a lot of manual work in order to find the corresponding object.

Please also note that if you plan on adding many properties from many objects to one or more ListBoxes, you might get better performance by implementing a more specific adapter. However, when doing this, you might also add a lot more code to your project.

Using the code

Please note that when using reference types, Reflection is done per property.

Here is the code for adding objects to a ListBox:

Person per = new Person(textBox1.Text, textBox2.Text, emailTextBox.Text);
            
ObjectsToStringAdapter otsa = new ObjectsToStringAdapter();

otsa.Format = Firstname: {0}.FirstName, Lastname: {0}.Lastname 
              {1}.ToString {2}.ToString {0}.Email;

otsa.Add<PERSON>(per);
otsa.Add<double>(31.7);
otsa.Add<string>("test" + " string");

theListBox.Items.Add(otsa);

Here is the code for retrieving code from a ListBox:

ObjectsToStringAdapter otsa = (ObjectsToStringAdapter)theListBox.SelectedItem;
string dlgStr = otsa.ToString();
MessageBox.Show(dlgStr);

Of course, it is possible to use an indexer to get objects from the ObjectsToStringAdapter and then cast it back to the original type.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

khebbie2005
Web Developer
Denmark Denmark
Formerly C++ programmer now moving to .Net

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411028.1 | Last Updated 18 Sep 2006
Article Copyright 2006 by khebbie2005
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid