Control Class Basics

This chapter explores the Control class, which provides basic functionality for the family of Windows controls. You will learn about fundamental topics like mouse and keyboard handling, focus, and control relations. Along the way, I'll also introduce some important pieces of the System.Drawing namespace that allow you to create structures that represent colors, fonts, rectangles, and points.

The Windows Forms Package

.NET provides two toolkits for user interface design: one for web applications, and one for Windows development. This chapter introduces the Windows Forms package, which allows you to create the traditional rich graphical interfaces found in everything from office productivity software to arcade games. The one detail that all these applications have in common is the fact that they are built out of windows-tiny pieces of screen real estate that can present information and receive user input.

It's easy to imagine that "Windows Forms" refers to a special part of the .NET class library, where fundamental classes like Form and Control are stored. This is true, but it isn't the whole story. More accurately, Windows Forms is the technology that allows the Common Language Runtime to interact with control objects and translate them into the low-level reality of the Windows operating system. In other words, you create objects that represent controls and windows, and the Common Language Runtime handles the details like routing messages, keeping track of window handles, and calling functions from the Windows API.

This idea isn't new. In the past, developers have used the MFC framework in C++, WFC in J++, and Visual Basic's own "Ruby" forms engine to insulate themselves from some of the low-level details of Windows programming. These frameworks all provide an object-oriented wrapper around the Windows API (which, on its own, is a disorganized collection holding hundreds of miscellaneous C routines). These frameworks were well intentioned, but they have all suffered from a few problems.

  • Lack of consistency. If you learn how to use MFC, you still won't know anything about creating Visual Basic user interfaces. Even though every framework ultimately interacts with the Windows API, they have dramatically different object models and philosophies.
  • Thin layer/thick layer problems. Frameworks tend to be either easy to use, or powerful, but not both. MFC is really only a couple of steps away from Windows messages and low-level grunt work. On the other hand, Visual Basic developers have the benefit of a simple framework, but face the lingering dread that they will need to delve into the raw Windows API for complex or unusual tasks that are beyond Visual Basic's bounds.
  • Subjugation to Windows API rules. The Windows API dictates certain harsh realities. For example, once you create a fixed-border window, you can't make its border resizable. These limitations make sense based on how the Windows API is organized, but they often lead to confusing inconsistencies in a framework's object model.

The result of these limitations is that there are essentially two types of frameworks: those that are complicated to use for simple tasks (like MFC), and those that are easy to use for simple tasks, but difficult or impossible to use for complex tasks (like VB). These object models provide a modern way to code user interfaces, but many programmers wonder why they should abstract the Windows API when its restrictions remain.

The NET Solution

.NET addresses these problems by becoming more ambitious. The result is a user interface framework that uses some innovative sleight-of-hand to perform tasks that are difficult or impossible with the Windows API. Here are some examples- tasks that .NET can perform but the Windows API cannot:

  • Change fixed style properties like the selection type of a list box or the border type of a window.
  • Change a form's owner.
  • Move an MDI child window from one MDI parent window to another.
  • Transform an MDI child window into an MDI parent and vice versa.
  • Move controls from one window to another.

Clearly this list includes a couple of tricks that a self-respecting application will probably never need to use. Still, they illustrate an important fact: .NET doesn't just provide an easier object model to access the Windows API; it also provides capabilities that extend it. The result is a framework that works the way you would intuitively expect it to work based on its objects.

  Note 

The samples for this chapter include a project called ImpossibleAPI, which shows one of these "broken rules"-a child window that can jump between different MDI parents whenever the user clicks a button.

All of this raises an interesting question. How can a programming model built on the Windows API actually perform feats that the Windows API can't? Truthfully, there's nothing in the preceding list that couldn't be simulated with the Windows API with a fair bit of effort. For example, you could appear to change the border style of a window by destroying and recreating an identical window. To do so you would have to rigorously track and restore all the information from the previous window.

In fact, this is more or less what takes place in .NET. If you examine the control or window handle (the numeric value that identifies the window to the operating system), you'll see that it changes when you perform these unusual operations. This signifies that, on an operating system level, .NET actually provides you with a new window or control. The difference is that .NET handles this destruction and re-creation automatically. The illusion is so perfect that it's hardly an illusion at all (any more than the illusion that .NET web controls can maintain state, or that television shows continuous movement, rather than just a series of still images).

