Showing posts with label Generics. Show all posts
Showing posts with label Generics. Show all posts

Sunday, July 18, 2010

Avoid Duplicate Detail Forms in MDI programs

In a MDI Windows Forms/CRUD application, I don't want to display the same Detail form more than once at the same time. What if the user saves the address on one form and the email on another? Even if you can trust the user to do the right thing, it just feels icky to have more than 1 form for "John Smith" up at the same time.

It is less dangerous to have duplicate list forms, but I still don't like to gunk up the application with extra forms hanging around.

This code is a modernization of some code I used a couple of years ago. I am using Generics and Extension methods to reduce the amount of code I used in the old .NET 2.0 code!

using System.Windows.Forms;
namespace AvoidDupeForms
{
    /// <summary>
    /// A Detail Form is a form that can display a single record.
    /// In this example, there can me more than 1 of these loaded
    /// at once, as long as a record is only loaded once.
    /// </summary>
    public interface IDetailForm
    {
        /// <summary>
        /// Get the Id loaded in this form
        /// </summary>
        int Id { get; }
        /// <summary>
        /// Set the id for the form to load
        /// </summary>
        /// <param name="id">Id # to be loaded</param>
        void SetId(int id);
    }
    public static class FormsUtil
    {
        /// <summary>
        /// Reveal a form, load a new one if one doesn't yet exist 
        /// in the context of this MDI 
        /// </summary>
        /// <typeparam name="T">The type of the form to be revealed</typeparam>
        /// <param name="thisForm">
        /// The calling form (used to get MdiParent to be searched)
        /// </param>
        /// <example>
        /// <![CDATA[
        /// // As an extention method
        /// this.RevealForm<ListForm>();
        /// // The oldfashioned way
        /// FormsUtil.RevealForm<ListForm>(this);
        /// ]]>
        /// </example>
        public static void RevealForm<T>(this Form thisForm) 
            where T : Form, new()
        {
            Form MdiParent = thisForm.GetMdiParent();
            if (MdiParent != null)
            {
                foreach (Form frm in MdiParent.MdiChildren)
                {
                    if (frm.GetType() == typeof(T))
                    {
                        frm.BringToFront();
                        return;
                    }
                }
                T flf = new T();
                flf.MdiParent = MdiParent;
                flf.Show();
            }
        }
        /// <summary>
        /// Reveal a form with a given detailId, load a new one if 
        /// one doesn't yet exist in the context of this MDI 
        /// NOTE: > 1 instance of T can exist, but only
        /// 1 fore each IDetailForm.Id
        /// </summary>
        /// <typeparam name="T">The type of the form to be revealed</typeparam>
        /// <param name="thisForm">
        /// The calling form (used to get MdiParent to be searched)
        /// </param>
        /// <param name="detailId">The ID of the Detail record to be displayed</param>
        /// <example>
        /// <![CDATA[
        /// // As an extention method
        /// this.RevealDetailForm<DetailForm>(42);
        /// // The oldfashioned way
        /// FormsUtil.RevealDetailForm<DetailForm>(this, 42);
        /// ]]>
        /// </example>
        public static void RevealDetailForm<T>(this Form thisForm, int detailId) 
            where T : Form, IDetailForm, new()
        {
            Form MdiParent = thisForm.GetMdiParent();
            if (MdiParent != null)
            {
                foreach (Form frm in MdiParent.MdiChildren)
                {
                    if (frm.GetType() == typeof(T))
                    {
                        T saFrm = (T)frm;
                        if (saFrm.Id == detailId)
                        {
                            saFrm.BringToFront();
                            return;
                        }
                    }
                }
                T modify = new T();
                modify.MdiParent = MdiParent;
                modify.SetId(detailId);
                modify.Show();
            }
        }
        /// <summary>
        /// Gets the MdiParent for a given form
        /// (it could be self)
        /// </summary>
        /// <param name="thisForm">
        /// The calling form (used to get MdiParent to be searched)
        /// </param>
        /// <returns>
        /// The MdiParent of thisForm, or null if thisForm has no parent
        /// </returns>
        /// <example>
        /// <![CDATA[
        /// // As an extention method
        /// Form MdiParent = thisForm.getMdiContainer();
        /// // The oldfashioned way
        /// Form MdiParent = FormsUtil.getMdiContainer(thisForm);
        /// ]]>
        /// </example>
        public static Form GetMdiParent(this Form thisForm)
        {
            if (thisForm.IsMdiContainer)
                return thisForm;
            if (thisForm.IsMdiChild)
                return thisForm.MdiParent;
            return null;
        }
    }
}

Sunday, December 13, 2009

Nullable Data Types Demo

The Problem

