Archives
-
Avalon/XAML: Cool, but...
I spent most of the past evening (European Time) reading blogs, soaking every little bit of information about the things to come and I must say that I was as excited as I am when following my favorite basketball team's away games reading text messages on the their home page.
Avalon/XAML sounds like a really good idea to me, but I'm a bit dissappointed that we still seem to be stuck with the same old VB model of "here's the control, it's got an event, there's the event handler". In all the years I have programmed desktop-style applications, it never has been a problem to get a function called when the user clicks on something. What I have been struggling with is the visible/invisible, enabled/disabled logic.
At some point in a developer's life, most either have written or had to maintain a monster application where adding one additional button would mean changing code in many different places just to make sure the view state of the button is correct. Of the different approaches how to solve this problem, I currently favor the use of the command pattern, which I have used successfully in several applications.
The command pattern is only one approach, I'm pretty open for other solutions if they're getting the job done. If Microsoft is all about creating next-gen stuff, I definitely expect an improvement in this area. Because that's where a lot of development and testing time is spent ("if I click here, that button is disabled until you select a list item here, which causes this icon to disappear...")
To those who are at the PDC:
- In the demos, are there examples of handling the view state (visible/enabled) logic? I suspect that demo code is usually limited to "hey, look how I call this method my clicking that button".
- Could somebody please ask about plans for solving this problem?
[For those interested in the command pattern]
In the C#/.NET world, I've seen the use of the command pattern in different variations. There was an article in MSDN magazine 10/2002 (which explained the basic ideas well, but I think the actual library sucked in many ways), and SharpDevelop does use the command pattern, too. (Regarding SharpDevelop, I must admit that I didn't look beyond the public interface of a command, as I wanted the library I was writing at that time to be as clean as possible from other peoples ideas/code. Right now I'm working on the third version of that library; I will release it under a BSD-style license in the coming weeks). -
More Layout with Docking
After my recent articles about visual inheritance (Visual Inheritance - How to escape Layout Hell and Visual Inheritance Revisited) I received some mails in which people asked me about docking and windows forms layout in general. One question was about how to resize elements of a layout proportionally when the form is resized, without calculating lots of coordinates.
Imagine you have a form like this:
and you want it to resize like this:
Using docking, this is really easy:
Step 1: Docking
- panel1: Dock left
- label1: Width 1, Dock left
- panel2: Dock top
- label2: Height 1, Dock top
- panel3: Dock fill
(DockPadding.All=8)
Step 2: Handling the form's resize event
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
int dx=this.ClientSize.Width - this.DockPadding.Left - this.DockPadding.Right - label1.Width;
int dy=this.ClientSize.Height - this.DockPadding.Top - this.DockPadding.Bottom - label2.Height;
panel1.Size=new Size(dx/3,panel1.Height);
panel2.Size=new Size(panel2.Width,dy/2);
}You only need to calculate one width and one height - everything else is taken care of automatically by simply docking the layout elements.
-
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?