» Home

  » Plugins

      » Mime

            » Custom Action Wizard

Brass Plugins: Mime

 

The Custom Action Wizard: Creating Good Criteria

When we created the custom Notepad gesture, we skipped over the criteria part of the Wizard a little. Time to fill in the blanks!

Criteria are easy to understand but hard to get right. If you've never dealt with the internals of how Windows works before, hold on tight - this will be interesting.

Within Windows, all applications that need to display "stuff" on screen must use, unsurprisingly, a window. This window is their little sheet of blank paper within the operating system, theirs to do whatever they want with it. Right now you're looking at this web page in Internet Explorer, Firefox or similar. No matter what web browser you use, you're still viewing the page in a window.

Applications can be only one of 3 types - dialog, SDI or MDI. Windows Calculator is a good example of a dialog application:

It comes in one single window, it doesn't resize, the display is fairly static and it performs one single function.

Notepad is a good example of an SDI (Single Document Interface) application:

 

It's a resizable window that displays changing data (the text document) in the main part of the window. The reason this is called an SDI (Single Document) is because Notepad can only have one file open at any one time.

Finally PaintShop Pro is a good example of an MDI (Multiple Document Interface) application:

 

Here you can see multiple windows open with different images on them. These windows live inside the main PaintShop Pro window.

The reasons behind SDI and MDI applciations are all to do with the document & view architecture, and you don't need to know anything about that. All you need to know is that there is a difference between each type of application. We're going to look at that now.

Here's the Mime window finder output for Calculator:

As you can see, the overall window name and class are the same as the window name and class. There is no parent window name or class. This is because there is only one single window in a dialog application.

Here's the Mime window finder output for Notepad:

Now you can see the window class and the parent window class are different. We'll come to the "Overall" fields in a second. The main Notepad window is an edit field, but the overall Notepad application is actually a container for this edit field. That's why we have a parent window class. All non-dialog applications (SDI and MDI apps) have a window class and a parent class.

Finally here's the Mime window finder output for Paintshop Pro. To get this output I dragged the window finder to a canvas/drawing window in PSP:

Here you can see the window class, parent class and overall class are all different. Let's look at PaintShop Pro a different way:

 

The boxout on the right of the image is a list of window names and classes. The red lines connect these entries to what is actually displayed on screen.

You don't have to understand what each of these fields means, just that they exist and what they refer to.

 

Back to Criteria

Let's go back to criteria for a second. Mime uses criteria to figure out what application and window a gesture is made in. Criteria is how Mime refers to the window titles and window classes of an application.

When you select a window with the window finder, you are saying to Mime:

"This is the window I want you to look for when a gesture is made. If a gesture gets made in a window that matches these criteria, you can execute the action that the gesture is linked to".

Mime can base action criteria on 3 different "levels". Level 1 is the window the gesture was made in. Level 2 is the parent window of the window that the gesture was made in. Level 3 is the overall parent window of the application that the gesture was made in.

The overall window is important because sometimes MDI applications have lots of windows inside each other. In the PaintShopPro screenshot above, the "AfxFrameOrView42" window has 3 parent windows. The overall window is the top level "PaintShop Pro" window.

If you're careful when you specify criteria for an action, you can create pretty cool actions. For example it's possible to create an action that will only fire when the gesture is made in the big grey background area of Paintshop Pro. That's really handy, because you need the right mouse button for drawing in the white windows so gestures won't be a good idea there.

 

Defining Criteria

Criteria match in 2 ways. For all the "class" criteria (window class, parent window class etc), the match must be explicit. For example:

Criteria Gesture Made In Match?
     
AfxFrameOrView42 AfxFrameOrView No
     
AfxFrameOrView42 AfxFrame42 No
     
AfxFrameOrView42 AfxFrameOrView42 Yes

The class name criteria must absolutely and explicitly match the window class the gesture was made in.

For all the "name" criteria (window name/title, parent name/title), pattern matching is possible. For example:

Criteria Gesture Made In Match?
     
PaintShop PaintShop Pro 6 - Image 1 Yes
     
PaintShop PaintShop Pro 6 Yes
     
PaintShop PSP 6 - Image 1 No

As long as Mime can find the criteria string in the window title, a match is made.

 

When you define criteria, you only need to define the fields that matter. For example, if I only wanted gestures to work in the grey background area of Paintshop Pro, my criteria would look like this:

 

Here I'm telling Mime that as long as the gesture is made in a window with a class of "MDIClient", it's ok to execute the action attached to the gesture. You could make gestures all over Paintshop Pro, but the action will only fire when you make the gesture on the grey background. I've left all the other fields blank, so Mime will ignore them when trying to match criteria.

Right now if I asked you to create your own criteria for a gesture that worked anywhere within Notepad, you might be tempted to try this:

 

WRONG! Good job we're not on Jeopardy! This criteria will only allow actions to be fired for gestures in the main Notepad editor window. Gestures anywhere else will be ignored. Instead, what we need to do is this:

 

Here I'm telling Mime that as long as the gesture is made in any child window of Notepad, it's ok to execute the action attached to the gesture. Now no matter what windows Notepad pops up, gestures for the associated action will always be recognised in them.

If you tried this yourself you might have noticed that the overall window class and the parent window class are the same. That's not a problem, and it's quite common. With SDI apps that only have a main window and a container (like Notepad), there is only one parent window. The parent window therefore is also the overall parent - it's the top of the tree. As far as Mime is concerned, there's no difference in using the "Overall" fields instead of the "Parent" fields in this case. Use whichever you like.

