Posts for category 'Programming'

IE add-on development: handling keyboard input

A little bit later than I had hoped, I now bring you the third article in my series on Internet Explorer add-on development. As promised, we're going to look at dealing with user input.

This article is really only relevant if you want to handle keyboard input, but you probably at least want to allow a user to use the arrow keys to navigate your toolbar buttons, so the odds are good that you need this if you're writing a toolbar. The Find As You Type add-on also has a textbox on the toolbar, so it needs this even more.

As it says on MSDN, if you want to handle keyboard input you must implement the IInputObject interface on your object, and when you implement IObjectWithSite::SetSite, you should QueryInterface the IUnknown object you get passed for IInputObjectSite and store this pointer somewhere.

This IInputObjectSite pointer is used to tell IE when you have the keyboard focus. It has a single method named OnFocusChangedIO, which you should call whenever your toolbar receives or loses the input focus.

This means you should handle the WM_SETFOCUS and WM_KILLFOCUS messages. Last time, we subclassed IE's rebar control, but this is not the place to handle these messages! Internet Explorer really isn't interested in you telling it when its own rebar control got focus. It wants to know when your controls got focus.

This means we must do yet more subclassing. Every control in your add-on that can get the keyboard focus must be subclassed. In the Find As You Type add-on, these are the toolbar itself and the textbox. You can use the same procedure as used previously, just substitute the window handles, window procedures and the variable used to store the old window procedure. Don't forget to put always call the original window procedure at the end of your own and to put the original back when the control is destroyed, just like last time.

In this case, you own the windows you are subclassing, so you can store the pointer to your toolbar object directly in those windows (for the toolbar, we already did this last time) and we don't need to use FindWindow in the window procedure, just call GetWindowLongPtr on the window handle that was passed to the WndProc. We can now handle the WM_SETFOCUS and WM_KILLFOCUS messages and call IInputObjectSite::OnFocusChangedIO passing true and false respectively.

In concert with this, you should implement IInputObject::HasFocusIO to return true when you have the focus and false when not. You can set a flag when handling the focus messages, or you can use GetFocus to see if one of your windows has the focus.

The more interesting part comes with TranslateAcceleratorIO. Even if you don't want to do anything special, you must call TranslateMessage and DispatchMessage here, otherwise keyboard input can break in strange ways, which tend to be inconsistent across browser version. In my case it seemed to work right in IE6, but broke in IE7. So call those two functions.

For IE7 compatibility, you should also check if the message is referring to any of the window navigation keys, such as tab, shift-tab and F6. When you get any of those keys, don't call TranslateMessage and DispatchMessage. Instead, return S_FALSE, so IE can properly respond to those keys. If you have more than one control, you can choose to handle the tab key messages to navigate between your own controls. IE6 doesn't seem to send tab key messages to toolbars, but IE7 does.

If you have any actual accelerators, here's the place to implement them. You can manually check the message, or add an accelerator table and call TranslateAccelerator. You can use this for shortcut keys and mnemonics used by your toolbar.

You might have noticed that TranslateAcceleratorIO is called by IE only when your toolbar has the input focus. It is indeed impossible to handle keyboard shortcuts if your toolbar doesn't have the focus using this method. Vista appears to have a way to solve this (although I have not tested if it actually works) with the IInputObject2 interface, which defines a TranslateAcceleratorGlobal method. This solution, if it works, would still be limited to when your toolbar is loaded (even if it doesn't need to have the focus) and would be Vista specific.

The other way to respond to keys globally that doesn't have these drawbacks is by using a Browser Helper Object and a keyboard hook. But I will leave that for a future post.

Categories: Programming
Posted on: 2006-09-28 15:12 UTC. Show comments (6)

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. Show comments (0)

IE add-on development: help, my toolbar looks ugly!

As promised, this is the first in what will be a few articles about some of the pitfalls involved in writing IE add-ons, based on my own experience in writing Find As You Type for Internet Explorer. This isn't meant to be a tutorial or a definitive guide to writing add-ons; it's assumed you have at least some knowledge on how to do this. For a review of the basics, you can check this article on MSDN.

A list of the articles in this series is available here.

The first item I will cover is visual styles. Suppose you were to write you own add-on based on the guide at the link above, or even if you just compile the samples from MSDN or CodeProject or similar, and then run them. You will soon notice that your toolbar or explorer bar doesn't use the Windows XP theme (or the Vista theme on Vista), or visual style. Instead it will use the Windows Classic look. Here's what the Find As You Type toolbar looks like without visual styles:

Find As You Type without XP Visual Styles applied

As you can see, this looks quite out of place, because the rest of Internet Explorer is using the proper theme.

So how to go about fixing this. Unfortunately, the answer to that is quite hard to find. Some casual googling will not reveal it (although it can be found that way, but it's quite buried). Of course it's my hope that if you googled this problem, you ended up on this page. :)

Fortunately, once you know the answer, it's quite simple to actually do it. The answer is described in Microsoft Knowledge Base article 830033, which, although it says it's about Office add-ons, it also applies to Internet Explorer add-ons.

As it turns out, you must add a special manifest resource to your application. This is quite similar to how you'd make any application support visual styles. The difference is that, instead of the normal CREATEPROCESS_MANIFEST_RESOURCE_ID, you must use the ISOLATIONAWARE_MANIFEST_RESOURCE_ID.

Note that if you are using Visual Studio 2005, you don't actually need to create (or modify) a resource file like it specifies in the article. You can simply add the manifest file to your Visual C++ Win32 DLL project, and it will be embedded automatically. For DLL projects, Visual Studio 2005 automatically uses the ISOLATIONAWARE_MANIFEST_RESOURCE_ID to do this. After this is done, Find As You Type looks like this.

Find As You Type with XP Visual Styles applied

