Comparing and contrasting C#Builder and Delphi forms by Clay Shannon

By: Clay Shannon

Abstract: Wherein the difference between Delphi forms and C#Builder forms are discussed, along with comparisons of some of their corresponding properties, methods, and events.

A Delphi programmer will find the C# Builder IDE quite familiar. It is not exactly the same, of course, but the familiar things are there: Object Inspector with Properties and Events tabs, a Tool Palette--albeit situated by default at the SouthEast corner of the screen instead of along the 20th parallel, and which morphs into a code snippet repository when you are in code mode (as opposed to design mode). The editor is similar, but enhanced (especially as regards the new code-folding feature). The debugging environment in the two tools is quite similar, too. In a nutshell, the C#Builder IDE has everything good that the Delphi IDE has, and more. This is a foregleam of what we will see with Delphi for .NET and possibly even Delphi 8.

So what is significantly different about C# Builder as compared to pre-.NET versions of Delphi? The primary differences are the languages (no surprise there, methinks) and the usage of the .NET class framework. If the only major difference were the languages, you could compare moving to C# Builder from Delphi to a native of Portland, Oregon relocating to Portland, Maine. By adding the .NET framework into the mix, though, it's more like a resident of Dublin, California relocating to Dublin, Ireland: not only does he have to learn to navigate the streets and find out where the best restaurants in his new town are, but he also needs to adjust to a whole new way of life, climate, and set of laws (such as driving on the "wrong" side of the road).

So there is both good news and bad news about taking up or taking on this challenge: the good news is that the .NET framework is feature-rich and apparently "bound for glory" (that is to say, it is ascendant, and no doubt will be around for quite some time, if not longer). The bad news is that it is a somewhat daunting task to familiarize yourself with the entire .NET framework. But even that black cloud has a silver lining for the industrious: the opportunists and bandwagon-jumpers will not have the staying power to tough it out, thereby allowing the cream (that's you, dear reader) to more quickly rise to the top.

So let's dive right in. We need to start somewhere, so in this article I'll concentrate on the first object you normally see in the Delphi IDE, namely TForm, and its "opposite number" in C# Builder, System.Windows.Forms.Form.

Comparing Form Classes

With Delphi32, Borland created an object-oriented framework of classes and superimposed them on what the "legacy" Windows OS made available. All Delphi classes descend from TObject, and the genealogical record from TObject to TForm looks like this:


TObject
  TPersistent 
    TComponent
      TControl
        TWinControl
          TScrollingWinControl
            TCustomForm
              TForm

As you probably know, each successive generation of objects inherits from, and builds on, what came before. Classes that are "siblings" (having the same parent, such as TPasswordDialog and TLoginForm, which both descend from TForm) have more in common than classes which are "cousins" (having the same grandparent, such as TLabeledEdit and TMemo, who share TCustomEdit as their grandparent--TEdit being the parent of TLabeledEdit, while TCustomMemo is the parent of TMemo).

With .NET, Microsoft has laid a single-rooted object-oriented framework similar to the one Borland created with the VCL. While Borland anointed TObject to be the "big daddy" of all classes in Delphi, in .NET that "father of all classes" is System.Object. All classes descend from that, and the genealogy from that root object to a WinForm appears like this:


System.Object
  System.MarshalByRefObject
    System.ComponentModel.Component
      System.Windows.Forms.Control
        System.Windows.Forms.ScrollableControl
          System.Windows.Forms.ContainerControl
            System.Windows.Forms.Form

With the exception of TWinControl, which has no exact corresponding class in the .NET framework, and the immediate parents of the target class (TCustomForm and System.Windows.Forms.ContainerControl), the classes seem--albeit alternately named--almost identical (compare TObject with System.Object, TPersistent with System.MarshalByRefObject, TComponent with System.ComponentModel.Component, TControl with System.Windows.Forms.Control, TScrollingWinControl with System.Windows.Forms.ScrollableControl, and TForm with System.Windows.Forms.Form).

Different Types of C#Builder Applications

The particular type of form we're considering in this article is used in C# Builder "WinForm" applications. "WinForm" refers to GUI Windows apps, similar to a generic app created in Delphi. The main categories of apps you can create with C# Builder are:

1) Console

2) WinForm

3) Web (ASP.NET)

4) Web Services (WebMethods)

5) Database (ADO.NET)--of course, you can mix and match database apps with WinForm, Web, and even WebService and Console apps (and probably will).

When you create a web application (File | New | ASP.NET application), the form class is System.Web.UI.Page, and is only remotely related to a WinForm form (System.Windows.Forms.Form). Here is the genealogy of a web form/page:


