AJ's blog

November 24, 2006

Phantom property values

Filed under: .NET, .NET Framework, ASP.NET, C#, Design Time, Software Development — ajdotnet @ 11:40 pm

Here’s a quick tip for design time coders: Suppose you had a control or component and you would want to change a design time property from your code (say as reaction to a designer verb or the change of another property). Something like the following:

[ToolboxData(“<{0}:PropertyTextBox runat=server />”)]
public class PropertyTextBox : TextBox
{
    public string StandardCss { … }
    public string ErrorCss { … }
    
    bool _noCssHandling;
    public bool NoCssHandling
    {
        get { return _noCssHandling; }
        set
        {
            _noCssHandling = value;
            if (_noCssHandling)
            {
                _standardCss = “”;
                _errorCss = “”;
            }
        }
    }
}

Looks good? OK. Put it on your page, switch to design view, go to the property dialog, and change the two string members. Now switch to code view and you”ll see the respective attributes in the contol tag:

<cc1:PropertyTextBox ID=”PropertyTextBox1″ runat=”server” ErrorCss=”TextBoxError1″ StandardCss=”TextBox1″/>

Looks still good? OK. Switch to design view, change the property NoCssHandling to true, and enjoy the fact that the other two properties are miraculously set to empty.

Looks still good? Still content? Well, just for the fun of it switch to code view…

<cc1:PropertyTextBox ID=”PropertyTextBox1″ runat=”server” ErrorCss=”TextBoxError1″ NoCssHandling=”True” StandardCss=”TextBox1″/>

Wait a moment! Empty strings are not supposed to contain old values, right? You could even debug the code and verify that the dependend properties have been set and returned the correct values. And yet, within the markup code of your .aspx file the repective HTML attributes remain unchanged.

Catching the phantom

The reason for this effect is … I may have mentioned that before😉 … design time is different. At runtime you have only one instance of your object. At design time there may be phantoms, ghosts and astral bodies – different incarnations of the same object, shadows that reflect different time spans. There is but one file, but what you see is just one of possibly many presentations of that file in the appearance in which the current designer presents it. This may be the .aspx markup view, the design surface, the property dialog, the code view, the component view.

If one designer triggers a change on your object, none of the other designers knows about it. And thus, none will reflect that change, leaving the different designers in an inconsistent state. The trick is to announce the change to the other designers and the means to do that is the IComponentChangeService interface. (This is something you would only want to happen at design time to avoid unexpected behaviour at runtime.)

BTW: This is called Document/View concept, a derivation of the Model/View/Controller pattern and well known from MFC. No fairies or other surreal creatures. No leprechauns either, what a pity.)

Here is a version that takes the design time requirements into account: 

public bool NoCssHandling
{
    get { return _noCssHandling; }
    set
    {
        _noCssHandling = value;
        if (_noCssHandling)
        {
            if ((this.Site != null) && (this.Site.DesignMode))
            {
                SetValue(this, GetPropertyDescriptor(this, “StandardCss”), “”);
                SetValue(this, GetPropertyDescriptor(this, “ErrorCss”), “”);
            }
            else
            {
                _standardCss = “”;
                _errorCss = “”;
            }
        }
    }
}

static PropertyDescriptor GetPropertyDescriptor(IComponent component, string property)
{
    PropertyDescriptorCollection properties =
        TypeDescriptor.GetProperties(component.GetType());
    foreach (PropertyDescriptor pd in properties)
    {
        if (pd.Name == property)
        return pd;
    }
    return null;
}

static void SetValue(IComponent component, PropertyDescriptor pd, object value)
{
    object oldValue = null;
    IComponentChangeService componentChangeService = (IComponentChangeService)
        component.Site.GetService(typeof(IComponentChangeService));
    
    if (componentChangeService != null)
    {
        try
        {
            // rememeber old value
            oldValue = pd.GetValue(component);
            // announce before change
            componentChangeService.OnComponentChanging(component, pd);
        }
        catch (CheckoutException ex)
        {
            // under source control, the checkout may be canceled by the user
            if (ex != CheckoutException.Canceled)
                throw;
        }
    }
    
    try
    {
        // try to set new value
        pd.SetValue(component, value);
    }
    catch
    {
        value = oldValue;
        throw;
    }
    finally
    {
        // announce after change
        if (componentChangeService != null)
            componentChangeService.OnComponentChanged(component, pd, oldValue, value);
    }
}

Well, that’s fairly much code just to set a property value. And to announce it to the design time environment. And to take source control and other effects into account. And it’s not even property specific? Wow. Reusable and a good candidate for a helper class. Not much code if you really think about it.

From now on, your property value changes at design time will be properly advertised to all designers and they will happily reflect those changed values.

That’s all for now folks,
AJ.NET

1 Comment »

  1. It took me nearly 3 hours of searching google to find this but finally you have solved my greatest problem!

    Comment by Karle — February 24, 2009 @ 6:42 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: