Creating Controls at RuntimeCreating a control at runtime involves a few simple steps:
To
public class ButtonMaker : System.
A System Tray ApplicationSometimes the only reason you create a control at runtime is for cleaner, more logical code. One example is found with "invisible" controls that don't really appear on a form. These include the standard dialog controls (for changing colors, choosing fonts, and viewing a print preview) that we saw in Chapter 5. You could add these controls to a form at design time, but why bother? Code that creates it dynamically is more readable. On the other hand, if the control is a part of the form (for example, the PrintPreviewControl instead of a PrintPreviewDialog) it makes sense to create and configure it when you are designing the form.
One common example of creating runtime controls for convenience occurs with system tray applications. Often, a system tray application is designed to run
If you build this program by adding a NotifyIcon control on a design-time form, your program will need to load the corresponding form before the icon system tray will appear. If you create the icon in a startup routine at runtime, however, no such limitation applies.
The
Here's the essential code for the dynamic system tray icon:
public class App { // Define the system tray icon control. private NotifyIcon appIcon = new NotifyIcon(); // Define the menu. private ContextMenu sysTrayMenu = new ContextMenu(); private MenuItem displayFiles = new MenuItem("Display New Files"); private MenuItem exitApp = new MenuItem("Exit"); // Define the file system watcher and a list to store filenames. private FileSystemWatcher watch = new FileSystemWatcher(); private ArrayList newFiles = new ArrayList(); public void Start() { // Configure the system tray icon. Icon ico = new Icon("icon.ico"); appIcon.Icon = ico; appIcon.Text = "My .NET Application"; // Place the menu items in the menu. sysTrayMenu.MenuItems.Add(displayFiles); sysTrayMenu.MenuItems.Add(exitApp); appIcon.ContextMenu = sysTrayMenu; // Show the system tray icon. appIcon.Visible = true; // Hook up the file watcher. watch.Path = "c:\\"; watch.IncludeSubdirectories = true; watch.EnableRaisingEvents = true; // Attach event handlers. watch.Created += new FileSystemEventHandler(FileCreated); displayFiles.Click += new EventHandler(DisplayFiles); exitApp.Click += new EventHandler(ExitApp); } // The static startup method. public static void Main() { App app = new App(); app.Start(); // Because no forms are being displayed, you need this // statement to stop the application from automatically ending. Application.Run(); } }
This
private void FileCreated(object sender, System.IO.FileSystemEventArgs e) { newFiles.Add(e.Name); }
To enable the system tray icon menu, you also need to add two more event handlers. The menu only provides two options: exit the application or display another window that lists the name of changed files (shown in Figure 11-3).
private void ExitApp(object sender, System.EventArgs e) { Application.Exit(); } private void DisplayFiles(object sender, System.EventArgs e) { FileList frmFileList = new FileList(); frmFileList.FillList(newFiles); frmFileList.Show(); }
Finally, the following code is used in the FileList form to display the ArrayList information. Figure 11-3 shows how the list of changed files might look.
public class FileList : System.Windows.Forms.Form { // (Designer code omitted.) private void cmdClose_Click(object sender, System.EventArgs e) { this.Close(); } public void FillList(ArrayList list) { lstFiles.DataSource = list; } }
Of course, though this is a useful example, it's not the only approach to freeing the NotifyIcon from the confines of a form. You could also create the App class as a component by inheriting from System.ComponentModel.Component. You'll also need to add some additional code to your class, as shown in this template:
public class Component1 : System.ComponentModel.Component { private System.ComponentModel.Container components = null; public Component1(System.ComponentModel.IContainer container) { container.Add(this); InitializeComponent(); } public Component1() { InitializeComponent(); } private void InitializeComponent() {
This code allows the component to support the disposable pattern and host design-time controls. It looks
Every component has the ability to host design-time controls-just drag and drop the control onto the design time view of the class, and Visual Studio .NET will create the code in the special hidden designer region. Using this approach, you could configure the NotifyIcon and the menu items at design time, without needing to tie them to a form. This is the approach used in Chapter 8 to create a design-time picture box that can store an icon file for the HelpIconProvider control.
Using Controls in a Drawing ProgramDrawing programs exist in two basic flavors. Painting programs, like Microsoft Paint, allow users to create a bitmap with static content. Once the user draws a shape or types some text onto the drawing area, it can't be modified or rearranged. In more sophisticated vector-based drawing programs (everything from Adobe Illustrator to Microsoft Visio), the drawing is actually a collection of objects. The user can click and change any object at any time or remove it entirely.
It is relatively easy to create a bitmap drawing program once you learn the appropriate functions for drawing on a form with GDI+. A vector-based drawing or diagramming program, however, is not so easy, because you need to keep track of every object and its location individually. When the user clicks on the drawing surface, you may need some
One shortcut to making a drawing program is allowing the user to create drawings out of dynamically generated controls. The next example does exactly that, and demonstrates some fundamentals about handling events and context
The basic application (shown in Figure 11-4) allows the user to create squares of any
The application begins with an empty canvas. To create a square, the user right-clicks the form drawing area, and chooses Create New Square from the context menu. The square then appears (with a default size) at the current mouse location. The code that creates the square is shown in the following:
private void mnuNewSquare_Click(object sender, System.EventArgs e) { // Create and configure the "square". Label newLabel = new Label(); newLabel.Size = new Size(40, 40); newLabel.BorderStyle = BorderStyle.FixedSingle; // To determine where to place the label, you need to convert the // current
There are three things the user can do with a square once it is created:
All these actions happen in response to the MouseDown event. At this point, the code retrieves a reference that points to the control that
// Keep track of when fake drag or resize mode is enabled. private bool isDragging = false; private bool isResizing = false; // Store the location where the user clicked the control. private int clickOffsetX, clickOffsetY; private void lbl_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) { // Retrieve a reference to the active label. Control currentCtrl; currentCtrl = (Control)sender; if (e.Button == MouseButtons.Right) { // Show the context menu. currentCtrl.ContextMenu.Show(currentCtrl, new Point(e.X, e.Y)); } else if (e.Button == MouseButtons.Left) { clickOffsetX = e.X; clickOffsetY = e.Y; if ((e.X + 5) > currentCtrl.Width && (e.Y + 5) > currentCtrl.Height) { // The mouse pointer is in the bottom right corner, // so resizing mode is appropriate. isResizing = true; } else { // The mouse is somewhere else, so dragging mode is // appropriate. isDragging = true; } } }
The MouseMove event changes the position or size of the square if it is in drag or resize mode. It also changes the cursor to the resize icon to alert the user when the mouse pointer is in the bottom right corner.
private void lbl_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) { // Retrieve a reference to the active label. Control currentCtrl; currentCtrl = (Control)sender; if (isDragging) { // Move the control. currentCtrl.Left += e.X - clickOffsetX; currentCtrl.Top += e.Y - clickOffsetY; } else if (isResizing) { // Resize the control. currentCtrl.Width = e.X; currentCtrl.Height = e.Y; } else { // Change the pointer if the mouse is in the bottom corner. if ((e.X + 5) > currentCtrl.Width && (e.Y + 5) > currentCtrl.Height) { currentCtrl.Cursor = Cursors.SizeNWSE; } else { currentCtrl.Cursor = Cursors.Arrow; } } }
Figure 11-5 shows the process of resizing a square.
The MouseUp event ends the dragging or resizing operation.
private void lbl_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) { isDragging = false; isResizing = false; }
Finally, the context menu provides a single option which, when clicked, allows the user to change the square's fill color using a common color dialog box. Note that the code retrieves the active control through the SourceControl property of the ContextMenu control.
private void mnuColorChange_Click(object sender, System.EventArgs e) { // Show color dialog. ColorDialog dlgColor = new ColorDialog(); dlgColor.ShowDialog(); // Change label background. mnuLabel.SourceControl.BackColor = dlgColor.Color; }
Figure 11-6 shows how a square's background color can be modified using this color dialog.
As written, this simple example could easily grow into a more sophisticated drawing framework. For example, you could add
|
|||||||||||||||||||||||||||||||||||||||