Thank you Omar for turning me on to Windows Live Writer!!! I've actually managed to reformat my previous blog post and write a new blog post with code snippets using WLW and the Code Snippet plugin for WLW.

Unfortunately, the Paste from Visual Studio plugin wasn't working for me. For some reason, it Community Server doesn't like the PRE and SPAN tags that the plugin used and as a result doesn't format the text properly.

Now I'm actually not dreading the formatting involved in writing another blog post. Still another one to come this week, and it's going to be "the one"... :)

 

There is a very simple requirement of most software that works with multiple forms, which is that there should only be one instance of a form that represents an instance of an object. For example, given a list of Customers, a user should only be able to open one instance of a Customer Form to edit/view a certain instance of a Customer. Once the form is open, selecting the attempting to view the same Customer should simply activate that same form. A new Customer Form will only be created if the existing instance of the form is closed and the Customer is selected again. This will mean that there is a 1 – 0..1 relationship between Customer and Customer Form.

Implementing this is quite simple. One way is to keep a list of open forms and the objects that they represent in a well known place and to only open a new form if one does not already exist in the list. Keeping references to the actual forms, however, will mean that failing to properly clean up the reference will leave the form in memory. This is where Weak References come in to play.

Weak References

Weak References allow the garbage collector to collect an object while still allowing the application to access the object. Weak references are useful for object that use a lot of memory, but can easily be recreated if they are collected by garbage collection - like forms. For more information on weak references, start at MSDN’s Using Weak References and the WeakReference class library.

Window Manager

To accomplish this scenario, I’ve come up with my own class I call Window Manager. Window manager is a generic object that utilizes the singleton pattern to provide all parts of an application access to the forms that represent instances of objects.

The first step to implementing the window manager is to create a generic class that implements the singleton pattern and stores a Dictionary of objects and forms: 

public class WindowManager<T>
{
    private Dictionary<T, WeakReference> m_WindowList;
    private static WindowManager m_WindowManager;
 
    private WindowManager()
    {
        m_WindowList= new Dictionary<T, WeakReference>();
    }
 
    public static WindowManager Instance
    {
        get
        {
            if (m_WindowManager == null)
            {
                m_WindowManager = new WindowManager();
            }
            return m_WindowManager;
        }
    }
}

As you can see, instead of holding references to the forms themselves, Window Manager holds weak references. Next we need to add methods to add and retrieve form references, so we add the following methods to the class:
public void Add(T businessObject, Form objectForm)
{
    // Subscribe to the form's FormClosing event so we can later remove it from the list.
    objectForm.FormClosing += new FormClosingEventHandler(objectForm_FormClosing);
    // Store a reference to the object and a weak reference to the form in the list.
    WeakReference wr = new WeakReference(objectForm, false);
    wr.Target = objectForm;
    m_WindowList.Add(businessObject, wr);
}
 
public Form this[T businessObject]
{
    get
    {
        Form returnValue = null;
        if (m_WindowList.ContainsKey(businessObject))
        {
            WeakReference weakRef = m_WindowList[businessObject];
            // Check the referenced form is still alive and return a strong reference to it.
            // If the reference is not alive then remove it from the list.
            if (weakRef.IsAlive)
            {
                returnValue = m_WindowList[businessObject].Target as Form;
            }
            else
            {
                m_WindowList.Remove(businessObject);
            }
        }
        return returnValue;
    }
}

Now, we need to ensure that when a form is closed, it is removed from this list so we implement the event handler for the FormClosing event that is created in the Add method.

void objectForm_FormClosing(T sender, FormClosingEventArgs e)
{
    Form window = sender as Form;
    if (window != null)
    {
        T businessObject = null;
        // Find the object that is the key for the form that is closing.
        foreach (KeyValuePair<T, WeakReference> pair in m_WindowList)
        {
            if (pair.Value.Target == window)
            {
                businessObject = pair.Key;
                break;
            }
        }
        // Remove the object from the collection.
        if (businessObject != null)
        {
            m_WindowList.Remove(businessObject);
        }
    }
}

Finally, to start using WindowManager<T> you just have to reference it like in the following code:

private void OpenCustomer(Customer customer)
{
    // Get the instance of the manager and see if it already has this customer.
    WindowManager<Customer> manager = WindowManager<Customer>.Instance;
    CustomerForm customerForm = manager[customer];
    if (customerForm == null)
    {
        // The customer does not have a form related to it, so create one.
        customerForm = new CustomerForm();
        manager.Add(customer, customerForm);
        customerForm.Customer = customer;
        customerForm.MdiParent = this.MdiParent;
        customerForm.Show();
    }
    else
    {
        // Activate the form returned.
        customerForm.Activate();
    }
}

That's it! Now we have a reusable window manager that uses weak references to manage forms in an MDI workspace. The window manager can be used in any part of the application to make sure an object is not already being viewed in another form.

While writing at the amount of code required to check a user's privileges, I've often thought that there must be an easier way. What I've always loathed is reading/writing code like this:

if (user.IsInRole(@"SOMEDOMAIN\SomeSG"))
{
   txtName.Enabled = true;
   txtPhone.Enabled = true;
   txtAddressLine1.Enabled = true;
   // ...
}
else
{
   txtName.Enabled = false;
   txtPhone.Enabled = false;
   txtAddressLine1.Enabled = false;
   //...
}