System.Object
  System.Web.UI.Control
    System.Web.UI.TemplateControl
      System.Web.UI.Page

Delving further into the web page class is beyond the scope of this article, so we will return to the WinForm type of form (System.Windows.Forms.Form).

Constructors

The constructors for an application's main form differ between C# and Delphi. In a C#

WinForm app, the main form is automatically created in the main form's file--which is named WINFORM.CS by default:


[STAThread]
static void Main() 
{
  Application.Run(new WinForm());
}

The STAThread attribute (in the angle brackets preceding the constructor signature) informs the common language runtime that Windows Forms use the single-threaded apartment model. Attributes and threading models are both beyond the scope of this article. Refer to the C# Builder documentation for more information.

In Delphi, the main form is created in the project (<PROJECTNAME>.DPR) file, like this (the most pertinent line is bolded):


program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Note: C# Builder apps do not have a project file corresponding closely to Delphi's <PROJECTNAME>.DPR file. The C#Builder project file is <PROJECTNAME>.BDSPROJ, but it contains XML metadata and project settings, not executable code as a <PROJECTNAME>..DPR file does. There are other project-specific files that C# Builder creates, but they play different roles than Delphi's project file does, and are--yes, you guessed it, beyond the scope of this article. You can find out about these by creating a "Hello, World" type app and then examining the files that are created. The default location where C#Builder projects are placed is C:Documents and Settings<UserNameOrRole>My DocumentsBorland Studio Projects<ProjectName>

Comparing Properties, Methods, and Events

Components such as forms contain properties, methods, and events. Various and sundry limitations prevent us from delving into every single property, method, and event of TForm and System.Windows.Forms.Form, so we will just mention a few highlights and leave the rest for you to explore on your own.

When I start a new app in Delphi, I usually change the default value of the following properties before doing anything else: Caption (to the name of the app), Constraints (I set max and min height and width to their design-time value so the user cannot resize the form), Name ('MotherOfAllAppsMainForm' or whatever), and Position (to poScreenCenter). Let's see if there are corresponding properties in System.Windows.Forms.Form.

In C#Builder, the property corresponding to Caption is named Text. Instead of Constraints, C#Builder provides MaximumSize.Height, MaximumSize.Width, MinimumSize.Height, and MinimumSize.Width. Setting these values the same as the form's design-time Height and Width properties works as it should, but I ran into a problem when I tried to set these property values in code. Something I often do in Delphi is 2-click the form at design-time, which opens the editor with the cursor in the FormCreate event. Doing the same in C#Builder puts you in the corresponding WinForm_Load event. So far so good. However, although in Delphi I can do this:


procedure TForm1.FormCreate(Sender: TObject);
begin
  { prevent user from re-sizing the form }
  with Constraints do begin
    MaxWidth := Width;
    MinWidth := Width;
    MaxHeight := Height;
    MinHeight := Height;
  end;
end;

in C#Builder the (seemingly) corresponding code:


private void WinForm_Load(object sender, 
  System.EventArgs e) 
{  
  this.MaximumSize.Height = this.Height;
  this.MinimumSize.Height = this.Height;
  this.MaximumSize.Width = this.Width;
  this.MinimumSize.Width = this.Width;
}

does not work, as it generates the error message "Cannot modify the return value of 'System.Windows.Forms.Form.MaximumSize' because it is not a variable.". Okay, to determine the problem, let's take a look at the code generated by the C#Builder form designer after setting the properties in the Object Inspector:


private void InitializeComponent() {
  . . .
  this.MaximumSize = 
    new System.Drawing.Size(592, 288);
  this.MinimumSize = 
    new System.Drawing.Size(592, 288);
  . . .
}

Aha! This everything-is-an-object-and-therefore-must-be-explicitly-created way of working will take a little getting used to for a hitherto diehard Delphian. The code snippet from the automatically generated InitializeComponent() method above shows that what would go in a Delphi form (<FORMNAME>..DFM) file is included right in with the rest of the code in C#Builder (there is no form file equivalent in C#Builder).

As for the Name property, it is named exactly the same ("Name") in both tools. As for Delphi's Position property for setting the location of the form, the C#Builder equivalent is StartPosition. With Delphi, there are eight possible values you can set; in C#Builder there are five. In Delphi I always choose poScreenCenter; the C#Builder equivalent is CenterScreen.

Object Inspector Features

