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.
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