Here's a last good one for you. What if we wanted to create an action that only fires when a gesture is made in Internet Explorer, and the current website is Google.com? Sounds tough? Not at all :-)

Have a look at a standard Internet Explorer window when it's at google.com:

 

Ok, well that's just the top left corner but it's all we need. See how the title of the IE window changes to "Google - Microsoft Internet Explorer"? No matter where we go on the Google site, it always says "Google <something>" in the titlebar.

As we already know, we can pattern match on window titles for criteria. So if we drag the Window Finder over to the main IE window, we'll see the following criteria:

 

Okay, so the parent class is different to the overall class - clearly we're dealing with multiple windows inside each other (an MDI application). But look at the "Overall Window Name" field: there's our window title. All we need to do is blank out the other fields and get a pattern match in the "Overall Window Name" field:

 

Here I've told Mime that as long as the gesture is made in any window that has an overall parent with a title that partially matches "google", it's ok to fire the action.

Those few examples should get you started. If you're still a bit confused by the process try playing around a little, or post a support query on the forums.

 

A quick note about pattern matching

Mime has been written to be fast, but no matter what system you use it's always a tiny bit slower to use pattern matching. You'll probably never notice the difference, but it is good practice to match criteria on the class fields (window class, parent class, overall class) where possible, and leave the name fields blank. The criteria fields are explicit matching, so it's a tiny bit faster.

 

The Sticky Problem - Toolkits, MFC and UI Widgets

This all seems pretty simple so far, right? Drag the window finder over the application window you want to make the gesture in, edit the fields so only the unique ones that matter are left and save it.

Ah, if only it was that easy. User interface toolkits are here to make our lives difficult.

It's a sad fact that developing the user interface for applications in Windows is very time consuming. Writing the "back end" code that does all the work is usually easy enough, but coding handlers for every button, widget and window in an application takes a hell of a long time.

That's why a lot of developers use UI (user interface) toolkits. These toolkits provide a framework that makes writing the UI part of an application much faster. You would be surprised if you knew just how many toolkits there were and how many applications use them.

Why is this a problem? Simple. Because these toolkits are templates, the window titles and classes they use are the same for every application that uses them. Not only that, many toolkits create the window classes dynamically each time the application runs - this means that the classes constantly change!

Here's a good example. Here I've run Paintshop Pro and dragged the window finder over the grey background window:

 

Next, I closed PaintShop Pro and ran it again. The second time I drag the window finder over the same grey background window, look at what happens:

Look closely at the end of the "Parent Window Class" and "Overall Window Class" fields in the 2 screenshots. See it? They're different!

Every time you run PaintShop Pro, the window classes change. Oh JASC, why do you do it to us...

Think this one over for a second - you can't simply drag the window finder over Paintshop Pro and use those criteria, because the next time PSP is launched all the window classes will be different. Therefore any action with criteria based on window classes will never get fired!

It gets worse. If you're not familiar with PaintShop Pro, here are 2 of the tool windows:

    

 

These are 2 separate windows that appear depending on which drawing tool you choose. Look what happens when I first drag the window finder over the PaintBrush window:

Okay, and now when I drag the window finder over the Airbrush window:

 

Take a look at the Window class and Overall Window Class. They're the same for both the paintbrush and airbrush dialogs!

Now think this one over for a second. If more than one window has the same window class, any criteria specified for an action in one of these windows will also fire if a gesture is made in the other window!

 

And you thought that was bad...

Okay, you might be sitting there thinking "Hey, I can live with my actions firing in 2 different Paintshop Pro tool windows". Oh no you can't :-)

One of the wonderful things about UI toolkits is, as I said at the start of this section, that every application that uses them has the same class names. Look what happens when I drag the window finder over the UltraEdit "Find" window:

 

Uh-oh. See the problem? If you just created an action that fires on a window class criteria of "#32770", your Paintshop Pro gesture is going to fire in UltraEdit too. And I pretty much guarantee UltraEdit won't have a clue what you want it to do :-)

In this case we have 2 MFC applications - UltraEdit and PaintShop Pro. MFC gives all dialog windows the same class name of "#32770", so any action configured with that classname criteria will fire in any MFC application.

 

How To Solve It

Well really, you can't solve it. It's a problem caused by the user interface toolkits, which generally don't provide the developer with any easy way of changing the class names.

What you can do is work around it. Whenever you find a window with these issues you'll need to figure a way of matching criteria only to that window and avoiding any conflicts.

Here's a sample of how I'd do it with Paintshop Pro. If I wanted an action to fire when a gesture was made in the white drawing canvas window, I'd use these criteria:

Here I'm telling Mime to only fire an action when a gesture is made in the drawing canvas window, which has a class of "AfxFrameOrView42". Because this is a standard MFC classname I've also said that the window must have an overall parent with "Paint Shop Pro" as part of the window title. The Overall Window Name field guarantees the action will only fire in Paintshop Pro, and the Window Class field guarantees the action will only fire when the gesture is made in the canvas window, not anywhere else inside Paintshop Pro.

 

How Do I Know If I Found a UI Toolkit Window?

Good question, and the only answer is "experience". Some windows are a dead giveaway: "#32770" or anything beginning or ending with "Afx" are usually MFC toolkit windows. Other than that, the only thing you can do is create your criteria and test them.

If you're having problems with a specific application, come along to the forums and get some technical support. We'll be happy to help!