Creating Windows Forms Applications

1
288

Working without a Visual Designer

Tools are a good thing because they help you perform tasks faster and more accurately. However, tools aren’t indispensible. You use a tool when it’s available to gain the benefits it provides, but a good developer can get by without the tools. The Visual Designer found in Visual Studio is simply a tool, nothing more. It doesn’t perform any sort of magic or suddenly make your application do things that it couldn’t in the past. In fact, you can dispense with the Visual Designer when creating any application using Visual Studio. Developers simply use the Visual Designer because it performs its task so admirably.

Unfortunately, tools can also hide the work they perform. The Visual Designer does a lot of work in the background that most developers could figure out given time but most developers don’t bother to learn. Consequently, when approaching a Windows Forms application for IronPython, you might find that you have to develop some new skills to get the window to display at all. Let’s hope that IronPython will eventually become integrated with the Visual Studio IDE enough that you won’t have to create your Windows Forms interface by hand. In the meantime, the following sections describe what the Visual Designer does in a little more detail and then tell you what you need to know to work without it.

Understanding How the Visual Designer Works

The Visual Designer is nothing more than a code writer. When you place a new control on the form, the Visual Designer writes the code to create the control in the form when the application executes. Sizing and other property changes are merely coding additions to the form code. When you’re done, you end up with a class that defines a form using code — code that you didn’t write.

The class that Visual Designer creates appears in a separate file so that the Windows Form code doesn’t interfere with code you write. For example, if you create a form named frmMain.CS, the designer code appears in frmMain.Designer.CS. When you open the frmMain.Designer.CS file, you see code such as that shown in Figure 8-1.

The code in Figure 8-1 shows that the form has two buttons (btnOK and btnCancel) and a label (label1) defined. You then see the code used to define the properties for each control. For example, the btnOK.DialogResult property is set to System.Windows.Forms.DialogResult.OK. There isn’t anything mysterious about this code — you’ve probably written similar code yourself, so you can easily do it now.

Understanding the Elements of a Window

Before you tackle an IronPython Windows Forms application, it’s a good idea to spend a little time looking at simple examples in languages you know. If you don’t have a simple example to review, check out CSharpExample, which is provided with the book’s source code. The frmMain .Designer.CS file is the best place to start.

The Visual Designer writes the code used to create a form.
Figure 8-1: The Visual Designer writes the code used to create a form.

All Windows Forms applications inherit from System.Windows.Forms.Form. This class provides the basic window, which is configurable for a variety of needs. All of the properties you see in the Visual Designer are also available programmatically. For example, if you want to create a dialog box, you simply change the FormBorderStyle property.

Inside the window are controls. It’s important to realize that even if controls appear within another control, you define them as part of the window. Now, you might add the control to another control, but eventually, the host control is added to the window or it isn’t displayed.

Emulating the Visual Designer Results

Emulating the Visual Designer Results When working with IronPython, you become the Visual Designer. It helps if you have a familiarity with what the Visual Designer does so that you can emulate its output and produce an application faster.

There are certain features the Visual Designer needs that you don’t need to consider when working with IronPython. For example, you don’t need to include a call to SuspendLayout() or ResumeLayout(), which prevent multiple Layout events when using the Visual Designer. You also don’t need to include any of the designer variables or the Dispose() method.

It helps to create two files when building a Windows Forms application. The first file contains the client application — the code that instantiates a copy of the Windows Forms class, provides event handlers, and actually runs the application. The second file contains the code used to create the form. This file contains the property settings that create a unique application window. Throughout this chapter, you’ll discover that the use of two files does make things considerably easier.

When you use the Visual Designer, you probably set up the controls and assign all the property values in one step. When working with IronPython, you want to start by creating the client application and a basic window and then try the code. After this step, you add controls, position them, and try the application again. Only after you complete these first two tasks do you start cluttering the files with the property settings that define a completed application.

Defining Windows Forms