In several of the projects I’ve worked on, we used some token value to represent null for non-nullable types. These values would be extreme values (for int, we would use int.MinValue or int.MaxValue), or null token. Recently I’ve come across some controls that don’t like this scheme and prefer using nullable types (like int?).

The original code was written in C# 1.X, so nullable types didn’t exist; the code works, so I don’t want to convert the non-nullables to nullable and risk breaking the rest of the application.

In production, I’ve solved the problem by creating “shadow” nullable properties. The code in these properties would translate the null tokens to null. For the most part the translation code is inline.

Taking it to 11 with Generics

This weekend, I played with implementing this stuff with a generic helper class. So the code for a nullable property would look something like this:

public DateTime? DobNullable
{
     get { return NullableHelper.GetNullable<DateTime>(this.Dob); }
     set { this.Dob = NullableHelper.SetNullable<DateTime>(value); }
}

To do this I created a class called NullableHelper that contained translation functions: The to set you use this:

public static T SetNullable<T>(T? futureValue)
     where T : struct
{
     T rVal;
     if (futureValue != null)
          rVal = (T)futureValue;
     else
          rVal = NullForT<T>();
     return rVal;
}

This function echo back the value passed in UNLESS it is null; if it is null, it returns the null token. And to get, you use this:

public static T? GetNullable<T>(T presentValue)
     where T : struct, IComparable
{
     T? rVal = null;

     // I can't get the generic do == & !=, this works, so I'm 
     // going with it for now.
     IComparable ic = presentValue as IComparable;
     if (ic != null)
     {
          T CompareValue = NullForT<T>();
          if (ic.CompareTo(CompareValue) != 0)
               rVal = presentValue;
          // I already set rVal to null above.
          return rVal;
     }
     else
     {
         throw new ApplicationException(
             string.Format("NullableDataTypesDemo.NullableHelper.GetNullable<T>()" +
                  "\r\n'{0}' is can't be cast to IComparable",
                  typeof(T).ToString())
          );
     }
}

In this function I needed to cast T as IComparable to compare the value with the null token.

Both functions use a null token from the following function:

public static T NullForT<T>() 
     where T : struct
{
     T rVal;
     // Here you will need to have an if for each of the 
     // supported types, this function could be really big in production:
     if (typeof(T) == typeof(DateTime))
     {
          DateTime x = DateTime.MaxValue;
          object o = x;
          rVal = (T)o;
     }
     // Set other null tokens here
     else
     {
          throw new ApplicationException(
                 string.Format("NullableDataTypesDemo.NullableHelper.NullForT<T>()" +
                 "\r\n'{0}' is an unsupported Type", 
                 typeof(T).ToString())
          );
     }
     return rVal;
}