The cost of this functionality is a runtime that requires a fair bit of intelligence. However, .NET programs already need an intelligent runtime to provide modern features like improved code access security and managed memory. The Windows Forms are just another part of the ambitious and sprawling .NET framework.

Some programmers may still feel they need to resort to the Windows API. You can still use API calls in your .NET applications without too much trouble. However, I encourage you to abandon those habits and start dealing with the new .NET abstractions. Not only is it easier, it also provides a short path to some remarkable features.

  Tip 

One of the best pieces of advice for beginning programmers in traditional development was to master the Windows API. However, in .NET the story changes. In .NET, you'll get the most benefit by studying the low-level details of the .NET object libraries, not the API. Believe it or not, the operating system details will not be as important in the next generation of software development. Instead, you'll need to know the full range of properties, methods, and types that are at your fingertips to unlock the secrets of becoming a .NET guru.

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

The Control Class

Chapter 2 introduced .NET control classes, and examined their place in the overall architecture of an application. To summarize:

  • You create and manipulate controls and forms using .NET classes. The Common Language Runtime recognizes these classes, and handles the low-level Windows details for you.
  • You use a control from the .NET class library by creating an instance of the appropriate class, and adding it to the Controls collection of a container control, like a panel or form. Whether you add the control at design-time or run-time, the task is the same.
  • You configure controls by setting properties. In addition, you can react to control events in two ways: by creating an event handler (typically in the Form class), or by deriving a custom control and overriding the corresponding method.

Every .NET control derives from the base class System.Windows.Forms.Control. Depending on the complexity of the control, it may pass through a few more stages in its evolution. Figure 3-1 shows the basic hierarchy of controls.

click to expand
Figure 3-1: Control hierarchy

On its own, the Control class has no real purpose. It's mainly interesting for the basic functionality that it defines. Sorting through the functionality is no easy task. The 200+ members include countless properties, events that fire to notify you when most common properties are changed (like VisibleChanged, TextChanged, SizeChanged, and so on), and methods that reset values to their defaults, along with some more useful and unusual members. The sections in this chapter sort through many of the properties under broad topic-specific headings like "color" and "focus." Before you begin your exploration, you may want to check out some of the basic and system-related members in Table 3-1.

Table 3-1: Basic Control Members

Member

Description


Name

Provides a short string of descriptive text that identifies your control. Usually (and by default, if you are using Visual Studio .NET), this is the same as the name of the form-level member variable that refers to the control. However, there's no direct relation; the Name property is just provided to help you when iterating through a control collection looking for a specific item.


Tag

Provides a convenient place to store an object. The Tag property is not used by the .NET framework. Instead, you use it to store associated data (like a data object or a unique ID). It's particularly useful when dealing with list controls. Unlike the Tag property in previous versions of Visual Basic, it can store any type of information or even a full-fledged object.


Controls and ControlAdded and ControlRemoved events

The Controls collection stores references to all child controls. You can use the associated events to automate layout logic, as you'll see in Chapter 11.


DesignMode

Returns true if the control is in design mode. It's useful when you are deriving or creating a custom control, so you don't perform time-consuming or system-endangering operations when the program is not running (like an automatic refresh).


Invoke() and InvokeRequired

These members are used in multithreaded programming. InvokeRequired returns true if the control is hosted on a different thread than the current thread of execution. In this case, you should not attempt to use the control directly. Instead, place the code that manipulates the control in a separate method, and pass a delegate that points to this method to the Control.Invoke() method. This ensures that your code will be marshaled to the correct thread.


Dispose()

This method, which is called automatically by the .NET framework as part of the form infrastructure, releases the resources held by a control (like the operating system window handle).

Because every control is derived from the Control class, you can always use it as a lowest common denominator for dealing with some basic Control properties in your application. For example, consider the form below that provides a text box, label, and button control. You'll find this example in the online samples as the ControlMedley project (see Figure 3-2).

click to expand
Figure 3-2: A medley of different controls

