Control inheritance spoiled by Form designer.

P

PAul Maskens

The form designer adds unnecessary code to the section when using a
subclassed control.
I've reproduced this in VS.NET 2002 and VS.NET 2003 so it's pretty
fundamental.

Outline steps:
Create a VB project.
Create a subclass of a UI control, I used TextBox.
Add no code to the subclass.
Public Class Component1
Inherits System.Windows.Forms.TextBox
#Region " Component Designer generated code "
#End Region
End Class

Change the forecolour property only (Red in this example).
The designer adds code to the InitialiseComponent() method to set the new
forecolour.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
'
'Component1
'
Me.ForeColor = System.Drawing.Color.Red
End Sub

Build it.

Create a VB windows form project.
Drag/drop a baseclass textbox.
Add reference to the DLL containing the control.
Add that to the toolbox.
Drag/drop a subclassed textbox.

All appears good.
But looking at the code it's not using inheritance !!

The form designer incorrectly (IMO) adds a line of code which duplicates the
code in the subclassed control's InitialiseComponent() which totally negates
the inheritance - I find this in the form's InitialiseComponent().

'Component11
'
Me.Component11.ForeColor = System.Drawing.Color.Red

If I distribute the control subclass as a separate assembly (dll) and then
update that dll, the compiled form has code that is hardwired to set the
colour of the instance of the control.
That's not right!
Because when I distribute a new version of the control with a different
forecolour, the code in the contorl's InitialiseComponent() might just as
well not be there - because the VB form has a line of code to set the colour
to that of the version of the component used at the time the form was
edited.


I can remove the offending code from the form's InitialiseComponent() and it
has no visual effect.
The colour is still as defined in the subclassed control.
I can change the control's forecolour, rebuild the dll and the form shows
the new forecolour.
There is still no offending code in the form InitialiseComponent()

Now we enter the twilight zone ...
In the form designer, if I move _either_ of the two textboxes, the offending
line of code reappears.


So, is this a known bug?
I don't want to have to put code in the subclassed control's Paint() method
for example, because that gets called far too often.
 
P

PAul Maskens

I've now reproduced this in a C# form too, using the same VB subclass of
textbox, so it's pretty fundamental!

There is no reason as far as I can see that the form should have a copy of
the code from the subclasses control's InitialiseComponent() in the form's
InitialiseComponent() - because that overrides any change you may make in
the control class.
 
A

Armin Zingler

PAul Maskens said:
The form designer adds unnecessary code to the section when using
a subclassed control.
I've reproduced this in VS.NET 2002 and VS.NET 2003 so it's pretty
fundamental.
[...]

It is not an inheritance issue. The designer is not interested in the
inheritance hierarchy. The reason why it creates the code is that the
property value (red) is not equal to default value of the property.

See also (didn't read the whole article):
http://msdn.microsoft.com/library/en-us/dndotnet/html/custcodegen.asp

quote: "...If the current value of a property matches that default value,
code is not generated."

--
Armin

How to quote and why:
http://www.plig.net/nnq/nquote.html
http://www.netmeister.org/news/learn2quote.html
 
P

PAul Maskens

It is not an inheritance issue.
Becauase it prevents us using inheritance to define visual characteristics,
and redefine in a new release of the assembly containing the subclass, it is
an inheritance issue.
The designer is not interested in the inheritance hierarchy.
Exactly, so the designer is breaking inheritcance.
The reason why it creates the code is that the property value (red)
is not equal to default value of the property.
What were they smoking when they designed that, then?
quote: "...If the current value of a property matches that default value,
code is not generated."
So the question then is, how do I define the new value in my subclass as the
new default?

It's not a show stopper, but I've been using OO languages for ten years and
this is unbelievable.
 
A

Armin Zingler

PAul Maskens said:
Becauase it prevents us using inheritance to define visual
characteristics, and redefine in a new release of the assembly
containing the subclass, it is an inheritance issue.

Exactly, so the designer is breaking inheritcance.

The designer creates code. Neither it changes your controls nor derives new
controls from yours, consequently it is impossible that it brakes
inheritance.
What were they smoking when they designed that, then?

Why? How can the designer know that the constructor of your control already
sets the value? It would have to analyse the source code or your control.
Not possible. Or, it would have to create the following code:

if not c.forecolor.equals(color.red) then
c.forecolor = color.red
end if

Wouldn't make sense, too, as we both know.
So the question then is, how do I define the new value in my subclass
as the new default?

As mentioned in the linked article, there's the DefaultValue attribute:

<System.ComponentModel.DefaultValue(GetType(Color), "Red")> _
Public Overloads Property ForeColor() As Color
Get
Return MyBase.ForeColor
End Get
Set(ByVal Value As Color)
MyBase.ForeColor = Value
End Set
End Property

Seems to work.

It's not a show stopper, but I've been using OO languages for ten
years and this is unbelievable.

IMO it is not related to OO and especially inheritance at all.
 
P

PAul Maskens

Thanks for sticking with me on this, Armin.

<System.ComponentModel.DefaultValue(GetType(Color), "Red")> _
Public Overloads Property ForeColor() As Color
Get
Return MyBase.ForeColor
End Get
Set(ByVal Value As Color)
MyBase.ForeColor = Value
End Set
End Property

Ouch !!
That's incredibly complex!


I was hoping this would work:

Function ShouldSerializeForeColor() As Boolean
ShouldSerializeForeColor = false
End Function

Like the C# example in the article.

Quote:
But what if the default value is not a simple one or this needs to be done more dynamically? This is often the case for values that are inherited from other components such as a parent Control. The dynamic case can be handled by adding a method of a certain signature. Using the example above, the code would look like this:

private bool ShouldSerializeText()
{
return Text != "OK";
}
By adding a method of the name ShouldSerialize<Property Name>, the code generation engine can ask the component if it would like the value to be serialized.


I can't get that to work in my simple test.
So I'm doing something wrong.
 
A

Armin Zingler

PAul Maskens said:
Thanks for sticking with me on this, Armin.

<System.ComponentModel.DefaultValue(GetType(Color), "Red")> _
Public Overloads Property ForeColor() As Color

This should have been

public OVERRIDES property....
Get
Return MyBase.ForeColor
End Get
Set(ByVal Value As Color)
MyBase.ForeColor = Value
End Set
End Property

Ouch !!
That's incredibly complex!

Well, attaching an attribute is not complex, but you can not attach an
attribute to a derived member, so you have to override it to get a procedure
to attach an attribute to.
I was hoping this would work:

Function ShouldSerializeForeColor() As Boolean
ShouldSerializeForeColor = false
End Function

Like the C# example in the article.

Quote:
But what if the default value is not a simple one or this needs to be
done more dynamically? This is often the case for values that are
inherited from other components such as a parent Control. The dynamic
case can be handled by adding a method of a certain signature. Using
the example above, the code would look like this:

private bool ShouldSerializeText()
{
return Text != "OK";
}
By adding a method of the name ShouldSerialize<Property Name>, the
code generation engine can ask the component if it would like the
value to be serialized.

I learn many new things in this thread. :) This works for me:

