A First Look at IronPython: Where Python meets .NET - ' Python All The Way ' (
Page 5 of 6 )
Generic Collections
One of the nicer features of .NET 2.0 is the introduction of generics. The purists will balk at what I'm about to say, but generics are a lot like C++ templates.
ADVERTISEMENT
When you create a generic collection, unlike the more, uh, generic collections of .NET 1.0, you specify exactly what type of items you'll put in the collections.
IronPython supports the use of the .NET generic collections.
Here's a sample IronPython session where I create a dictionary object that maps strings to integers.
I then iterate through the keys, grabbing the individual values stored in the dictionary:
>>> from System.Collections.Generic import *
>>> d = Dictionary[str, int]()
>>> d.Add('abc', 1)
>>> d.Add('def', 2)
>>> d['abc']
1
>>> for i in d.Keys:
... print d[i]
...
1
2
Don't forget the () after the declaration of Dictionary. Otherwise, your calls to Add will fail, because you're simply storing the type Dictionary[str, int] into your d variable, and not actually creating a new instance.
Incidentally, notice what I place inside the type parameters for the Dictionary class: str and int. These are built-in Python type names that are available to regular Python, not just IronPython. However, once again, inside IronPython, these items map to the .NET classes:
>>> d['abc'].GetType()
System.Int32
Delegates and Events
If you're a seasoned .NET programmer, then you're likely familiar with delegates and the power they provide. If you're not familiar with delegates, think of them more-or-less as pointers to a member function of a particular object (called an instance method). Delegates can also point to static methods that are not instances of a particular object, and further, delegates can multicast, meaning they can be chained together and viewed as a single delegate that calls multiple methods.
Delegates are particularly useful in the event-handling system of .NET. When you create a button, you will likely have code that you want called when the user of your program clicks the button. You put the code inside an object's member function. The .NET system uses delegates for the event. The Button class includes a Click member, which is a delegate. Inside this delegate, you supply the instance function (called an event handler) containing the code that runs in response to the button click.
IronPython provides native support for event delegates. As you already saw in the preceding example, adding handlers for events is easy:
this.Click += this.ClickHandler
This code added a Click event to the Form object.
Another interesting example is described in a tutorial that ships with IronPython; I'll provide a similar but more complete example here. This example makes use of the FileSystemWatcher class, which is a .NET class that lets you monitor file system activity. For example, you might be writing a program that lets users edit a file on disk. Suppose the file is modified from outside your program. A nice feature is for your program to detect that the file changed, and to notify the user.
The FileSystemWatcher class is part of the System.IO namespace. It's easy to use; you simply create a new instance, passing the name of the directory containing the files you wish to watch. Then you specify a pointer to the function that runs (that is, the event handler) when a change occurs to the files being watched.
Here's a complete sample.
import clr
clr.AddReference("System.Windows.Forms")
from System.IO import *
from System.Windows.Forms import *
class MyForm(Form):
def __init__(self):
Form.__init__(self)
self.watcher = FileSystemWatcher()
self.watcher.Changed += self.FilesChanged
self.watcher.Deleted += self.FilesChanged
# Create the menu
mainMenu1 = MainMenu()
fileMenu = MenuItem()
fileMenu.Text = "File"
menuItem1 = MenuItem()
menuItem1.Text = "Open..."
menuItem1.Click += self.OpenMenuHandler
menuItem2 = MenuItem()
menuItem2.Text = "Exit"
menuItem2.Click += self.ExitMenuHandler
fileMenu.MenuItems.Add(menuItem1)
fileMenu.MenuItems.Add(menuItem2)
mainMenu1.MenuItems.Add(fileMenu)
self.Menu = mainMenu1
self.RichText1 = RichTextBox()
self.RichText1.Dock = DockStyle.Fill
self.RichText1.ReadOnly = True
self.Controls.Add(self.RichText1)
self.OpenDialog = OpenFileDialog()
self.OpenDialog.InitialDirectory = "c:\\"
self.OpenDialog.Filter = "*.txt|*.txt|*.rtf|*.rtf|*.*|*.*"
print "Ready"
def ButtonHandler(self, source, ev):
pass
def OpenMenuHandler(self, source, ev):
if self.OpenDialog.ShowDialog() == DialogResult.OK:
# Turn off current watcher
self.watcher.EnableRaisingEvents = False
ext = self.OpenDialog.FileName.lower().split(".")[-1]
# or you can use the .NET framework to extract ext
# ext = Path.GetExtension(self.OpenDialog.FileName)
# ...but be careful, you'll have a dot at the beginning
# e.g. if ext == ".rtf"
if ext == "rtf":
richtype = RichTextBoxStreamType.RichText
else:
richtype = RichTextBoxStreamType.PlainText
print richtype
print self.OpenDialog.FileName
self.RichText1.LoadFile(self.OpenDialog.FileName, richtype)
# Set up watcher for this file
self.watcher.Path = Path.GetDirectoryName(self.OpenDialog.FileName)
self.watcher.EnableRaisingEvents = True
def ExitMenuHandler(self, source, ev):
self.Close()
def FilesChanged(self, source, ev):
if self.OpenDialog.FileName == ev.FullPath:
if ev.ChangeType == WatcherChangeTypes.Changed:
MessageBox.Show(self, "Warning: File Changed.",
"Warning", MessageBoxButtons.OK)
elif ev.ChangeType == WatcherChangeTypes.Deleted:
MessageBox.Show("Warning: File has been deleted.",
"Warning", MessageBoxButtons.OK)
def ClosedHandler(self, f, a):
self.watcher.Changed -= self.FilesChanged
print "finished"
f = MyForm()
Application.Run(f)
This code creates a form that contains a menu bar and a rich text box in read-only mode. When the user clicks the File'Open menu, an Open dialog box appears, letting the user choose either a text or RTF file to display in the rich text box.
You can see how I added handlers for the menu items:
menuItem1.Click += self.OpenMenuHandler
This tells IronPython to run my OpenMenuHandler code in response to the Open menu item. Similarly, I set up events for the FileSystemWatcher class, like so:
This code creates a FileSystemWatcher object, and assigns my FilesChanged method to both the Changed event and Deleted events. The FileSystemWatcher class doesn't do anything, however, until you tell it what to watch for, and then tell it to start watching. I do those two things after the user opens a file:
Notice that I use the .NET framework's Path class to extract the DirectoryName from the Filename string. I save that directory in the watcher's Path member. Then I start watching events by setting EnableRaisingEvents to True. (The word True is a built-in regular Python identifier; it wasn't available in early versions of Python.)
The handler function is easy then; I check the event object that comes in for the type of event (changed or deleted), and then pop up a message box with a warning.
To try out the program, run it, and use its Open menu to open a text or RTF file. Then go to either Windows Explorer or a command prompt, and delete or otherwise modify the file you're looking at. When you switch back to your Python program, a message box warns you that the file changed. In a real application, you would probably include an option to reload the file if it changed, or to save or close it if it was deleted.