The Click event for all these controls (and the underlying form) is handled by one event handler. Using Visual Studio .NET, you can configure event handlers through the Properties window, or enter the code directly:

this.Button1.Click += new System.EventHandler(this.ctrlClick);
this.TextBox1.Click += new System.EventHandler(this.ctrlClick);
this.Label1.Click += new System.EventHandler(this.ctrlClick);

The event handler code is generic: it converts the object reference of the sender into the control type, and then displays a message with the name of the clicked control.

private void ctrlClick(System.Object sender, EventArgs e)
{
 Control ctrl = (Control)sender;
 MessageBox.Show("You clicked: " + ctrl.Name);
}

This is one of the ways that you can replace control arrays, which were a Visual Basic 6 standby. In fact, this technique is more powerful than control arrays because it allows you to handle similar events from any type of control, rather than limiting you to one type of control (e.g., a Button) and one type of event (e.g., Button.Click).

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Position and Size

A control's position is defined by the distance between its top-left corner and the top-left corner of its container. Often, the container is a form, but it could also be a container control like a panel or group box. Similarly, the size is measured as the width and height of the control from the top-left point. By convention, the position measurement is positive in the downward and rightward directions.

  Note 

In the later chapters on GDI+, you will discover that it is possible to create specialized controls that have irregular boundaries by using the Region property. Chapter 5 previews this technique with irregularly shaped forms.

All values are integers measured in pixels. They are provided through several properties (including Top, Left, Right, and Bottom for position, and Width and Height for size), as shown in Figure 3-3. Although you can manipulate any of these properties, the preferred way for setting position is by using the Location property with a Point structure. Similarly, the preferred way to define size is to use the Size property with a Size structure. These basic structures originate from the System.Drawing namespace.

click to expand
Figure 3-3: Control measurements

The following code shows how you can set the location and size of a control using the Point and Size structures.

System.Drawing.Point pt = new System.Drawing.Point();
pt.X = 300; // The control will be 300 pixels from the left
pt.Y = 500; // The control will be 500 pixels from the top.
ctrl.Location = pt;

System.Drawing.Size sz = new System.Drawing.Size();
sz.Width = 500;
sz.Height = 60;
ctrl.Size = sz;

// Just for fun, set another control to have the same size.
ctrl2.Size = ctrl.Size;

By importing the System.Drawing namespace and using some handy constructors, you can simplify this code considerably.

ctrl.Location = new Point(300, 500); // Order is (X, Y)
ctrl.Size = new Size(500, 60); // Order is (Width, Height)

This latter approach is the one that Visual Studio .NET takes when it creates code for your controls at design-time. There are other size and position-related properties, such as those used for anchoring and docking when creating automatically resizable forms. These are described in detail in Chapter 5.

  Tip 

The Visual Studio .NET designer provides a slew of tools that make it easier to lay out controls. Look under the Format menu for options that let you automatically align, space, and center controls.You can also right-click a control and choose to "lock" it in place, ensuring that it won't accidentally be moved while you create and manipulate other controls.

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Color

Every control defines a ForeColor and BackColor property. For different controls, these properties have slightly different meanings. In a simple control like a label or text box, the foreground color is the color of the text, while the background color is the area behind it. These values default to the Windows system-configured settings.

Colors are specified as Color structures from the System.Drawing namespace. It's extremely easy to create a color object, because you have several different options. You can create a color using:

  • An ARGB (alpha, red, green, blue) color value. You specify each value as integer.
  • An environment setting from the current color scheme. You choose the correspondingly named property from the SystemColors class.
  • A predefined .NET color name. You choose the correspondingly named property from the Color class.
  • An HTML color name. You specify this value as a string using the Color-Translator class.
  • An OLE color code. You specify this value as an integer (representing a hexa-decimal value) using the ColorTranslator class.
  • A Win32 color code. You specify this value as an integer (representing a hexadecimal value) using the ColorTranslator class.

The code listing that follows shows several ways to specify a color using the Color, ColorTranslator, and SystemColors types. In order to use this code as written, you must import the System.Drawing namespace.

// Create a color from an ARGB value
int alpha = 255, red = 0;
int green = 255, blue = 0;
ctrl.ForeColor = Color.FromARGB(alpha, red, green, blue);