Of course you could have the amount by setting the Enabled property to false by default, but that's still not good enough.

While chatting with Mitch Denny the other day he passed on to me the idea of using an Extender Provider to let the developer specify the group at design time.

So, what is an Extender Provider?

Well, that's what MSDN is for... http://msdn2.microsoft.com/en-us/library/ms171836.aspx

In short, it adds properties to a control in the Visual Studio designer at design time. And to use MSDN's example, when a ToolTip component is added to a form it provides a property called ToolTip to each control on that form.

How do we accomplish the behaviour of the above ugly code with an Extender Provider?

1. Create a Component and implement IExtenderProvider. The IExtenderProvider interface provides the CanExtend method. This allows the VS designer at runtime to determine whether a control can use the provider.

public class PermissionExtenderProvider : Component, IExtenderProvider
{
    public PermissionExtenderProvider(IContainer parent)
    {
       parent.Add(this);
    }
   
    #region IExtenderProvider Members
    bool IExtenderProvider.CanExtend(object extendee)
    {
       if (extendee is Control)
          return true;
       else
          return false;
    }
    #endregion
}


2. Add a ProviderProperty attribute to the class. This tells the designer the name of the property to add to the list.

[ProvideProperty("EnabledRole", typeof(Control))]
public class PermissionExtenderProvider : Component, IExtenderProvider
{
    // ...
}


3. Create a list of controls to manage the values that have been assigned to them. This is required because there is one provider that determines what value the property will have for each control, which means that this provider needs to keep the role assigned to each control.

private Dictionary<Control, string> m_ControlRoles = new Dictionary<Control, string>();


4. Create Get & Set methods for the new property. Once again, because one instance of the provider manages all the controls that use it, the get/set methods need to accept the control as a parameter. The get method should be decorated with the Description and Category attributes. Using the Description attribute will mean the designer will display a message describing the property. Using the Category attribute will mean the designer will place the property under a certain category.

[Description("The role required by the user for the control to be enabled")]
[Category("Behavior")]
public string GetEnabledRole(Control control)
{
   if (m_ControlPermissions.ContainsKey(control))
      return m_ControlRoles[control];
   else
      return string.Empty;
}
public void SetEnabledRole(Control control, string value)
{
   ControlPermission permission;
   if (m_ControlRoles.ContainsKey(control))
      m_ControlRoles[control] = value;
   else
      m_ControlRoles.Add(control, value);
}


5. Implement ISupportInitialize to set the the enabled property depending on whether the user is in the required role. Using this interface means that the settings won't take effect until the form's InitializeComponents method calls the provider's EndInit method.

#region ISupportInitialize Members
void ISupportInitialize.BeginInit() { }void ISupportInitialize.EndInit()
{
   if (!DesignMode)
   {
      foreach (KeyValuePair<Control, string> role in m_ControlRoles)
      {
         Control control = role.Key;
         if (string.IsNullOrEmpty(role.Value))
         {
            control.Enabled = true;
         }
         else
         {
            WindowsIdentity user = System.Security.Principal.WindowsIdentity.GetCurrent();
            if (user.IsInRole(role.Value))
               control.Enabled = true;
            else
               control.Enabled = false;
         }
      }
   }
}
#endregion

Now, after adding the component to your toolbar you can just drag it on to your form and set role required for multiple controls to be enabled at design time.

This solution is also easy to customise if you want to use a GenericPricipal instead of a WindowsIdentity. By implementing a credential provider, you can replace the call to WindowsIdentity.GetCurrent() with something like Provider.GetCurrentUserPrincipal().

Being ex-"dash trash" myself (of the t-, i- and v- varieties), I can really appreciate this one.

Thanks to Chris Burke for pointing me to this...

http://ninemsn.video.msn.com/v/en-au/v.htm?g=79d24aca-16d3-4c08-9de1-70341b3f49fa&f=auifilm&fg=copy

I’ve been part of Readify for almost a month now and one thing I have noticed is that the size of your blog does matter. So, as of today I am am undergoing a new endeavour to keep my blog as active as possible.

This week I am committing myself to writing at least 3 blog posts (not including this one) on technical topics that I encounter. I already have a few ideas, but I’m giving myself the week to make them concrete.

In the meantime, I’m going to go a little off topic and give a minor update on My New Computer.

<gadgetGeek><![CData[

I’ve replaced my E6320 chip with an E6420, which was what I initially wanted. My computer is now on the same WEI with no overclocking. Now I can actually put my computer to sleep and wake it as I please. :)

What did I do with the 6320, you may ask…? I built my dad a new computer. He ended up with almost exactly the same setup as my initial one, but with a lesser video card…

Motherboard: Gigabyte 965P-DS3P v3.3

Chip:                 Intel E6320

Video Card:      HIS Radeon X1650Pro 256MB

RAM:                2GB Corsair XMS2 DDR2 800MHz

HDD:                250GB SATA2 Samsung

Case:                CoolerMaster with 430W PSU

And the WEI…

Dad's WEI - 4.4

Nothing spectacular, but a great business pc that I’m sure he will enjoy for the next few years. Now I just have to provide daily Vista/Office12 training…

]]></gadgetGeek>