And that, of course, looks much nicer. There remains one problem that I do not yet know how to solve. If you open any windows from your add-on, for instance using the Win32 MessageBox or DialogBox function, they will still not have visual styles. Considering that even Microsoft's own Internet Explorer Developer Toolbar suffers from this problem, I don't know if there even is a solution for that. But if anyone knows how, I'd appreciate it if they'd let me know.

That concludes my first IE add-on article. Next time, we'll look at how to handle messages if you're writing a toolbar.

Categories: Programming
Posted on: 2006-08-31 21:35 UTC. Show comments (6)

The joys of overflow:auto and cross-browser development

Some of you may have wondered why I initially didn't post the code for my Map/Reduce example on my blog, but only linked to the Channel9 post, and why I added it now. The answer is, because figuring out a good way to add code samples to my blog was still on the TODO list.

"But," I hear you say, "that's simple isn't it? A <pre>, maybe some fancy colouring, end of story, right?"

Right. Except, no. You may have noticed that the content on this is in a container that has a width relative to the page (70%, to be precise). So what would happen if I put a <pre> in there, and that contains a line that wouldn't fit. In Internet Explorer 7, Firefox, Opera and other browsers with a proper overflow model, the line would run out of the box. In IE6, it grows the box to fit the line, destroying the entire page layout. Neither is very desirable.

So there are two solutions. One is to have the lines wrap. The simple way to do that would be to use white-space: pre-wrap;, but alas, no browser that I know of actually supports that. The other way would be to manually format it, inserting &nbsp; and <br /> elements where needed. I don't much like doing that, and I don't actually like having lines of code wrap at arbitrary points either, so this solution is out the window.

Thus we end up with the solution I eventually used as you can see in the previous post. I used scrollbars.

The mechanism to create scrollbars, namely overflow:auto has its own problems. You see, CSS 2 does not tell browsers where these scrollbars should go, and as a result everybody does it differently. CSS 2.1 rectified this oversight by saying the scrollbars belong on the inside of the box with the overflow style, i.e. the box does not grow to accomodate the scrollbars.

Perhaps surprisingly, the only browser that does this is IE. All the others place the scrollbar outside the box. This means that in IE if overflow:auto mandates a horizontal scrollbar, this scrollbar now obscures part of the content, so a vertical scrollbar is also needed. So setting overflow:auto will cause the code sample to always have two scrollbars in IE, and only one in Firefox et al. Not strictly a problem, especially since I wanted to add a max-height so a vertical scrollbar is needed anyway.

There is, for those interested, a way to avoid the vertical scrollbar even in IE. First, IE will behave differently if the element being scrolled is a table. Second, you can force the scrollbar to the outside using the CSS 3 properties overflow-x:scroll;overflow-y:visible. Unfortunately, that is not a legal combination according to CSS 3, but it works in IE. Firefox, Opera etc. do not support overflow-x/y at all. The drawback is that it only works with overflow-x:scroll, not overflow-x:auto so now the element gets a scrollbar even if it doesn't need one!

IE6 adds additional difficulty. Because of the broken overflow model (thankfully fixed in IE7), it never thinks the <pre> needs a scrollbar unless you give it a specific width. Unfortunately, that prevents IE7, Firefox, et al from automatically adjusting the width if the element is next to the sidebar or not. Even worse, in Firefox, an explicit width that's wider than the space next to the sidebar causes the element to overlap the sidebar (why Firefox doesn't move it below the float like IE does in this case I don't know). So that's out.

So in the end, we have overflow:auto, with the knowledge of the vertical scrollbar that will cause in IE (which is right according to spec), and some extra rules for IE6 (using the "* html" hack so other browsers (including IE7) ignore them). And then I could add the code.

Ah, the joys of cross-browser development...

Categories: Programming
Posted on: 2006-08-04 14:26 UTC. Show comments (7)

Can your programming language do this? C# can!

Joel on Software talks about the Swedish Chef and the advantages of anonymous functions and functional style programming with an example of map and reduce in Javascript.

He notes how terrible this is to implement in C with function pointers, or even worse in Java with functors. Thankfully, C# 2.0, with anonymous delegates and generics, offers a clean, type-safe way to do this, which doesn't require much more code than his javascript version. The only real additions are the declarations of the delegates used, and of course some necessary type specifications, because C# is still a strongly typed language.

So what does this code look like? The answer can be found below, with some more discussion on this Channel9 post made by me.

public delegate T MapDelegate<T>(T input);
public delegate TAggregate ReduceDelegate<TAggregate, TInput>(TAggregate aggregate, TInput input);

public static void Map<T>(IList<T> list, MapDelegate<T> function)
{
    for( int x = 0; x < list.Count; ++x )
        list[x] = function(list[x]);
}

public static TAggregate Reduce<TAggregate, TInput>(IEnumerable<TInput> list, TAggregate initial, ReduceDelegate<TAggregate, TInput> function)
{
    TAggregate aggregate = initial;
    foreach( TInput item in list )
        aggregate = function(aggregate, item);

    return aggregate;
}

public static void Foo()
{
    List<int> list = new List<int>(new int[] { 1, 2, 3 });

    // Multiply each element by 2.
    Map(list, delegate(int x) { return x * 2; });
    // Sum values: result with be 12
    int sum = Reduce(list, 0, delegate(int aggregate, int input) { return aggregate + input; });
    // Concatenate values using a StringBuilder, result will be "246"
    string concat = Reduce(list, new StringBuilder(), delegate(StringBuilder aggregate, int input) { return aggregate.Append(input.ToString()); }).ToString();
}

EDIT 2006-08-04: Added code.

Categories: Programming
Posted on: 2006-08-02 21:00 UTC. Show comments (4)

Latest posts

Categories

Archive

Syndication

RSS Subscribe

;