After 10 years, I finally found a use for this...

| | Comments (9)

Since Windows 95, Microsoft has included APIs for working with a set of objects called "App Bars." An app bar is a window that is docked to one edge of the screen, and constitutes area which nothing else can reside in. They can be set to automatically show and hide, be always on top, etc. You've used an app bar yourself, probably without ever even thinking about it: the task bar is an app bar.

It's very rare that you'd ever want to use one of these constructs since they've a very invasive piece of user interface. When you install on on an edge of the screen, nothing else can be "under" it, so everything has to shift out of the way. However, I finally found a good use for one.

If you followed along with the comments from yesterday's accessibility post, Sam told us about an application on OS X which will filter screen captures and show you a view of how things look to someone with a color weakness. So I decided it would be fun to make this same type of application on Windows as well.

My first instinct was to use a plain box window and allow the user to drag it around, sort of like a magnifying glass. However, this comes with a major problem: the screen shot will include this view-finder window as well. There's no way to get a screen shot of what's "under" the view-finder without first moving it out of the way. Failing to move it out of the way means you're just taking a picture of what's already in the view-finder, which isn't very helpful. After spending an hour or so exploring ways around this, I realized that another application ran into this same problem and has already solved it for me. The Magnifier accessibility app which comes with Windows does the same sort of operations (only with magnification and not color skewing), and it uses an app bar.

So I decided to go with the app bar approach, which really isn't too difficult to do. You work with app bars via a single Shell declare called SHAppBarMessage. So I wrapped this API in a module:[rbcode]Protected Function SHAppBarMessage(msg as AppBarMessage, ByRef data as AppBarData) As UInt32
Declare Function MySHAppBarMessage Lib "Shell32" Alias "SHAppBarMessage" ( msg as UInt32, ByRef data as AppBarData ) as UInt32

return MySHAppBarMessage( UInt32( msg ), data )
End Function[/rbcode]
Then I added some helper enumerations so I wouldn't have to go look up their values all the time:[rbcode]Enum AppBarEdge
Left
Top
Right
Bottom
End Enum

Enum AppBarMessage
ABM_NEW
ABM_REMOVE
ABM_QUERYPOS
ABM_SETPOS
ABM_GETSTATE
ABM_GETTASKBARPOS
ABM_ACTIVATE
ABM_GETAUTOHIDEBAR
ABM_SETAUTOHIDEBAR
ABM_WINDOWPOSCHANGED
ABM_SETSTATE
End Enum[/rbcode]
Finally, I made a helper method which would properly set a window onto an edge of the screen. This code was pretty much a straight port out of the example from MSDN, in case you're wondering why I didn't comment anything.[rbcode]Protected Sub QuerySetPos(uEdge as AppBarEdge, ByRef lprc as RECT, ByRef pabd as AppBarData)
dim iHeight, iWidth as Integer

pabd.rc = lprc
pabd.uEdge = UInt32( uEdge )

if uEdge = AppBarEdge.Left or uEdge = AppBarEdge.Right then
iWidth = pabd.rc.right - pabd.rc.left
pabd.rc.top = 0
pabd.rc.bottom = Screen( 0 ).Height
else
iHeight = pabd.rc.bottom - pabd.rc.top
pabd.rc.left = 0
pabd.rc.bottom = Screen( 0 ).Width
end if

call AppBar.SHAppBarMessage( AppBarMessage.ABM_QUERYPOS, pabd )

select case uEdge
case AppBarEdge.Left
pabd.rc.right = pabd.rc.left + iWidth

case AppBarEdge.Right
pabd.rc.left = pabd.rc.right - iWidth

case AppBarEdge.Top
pabd.rc.bottom = pabd.rc.top + iHeight

case AppBarEdge.Bottom
pabd.rc.top = pabd.rc.bottom - iHeight

end select

call AppBar.SHAppBarMessage( AppBarMessage.ABM_SETPOS, pabd )

Declare Sub MoveWindow Lib "User32" ( hwnd as Integer, x as Integer, y as Integer, width as Integer, height as Integer, update as Boolean )
MoveWindow( pabd.hwnd, pabd.rc.left, pabd.rc.top, pabd.rc.right - pabd.rc.left, pabd.rc.bottom - pabd.rc.top, true )

End Sub[/rbcode]
Using these helper methods (and some assistance from the Windows Functionality Suite, since I wanted to get messages from the WndProc, as well as modify the window's frame style), it's very easy to make a window into an app bar.[rbcode]
self.IsToolbarWindow = true
self.Topmost = true

WndProcHelpers.Subclass( me, me )

Declare Function RegisterWindowMessageA Lib "User32" ( name as CString ) as UInt32

dim data as AppBarData
data.cbSize = data.Size
data.hwnd = me.Handle
data.uCallbackMessage = RegisterWindowMessageA( "Aaron's App Bar" )
mCallbackMessage = data.uCallbackMessage

if AppBar.SHAppBarMessage( AppBarMessage.ABM_NEW, data ) = 0 then
MsgBox "No App bar!"
end if

dim rc as RECT
rc.top = 0
rc.bottom = 100
rc.left = 0
rc.right = Screen( 0 ).Width
AppBar.QuerySetPos( AppBarEdge.Top, rc, data )[/rbcode]
That code makes an app bar which is 100 pixels tall and is docked to the top window edge. Viola, that's all there is to it!

Now, I wouldn't recommend anyone run right out and start turning their windows into app bars, obviously. But after ten years of programming for Windows, I've finally found an occasion to use this UI widget, which is certainly something worth blogging about. ;-) Enjoy!

9 Comments

Aaron, you ever use Yojimbo? I do everyday, and they hav ethis cool tab that sticks out of the side of the window that allows me access to stuff, from anywhere.

I've never used it. Can you take some screenshots and post some links to them?

I IM''ed you a couple of pixs, one in its non-extended view, the other, extended.

http://www.barebones.com/products/yojimbo/index.shtml

Same guys who make BBEdit.

There are a number of Mac apps that use the panel like Yojimbo. I believe it uses a non-activating, systemwide NSPanel that allows it to sit on top of everything and access its controls without having an app switch. I'm trying to figure out how to make one inside of RB.

Chris, make it into a Plugin and I will buy it off you .

@Bill -- So you're wondering whether this is possible on Windows? Or on the Mac?

I want it as a simple to use plugin. Cross platform goodness and all. Whatcha got?

Not a thing. ;-)

Well snap to it then Big Guy. I got Plugins to sell.

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.