This function is probably the Achilles’ heel of this design. You will need an if (typeof(T) block for each type you want to support in this function. Most of the complexity points of this solution are borne by this function.

Conclusion

Over the weekend I came up with a cool way of translating from a non-nullable with null tokens to nullables. Will I implement this in production? Probably not, the code works and I don’t want to risk breaking it. If I need to do similar things in a future project, I may come back to this post.

The Whole Experiment

using System;

namespace NullableDataTypesDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create an instance of the demo entity
            DemoEntityClass x1 = new DemoEntityClass(
                1, 
                100m,
                "Jill Stephens", 
                NullableHelper.NullForT<DateTime>(), 
                NullableHelper.NullForT<int>());

            // Do some querying
            if (x1.IdNullable != null)
            {
                Console.WriteLine(string.Format("ID = {0}", x1.IdNullable));
            }
            if (x1.NumOfChildren == NullableHelper.NullForT<int>())
            {
                Console.WriteLine("Children Unknown (classic)");
            }
            // Set a DOB
            x1.DobNullable = new DateTime(1776, 7, 4);
            Console.WriteLine(string.Format("{0:d}", x1.DobNullable));
            // Set # of children and display
            x1.NumOfChildrenNullable = 2;
            Console.WriteLine("value of Number of Children: {0}", x1.NumOfChildren);
            // Unset # of children
            x1.NumOfChildren = NullableHelper.NullForT<int>();
            if (x1.NumOfChildrenNullable == null)
            {
                Console.WriteLine("Children Unknown (new)");
            }
            // Won't comile, 
            // NullableHelper.NullForT<DemoEntityClass>();
            // Some NullForT that won't compile
            // (you can fix these by adding code to NullForT())
            try
            {
                NullableHelper.NullForT<DemoStruct>();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            try
            {
                NullableHelper.NullForT<double>();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            // Force read line, so this won't go away in VS
            Console.ReadLine();
        }

    }
    /// <summary>
    /// My Demo Entity Class
    /// </summary>
    class DemoEntityClass
    {
        public DemoEntityClass(int id, decimal amount, string name, DateTime dob, 
            int numOfChildren)
        {
            this.Id = id;
            this.Amount = amount;
            this.Name = name;
            this.Dob = dob;
            this.NumOfChildren = numOfChildren;
        }
        public int Id { get; set; }
        public decimal Amount { get; set; }
        public string Name { get; set; }
        public DateTime Dob { get; set; }
        public int NumOfChildren { get; set; }

        public int? IdNullable
        {
            get { return NullableHelper.GetNullable<int>(this.Id); }
            set { this.Id = NullableHelper.SetNullable<int>(value); }
        }
        public decimal? AmountNullable
        {
            get { return NullableHelper.GetNullable<decimal>(this.Amount); }
            set { this.Amount = NullableHelper.SetNullable<decimal>(value); }
        }
        // string is nullable, so I am including this for completeness
        public string NameNullable
        {
            get { return this.Name; }
            set { this.Name = (string)value; }
        }
        public DateTime? DobNullable
        {
            get { return NullableHelper.GetNullable<DateTime>(this.Dob); }
            set { this.Dob = NullableHelper.SetNullable<DateTime>(value); }
        }
        public int? NumOfChildrenNullable
        {
            get { return NullableHelper.GetNullable<int>(this.NumOfChildren); }
            set { this.NumOfChildren = NullableHelper.SetNullable<int>(value); }
        }
    }
    /// <summary>
    /// My Demo struct
    /// </summary>
    struct DemoStruct
    {
        public int x;
        public int y;
        public int xy { get { return x * y; } }
    }
    static class NullableHelper
    {
        /// <summary>
        /// Gets the Null marker value for value types
        /// </summary>
        /// <remarks>
        /// In some old applications that date back to before nullable types
        /// maked fields as being null with extreme values (int.MaxValue for
        /// example).
        /// </remarks>
        /// <typeparam name="T">Tye value type being tested</typeparam>
        /// <returns>The Null marker value</returns>
        public static T NullForT<T>() 
            where T : struct
        {
            T rVal;
            // Here you will need to have an if for each of the 
            // supported types, this function could be really big in production:
            if (typeof(T) == typeof(int))
            {
                int x = int.MaxValue;
                object o = x;
                rVal = (T)o;
            }
            else if (typeof(T) == typeof(decimal))
            {
                decimal x = decimal.MaxValue;
                object o = x;
                rVal = (T)o;
            }
            else if (typeof(T) == typeof(string))
            {
                string x = string.Empty;
                object o = x;
                rVal = (T)o;
            }
            else if (typeof(T) == typeof(DateTime))
            {
                DateTime x = DateTime.MaxValue;
                object o = x;
                rVal = (T)o;
            }
            else
            {
                throw new ApplicationException(
                    string.Format("NullableDataTypesDemo.NullableHelper.NullForT<T>()" +
                      "\r\n'{0}' is an unsupported Type", 
                    typeof(T).ToString())
                );
            }
            return rVal;
        }
        /// <summary>
        /// Used in a set block to set a non-nullable
        /// field from a nullable value
        /// </summary>
        /// <remarks>
        /// Since the null values are gotten from NullForT, this function can be short
        /// </remarks>
        /// <see cref="NullForT"/>
        /// <typeparam name="T">Tye value type being set</typeparam>
        /// <param name="futureValue">nullable new value</param>
        /// <returns>non nullable value  with Null marker value to denote null</returns>
        public static T SetNullable<T>(T? futureValue)
            where T : struct
        {
            T rVal;
            if (futureValue != null)
                rVal = (T)futureValue;
            else
                rVal = NullForT<T>();
            return rVal;
        }
        /// <summary>
        /// Used in a get block to get a nullable field
        /// from the non-nullable value
        /// </summary>
        /// <remarks>
        /// This function is a little more complex because we need to cast
        /// presentValue to IComparable to compare it to the null token 
        /// </remarks>
        /// <see cref="NullForT"/>
        /// <typeparam name="T">Tye value type being set</typeparam>
        /// <param name="presentValue">
        /// non-nullable value with Null marker value to denote null
        /// </param>
        /// <returns>nullable value returned</returns>
        public static T? GetNullable<T>(T presentValue)
            where T : struct, IComparable
        {
            T? rVal = null;

            // I can't get the generic do == & !=, this works, so I'm 
            // going with it for now.
            IComparable ic = presentValue as IComparable;
            if (ic != null)
            {
                T CompareValue = NullForT<T>();
                if (ic.CompareTo(CompareValue) != 0)
                    rVal = presentValue;
                // I already set rVal to null above.
            }
            else
            {
               throw new ApplicationException(
                 string.Format("NullableDataTypesDemo.NullableHelper.GetNullable<T>()" +
                      "\r\n'{0}' is can't be cast to IComparable",
                 typeof(T).ToString())
               );
            }
            return rVal;
        }
    }
}