// Create a color from an environment setting
ctrl.ForeColor = SystemColors.HighlightText;

// Create a color using a .NET name
ctrl.ForeColor = Color.Crimson;

// Create a color from an HTML code
ctrl.ForeColor = ColorTranslator.FromHtml("Blue");

// Create a color from an OLE color code
ctrl.ForeColor = ColorTranslator.FromOle(OxFF00);

// Create a color from a Win32 color code;
ctrl.ForeColor = ColorTranslator.FromWin32(0xA000);

The next code snippet shows how you can transform the KnownColors enumeration into an array of strings that represent color names. This can be useful if you need to display a list of valid colors (by name) in an application.

String[] colorNames;
colorNames = System.Enum.GetNames(typeof(KnownColor));

Changing a color name string back to the appropriate enumerated value is just as easy using the special static Enum.Parse() method. This method compares the string against all the available values in an enumeration, and chooses the matching one.


KnownColor myColor;
myColor = (KnownColor)System.Enum.Parse(typeOf(KnownColor), colorName);

// For example, if colorName is "Azure" then MyColor will be set
// to the enumerated value KnownColor.Azure (which is also the integer value 32).

Incidentally, you can use a few useful methods on any Color structure to retrieve color information. For example, you can use GetBrightness(), GetHue(), and GetSaturation().

Here's a complete program that puts all of these techniques to work. When it loads, it fills a list control with all the known colors. When the user selects an item, the background of the form is adjusted accordingly (see Figure 3-4).

click to expand
Figure 3-4: A color changing form

public class ColorChange : System.Windows.Forms.Form
{
 // (Windows designer code omitted.)
 System.Windows.Forms.ListBox lstColors;

 private void ColorChange_Load(object sender, System.EventArgs e)
 {
 string[] colorNames;
 colorNames = System.Enum.GetNames(typeof(KnownColor));

 lstColors.Items.AddRange(colorNames);
 }

 private void lstColors_SelectedIndexChanged(object sender,
 System.EventArgs e)
 {
 KnownColor selectedColor;
 selectedColor = (KnownColor)System.Enum.Parse(
 typeof(KnownColor), lstColors.Text);
 this.BackColor = System.Drawing.Color.FromKnownColor(selectedColor);

 // Display color information.
 lblBrightness.Text = "Brightness = " +
 this.BackColor.GetBrightness().ToString();
 lblHue.Text = "Hue = " + this.BackColor.GetHue().ToString();
 lblSaturation.Text = "Saturation = " +
 this.BackColor.GetSaturation().ToString();
 }
}
 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Fonts and Text

The Control object defines a Text property that is used by derived controls for a variety of purposes. For a text box, the Text property corresponds to the information displayed in the text box, which can be modified by the user. For controls like labels, command buttons, or forms, the Text property refers to static descriptive text displayed as a title or caption.

The font of a control's text is defined by the Font property, which uses an instance of the System.Drawing.Font class. Note that a Font object does not just represent a typeface (like Tahoma). Instead, it encapsulates all details about the font family, point size, and styles (like bold and italic).

// You can create a font with one of the 13 constructors.
ctrl.Font = new Font("Tahoma", 8, FontStyle.Bold);
  Tip 

A traditional default font for Windows programs is often Microsoft Sans Serif. However, newer applications since Windows 98 consistently use the slightly more attractive Tahoma font (which is also better for input, as it distinguishes between characters like a lowercase "l" and uppercase "I").You should use the Tahoma font in your applications.

A Control.FontHeight property is also provided, which returns the height of your chosen font in pixels. This setting allows you to perform calculations when you are drawing special graphics or text on a control manually. For example, you could manually space lines the appropriate amount when drawing text directly onto a form background.

Note that font families are set using a string, rather than a type-safe enumerated property. If you try to create an object using a name that does not correspond to an installed font, .NET automatically (and unhelpfully) defaults to the Microsoft Sans Serif font. An error does not occur. You may want to explicitly check the Font.Name property to check if this automatic substitution has been made.

To determine what fonts are installed on the system, you can enumerate through them with the System.Drawing.Text.InstalledFontCollection class. The example below adds the name of every installed font to a list box.

