IE add-on development: handling toolbar messages

This is the second article in my series on the caveats of writing an IE add-on.

Last time, we looked at making your toolbar look pretty. This time, we're going to make the toolbar actually do something: we're going to see how to handle window messages from the toolbar.

If you have any experience with Win32, you know that handling window messages is fairly straight forward. You create a window procedure which you specify when you register your window class. Controls such as buttons, text boxes - and indeed toolbars - sent notification messages to their parent, usually in the form of a WM_COMMAND or WM_NOTIFY message, which the parent can handle in it's window procedure.

If we look once more at the MSDN article on IE add-ons we see that it is indeed no more difficult than that. In all the examples provided there, the add-on creates a window in the IObjectWiteSite::SetSite as a child of the window provided by IE. Toolbars are largely glossed over by that article. It is remarked that you can treat a toolbar exactly the same as a horizontal explorer bar, and that it true; all it takes is a different registry entry to turn your explorer bar into a toolbar. That's nice if you want to make your toolbar look like an explorer bar that's been shoved into a toolbar, but if you're like me, you're more interested in making a toolbar look like a toolbar.

Making a toolbar that looks like a toolbar is easy: Internet Explorer's toolbar area is a Rebar control , sometimes also called a coolbar. All you need to do to make a toolbar is to create a toolbar control as a child of the rebar (I recommend you use CreateWindowEx to do this, not the deprecated CreateToolbar approach).

The problem then is this: if you create a toolbar as a child of the rebar, the rebar gets the WM_COMMAND messages from the toolbar. And IE, not you, owns the rebar. The easy way out would seem to be to create a window in between the rebar and the toolbar. But, since that window wouldn't be transparent, you would loose the fancy gradient effects of the rebar, and that would defeat the work we did in the last post. There are some examples on the web, noticably on code project, that seem to suggest you can create an invisible child window to catch the messages, but I couldn't get those to work right. Either the toolbar remained invisible as well or IE decided to reassign the toolbar's parent to be the rebar leaving me exactly where I was. Whether this is a problem with the example or with me is something I won't go into here. ;)

So if we want the toolbar to look right, it must be a child of the rebar control. So how can we get at the messages then? The answer is subclassing. Subclassing is the practice of replacing the window procedure of a control or window with one of your own. This is done as follows:

_oldWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(_parentWindow, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WndProc)));

In this line of code, we are assigning our own window procedure, named WndProc (which is a static member of our class) to be the window procedure for the parent window that we got from IE (using IOleWindow::GetWindow), which for a toolbar is the rebar control. SetWindowLongPtr returns the old window procedure, which we store in a class member variable named _oldWndProc.

When implementing the WndProc function, we will need a way to get at the data from our toolbar; you'll likely want to interact with it, and at the least you'll need it to get the old window procedure, because it's very important to call that. Since the window procedure must be a global function or a static member, it can't do that without some additional effort. The MSDN article on toolbars uses a simple trick for this: use SetWindowLongPtr to store a pointer to the class instance for the toolbar in the window. We can still use this approach, but it is very imporant that you do not store this pointer in the rebar. There is only one user data slot for a window, so imagine if every toolbar tried to store their pointer in the rebar.

Instead, we store this pointer in the toolbar window. This means that in the window procedure, we must be able to find our toolbar window. Since the toolbar window uses the standard toolbar class name, that isn't enough. So to be able to identify it, we also give it a caption (which won't be visible so it can be anything, just make sure it's reasonably sure to be unique). Passing this text to CreateWindowEx when creating the toolbar didn't seem to work for some reason, so I used SetWindowText instead.

Next we get to implementing the window procedure. What we need to do is this: find the toolbar window, extract the pointer to the toolbar class, handle any messages from the toolbar, and call the old window procedure to make sure we don't break IE.

LRESULT CALLBACK SearchBar::WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    // Find the toolbar window
    HWND barWindow = FindWindowEx(hWnd, NULL, TOOLBARCLASSNAME, L"Example toolbar window text");
    ToolBar *bar = reinterpret_cast<SearchBar*>(GetWindowLongPtr(barWindow, GWLP_USERDATA));

    switch( uMessage )
    {
    case WM_COMMAND:
        if( reinterpret_cast<HWND>(lParam) == barWindow )
        {
            // Handle the message, and don't call the rebar's original window procedure
            return 0;
        }
        break;
    }
    return CallWindowProc(bar->_oldWndProc, hWnd, uMessage, wParam, lParam);
}

Make sure you only handle messages that actually come from your toolbar. Call the original window procedure for all other messages.

The last thing to do is make sure that before the toolbar is destroyed, we put the original window procedure back. If you don't do this, IE will end up calling your window procedure, which then tries to use a pointer to the ToolBar class that is no longer valid, crashing IE. The best place to do this is the destructor.

ToolBar::~ToolBar()
{
    if( _oldWndProc != NULL )
        SetWindowLongPtr(_parentWindow, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(_oldWndProc));
}

That's it. We've now got a toolbar that looks like a toolbar should in IE, and we can handle messages from it so we can respond if somebody clicks a button. I want to stress that you should be very careful in doing this. You're messing with IE's inner workings here; if you get it wrong, IE will likely crash or misbehave some other way. And our goal was to extend IE, not to break it (unless you're writing a spyware toolbar, in which case I'd kindly ask you to get off my site :P ). If you want more details on how I implemented it, you can also check out the source code of the Find As You Type for Internet Explorer add-on.

That was a long one! Next time, we'll look at keyboard input. We'll do some more subclassing, and figure out how to implement IInputObject::TranslateAcceleratorIO.

Categories: Programming
Posted on: 2006-09-01 18:55 UTC.

Comments

No comments here...

Add comment

Comments are closed for this post. Sorry.

Latest posts

Categories

Archive

Syndication

RSS Subscribe