Creating a Windows Forms application from scratch need not be difficult. The basic problem is one of layout. Often, you can perform the layout using another language and copy the layout information from the Visual Designer window (such as the one shown in Figure 8-1). In fact, later chapters will show you how to mix languages so you can get the best of all worlds. For now, it’s important to see how you’d implement a simple Windows Forms application using IronPython. The following sections describe the five basic steps in creating a Windows Forms application:

  1. Define any required forms.
  2. Initialize the application, including the forms.
  3. Add event handlers for control events.
  4. Perform useful work when an event occurs.
  5. Run the application by starting the main form.

Creating the Window Code

Every Windows Forms application begins with one or more windows. You define the code used to create the window, starting with the window itself. As previously explained, creating a class that inherits from the System.Windows.Forms.Form class is enough to create the window, but then you need to add controls to the window to define the interface. The following sections describe how to create a basic control structure for a window and then how to enhance that structure to make life easier for the user.

Creating the Basic Control Structure

It’s helpful to lay out your controls and give them the visual appearance that you expect before doing anything else. The example is relatively simple — it includes two buttons and a static label containing a message. The first button displays a message, while the second ends the application. Listing 8-1 shows the code required to create the interface for this example.

Listin g 8-1: Designing the form and controls

[code]

# Set up the path to the .NET Framework.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
clr.AddReference(‘System.Drawing.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
import System.Drawing.Point
class frmMain(System.Windows.Forms.Form):
# This function performs all of the required initialization.
def InitializeComponent(self):
# Configure btnOK
self.btnOK = System.Windows.Forms.Button()
self.btnOK.Text = “&OK”
self.btnOK.Location = System.Drawing.Point(263, 13)
# Configure btnCancel
self.btnCancel = System.Windows.Forms.Button()
self.btnCancel.Text = “&Cancel”
self.btnCancel.Location = System.Drawing.Point(263, 43)
# Configure lblMessage
self.lblMessage = System.Windows.Forms.Label()
self.lblMessage.Text = ‘This is a sample label.’
self.lblMessage.Location = System.Drawing.Point(13, 13)
self.lblMessage.Size = System.Drawing.Size(120, 13)
# Configure the form.
self.Text = ‘Simple Python Windows Forms Example’
self.ClientSize = System.Drawing.Size(350, 200)
# Add the controls to the form.
self.Controls.Add(self.btnOK)
self.Controls.Add(self.btnCancel)
self.Controls.Add(self.lblMessage)

[/code]

The code begins by adding the .NET Framework path to sys.path. It then uses clr.AddReference() to add references for both System.Windows.Forms.DLL and System.Drawing.DLL. Finally, the code imports a number of .NET assemblies. The example uses individual import statements for each assembly. Theoretically, you can combine all the imports into a single import statement, but most developers find using individual import statements is a lot more readable and easy to maintain.

The next step is to create the window class, frmMain, which inherits from System.Windows.Forms .Form. It’s important to remember that this class is simply a blueprint, as all classes are, and that your application can create as many instances of it as required. This is the reason that every element in the code is prefaced with self, which refers to the current instance.

You can perform the configuration tasks found in InitializeComponent() in any order. Most developers like to create the controls first, followed by the client area, as shown in Listing 8-1. However, you must add the controls to the window after you configure them, or the controls will appear partially configured onscreen (and make for a difficult debugging chore). For this reason, the code calls on self.Controls.Add() last to add the controls to the window. Figure 8-2 shows the output from the example based on the configuration criteria in Listing 8-1.

The form created by this example is simple but works for demonstration purposes.
Figure 8-2: The form created by this example is simple but works for demonstration purposes.

It’s easy to get lost while configuring the controls, especially if you have multiple control levels to consider (such as when using container controls). Most controls require five configuration steps and you want to perform them in this order to ensure you accomplish them all:

  1. Instantiate the control.
  2. Set the control’s output text, if any.
  3. Set the control’s position on the window (starting from the upper-left corner).
  4. (Optional) Set the control’s size when the default size won’t work.
  5. (Optional) Add control enhancements, such as the tooltips discussed in the section “Making the Application Easier to Use,” which follows.

Making the Application Easier to Use

One interesting point about using IronPython to create a Windows Forms application is that it tends to reduce the number of unnecessary bells and whistles. However, don’t make your application so Spartan that the user spends a lot of head-scratching time trying to figure out how to use it. You have to create a balance between the amount of code you must write and the needs of the user.

An easy addition is the use of speed keys. Simply type an ampersand (&) in front of the letter you want to use for a speed key. When the user presses Alt+<Speed Key Letter>, the application selects the associated control. Providing speed keys is incredibly easy, yet has a big impact on user productivity and also makes it possible for users who can’t work with a mouse to use your application. The sample application described in Listing 8-1 already has this feature.

When users rely on the keyboard instead of the mouse, they also want to select the controls in order — from left to right and from top to bottom (unless your language has a different natural order for reading text). In some cases, this means changing the TabIndex property. The form defaults to the tab order provided by the order in which the controls appear in the code. Careful placement of the controls often negates the need to change the TabIndex property.

Windows can also have default actions. The two most common default actions occur when you press Enter (default acceptance) and Escape (default cancel). Providing controls for these two features helps users move quickly through a form and can speed processing of wizards. Here’s the code you use to provide default actions for the example application.

[code]

self.AcceptButton = self.btnOK
self.CancelButton = self.btnCancel

[/code]

You must configure the controls for a form before you configure any default actions. The control must exist before you make the assignment to either self .AcceptButton or self.CancelButton. Otherwise, you receive an ambiguous error message when you run the application that will prove difficult to debug.

Another useful enhancement that doesn’t require a lot of work is the ToolTip component. Using tooltips makes it easier for the user to figure out how an application works. These bits of mini-help are quite useful and they provide all that many users need to become proficient quickly. Tooltips also make it easy for someone who hasn’t used the application for a while to get back up to speed quickly. Tooltips also provide input to screen readers (commonly used by those with special visual needs) so that the screen reader can tell the user the control’s purpose. Here’s the code used to add ToolTip to the window.

[code]
# Add a tooltip control.
self.Tips = System.Windows.Forms.ToolTip()
[/code]

You don’t add the ToolTip component to the window itself using self.Controls.Add() because ToolTip lacks a visual interface. The ToolTip component sits in the background and waits for a visual control to require its services. The following code shows how to add a ToolTip to individual controls.

[code]
self.Tips.SetToolTip(self.btnOK, ‘Displays an interesting message.’)
[/code]

The SetToolTip() method of the ToolTip adds a tooltip to the control specified by the first argument. The message appears as the second argument. Figure 8-3 shows a typical example of a tooltip created using this technique.

Visual Studio provides a wealth of additional enhancements that usually won’t require a lot of implementation time, but can make a big difference to the user. The best way to determine how to add these enhancements is to add them to a C# or Visual Basic.NET application and then view the Visual Designer file. For example, you might want to add information to the AccessibleDescription, AccessibleName, and AccessibleRole

Adding tooltips is easy and helps most users considerably.
Figure 8-3: Adding tooltips is easy and helps most users considerably.

properties when your application will see even moderate use by those with special physical needs. Obviously, you don’t add precisely the same code to your IronPython application that the Visual Designer adds to the C# or Visual Basic.NET application, but the Visual Designer file provides enough insight to make the transition to IronPython work with relative ease.

Performing Quick Form Tests

You’ll want to see your form from time to time, before you’ve written the rest of the application code. Fortunately, you’re using IronPython, so you have the interpreter at your disposal. The following steps outline a quick way to see what your form looks like.

  1. Start the interpreter (IPY.EXE) in the directory that contains the form code.
  2. Type from frmMain import * and press Enter. This step imports the form into the interpreter for testing.
  3. Type TestForm = frmMain() and press Enter. This step instantiates a copy of frmMain that you’ll use for testing.
  4. Type TestForm.InitializeComponent() and press Enter. In this step, you execute the code used to create and configure the controls within the form.
  5. Type TestForm.ShowDialog() and press Enter. At this point, you see the dialog box appear onscreen, just as you’d normally see it when executed from the application. However, you see just the dialog box and its controls. The dialog box is pretty much non-functional because you don’t have any event handlers in place.
  6. After you finish viewing the dialog box, click Cancel or the close button. This act closes the dialog box. You’ll see a dialog result value as part of the output, as shown in Figure 8-4.
This output shows a typical form-viewing session in the interpreter.
Figure 8-4: This output shows a typical form-viewing session in the interpreter.

Even though this technique is a little limited, it does give you a view of your form so that you can easily fix any problems. You must repeat the entire process every time you want to test changes to the form. The act of importing the form code means that you won’t see any changes within the interpreter until you import the form code again. In short, if you make changes and don’t see them during testing, it’s probably because you didn’t exit the interpreter and start over.

Initializing the Application

After you’ve created a form and tested its functionality, you can begin to create an application that uses the form. The first step in this process is to initialize the application as shown in Listing 8-2. You might think that adding all of the .NET assembly imports is unnecessary given that the form code already has them, but it turns out that you really do need to include the imports or the code won’t work.

Listin g 8-2: Starting the application

[code]
# Set up the path to the .NET Framework.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
# import the form.
from frmMain import *
# Define the Windows Form and the elements of this specific instance.
TestForm = frmMain()
TestForm.InitializeComponent()
[/code]

This part of the example shows all the required startup code. It begins by adding the path and making all the required imports, as usual. The code ends by creating TestForm, the object used to interact with the frmMain instance. A common error developers make is that they don’t call TestForm.InitializeComponent(). When this problem occurs, the form won’t look as expected — it may not appear at all. In fact, sometimes the IronPython interpreter will simply freeze, requiring you to force a restart.

Providing Handlers for the Control Events

After the form initialization is complete, you have access to a form object and could display it using ShowDialog(). However, the form object still won’t perform any useful work because you don’t have any event handlers in place. You might be used to working with other .NET languages, where double-clicking the control on the form or double-clicking one of the event entries in the Properties window performs all the configuration for you. However, when working with IronPython, you must perform these steps manually. Here is the code used to assign an event handler to a particular control event.

[code]
# Always add event handlers after defining the Windows Form.
TestForm.btnOK.Click += btnOK_Click
TestForm.btnCancel.Click += btnCancel_Click
[/code]

Notice that you must drill down into the form in order to access the controls. Consequently, you must provide the full path to the event, such as TestForm.btnOK.Click. IronPython lets you use the += operator to add an event handler to the Click event. If you want to remove the event handler, all you need to use is the –= operator instead. Assigning the event handler is as easy as providing the method name as shown in the code.

IronPython events can have more than one handler, as you’ll see in the section “Developing Your Own Events.” As with any OOP language, you simply keep adding event handlers with the correct signature (a combination of the right return type and input arguments). Unlike most OOP languages, IronPython tends to make things very simple and you could actually find this approach detrimental because there are times where the interpreter will output odd messages instead of telling you that the event handler won’t work for the event to which you assigned it.

Performing Some Useful Work as the Result of User Input

The Windows Forms application now has a form object and methods assigned to some of the events. Of course, this means you need to provide the event handlers required to do the work. Event handlers can come in a number of forms. When working with Windows Forms controls, you may never even need the arguments that IronPython passes. The following code shows the event handlers used for this example.

[code]
# Define the event handlers.
def btnOK_Click(*args):
# Display a message showing we arrived.
System.Windows.Forms.MessageBox.Show(‘Hello!’)
def btnCancel_Click(*args):
# Close the application.
TestForm.Close()
[/code]

The code is a standard IronPython function. However, notice that the arguments have an asterisk (*) in front of them. Essentially, this means that all the arguments passed to the event handler end up in a sequence. In this case, that means you’ll end up with a list of event arguments. For a button handler, you obtain two arguments:

  • Sender
  • Mouse arguments

Let’s say for a minute that the user has clicked OK. Then the sender argument would contain

[code]
System.Windows.Forms.Button, Text: &OK
[/code]

and the mouse arguments would contain the following (in a single line, rather than the multiple lines

[code]
<System.Windows.Forms.MouseEventArgs object at 0x000000000000002B
[System.Windows.Forms.MouseEventArgs]>
[/code]

Sometimes you need to access the event arguments. In this case, you could easily rewrite this event handler as shown here:

[code]
def btnOK_Click(Sender, MArgs):
# Display a message showing we arrived.
SenderText = ‘Text: ‘ + Sender.Text
MouseText = ‘nButton: ‘ + MArgs.Button.ToString()
MousePosit = ‘nX/Y: ‘ + MArgs.X.ToString() + ‘/‘ + MArgs.Y.ToString()
System.Windows.Forms.MessageBox.Show(SenderText + MouseText + MousePosit)
[/code]

When you run this code, you see more of the information that the event handler receives. It turns out that Sender is actually a System.Windows.Forms.Button object and you can perform any task you’d normally perform with that object. Likewise, MArgs is a System.Windows.Forms.MouseEventArgs object. The example code shows only a few of the items you receive. Figure 8-5 shows the output when you click OK using this alternate event handler.

Event handlers receive a number of pieces of information from .NET.
Figure 8-5: Event handlers receive a number of pieces of information from .NET.

Figure 8-5 shows that the button Text property is &OK. You can read more about the Button class at http://msdn.microsoft.com/library/system.windows .forms.button.aspx. The user clicked the left mouse button, and the X/Y position shows the mouse pointer location within the control. You can read more about the MouseEventArgs class at http://msdn.microsoft.com/library/system.windows .forms.mouseeventargs.aspx.

The IronPython environment won’t always provide a default value for some class properties. In many cases, the Visual Designer provides these defaults in the background. If you don’t define a property, such as Name, then you won’t see this property defined for the object sent to the event handler.

Running the Application

The window is complete. A user can now see the window displayed, interact with it, and expect some type of output from the application. However, one more task remains. The application that’s running now is an IronPython interpreted application, not a .NET Windows Forms application. In order to get a true Windows Forms application, you must perform one more step as shown in the following code.

[code]
# Run the application.
System.Windows.Forms.Application.Run(TestForm)
[/code]

This code exists in your C# and Visual Basic.NET applications as well. However, you normally find it hidden in the Program.CS or Program.VB file (along with a lot of other code that you won’t need for this example). All that this code says is that the Common Language Runtime (CLR) should execute the .NET application using TestForm as a starting point.

Clicking OK displays a familiar message box.
Figure 8-6: Clicking OK displays a familiar message box.

When you run the default version of the application, you see a dialog box like the one shown previously in Figure 8-1. Clicking OK displays a simple message like the one shown in Figure 8-6. Clicking Cancel will close the dialog box, much as you might expect. Congratulations! You’ve just created a simple Windows Forms application using IronPython — an example that will help you create more complex Windows Forms applications in the future.

Interacting with Predefined and Custom Events

Being able to define, create, and respond to events is a major part of working in a windowed environment. Even when a .NET developer isn’t working in a windowed environment, the use of events and delegates is an important part of creating responsive applications. Whenever an event occurs, the application must be able to respond to it, no matter what the source of the event might be (including Windows messages, such as a shutdown warning). The following sections discuss events and delegates.

Handling Events Using Existing Classes

The .NET Framework includes a host of controls and components that you find useful in IronPython. For example, one of the more interesting (and useful) components is Timer. The Timer component lets you set an interval for automatic events. All you need to do is set the interval between event ticks. You’ve probably used this component before, but working in IronPython introduces a few twists. Listing 8-3 shows the form code for this example.

Listin g 8-3: Creating a form containing a component

[code]
# This function performs all of the required initialization.
def InitializeComponent(self):
# Configure the form.
self.ClientSize = System.Drawing.Size(350, 200)
self.Text = ‘Using a Timer Example’
# Configure btnStart
self.btnStart = System.Windows.Forms.Button()
self.btnStart.Text = “&Start”
self.btnStart.Location = System.Drawing.Point(263, 13)
# Configure btnQuit
self.btnQuit = System.Windows.Forms.Button()
self.btnQuit.Text = “&Quit”
self.btnQuit.Location = System.Drawing.Point(263, 43)
# Configure lblTime
self.lblTime = System.Windows.Forms.Label()
self.lblTime.Text = System.DateTime.Now.ToLongTimeString()
self.lblTime.Location = System.Drawing.Point(13, 13)
self.lblTime.Size = System.Drawing.Size(120, 13)
# Configure objTimer
self.objTimer = System.Windows.Forms.Timer()
self.objTimer.Interval = 1000
# Add the controls to the form.
self.Controls.Add(self.btnStart)
self.Controls.Add(self.btnQuit)
self.Controls.Add(self.lblTime)
[/code]

Quite a bit of this form code should look familiar from Listing 8-1. However, lblTime is set to show the long time format (using ToLongTimeString()) based on the application starting time. You can use any value desired for control properties as long as you perform the required conversions. In this case, the code calls System.DateTime.Now.ToLongTimeString() to obtain the correct string value.

Notice that the code doesn’t add objTimer to the form using self.Controls.Add(). As with all components, objTimer waits in the background for a control to call on it for services. However, unlike the ToolTip component used in the section “Making the Application Easier to Use,” you must configure a Timer component as part of the form code. This example sets the objTimer interval to 1 second (1,000 ms). Figure 8-7 shows the initial presentation of the dialog box.

The started application shows the current long time format.
Figure 8-7: The started application shows the current long time format.

The initial form shown in Figure 8-7 appears quite useful, but the time doesn’t update. In order to make the time update, you must provide an event handler for the objTimer.Tick event. Listing 8-4 shows the code required to create the form, display it onscreen, and then handle the various events required to make this application work.

Listin g 8-4: Handling timer events in a form

[code]
# Set up the path to the .NET Framework.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
# import the form.
from frmUseTimer import *
# Define the event handlers.
def btnStart_Click(*args):
# Check the button status.
if TestForm.btnStart.Text == ‘&Start’:
# Start the timer.
TestForm.objTimer.Start()
# Change the button text.
TestForm.btnStart.Text = ‘&Stop’
else:
# Start the timer.
TestForm.objTimer.Stop()
# Change the button text.
TestForm.btnStart.Text = ‘&Start’
def btnQuit_Click(*args):
# Close the application.
TestForm.Close()
def objTimer_Tick(*args):
# Handle the timer tick.
TestForm.lblTime.Text = System.DateTime.Now.ToLongTimeString()
# Define the Windows Form and the elements of this specific instance.
TestForm = frmMain()
TestForm.InitializeComponent()
# Always add event handlers after defining the Windows Form.
TestForm.btnStart.Click += btnStart_Click
TestForm.btnQuit.Click += btnQuit_Click
TestForm.objTimer.Tick += objTimer_Tick
# Run the application.
System.Windows.Forms.Application.Run(TestForm)
[/code]

This example begins as any Windows Forms application does, by providing access to the .NET Framework directory and then importing the required assemblies after adding any required references. The btnStart_Click() event handler doesn’t just display a simple message this time. When the user clicks Start, the code checks the current TestForm.btnStart.Text value. When this value is &Start, the code calls TestForm.objTimer.Start(), which starts the timer and changes the TestForm.btnStart.Text value to &Stop. When the value is &Stop, the opposite sequence of events occurs. Figure 8-8 shows the dialog box with the timer started.

The Timer component updates the time shown in this example.
Figure 8-8: The Timer component updates the time shown in this example.

Starting objTimer causes the component to begin emitting Tick events. The objTimer_Tick() handles these Tick events by updating the TestForm.lblTime.Text with the latest time. Notice that even though you don’t add objTimer to the window, you must still add the event handler to the event using TestForm.objTimer .Tick += objTimer_Tick. In fact, you’ll often find that components require you to handle a number of events because events are the main form of communication for components (versus controls, which rely on their interface elements for interaction and use events only to register control changes so you can act on them).

Developing Your Own Events

It’s true that the .NET Framework comes with more events than you’ll probably use in an entire lifetime. However, it’s also true that IronPython developers simply can’t see every need (and even if they did, it just wouldn’t pay to create a general event that only two people would ever use). Consequently, you’ll eventually need to create your own events to handle those situations that don’t neatly fit within someone else’s pigeonhole. The following sections show how to create a simple custom event that you can use as a model for creating events of your own.

Creating the Event Class

An event class defines the custom event. Of course, you have to also write code to implement and respond to the custom event, but let’s focus on the definition first. The most basic event class must include four activities:

  • An initialization that defines a container for holding a list of event handlers
  • A method for adding new event handlers
  • A method for removing old event handlers
  • A method that calls the event handlers in turn whenever an external source invokes (fires) the event

You can always add more items to your event handler, but a basic event handler must include these four items. With this in mind, Listing 8-5 shows a basic event definition. You might be surprised at how little code you need to perform this task.

Listin g 8-5: Defining a simple event class

[code]
class MyEvent:
# Create the initial HandlerList.
def __init__(self):
self.HandlerList = set()
# Add new handlers to the list.
def Add(self, NewHandler):
self.HandlerList.add(NewHandler)
# Remove existing handlers from the list.
def Remove(self, OldHandler):
try:
self.HandlerList.remove(OldHandler)
except KeyError:
pass
# Invoke the handler.
def Fire(self, Msg):
# Call each of the handlers in the list.
for self.Handler in self.HandlerList:
self.Handler(Msg)
[/code]

The code begins with __init__(), which IronPython calls automatically anytime someone creates an event of this type. The only purpose of __init__() is to create a container for storing event handler references. You can use any container you want, but the example relies on a set() because this particular container works very well as a means of storing event handlers. The initialization code creates an empty set() that you’ll later fill with event handler references.

There are many different ways to create a delegate using IronPython — this chapter shows one of the more basic techniques you can use. However, you might find that this technique doesn’t work for your particular need. It’s always a good idea to look at what other people are doing with Python and IronPython. For example, there’s another example of an event system at http://www.valuedlessons.com/2008/04/events-in-python.html. In this case, the author wanted to create a lightweight event system that mimicked C#. Another, more ambitious example is at http://code.activestate.com/ recipes/410686/. Don’t forget the pyevent.py class provided with the IronPython tutorial (it does work). The point is that you don’t want to give up on events in IronPython. If what you want doesn’t exist now, you can probably create it without too much trouble.

The Add() method simply adds a reference to the event handler passed as one of the arguments. In this case, the code uses the self.HandlerList.add() method to perform the task.

Likewise, Remove() takes the requested event handler reference out of the container using the self.HandlerList.remove() method. Because someone could pass an invalid reference, the code must perform this task within a try…except block, capturing the KeyError exception as needed. The pass keyword tells IronPython not to do anything about the exception. Normally, you’d provide some type of error handling code.

Invoking (firing) the event is the responsibility of the Fire() method. This event accepts a message, Msg, and does something with it. Precisely what happens with Msg is up to the event handler. All the event knows is that it must accept a Msg object and pass it along to the event handler. As you can see, the code calls each event handler reference in turn and passes the Msg to it. In short, the event handler must have the proper signature (set of input arguments) to handle the event correctly.

It’s important to realize that event handlers may not receive calls in the order in which you add them to the event. Consequently, you should never create event handlers that depend on a specific order of calling.

Devising an Event Class Test Form

Now that you have a new event, you’ll want to do something with it. Listing 8-6 shows code used to create the form for this example.

Listin g 8-6: Creating an event class test form

[code]
# Set up the path to the .NET Framework.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
clr.AddReference(‘System.Drawing.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
import System.Drawing.Point
class frmMain(System.Windows.Forms.Form):
# This function performs all of the required initialization.
def InitializeComponent(self):
# Configure the form.
self.ClientSize = System.Drawing.Size(350, 200)
self.Text = ‘Creating an Event Example’
# Configure btnFireEvent
self.btnFireEvent = System.Windows.Forms.Button()
self.btnFireEvent.Text = “&Fire Event”
self.btnFireEvent.Location = System.Drawing.Point(263, 13)
# Configure btnQuit
self.btnQuit = System.Windows.Forms.Button()
self.btnQuit.Text = “&Quit”
self.btnQuit.Location = System.Drawing.Point(263, 43)
# Add the controls to the form.
self.Controls.Add(self.btnFireEvent)
self.Controls.Add(self.btnQuit)
[/code]

It’s generally a good practice to keep the event, form, and operational code separate. Doing so will make the application easier to debug later. This example actually uses three separate files, even though it’s a very simple example. Make sure you follow this principle when creating events of your own.

As you can see, this form is a very simple version of the other forms used so far in this chapter. All it does is provide access to two buttons: one to fire the event and the other to close the form. Figure 8-9 shows the output from this code.

A simple form used to test the custom event and associated handler.
Figure 8-9: A simple form used to test the custom event and associated handler.

Running the Code

It’s time to try out the new event and its associated test form. Listing 8-7 shows the code required for this part of the example.

Listin g 8-7: Testing the event class

[code]
# Set up the path to the .NET Framework.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
# import the form.
from frmCreateEvent import *
# import the NewEventClass.
from NewEventClass import *
# Create the event handler.
def HandleMsg(Msg):
System.Windows.Forms.MessageBox.Show(Msg)
# Define the event and add the handler to it.
ThisEvent = MyEvent()
ThisEvent.Add(HandleMsg)
# Define the event handlers.
def btnFireEvent_Click(*args):
# Fire the event.
ThisEvent.Fire(‘Hello World’)
def btnQuit_Click(*args):
# Close the application.
TestForm.Close()
# Define the Windows Form and the elements of this specific instance.
TestForm = frmMain()
TestForm.InitializeComponent()
# Always add event handlers after defining the Windows Form.
TestForm.btnFireEvent.Click += btnFireEvent_Click
TestForm.btnQuit.Click += btnQuit_Click
# Run the application.
System.Windows.Forms.Application.Run(TestForm)
[/code]

The code begins like many of the other examples in this chapter — it makes the proper additions to sys.path, creates references to .NET assemblies, and then imports them. Make sure you import all the classes required to make your event work. In this case, the code imports both frmCreateEvent and NewEventClass.

The next step is to define an event handler for the new event. This event handler takes a simple approach. It accepts Msg from Fire() and displays it using a simple message box similar to the one shown in Figure 8-6 (the message is slightly different, but the idea is the same).

Now that there’s an event handler, HandleMsg(), to use, it’s time to assign it to the event. The code creates a new event, ThisEvent, and assigns HandleMsg() as an event handler to it. In many respects, this approach is no different from using a delegate in a .NET language such as C# or Visual Basic .NET. The techniques are a bit different, but the basic concept is the same.

Whenever the user clicks Fire Event, the code calls btnFireEvent_Click() because this is the event handler assigned to the btnFireEvent.Click event. The code inside btnFireEvent_Click() simply calls ThisEvent.Fire() with a message of Hello World. At this point, the event calls HandleMsg() to display the message using a standard message box.

Why Not Use Delegates ?

When you write an application using a language such as C# or Visual Basic.NET, you usually rely on delegates to create custom events. Using a delegate is simple and well understood. You can see examples all over the place, but check out the example at http://www.akadia.com/services/dotnet_delegates_and_events.html for a good overview.

However, it’s interesting to view one particular issue when considering delegates in IronPython. You must provide a method that’s compatible with delegates in order to use delegates. As part of the preparation for this chapter, I played around with delegates for a while and found that you simply can’t obtain the method information in a way that delegates will understand. To see this for yourself, try this code:

[code]
import System
class MyClass:
def MyMsgDisplay(self, Msg, Title):
print Msg, Title
for Methods in System.Type.GetMethods(type(MyClass)):
print Methods
[/code]

When you run this code, you’ll begin to understand something interesting about IronPython. The output from this example does include methods such as __new__ (), ToString(), and __repr__(), but nowhere will you see MyMsgDisplay(). It turns out that MyMsgDisplay() is implemented as part of the IronPython run time, so it looks like this (even though the code appears on two lines in the book, you must type it as a single line in your code):

[code]
System.Object Call(IronPython.Runtime.CodeContext, System.Object[])
[/code]

All that code is the little MyMsgDisplay() method. What you’re seeing is the method that marshals information to the IronPython run time. For example, IronPython .Runtime.CodeContext is actually self. The System.Object[] array is a collection of two objects, Msg and Title, sent to MyMsgDisplay(). Unless something changes drastically, you won’t ever be able to use delegates in IronPython.

 

1 COMMENT

  1. Add the OnConnect event and add this:procedure TForm1.IdFTPServer1Connect(AContext: TIdContext);var IPAdd: string;begin IPAdd := AContext.Connection.Socket.Binding.PeerIP;end;Have not done this myelsf but it should work.Also look at following sites for possible help:

Comments are closed.