InstalledFontCollection fonts = new InstalledFontCollection();
foreach (FontFamily family in fonts.Families)
{
 lstAvailableFonts.Add(family.Name);
}

The online samples for this chapter include a FontViewer utility that uses this technique to create a list of fonts. The user can choose a font from a drop-down list control, and a sample line of text will be painted directly on the window (see Figure 3-5). To perform the font painting, the application uses some of the GDI+ methods you'll see in Chapter 12.

click to expand
Figure 3-5: A simple font viewer

  Note 

Note that the Font property does not always configure the appearance of the text in the control. For example, the font of all windows is defined by the system and is unchangeable. Instead, the Form.Font property defines the default font for all contained controls.

Access Keys

Some controls (namely buttons and menu items) allow a character in their caption to be highlighted and used as an access key. For example, button controls often underline one character in the caption. If the user presses the Ctrl key and that character, the button is "clicked" automatically. To configure these shortcuts keys just add an ampersand (&) before the special letter, as in "Sa&ve" to make "v" the access key. (If you actually want to use an ampersand, you'll need to include the text "&&".)

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Control Relations

Table 3-2).

Table 3-2: Members for Control Relationships

Member

Description


HasChildren

Returns true if the Controls collection has at least one child control.


Controls

A special collection that allows you to add or examine child controls.


Parent

A reference to the parent control (the control that contains this control). This could be a form, or a container control like a group box. You can set this property to swap a control into a new container.


TopLevelControl and FindForm()

The TopLevelControl returns a reference to the control at the top of the hierarchy. Typically, this is the containing form. The FindForm() method is similar, but it returns null if the control is not situated on a form.


Contains()

This method accepts a control, and returns true if this control is a child of the current control.


GetChildAtPoint()

This method accepts a Point structure that corresponds to a location inside the current control. If a child control is located at this point, it is returned.


ContextMenu and Menu

These properties return the associated context menu (for a basic control) or menu object (for a form). Note that context menus must be explicitly displayed by your code before the user can interact with them, while pull-down menus appear automatically.

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Focus and the Tab Sequence

In the Windows operating system, a user can only work with one control at a time. The control that is currently receiving the user's key presses is the control that has focus. Sometimes this control is drawn slightly differently. For example, the button control uses a dotted line around its caption to show that it has the focus. Figure 3-6 shows focused and unfocused buttons with both the classic Windows look and Windows XP visual styles.

click to expand
Figure 3-6: Focused buttons

To move the focus, the user can click the mouse or use the tab key. The developer has to take some care to make sure that the tab key moves focus in a logical manner (generally from left to right and then down the form). The developer also has to choose the control that should receive the focus when the window is first presented.

All controls that support focusing provide a Boolean TabStop property. When set to true, the control can receive focus. When set to false, the control is left out of the tab sequence and can only be reached using a mouse click.

  Tip 

You should set the TabStop property to false for controls that can accept key presses but are not directly accessed by the user in your application. For example, you might provide a DataGrid control, but use it to display static information. Of course, setting the TabStop to false also means the user will need to use the mouse to scroll the control, if its contents extend beyond the bounds of its display region.

To set the tab order, you configure a control's TabIndex property. The control with a TabIndex of 0 gets the focus first. When the user presses the tab key, the focus moves to the next control in the tab order, as long as it can accept focus. Visual Studio .NET provides a special tool, shown in Figure 3-7, that allows you to quickly set tab order. Just select View → Tab Order for the menu. You can then assign TabIndex values by clicking controls in the desired order.

click to expand
Figure 3-7: The Visual Studio .NET tab order tool

Label controls have a TabIndex setting even though they cannot receive focus. This allows you to use a label with an access key. When the user triggers the label's access key, the focus is automatically forwarded to the next control in the tab order. For that reason, you should give your labels an appropriate place in the tab order, especially if they use access keys. (You create an access key by placing an ampersand character before a letter in the label's text.)

Controls that are invisible or disabled (commonly known as "greyed out") are generally skipped in the tab order, and are not activated regardless of the TabIndex and TabStop settings. To hide or disable a control, you set the Visible and Enabled properties, respectively. Note that if you hide or disable a control at design time, the appearance is not modified. This is a deliberate idiosyncrasy designed to make it easier to work with controls at design time, and it is recommended that custom controls also follow this pattern.