In C#Builder (as in Delphi 7), you can arrange properties in the Object Inspector by category or by name. If arranging by category, you'll find the Text property under the Appearance category, MaximumSize[] and MinimumSize[] and StartPosition are under Layout, and Name is part of the Design category.

Form Designer Features

At design-time, the C#Builder form differs from Delphi32 forms in that nonvisual components do not appear directly on the form. Instead, they reside in a separate area at the bottom of the form designer. This is a very welcome improvement over the current Delphi behavior. You no longer need a "data" module as a "closet" for stashing nonvisual controls, just to keep them out of sight.

The Differences In the Code

The underlying code behind forms in the two tools, besides the obvious difference of being in different programming languages, differ in several other respects:

  1. There is no interface section in C#Builder units.
  2. There are no visibility specifier sections in C#Builder units as there are in Delphi units. Instead, each class and class member is individually designated as being either private, internal, protected, protected internal, or public. For more information about these "accessibility levels" (normally termed "visibility specifiers" in the Delphi world), see ms-help://borland.bds/csref/html/vclrfdeclaredaccessibilitypg.htm in the C# online help.
  3. More code is written for you automatically in C#Builder. An example of this is the WinForm() method (which is the constructor-constructors have the same name as their class, so WinForm() is the default name of your constructor until when/if you change the namespace for your app). This constructor calls InitializeComponent(), which houses the automatically-written form-file type data. C#Builder also automatically writes the InitializeComponent() method itself, the Dispose() method (destructor), and the Main() method, whose functionality (to start the app running) is housed in the project (<PROJECTNAME>.DPR) file in a Delphi app, as noted earlier. So the main unit of a C#Builder app (named WINFORM.CS by default) includes what are spread across three files (<PROJECTNAME>.DPR, <FILENAME>.PAS, and <FILENAME>.DFM) in Delphi.
  4. Code documentation comment placeholders for generating XML files are automatically added to your method headers. This semi-automation of code comments and the resultant XML file[s] that you can produce are, alas, beyond the scope of this article.

System.Windows.Forms.Form Properties

Some interesting properties are:

1. The three properties found under the Accessibility category (AccessibleDescription, AccessibleName, and AccessibleRole). These have to do with creating apps that are accessible for low-vision or blind users. As most people will not need to deal with these settings in an average app, we will not discuss these any further.

2. The BackgroundImage property. You can easily set any image as the "wallpaper" of your form, as shown here:

Figure A

A man dressed in 19th century garb serves as the background image on this form

Note: Instead of storing the hex value for the bitmap in the InitializeComponents() method, as takes place in a <FILENAME>.DFM form in Delphi, the Image assigned to the BackgroundImage property is referenced inside the InitializeComponents() method like this:


this.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("$this.BackgroundImage")));

The resource is stored in a file named <FILENAME>.RESX. For example, if you don't change the name of the default file C#Builder created for you, a WinForm app's main form resource file is named WINFORM.RESX.

The resource is stored there in a similar format to Delphi's <FILENAME>.DFM file, but inside an XML-formatted file:

. . .

<data name="$this.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" mimetype="application/x-microsoft.net. object.bytearray.base64"> <value> /9j/4AAQSkZJRgABAgAASABIAAD/2wBDAA. . . </value>

3. The CausesValidation property of the Focus category. At the bottom of the ObjectInspector, short descriptions are provided that tell you the raison detre of the currently highlighted property (including event properties on the Events tab). In the case of the CausesValidation property, it says, "Indicates whether this control causes and raises validation events." These validation events, which can be toggled on or off depending on the boolean value of the CausesValidation property, are Validated and Validating. If CausesValidation is True, these events will be called. This property is True by default.

4. The AcceptButton property of the Miscellaneous category should be a popular one with Delphi developers learning C#Builder, as "How do I make a button act as the <Enter> key" is a very FAQ in Delphi newsgroups. By setting AcceptButton to a specific button control on the form (by selecting one from the drop down list in the Object Inspector), when the user selects the <Enter> key the code attached to the Click event of the button you assigned to this property will fire.

5. The CancelButton property, also of the Miscellaneous category, is similar to the AcceptButton property, the difference being the code in the selected button is fired when the user selects the <Esc> key.

6. The Language property of the Miscellaneous category allows you to select a language for your app. The default value for this property is (Default), but when you select a language from the list (Portuguese , for example), the Localizable property toggles from its default value of False to True. Before selecting a language, the InitializeComponent() method in a brand new WinForm app looks like this:


private void InitializeComponent()
{
  this.components = 
    new System.ComponentModel.Container();
  this.Size = 
    new System.Drawing.Size(300,300);
  this.Text = "WinForm";
}

