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!
|