Some other properties and methods for managing the focus programmatically are described in Table 3-3.

Table 3-3: Members for Dealing with Focus at Runtime

Member

Description


Focused

Returns true if the control currently has the focus.


ContainsFocus

Returns true if the control or one of its children currently has the focus.


Focus()

Sets the focus to the current control.


SelectNextControl()

Sets the focus to the next control in tab order. This is the programmatic equivalent of pressing the tab key.


GetNextControl()

Returns a reference to the next control in the tab order, without moving the focus.


LostFocus and GotFocus events

These events fire after the focus has moved. They do not give you the chance to stop the focus change, and are thus poor choices for validation routines. If you insist on programmatically resetting the focus in an event handler for one of these events, you may trigger a never-ending loop of focus events. Instead, use the validation events or the ErrorProvider control, which are described in Chapter 4.

  Tip 

The GetNextControl() and SelectNextControl() methods are particularly useful when you are combining some type of interactive wizard or application help, as it can direct the user to an important control or part of the screen.

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Responding to the Mouse and Keyboard

Controls also provide some built-in intelligence for dealing with the keyboard and mouse. These include low-level events that react to key presses and mouse movement, and methods that return key and mouse button state information (Table 3-4).

Table 3-4: Events for Reacting to the Keyboard

Event

Description


KeyDown

Occurs when a key is pressed while the current control has focus. The event provides additional information (through KeyEventArgs) about the state of the Alt and Ctrl keys and the key code.


KeyPress

This is a higher-level event that occurs once the key press is complete (but before the character appears, if the control is an input control). The event provides a KeyPressEventArgs object with information about the key character. The KeyPressEventArgs object also provides a Handled property, which you can set to true to cancel further processing, effectively canceling the character and suppressing its display in an input control.


KeyUp

This occurs when a key is released, just after the KeyPress event. It provides information through a KeyEventArgs object.

Generally you will react to the KeyDown and KeyUp events when you need to react to special characters like the arrow keys, which do not trigger KeyPress events. The KeyPress event is used when you need to restrict input and perform character validation.

If you want to update the display or react to a changed text value in an input control, you would probably not use any of these events. Instead, you should react to the higher-level Changed event, which fires when any modifications are made. The Changed event will fire if you modify the text programmatically or the user deletes the text with the right-click menu.

Forms provide a Boolean KeyPreview property. If you set this to true, your form receives key press events when any of its controls have focus, and it receives these events before the control does (Table 3-5). This technique is useful if you are programming a customized interface or a game where you need to take complete control of the keyboard. You'll also see in Chapter 14 that this technique is useful when you are handling the F1 key to create your own context-sensitive help system.

Table 3-5: Events for Reacting to the Mouse

Event

Description


MouseEnter

Occurs when the mouse moves into a control's region.


MouseMove[*]

Occurs when the mouse is moved over a control by a single pixel. Event handlers are provided with additional information about the current coordinates of the mouse pointer. Be warned that a typical mouse movement can generate dozens of MouseMove events. Event handlers that react to this event can be used to update the display, but not for more time-consuming tasks.


MouseHover

Occurs when the mouse lingers, without moving, over the control for a system-specified amount of time (typically a couple of seconds). Usually, you react to this event to highlight the control that is being hovered over, or update the display with some dynamic information.


MouseDown[*]

Occurs when a mouse button is clicked.


MouseUp[*]

Occurs when a mouse button is released. For many controls, this is where the logic for right-button mouse clicks is coded, although MouseDown is also sometimes used.


Click

Occurs when a control is clicked. Generally, this event occurs after the MouseDown event but before the MouseUp event. For basic controls, a Click event is triggered for left-button and right-button mouse clicks. Some controls have a special meaning for this event. One example is the button control. You can raise a Button Click event by tabbing to the button and pressing the Enter key, or clicking with the left mouse button. Right-button clicks button trigger MouseDown and MouseUp events, but not Click events.


DoubleClick

Occurs when a control is clicked twice in succession. A Click event is still generated for the first click, while the second click generates the DoubleClick event.