After selecting a language, it becomes:


private void InitializeComponent() {
  System.Resources.ResourceManager 
    resources = new System.Resources.
      ResourceManager(typeof(WinForm));
// 
// WinForm
// 
this.AccessibleDescription = 
  resources.GetString(
    "$this.AccessibleDescription");
this.AccessibleName = 
  resources.GetString(
    "$this.AccessibleName");
this.AutoScaleBaseSize =     
  ((System.Drawing.Size)
  (resources.GetObject(
    "$this.AutoScaleBaseSize")));
this.AutoScroll = ((bool)
  (resources.GetObject(
    "$this.AutoScroll")));
this.AutoScrollMargin = 
  ((System.Drawing.Size)
    (resources.GetObject(
      "$this.AutoScrollMargin")));
this.AutoScrollMinSize = 
  ((System.Drawing.Size)
    (resources.GetObject(
      "$this.AutoScrollMinSize")));
this.BackgroundImage = 
  ((System.Drawing.Image)
    (resources.GetObject(
      "$this.BackgroundImage")));
this.ClientSize = ((System.Drawing.Size)
  (resources.GetObject(
    "$this.ClientSize")));
this.Enabled = ((bool)
  (resources.GetObject(
    "$this.Enabled")));
this.Font = ((System.Drawing.Font)
  (resources.GetObject("$this.Font")));
this.Icon = ((System.Drawing.Icon)
  (resources.GetObject("$this.Icon")));
this.ImeMode = ((System.Windows.Forms.
  ImeMode)(resources.GetObject(
    "$this.ImeMode")));
this.Location = ((System.Drawing.Point)
  (resources.GetObject(
  "$this.Location")));
this.MaximumSize = ((System.Drawing.Size)
  (resources.GetObject(
    "$this.MaximumSize")));
this.MinimumSize = ((System.Drawing.Size)
  (resources.GetObject(
  "$this.MinimumSize")));
this.Name = "WinForm";
this.RightToLeft = 
  ((System.Windows.Forms.RightToLeft)
    (resources.GetObject(
      "$this.RightToLeft")));
this.StartPosition = 
  ((System.Windows.Forms.
    FormStartPosition)(resources.
      GetObject("$this.StartPosition")));
this.Text = resources.GetString(
  "$this.Text");
}

