Contents tagged with English
-
Visual Inheritance Revisited
- V...I.... What's the "V" stand for?
- Visual.
- What's the --
- Inheritance.
- Ohhhhh........ What was the "V" again?My recent article about visual inheritance generated a lot of positive feedback (at least what I would call "a lot"). In this post, I'll try to answer some the questions I received via email.
Question: How can I reproduce the problems you describe? My first quick test ran without problems...
Create a new Windows Forms application, and add two labels and a button to the form "Form1" (setting the background colors of the labels makes it easier to see what is happening later). Arrange the controls on the form as shown in the following screenshot:
Now anchor the controls:
- label1: Top, Left, Right
- button1: Top, Right
- label2: Top, Bottom, Left, Right
Compile the project, and add an inherited form ("Form2"). The form will look like this:
Set the
Text
property to "Form2". Resize the form and add a button ("button2"):Anchor the button to Top, Right, then compile again.
Be prepared for a surprise: after the project is compiled, you get something like this...
Question: What's the reason for this odd behavior?
Let's take a look at the code of the
InitializeComponent()
method of base form:private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.SuspendLayout();...code for the controls...
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 169);
this.Controls.Add(this.label2);
this.Controls.Add(this.button1);
this.Controls.Add(this.label1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}Obviously, this is generated code. When speaking of "serialization" of objects, people usually think of serializing an object to binary data or XML; in this case the object (instance of class
Form1
) is serialized to code. This code contains everything that is required to create the layout and the controls ofForm1
exactly the way it was specified in the forms designer. Every property is set exact to the required value, e.g. if a panel is docked, not only is theDock
property set, but also theLocation
andSize
. So events are neither necessary nor wanted (because of their sheer quantity when settings properties like like e.g.Size
for each control). This is why basically everything except the creation of the control instances is kept inside the linesthis.SuspendLayout();
and
this.ResumeLayout(false)
The call to
SuspendLayout()
temporarily suspends the layout logic,ResumeLayout()
turns it back on. The argumentfalse
tells the form not to handle pending layout requests, because there's no need to - everything is already laid out as it was designed. So far, everything is ok.Now, what's happening inside the derived class
Form2
?In each form class, the constructor of the class calls the method
InitializeComponent()
. This method is private, so each class has its own method. When an instance of classForm2
is created, the order of execution is- Enter Constructor
Form2()
- Enter Constructor
Form1()
- Call
InitializeComponent()
of classForm1
- Exit Constructor
Form1()
- Call
InitializeComponent()
of classForm2
In the moment the constructor of the base class is left, the layout and the controls are perfect for a
Form1
instance - but that's not what we want. The methodInitializeComponent()
of classForm2
contains the code that is necessary to achieve the desired layout by tweaking the base form and adding the second button. While setting theText
property to "Form2" is rather trivial, setting theClientSize
property causes serious trouble. The problem is that the size is set while the layout logic is suspended; i.e. the size of the form is changed, but the anchored elements of the base class are not updated.If you're interested, try the following:
- Remove class
Form2
- Save everything, close all windows
- Recompile
- Create
Form2
again (as described above), but don't recompile. - Close the designer window, open the code window
- Remove the
SuspendLayout()
andResumeLayout()
statements fromInitializeComponent()
in classForm2
. - Save and close the window
- Recompile
If you now open
Form2
in the designer, the form will be fine. But if you change anything (e.g. move the second button) and recompile, the form will be scrambled again. This is because the code ofInitializeComponent()
was generated again - including theSuspendLayout()
andResumeLayout()
statements. By the way: if you don't put a control on the derived form, the two statements are not generated, and thus the derived form is displayed as expected. This explains why some people at first couldn't reproduce the effects I described in the other article.Question: So why does docking work, but not anchoring?
- Docking tells a control to grab as much space as possible (for top/bottom/left/right: towards to the specified direction) until a border of the container is reached. When the size of the control's container changes, the border is moving, so the docked control will resize itself accordingly.
- Anchoring tells a control to keep a constant distance between a specified border (top/bottom/left/right) of the control and the corresponding border of the container at all times. Now if the container is resized while layout is suspended (which is the case in
InitializeComponent()
of classForm2
), the anchored control does not notice the change. When layout is resumed, the control will keep the distance that exists at this time. This can be observed if you resize the form either inside the forms designer or at run time. The anchoring works perfectly, but unfortunately using the wrong offsets...
-
I just can't get used to the Command Window
I have been using VS.Net for slightly more than 2 years now, and still there's one thing that really can't get used to: the command window (to be exact: the command window in immediate mode). Maybe it's because it is different from the command window of VB6 (which is more like a C64-style full-screen editor). This is one of those cases where I'm really willing to get used to something but I just can't.
- I want to clear the command window - so what do I do? When I think about doing it, I use the context menu (right click -> popup menu -> clear all). But when I act instinctively, I hit Ctrl-A, Del (which doesn't work)
- I want to copy something from the text in the lines above the current line and use it - not possible without using the mouse, because I cannot move the cursor with the cursor keys. It is a fully understandable design decision to use the cursor keys for scrolling in the command history, but I somehow don't get it. Maybe it's because I don't need items from the command history that much (unlike when I use "cmd.exe" where I don't have a problem with the line-oriented editor).
- I want to clear only parts of the output - not possible. That can be very frustrating.
Is it just me? Am I getting inflexible? Am I missing some option I can switch? Or did somebody write an addin for VS.Net?
-
Just for Fun: Simple Control for Terminal-style Output
The other day I was playing around with the RichTextBox, learning how to use this control. One of my experiments produced the effect of text being displayed on an old terminal, character by character.
Now I've wrapped this code in a simple control. Please don't ask me what it can be used for. Maybe for an "About..." dialog. I don't even know what I will do with it. It's just that from time to time I have to write some useless fun stuff to relax a little ;-).
You can download the code (VS.Net 2003) here; an example project is included so it's just "unzip, open solution, hit F5".
The
Write()
andWriteLine()
methods are used for adding text to the output,ClearScreen()
empties the output area. The control can be customized using the propertiesBackColor
,ForeColor
,Font
andCharactersPerSecond
. -
Visual Inheritance - How to escape Layout Hell
Visual inheritance (VI) is a very powerful feature; unfortunately, some of the more tricky bits are not that well-documented. A lot of things can go terribly wrong (like controls moving themselves to unexpected locations), as I had to learn the hard way in the last weeks. But if you follow a few simple rules, VI does work reliably.
First a short introduction for those who haven't looked into VI yet (actually, it took me some time to really discover VI). In C# (and other .NET languages) forms and controls are no longer "magic" things (like e.g. in VB6) but simply classes derived from special base classes (e.g. System.Windows.Forms.Form). Such a class can in turn be used as the base class for another class; if a base form contains controls, the derived form inherits them. Again, there's nothing magical to this all; when looking at the code (or better: single stepping through it), it's pretty obvious how VI works. The constructor of each form/control class contains a call of a private method "InitializeComponent()". If B is derived from A, then the constructor of A is called first (thus "InitializedComponent()" of A), then the constructor of B (and "InitializeComponent()" of B).
VS.NET directly supports VI, i.e. it is possible to edit a derived form (or user control) in the windows forms designer. By default, "editing" a derived form means adding additional controls. This is because controls added to a form are marked as "private", i.e. they are not accessible from a derived class. Again, as we're simply dealing with classes, it's just a matter of changing the access specifier to e.g. "protected". Inside the forms designer, this can be done by changing the control's "Modifiers" property.
Here's a typical example for visual inheritance: Imagine you want to have a nice looking header on all of your dialog windows, maybe something like this:
With VI, you define a base class for all your dialogs, containing the header (a panel docked to the top, with a picture box for the image and a label for the text). You then define two properties on the form for setting the icon and title text:
public string TitleText
{
get { return m_lblTitle.Text; }
set { m_lblTitle.Text=value; }
}
public Image TitleImage
{
get { return m_picTitle.Image; }
set { m_picTitle.Image=value; }
}If you derive a form from this base class, your derived form will automatically feature the header defined in the base class; using the two properties, the header can be customized. Cool, huh?
This is usually the point where articles describing visual inheritance stop, leaving out some of the really important questions:
Question: How do I make sure the controls on the derived form are positioned correctly if the size of the header changes?
Define a panel on the base form below the header and make it accessible from the derived form by setting its access specifier to e.g. "protected". Set "Dock" to "Fill". In your derived form, you drop your controls into this panel. If the size of the header changes, the controls will be repositioned automatically, as they are positioned relative to the upper left corner of the panel they live in.
DON'T position the panel manually and set the anchor to "Top, Bottom, Left, Right". This will not work correctly if the size of the derived form differs from the size of the base form.
Question: But I want to define specific areas for the controls on the derived form - how do I do this without using anchors?
Use nested panels and the "DockPadding" property. The following image shows a base form with two panels to be used in derived forms (panels #3 for buttons like OK and Cancel, and #5 for all other controls):
Of all the panels, only #3 and #5 are set to "protected", the remaining are set to "private".
Question: What is so bad about anchors?
As a rule of thumb, use anchors other than "Top, Left" only on forms/controls that are not used as the base for other forms/controls. This is because the layout of anchored controls will not be updated if the size of the derived form/control is changed in the "InitializeComponent()" method of the derived class. If the form is resized later, things are Ok. This is one of the reasons why this problem sometimes remains unnoticed until some time later, when suddenly VI seems to be broken.
Question: But I can fix it by making the controls "protected"?
Yes and no. First of all, if form A contains a control X that is anchored, and form B is derived from A and has a different size, then setting X to "protected" fixes the problem only for B, but not for a form C derived from B. And under some circumstances there still seem to be some problems left even in B (I didn't invest too much time into researching this, though)
From a OOD point of view, making lots of controls accessible from derived classes only to fix layout problems isn't such a good idea, anyway. If your base form contains e.g. a list, then it's better to offer only the concept of a list through properties, methods and events, but not the actual listbox control. If you decide to switch to a dropdown list or a listview at a later time, you don't have to touch the code in the derived classes.
Question: How do I position OK and Cancel buttons in the lower right corner without a gazillion nested panels?
A lot of layout can be done with surprisingly few panels. Of the panels shown in the image shown above, the divider panel is actually not necessary (I just added it to make things a bit easier to understand in the first step). The secret is the right combination of docking and dock padding. Take a look at this:
The buttons are 23 pixels high, the padding at the top is 8 pixels, so the height of the panel was set to 31 pixels.
Question: But what about more complex layouts?
From my own experience, if you have a base form with a complex layout, something already went wrong in the (non-visual) design of your code. Note the word "base": On a normal form e.g. for data entry, a complex layout is nothing unusual or even bad. But if you have a base form, you want to reuse something on that form - if this "something" is too complex, it should be encapsulated in a user control. User controls are a powerful tool for reducing the complexity of layout and code. Most layouts can be broken down to a few user controls which in turn can be positioned easily using docking and dock padding. And it's not a secret that code definitely benefits from dividing it into smaller units.
If you know that your user control will not be used as the base for another control (you could make it
sealed
as a reminder), you can again use anchoring. Here's the funny part: You can use anchoring inside a control that lives on a form that in turn is used as the base for another form. But of course, you cannot use anchoring to position the user control on the base form.Conclusion
- Don't use anchoring on forms or (user) controls you want to use as the base for other forms/controls
- Look at what you can achieve with panels, docking and dock padding
- Reduce complexity by using user controls (you may want to read a bit about user controls here).
Update (28.10.2003)
Since the original post, I have written two follow-ups that may be of interest for developers using VI and/or docking:
- Visual Inheritance Revisited (more detailed information about VI) and
- More Layout with Docking (not VI-related)
-
Find (Wouldn't it be cool, part 3)
Each time I'm searching my source code for the word "is", I wish VS.Net had something like this:
-
When your WinForms UserControl drives you nuts
WinForms user controls are that kind of technology that seems to be very easy at first, works pretty well for quite a while and then BAM! explodes in your face. I spent some hours today solving problems with a control that was working fine in the running application, but just didn't like the IDE's designer anymore, throwing exceptions all over the place. Here are some tips based on what I learned today searching Google's Usenet archive:
1. Don't take things for granted
Just because you can create a simple user control (e.g. combining a text field and a buttons) without reading any documentation, doesn't mean that knowledge about what is actually happening in design mode isn't necessary. Always keep in mind that there's some code running even though your program is not started. When a form or a control is displayed in the IDE in designer view, its (default) constructor is called which in turn calls InitializeComponent(). And this is where the trouble usually starts when this form/control contains a badly written user control.2. Take care of your control's public properties
If you don't add any attributes to a read/write property (huh, what attributes?), two things happen:- The property is shown in the "properties" window in the IDE.
- The initial value of the property is explicitly set in the
InitializeComponent()
method.
Hiding properties that don't make sense to be edited at design time is a good idea even if you don't intend to write a commercial-quality, bullet-proof control ready to be shipped to thousands of developers. It's simply a matter of good style and it's done with very little effort using the
Browsable
attribute:[ Browsable(false) ]
public string MyProperty
{
...
}The second point can be quite a shocker in some cases, because it means that
- the property is read when the control is placed on a form (quick experiment: throw an exception in the "getter" and see what happens in the designer).
- the property is set at the time the control is created, not when it is actually added to the form.
As long as your control only gets or sets the value of a private member variable, things are fine. But if e.g. reading the property triggers some actions that require the control to be completely initialized, you can get into trouble (remember the "Add any initialization after the InitializeComponent call" comment in the constructor?). And setting the property "too early" can be a problem e.g. if your property's setter contains code that performs some kind of automatic update.
This behavior of the designer can be avoided by using the
DefaultValue
attribute:private string m_strMyText="";
...
[ DefaultValue("") ]
public string MyText
{
...
}The
MyText
property will not be set to "" in theInitializeComponent()
method (which would be the case without the attribute).While you're at it, consider adding the
Description
andCategory
attributes as well. Adds a nice touch.3. Don't rely on
this.DesignMode
too much
The inheritedDesignMode
property of your user control is an easy way to find out whether the control's code is running in the designer. Two problems:- You can't use it in the control's constructor. At that point, the control's "Site" (which provides the actual value of
DesignMode
) hasn't been set, thusDesignMode
is always false in the constructor. - "Running in the designer" is a matter of point of view:
- When editing a control
MyControl
in the designer,MyControl
is obviously in "design mode". - When using
MyControl
on a form being edited in the designer,MyControl
is still in "design mode". - But if
MyControl
is placed onMyOtherControl
, andMyOtherControl
is placed on a form,MyControl
is no longer in design mode (butMyOtherControl
still is)
- When editing a control
4. If you just don't know what to do: debug!
Open the project's properties dialog, set the debug mode to "program", apply, set "start application" to your VS.Net IDE ("devenv.exe" in the "Common7\IDE" directory). Set some breakpoints in your code (e.g. in property getters/setters) and start debugging. A new IDE will open (empty), you load the project and then open the control in designer view. Cool stuff. -
Intellisense (Wouldn't it be cool, part 2)
Everytime I use intellisense on a class with a lot of members (e.g. a class derived from System.Windows.Forms.Form) I get overwhelmed by the huge dropdown list if I'm looking for a member I don't know the exact name of.
So wouldn't it be cool if instead of this...
... VS.Net 200x would offer something like this:
Hiding inherited members...
...or showing only certain kinds of members (e.g. only events)...
...would be really nice features (and of course something like this is still on my wish list).
-
C# Project File Quote Madness
When looking at a ".csproj" file, a developer's first reaction is "cool, it's XML, I can read and write it". It may be missing the "<?xml ... ?>" processing instruction, but who cares - you can Load() it into a System.Xml.XmlDocument, so everything's fine.
As long as you intend to only read from the ".csproj" file, that's the end of the story. But if you actually want to write to a ".csproj" file (not such a good idea anyway), be prepared for an unpleasant surprise. If you Save() the XmlDocument to a file, and one of the of the gazillion attributes contains a quote, the saved project file is virtually worthless.
An example: Say you put echo "Hello World!" into your post build step. If you load the resulting project file into an XmlDocument and save it back to a file, you'll get something like this:
... PostBuildEvent="echo "Hello World!"" ...
Looks ok, right? Not to VS.Net. Because VS.Net expects this:
... PostBuildEvent = 'echo "Hello World!"' ...
So when you load the project file back into VS.Net, the next time the post build step is performed, VS.Net tries to execute "echo "Hello World"", which of course fails with a lot of noise (remember that "&" has a special meaning on the command line). XmlDocument is smart enough to read and understand the original ".csproj" file (the single quotes are valid XML after all), but when saving to a file, it uses double quotes to surround the attribute value and escapes quote characters inside the value if necessary.
The fact that VS.Net is not able to read the file makes me wonder what kind of XML parser is used inside VS.Net - did somebody have the stupid idea to write one from scratch?
Before somebody tells me that I'm not supposed to manipulate the ".csproj" file, here's a way to make VS.Net look stupid without any external help:
Put echo "Hello 'World'!" into the post build step. VS.Net writes the following into the project file:
... PostBuildEvent = "echo "Hello 'World'!"" ...
Oops... attribute value surrounded by double quotes, escaped quotes inside... that means trouble. If you build the project immediately after setting the post build step, "Hello 'World'!" appears in the build output as expected. But if you (re)load the project, BLAM! "Post-Build Event failed".
-
Documentation Patterns
Keith writes about documentation - or the lack thereof. Even though writing documentation inside C# source files brings immediate benefits (e.g. tooltips when using intellisense) and is generally a good idea for any non-trivial library, a lot of code is written without proper documentation. An amazing fact is the huge difference between the number of people who have heard about NDoc and the number of people who have actually tried it (let alone use it on a regulary basis). C'mon, spend the 15 minutes of your life to install and try it. I tend to be rather impatient when trying new tools, and even I got quick results that did convince me to spend a little more time getting to know the different options.
On the non-technical side, there's the problem that good developers are not necessarily good authors (note that I'm not speaking about very good developers). So even if IDE support for writing documentation gets better and better (maybe something like this?), some people feel that they would have to spend too much time to write useful documentation.
Let's forget about that extensive seven-part hands-on tutorial for your library that you would write if you were a fast writer so could squeeze it into the typical tight schedule (did I just hear somebody saying "I don't read these anyway, just gimme the reference docs, some short samples and I'll get started myself"?).
What really gets on my nerves is a library without the minimal online documentation for intellisense while typing and a help file for browsing through the library to get a "feel" for the overall structure. Every public method or property should be described in one or two sentences. If this is not possible, the method or property either a) does something absolutely incomprehensible amazing in one single step, or b) it sucks. While a) may be fascinating, not being able to write the short summary usually means b), i.e. identifies bad structure in the code.
What about "self-describing" method or property names? What else to write e.g. about a method "BuildCache" rather than "Builds the cache"? If you look at such a method and think about it just a bit longer, you'll come up with at least one more fact (no implementation details, I think we'll all agree on that), that may be trivial at first sight (e.g. what cache?), but can be a help for future users (or for yourself in a couple of weeks). If a method has parameters, it's a good idea to mention the most important one or two, e.g. "Builds the foo cache from the specified bar" - this helps getting a rough idea about the method. By the way, keep in mind that online documentation is not necessarily read in a particular order.
But what if you still have problems finding the right words for even a short description? Well, simply look at the .NET framework documentation and
steal sentencesget inspiration from there. A lot of bread-and-butter stuff can be documented using simple "patterns" you'll find there. An example: Before switching to .NET, I used to have many different ways to document a flag. Now I always begin with "Gets or sets a value indicating whether ..." for boolean properties. This may not be the best way to do it, but people are so used to it from the Microsoft docs, that they feel right at home.Conclusion: Reading other people's documentation with open eyes (to detect the patterns used over and over again to achieve consistency) is an important step to writing documentation with less effort.
Update 2006-09-28: Fixed some broken links
-
One Reason why I use Hungarian Notation
While I adjusted my coding style for "everything publicly visible" to match the Microsoft guidelines, I still use Hungarian Notation internally (in a simplified form). One can argue whether or not it increases the readability of code, resulting in long discussions about the pros and cons.
One feature of HN I really love (and just don't want to give up) is how well it works in conjunction with Intellisense.
Example 1: Member variables ("m_" prefix):
Example 2: Controls ("c_" prefix):