MouseWheel

Occurs when the mouse wheel moves while the control has focus. The mouse pointer is not necessarily positioned over the control.


MouseLeave

Occurs when the mouse leaves a control's region.


[*]Indicates that the event handler uses the MouseEvent delegate, and provides additional information about the location of the mouse pointer (and X and Y properties), the mouse wheel movement (Delta), and the state of the mouse buttons (Button).

The MouseMove, MouseDown, and MouseUp events provide additional information about the state of the mouse buttons. Separate MouseDown and MouseUp events are triggered for every mouse button. In this case, the MouseEventArgs.Button property indicates the button the caused the event.

private void lbl_MouseUp(Object sender, System.Windows.Forms.MouseEventArgs e)
{
 if (e.Button == MouseButtons.Right)
 {
 // This event was caused by a right-click.
 // Here is a good place to show a context menu.
 }
}

In the MouseMove event, however, the Button property indicates all the buttons that are currently depressed. That means that this property could take on more than one value from the MouseButtons enumeration. To test for a button, you need to use bitwise arithmetic.

private void lbl_MouseMove(Object sender, System.Windows.Forms.MouseEventArgs e)
{
 if ((e.Button & MouseButtons.Right) == MouseButtons.Right)
 {
 // The right mouse button is currently being held down.
 if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
 {
 // You can only get here if both the left and the right mouse buttons
 // are currently held down.
 }
 }
}

Every control also provides a MousePosition, MouseButtons, and ModifierKeys property for information about the mouse and keyboard. These properties are less useful than the event data. For example, if you use them in an event handler they retrieve information about the current location of the mouse pointer, not the position where it was when the event was triggered. Additionally, the MousePosition property uses screen coordinates, not control coordinates. However, this information can still be useful when you are reacting to an event that doesn't provide mouse information. In Chapter 11 you'll see how it can be used with a dynamic drawing application.

A Mouse Keyboard Example

The mouse and keyboard events have some subtleties, and it's always best to get a solid and intuitive understanding by watching the events in action. The sample code for this chapter provides an ideal example that creates a list of common mouse and keyboard events as they take place. Each entry also includes some event information, giving you an accurate idea of the order these events occur, and the information they provide.

MouseMove events are not included in the list (because they would quickly swamp it with entries), but a separate label control reports on the current position of the mouse (see Figure 3-8).

click to expand
Figure 3-8: An event tracker

For example, here's the code that adds an entry in response to the pic.MouseLeave event:

private void pic_MouseLeave(object sender, System.EventArgs e)
{
 Log("Mouse Leave");
}

The private Log() method adds the string of information, and scrolls the list control to the bottom to ensure that it is visible.

private void Log(String data)
{
 lstLog.Items.Add(data);
 int itemsPerPage = (int)(lstLog.Height / lstLog.ItemHeight);
 lstLog.TopIndex = lstLog.Items.Count - itemsPerPage;
}

Mouse Cursors

One other useful mouse-related property is Cursor. It sets the type of mouse cursor that is displayed when the mouse is moved over a control, and it applies to all child controls. If your application is about to perform a potentially time-consuming operation, you might want to set the form's Cursor property to an hourglass. You can access standard system-defined cursors using the static properties of the Cursors class.

myForm.Cursor = Cursors.WaitCursor;
// (Perform long task.)
myForm.Cursor = Cursors.Default;

You can also create a custom cursor using the Cursor class, load a custom cursor graphic, and assign it to a control.

Cursor myCursor = new Cursor(Application.StartupPath + "\mycursor.cur");
myCustomControl.Cursor = myCursor;

Cursor files are similar to icons, but they are stored in a special .cur file format. Currently, animated cursors (.ani files) are not supported.

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Graphics and Painting

Controls provide a variety of events and properties related to painting (Table 3-6). You'll find information about how to take control of these details and draw your own custom controls in the GDI+ chapters later in this book.

Table 3-6: Graphics-Related Members

Member

Description


BackgroundImage

Allows you to show a picture in the background of a control.


Image (or ImageList and ImageIndex)