Private Function ShouldSerializeForeColor() As Boolean
Return (Not Me.ForeColor.Equals(Color.Red))
End Function

But, _in addition_ you have to override the ForeColor property:

Public Overrides Property ForeColor() As Color
Get
Return MyBase.ForeColor
End Get
Set(ByVal Value As Color)
MyBase.ForeColor = Value
End Set
End Property


Why? The ForeColor property is part of the TextBoxBase class. The designer
looks at the members of the same type that contains the property to be
serialized to find the ShouldSerialize* function. It does find it, but it's
derived from the Control class. So, the designer calls
Control.ShouldSerializeForeColor. To change this behavior, you have to
override the property. Consequently the designer looks for
ShouldSerializeForeColor in your derived class.
 
P

PAul Maskens

if not c.forecolor.equals(color.red) then
c.forecolor = color.red
end if
Why does the designer effectively do just that?
True, in this case it's analysing the value of the property.

What it does is if the current colour of my control subclass is not the
default colour of the parentclass, it writes a line of code into the form's
InitializeComponent().
Logically it's doing something like this (if parentclass was a property of a
control subclass) in the designer.

if not c.corefolor.equals(c.parentclass.forecolor) then
' write following code to the form InitializeComponent()
me.c.foreColor = color.green
end if

It would have to analyse the source code or your control.
In this case it's analysing the property values.
 
A

Armin Zingler

PAul Maskens said:
Why does the designer effectively do just that?

It does *not* create this code. I wrote "it would have to create...".
True, in this case it's analysing the value of the property.

What it does is if the current colour of my control subclass is not
the default colour of the parentclass, it writes a line of code into
the form's InitializeComponent().
Logically it's doing something like this (if parentclass was a
property of a control subclass) in the designer.

if not c.corefolor.equals(c.parentclass.forecolor) then
' write following code to the form InitializeComponent()
me.c.foreColor = color.green
end if

What is a corefolor? ;-)) Anyway, I currently can not follow you. I wanted
to say that the designer compares the forecolor to the color specified in
the DefaultValueAttribute. If not equal => code is generated.
In this case it's analysing the property values.

I'm not sure if I understand you. What I'm trying to say is that the
designer does not know if you already set Forecolor = Red within the
constructor. Consequently the designer must generate the code that you don't
want to be created.
 
P

PAul Maskens

Do you have any idea why the ShouldSerialize<property>() method isn't
working?
Can you see any mistake in what I tried in VB?

Function ShouldSerializeForeColor() As Boolean
ShouldSerializeForeColor = false
End Function


I am trying to use this approach. Because it talks exactly about what we are
doing, with a value inherited from a parent control.
Quote:
But what if the default value is not a simple one or this needs to be done
more dynamically? This is often the case for values that are inherited from
other components such as a parent Control. The dynamic case can be handled
by adding a method of a certain signature. Using the example above, the code
would look like this:

private bool ShouldSerializeText()
{
return Text != "OK";
}
By adding a method of the name ShouldSerialize<Property Name>, the code
generation engine can ask the component if it would like the value to be
serialized.
 
A

Armin Zingler

PAul Maskens said:
Do you have any idea why the ShouldSerialize<property>() method
isn't working?
Can you see any mistake in what I tried in VB?

It works for me. See my answer to your other message.
Function ShouldSerializeForeColor() As Boolean
ShouldSerializeForeColor = false
End Function

I would not always set it to false. If you change the Color to Green within
the designer, the setting won't be stored.
 
P

PAul Maskens

That's fine.
We don't want colour changes to be made in the designer.
We are using an ObjectMode proeprty which says what kind of control it is
(in our application) and that drives the setting of appropriate colours
depending on control and application state.
Yes, we're ignoring windows standards, which I don't think is such a good
thing.
But it emulates an existing application's UI.

We planned to do this in a root class layer, inheriting directly from the
base controls, so that we could apply comany-wide standards there. Then
lower layers can add application specializations if necessary.
Designer support for this is critical, so we can reissue the libraries
separately from the application.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top