7. The value of the ControlBox boolean property of the WindowStyle category determines whether the Maximize box, Minimize box, and Close box appear in the NorthEast corner of the form. Unlike Delphi, if you set this to False, they disappear even at design-time (In Delphi, you can cause the same run-time effect by setting the BorderIcons' elements of biMaximize, biMinimize, and biSystemMenu to False, but they will still be visible at design-time). In C#Builder, you can leave the ControlBox property True and disable the Maximize or Minimize box[es], but in this case they will all stay visible (but disabled).

8. The Opacity property of the WindowStyle category controls the opacity (or lack thereof) of the form. The property is expressed in percentage, from 0% (completely transparent) to 100% (completely opaque). In other words, if you set this value to 0%, your form will be invisible. Here is a screenshot of what that looks like:

Figure B

 

 

 

A completely transparent form ([not] shown above)

9. The ShowInTaskBar boolean property of the WindowStyle category determines whether your program's icon appears in the Windows task bar when it is running.

10. The SizeGripStyle property of the WindowStyle category lets you determine whether you want a size grip to appear at the SouthEast corner of the form, or to leave it set to the default value of Auto, which will set the property based on the value of the Appearance category's FormBorderStyle property. If set to Sizable or SizableToolWindow, a sizegrip displays, while selecting any of the other FormBorderStyle possibilities (None, FixedSingle, Fixed3D, and FixedToolWindow) prevent a sizegrip from displaying.

11. By setting the WindowStyle category's TopMost property to True (it is False by default), you can display the form on top of all other forms that are not designated as being topmost forms. This holds true even when the form is deactivated.

12. The TransparencyKey property of the WindowStyle category allows you to designate a color that, when appearing on a form, will be transparent (underlying screen elements will show through).

System.Windows.Forms.Form Methods

Some interesting methods contained in C#Builder WinForms are:

1. The GetNextControl() method takes two arguments, a control and a boolean value, and returns a control. It returns the next control in the tab order after the one you specify in the first argument if the second boolean argument is set to true, and the previous control in the tab order if that boolean argument is set to false (and remember that C# is case-sensitive, so the compiler won't recognize "True" or "False"-you must pass "true" or "false"). Here's an example snippet of code and a screen shot that shows the results of the snippet's execution:


private void button2_Click(object sender,
  System.EventArgs e) 
{
  System.Windows.Forms.Control ctl;
  ctl = this.GetNextControl(
    button1, true);
  this.Text = ctl.Name;
}

Figure C

What the form looks like after button2_Click fires.

You can accomplish the same thing in Delphi, but not in the same way, and not in as straightforward a manner (IMO, of course).

2. SelectNextControl() is an interesting and powerful method that takes five arguments and returns a bool (true if a control was activated as a result of the call to the method, false otherwise). The first two are the same as the two in GetNextControl(). The subsequent three are the boolean arguments TabStopOnly, Nested, and Wrap. The value of these arguments determines, in order: whether controls with TabStop property set to False are included/selectable; whether to examine controls that are nested inside other controls (child controls); and whether the search should wrap back to the beginning of the tab order after it reaches the last control in the tab order. Here's an example:


private void button1_Click(object sender, 
  System.EventArgs e) 
{
  if (this.SelectNextControl(textBox1, 
       true, true, true, true))
    this.Text = "Yesterday I couldn't"+
      "spell "C# progammer", and now"+
        "I is one!";
}

There is a somewhat equivalent method of TForm in Delphi, namely SelectNext(), which takes a TWinControl and two Boolean arguments (CurrentControl, GoForward, and CheckTabStop). SelectNext() is a procedure, so you cannot use an if statement with it as you can with C#Builder's SelectNextControl(), though:


procedure TForm1.Button2Click(
  Sender: TObject);
begin
  Form1.SelectNext(Button1, True, False);
end;

3. The GetChildAtPoint() method returns the control at the coordinates you pass in the Point argument. As an example, here is a code snippet that sets focus to the control found at the point passed:


private void button3_Click(object sender,
  System.EventArgs e) 
{
  System.Windows.Forms.Control ctl;
  Point WhatIsYour;

  WhatIsYour = new System.Drawing.Point(
    53, 113);
  ctl = this.GetChildAtPoint(WhatIsYour);
  ctl.Focus();
}

The Delphi equivalent would differ substantially. For one thing, you would need to declare a TPoint type (not object) and assign its X and Y values, like this:


procedure TForm1.Button3Click(
  Sender: TObject);
var
 WhatIsYour: TPoint;
  TWinCtrl: TWinControl;
begin
 WhatIsYour.X := 188;
 WhatIsYour.Y := 188;
  . . .
end;

The System.Windows.Forms.Form class contains many other interesting methods you might want to explore, such as GetService(), MemberwiseClone(), and GetHashCode().

System.Windows.Forms.Form Events

Some interesting events contained in C#Builder WinForms are:

1. HelpRequested() in the Behavior category. This event is fired when the user presses the F1 key when a control which handles this event is active. By writing an event handler, you can programmatically determine what to do depending on the currently active control. This is more granular than Delphi's OnHelp() event, which is a member of TCustomForm. To respond to individual controls from within Delphi's OnHelp() event handler, you need to add code to check the value of the form's ActiveControl property and then branch accordingly.

2. The MenuStart() event of the Behavior category allows you to add code when a menu is first accessed. In this way you can dynamically create menu items when they are necessary, or programmatically determine "just in time" which menu items to enable based on the current user's role and/or the state or mode of the application. In Delphi, you would use a top menu item's OnClick() event to determine which menu items below it to create, disable, or enable.

3. The MenuComplete() event of the Behavior category is MenuStart's opposite number-it fires after the menu is closed up. You might use this to remove sensitive menu items (which allow access that not all users should have, for example, or that are not applicable to all program modes or states). Delphi does not have a corresponding event, such as OnCloseUp().

Conclusion

This concludes our overview comparing and contrasting Delphi's TForm with C#Builder's System.Windows.Forms.Form. In future articles, we will delve into other aspects of C#Builder from the perspective of a Delphi developer. Until thenhappy coding!

Clay Shannon is a Borland and PDA-certified Delphi developer and the author of "Tomes of Delphi: Developer's Guide to Troubleshooting" (Wordware, 2001) as well as the novel he claims is the strangest one ever written, "the Wacky Misadventures of Warble McGorkle" (see Wacky Warble, etc. for more information on the 4 Novels application, which contains this and three other novels he has penned).

You can find out more about Clay at: Clay's personal page
You can look into Clay's shareware and determine his current availability at:
Clay's business page
You can contact him at:
BClayShannon@aol.com


Server Response from: ETNASC04