These properties aren't a part of the basic Control class, but they do appear in many common controls, including labels, buttons, check boxes, and radio buttons. It allows you to insert a picture alongside or instead of text, as shown in Chapter 6.


ImageAlign

Allows you to align a picture to any side or corner. It's not a part of the basic Control class, but it is found in many common controls.


ResizeRedraw

Indicates whether the control should automatically redraw itself when its size changes. The default is true.


CreateGraphics()

Creates a System.Drawing.Graphics object for the control (otherwise known as a device context). Used for custom drawing, as explained in Chapter 12.


Invalidate() and Refresh()

Triggers a repaint of the control.


Paint event

Occurs when the control is redrawn. Provides the device context, allowing you to perform some custom GDI+ drawing.


Resize event

Occurs when the control is resized, just before redrawing takes place.

You can create an Image object using the static Image.FromFile() method, which reads a standard bitmap format (like a BMP, GIF, JPEG, or PNG file).

button1.Image = Image.FromFile(Application.StartupPath + "\mypic.bmp");

The Image class provides its own set of properties and methods. Some of the most interesting include RotateFlip(), which changes the picture orientation by rotating or inverting it, and GetThumbnailImage(), which returns an image object of the specified size that condenses the information from the original Image.


Image myImage, myThumbnail;
myImage = Image.FromFile(Application.StartupPath + "\mypic.bmp");

// Rotate by 270 degrees and flip about the Y-axis.
myImage.RotateFlip(RotateFlipType.Rotate270FlipY);

// Create a 100 x 100 pixel thumbnail.
myThumbnail = myImage.GetThumbnailImage(100, 100, null, null);

Figure 3-9 shows common controls with embedded pictures. These can be added using the thoughtfully included Image and ImageAlign properties. However, if you place an image over a portion of the control text, the text will overwrite the image. No word wrapping is provided.

click to expand
Figure 3-9: Common control picture support

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Low Level Members

The .NET framework hides the low-level ugliness of the Windows API, but it doesn't render it inaccessible. This is a major advantage of .NET over other frameworks like traditional VB: it adds features without removing any capabilities.

For example, if you want to use a DLL or Windows API function that requires a window handle, you can just retrieve the control's Handle property. The only special .NET consideration is that you should retrieve the handle immediately before you use it. Changing some properties can cause the control to be recreated, and receive a new handle.

You've probably also realized by now that low-level Windows messages are abstracted away in .NET controls, and replaced with more useful events that bundle additional information. If, however, you need to react to a message that doesn't have a corresponding event, you can handle it directly by overriding the PreProcessMessage() method. (You can also attach global message filters for your entire application by using the Application.AddMessageFilter() method).

This book focuses on pure .NET programming, and won't get into most of these tricks, which are really workarounds based on traditional Windows programming. If you do want to examine these features, try starting with the members described in Table 3-7.

Table 3-7: Low-Level Members

Member

Description


Handle

Provides an IntPtr structure (a 32-bit integer on 32-bit operating systems) that represents the current control's window handle.


RecreatingHandle

Set to true while the control is being re-created with a new handle.


GetStyle() and SetStyle()

Sets or gets a control style bit. Generally you will use higher-level properties to accomplish the same thing.


PreProcessMessage(),
cessCmdKey(),
ProcessKeyMessage(),
ProcessKeyPreview(),
and WndProc()

These methods are involved in processing a Windows message, which is represented as a Message structure. You can override these methods and use them to process special messages or block standard ones.

 
Chapter 3 - Control Class Basics
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

The Last Word

This chapter provided a lightning tour through the basics of .NET controls, including how they interact, receive messages, process keystrokes and mouse movements, and handle focus. It also detailed the basic ingredients from the System.Drawing namespace for creating and managing colors, fonts, images, and more. The next chapter puts this high-level theory to a more practical purpose by teaching you to master the basic set of Windows controls.

 
Chapter 4 - Classic Controls
 
byMatthew MacDonald  
Apress 2002
has companion web siteCompanion Web Site
 

Classic Controls



User Interfaces in C#(c) Windows Forms and Custom Controls
User Interfaces in C#: Windows Forms and Custom Controls
ISBN: 1590590457
EAN: 2147483647
Year: 2005
Pages: 142

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net