Introduction
I know that in my last article, I've promised to begin explaining how to extend the shell but I found some very interesting information that concerns the shell and I must write about it before I start extending the shell.
So, what is this all about? This article is about Application Desktop Toolbars, shortly called Appbars. What are those appbars? Appbars are applications that can be aligned to one of the screen edges. For example, ICQ is an appbar, because it can be aligned to the right or left edge of the screen, and when it is docked, if you maximize some other window, it is still visible because the system tells the rest of the windows that the working area is smaller. Another famous example of an appbar is the TaskBar
, yes, the bar with the list of opened applications, that we all have. The taskbar
can be aligned to all the edges but cannot be in a floating state.
During this article, we will develop a class named ApplicationDesktopToolbar
. This class inherits System.Windows.Forms.Form
. So when we want to make our application behave like an appbar
, all we need to do is inherit from ApplicationDesktopToolbar
instead of System.Windows.Forms.Form
.
Note: No extra reading material is needed for understanding this article. But here are some links you might want to read after you read this article:
MSDN: Using Application Desktop Toolbars
Another Note: The code added to this article contains a new version of the ShellLib
library we started to develop in the previous parts.
Section 1: Basics
So now that we know what an Application Desktop Toolbar is (I told you, like taskbar but can also float!), let's get busy. There is good news and bad news when we come to develop such an application. The bad news is that there is no magical API that does the work for us, we need to do the work ourselves. The good news is that the operating system helps us know when to use the functions we have made in the bad news section..
Let's begin with the good news. What does the operating system supply us? The OS supplies us information about windows movements that might affect our window, so we could act accordingly. Imagine that you have an appbar
that is aligned to the bottom, where the taskbar
is, and the user resizes the taskbar
, in that case, we should resize our appbar
.
Another info the OS supplies us is the free working space. When we want to dock our appbar
, we need to know what size we want to set it. Again, considering other appbar
s in the system and the taskbar
.
All we need to do in order to accept the OS information is to register in a special list of the OS. The OS keeps a list of all the appbar
s in the system and when something happens that might affect an appbar
, the OS sends messages to the appbar
s in this list, so that the appbar
s can respond (move
, size
, disappear
, etc.).
You might wonder how this registration is done, or how the OS notifies us about the changes. This is explained in the following section.
Section 2: Communicating with the OS
How does the OS and our program communicate? The answer is divided in two. First, how does the OS communicates with our program? Simply, by sending window messages into our window procedure. But what about the second part? How do we communicate with the OS? We communicate with the OS with the help of an API function called SHAppBarMessage
. This function has two parameters, the first is the message we want to send to the OS, and the second parameter is a struct
that contains more information about the message being sent.
Here is the original declaration of the SHAppBarMessage
API and the APPBARDATA
struct
in C++:
UINT_PTR SHAppBarMessage(
DWORD dwMessage, PAPPBARDATA pData);
typedef struct _AppBarData {
DWORD cbSize;
HWND hWnd;
UINT uCallbackMessage;
UINT uEdge;
RECT rc;
LPARAM lParam;
} APPBARDATA, *PAPPBARDATA;
and the C# equivalent:
[DllImport("shell32.dll")]
public static extern UInt32 SHAppBarMessage(
UInt32 dwMessage,
ref APPBARDATA pData);
[StructLayout(LayoutKind.Sequential)]
public struct APPBARDATA
{
public UInt32 cbSize;
public IntPtr hWnd;
public UInt32 uCallbackMessage;
public UInt32 uEdge;
public RECT rc;
public Int32 lParam;
}
Let's explain some of this. The first parameter of the SHAppBarMessage
API is dwMessage
, this parameter is the message being sent. It can be one of the following enum
values:
public enum AppBarMessages
{
New = 0x00000000,
Remove = 0x00000001,
QueryPos = 0x00000002,
SetPos = 0x00000003,
GetState = 0x00000004,
GetTaskBarPos = 0x00000005,
Activate = 0x00000006,
GetAutoHideBar = 0x00000007,
SetAutoHideBar = 0x00000008,
WindowPosChanged = 0x00000009,
SetState = 0x0000000a
}
The messages will be explained later. The second parameter pData
, contains extra information we need to supply when we send the message. Note that not all the fields of the struct
should be filled. It depends on the message.
Section 3: Registering our AppBar
The first step in making our program an appbar
is register it in the OS appbar
s list. The registration is done by sending the message AppBarMessages.New
. When this message is sent, the following members of the APPBARDATA
struct
should be set. The hWnd
should be set to our window handle, the uCallbackMessage
should be set to a unique message id that the OS will use to communicate with our program.
Let's review this one more time because I know it's a little fuzzy. We want to register our window as an appbar
, so we use the SHAppBarMessage
to send the OS the message AppBarMessages.New
with our window handle and a unique message id. When the OS wants to notify my window of something that I should know about, it sends a message to my window procedure, the message that the OS will send is the unique message id I gave during the registration. The OS probably does something like:
SendMessage([window handle],[unique message id],[notify code],
[notify extra info]);
So in my window procedure, I need to respond to the unique message id, and according to the wParam
, I'll know which notification message the OS sends me.
Why should the message id be unique? Because I need to respond to it in my window procedure, I can't just give a random value because it might be a valid message id. So how do I get a number that doesn't fit any window message? To do that, we use the API function RegisterWindowMessage
, this function receives a string
and returns the message id attached to that string
, if the string
does not exist, it creates a new unique message id and returns it. This API is usually used to communicated between two applications on the same computer (each application calls this API with the same string
and they both get the same message id and now they can communicate by sending this message id). So we need to declare this function in order to use it, here is the C++ declaration:
UINT RegisterWindowMessage(
LPCTSTR lpString);
and the C# equivalent:
[DllImport("user32.dll")]
public static extern UInt32 RegisterWindowMessage(
[MarshalAs(UnmanagedType.LPTStr)]
String lpString);
Now that we know how to do the registration of an appbar
, let's see some code that does it. First, here is how we get a new unique message id for later use, this function is called from the constructor:
private UInt32 RegisterCallbackMessage()
{
String uniqueMessageString = Guid.NewGuid().ToString();
return ShellApi.RegisterWindowMessage(uniqueMessageString);
}
There is not much to explain, first I get a unique string
by generating a GUID
and then I use this string
to get a message id.
Here is a function that does the registration:
private Boolean AppbarNew()
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
msgData.uCallbackMessage = RegisterCallbackMessage();
UInt32 retVal = ShellApi.SHAppBarMessage((UInt32)AppBarMessages.New,
ref msgData);
return (retVal!=0);
}
Here, I create a new APPBARDATA
struct
, set its size, my window handle, and a unique message id and then call the SHAppBarMessage
with the AppBarMessages.New
message. The code is not complete because I need to store the unique message id for later use in the window procedure.
Section 4: Setting the AppBar Size
The next step to pay attention to is what happens when we want to set the appbar
size and location. The first thing to remember is that when we set the appbar
position, we need to take care that it doesn't disturb any other appbar
including the taskbar
. So in order to change the appbar
position, we need to follow these steps:
- Our application should propose the OS where we want to position our
appbar
. This proposition should include the edge we want to be docked to, and the size of our window (in a RECT
struct
). We do that by sending the OS the message AppBarMessages.QueryPos
. After sending this message, the OS will check if it has problems with the proposed position and if it does, it will fix our rectangle so that it doesn't disturb any other appbar
. Note that by sending the message, we don't change anything yet, we only check if the position we want is valid, and get in return a valid position, no moving has accord yet. - After doing repairs to the returning rectangle, we need to send the OS the message
AppBarMessages.SetPos
. This message changes the position of our appbar
in the OS internal appbar
list. Two things are important to understand in this step.
- The OS will first recheck the position that we asked to see if we do not disturb any other
appbar
, after checking it will again fix the rectangle so that it won't disturb any appbar
and only then, it will set the rectangle info in the internal list of appbars. - The second thing to notice is that this message does not actually move the window! It only changes the rectangle in the internal
appbar
list.
- After the
AppBarMessages.SetPos
message has returned, we need to actually move our window to the position registered in the internal list. This is done by calling the MoveWindow
API or even more simple, just setting the Size
and Location
properties of the form. One thing to notice is we need to move the window to the rectangle that returned from the AppBarMessages.SetPos
message, because the OS might have changed it.
In order to do these steps in our program, we will define two helper functions that take care of sending the messages:
private void AppbarQueryPos(ref ShellApi.RECT appRect)
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
msgData.uEdge = (UInt32)m_Edge;
msgData.rc = appRect;
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.QueryPos, ref msgData);
appRect = msgData.rc;
}
private void AppbarSetPos(ref ShellApi.RECT appRect)
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
msgData.uEdge = (UInt32)m_Edge;
msgData.rc = appRect;
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.SetPos, ref msgData);
appRect = msgData.rc;
}
These two functions are very simple. Both accept the proposed rectangle, create an APPBARDATA
struct
and fill it with the needed information (the edge comes from a private
variable of the class). And then, send the OS the message using the API SHAppBarMessage
. One thing to note about this code is that after we send the message, we set the appRect
variable to the rectangle returned by the message, because it might have changed.
Now I'll present here the code that sizes our class. The code follows the 3 steps. One thing to note about the code is that m_PrevSize
is a private
variable that stores the size of window when it was in float mode. The idea behind the function is that if the width of the window was 90 and the height was 40 and you dock the window to the top, then the height will remain 40 and the width will be the width of the entire screen. Here is the code:
private void SizeAppBar()
{
ShellApi.RECT rt = new ShellApi.RECT();
if ((m_Edge == AppBarEdges.Left) ||
(m_Edge == AppBarEdges.Right))
{
rt.top = 0;
rt.bottom = SystemInformation.PrimaryMonitorSize.Height;
if (m_Edge == AppBarEdges.Left)
{
rt.right = m_PrevSize.Width;
}
else
{
rt.right = SystemInformation.PrimaryMonitorSize.Width;
rt.left = rt.right - m_PrevSize.Width;
}
}
else
{
rt.left = 0;
rt.right = SystemInformation.PrimaryMonitorSize.Width;
if (m_Edge == AppBarEdges.Top)
{
rt.bottom = m_PrevSize.Height;
}
else
{
rt.bottom = SystemInformation.PrimaryMonitorSize.Height;
rt.top = rt.bottom - m_PrevSize.Height;
}
}
AppbarQueryPos(ref rt);
switch (m_Edge)
{
case AppBarEdges.Left:
rt.right = rt.left + m_PrevSize.Width;
break;
case AppBarEdges.Right:
rt.left= rt.right - m_PrevSize.Width;
break;
case AppBarEdges.Top:
rt.bottom = rt.top + m_PrevSize.Height;
break;
case AppBarEdges.Bottom:
rt.top = rt.bottom - m_PrevSize.Height;
break;
}
AppbarSetPos(ref rt);
Location = new Point(rt.left,rt.top);
Size = new Size(rt.right - rt.left,rt.bottom - rt.top);
}
First, we build our first proposition for the rectangle. Then, we send the AppBarMessages.QueryPos
message, then we fix the proposed rectangle and send the AppBarMessages.SetPos
message and finally we do the actual moving of the window. Simple as that.
Section 5: Other Messages
So, until now, we saw several uses of the SHAppBarMessage
API. We used it in the functions: AppbarNew
, AppbarQueryPos
and AppbarSetPos
. In this section, we will quickly specify all the rest of the messages that can be sent and their wrapping functions.
We will start with the AppBarMessages.Remove
message. This message is sent when we want to remove our window from the OS internal appbar
list. After sending this message, we will stop receiving notifications from the OS and our window will be treated as a normal window. All we need to specify is the window handle to be removed. Here is a nice wrapper function that do it:
private Boolean AppbarRemove()
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
UInt32 retVal = ShellApi.SHAppBarMessage((UInt32)AppBarMessages.Remove,
ref msgData);
Size = m_PrevSize;
Location = m_PrevLocation;
return (retVal!=0);
}
The function creates an APPBARDATA
struct
, set the hWnd
member and send the AppBarMessages.Remove
message, after sending the message, the function restores the position of the window.
On to the next message. The rules of the Appbar
s say that when an appbar
application receives the message WM_ACTIVATE
, it should send the message AppBarMessages.Activate
to the OS. So here is the AppbarActivate
function code:
private void AppbarActivate()
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.Activate, ref msgData);
}
Nothing to explain. Again, according to the rules, when our application receives the message WM_WINDOWPOSCHANGED
, it should send the message AppBarMessages.WindowPosChanged
to the OS. Here is the code:
private void AppbarWindowPosChanged()
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.WindowPosChanged,
ref msgData);
}
The OS allows us to set a property of the appbar
called autohide
, which usually means that the appbar
disappears when not in focus, and reappears when needed. One limitation is that you can set only one autohide appbar
per edge. So if the taskbar
has autohide
set, you cannot set your appbar
on the same edge of the taskbar
and to set it to autohide
. Two messages that help with the autohide
stuff are AppBarMessages.GetAutoHideBar
and AppBarMessages.SetAutoHideBar
. The first one gets an edge and returns the handle of the window that has autohide
set on that edge, if no window has it, returns null
. The second message gets an edge, a state and a window handle and sets it as autohide
on the specified edge. Note that the second message can return failure if there is already an autohide appbar
on that edge. One important thing to notice is that these messages do not take care of the hiding and showing of the window. This is entirely up to you to write, they only manage the states internally so you won't set two autohide appbar
s on the same edge. The code for using these messages is as follows:
private Boolean AppbarSetAutoHideBar(Boolean hideValue)
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
msgData.uEdge = (UInt32)m_Edge;
msgData.lParam = (hideValue) ? 1 : 0;
UInt32 retVal = ShellApi.SHAppBarMessage(
(UInt32)AppBarMessages.SetAutoHideBar,
ref msgData);
return (retVal!=0);
}
private IntPtr AppbarGetAutoHideBar(AppBarEdges edge)
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.uEdge = (UInt32)edge;
IntPtr retVal = (IntPtr)ShellApi.SHAppBarMessage(
(UInt32)AppBarMessages.GetAutoHideBar,
ref msgData);
return retVal;
}
Finally, we have three more simple messages that might help us, those messages are used when working with the taskbar. The first two: AppBarMessages.GetState
and AppBarMessages.SetState
let us get or set the state of the taskbar
. When I say state
, I mean whether it's AlwaysOnTop
and whether it's AutoHide
. It can be useful for example if you want to set the AlwaysOnTop
like the taskbar
state. The last message is AppBarMessages.GetTaskBarPos
which returns the rectangle of the taskbar
. Here are the wrapping functions:
private AppBarStates AppbarGetTaskbarState()
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
UInt32 retVal = ShellApi.SHAppBarMessage(
(UInt32)AppBarMessages.GetState,
ref msgData);
return (AppBarStates)retVal;
}
private void AppbarSetTaskbarState(AppBarStates state)
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.lParam = (Int32)state;
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.SetState, ref msgData);
}
private void AppbarGetTaskbarPos(out ShellApi.RECT taskRect)
{
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.GetTaskBarPos,
ref msgData);
taskRect = msgData.rc;
}
Quite simple. So these are all the functions we can send to the operating system. In the next section, we will see what notifications the OS might send to us.
Section 6: OS Notifications
Remember we have sent the AppBarMessages.New
message? We had to supply to the OS a unique message id. So as I've mentioned before, when the OS wants to send us notifications, it uses the unique message id we supplied and sends it to our window procedure with the wParam
as the notification code. Fortunately, there are only four notifications we might receive, which are declared nicely in the enum AppBarNotifications
:
public enum AppBarNotifications
{
StateChange = 0x00000000,
PosChanged = 0x00000001,
FullScreenApp = 0x00000002,
WindowArrange = 0x00000003
}
We will start with AppBarNoifications.PosChanged
. This notification tells us that something happened that might affect our window position or size, so our response we be probably resizing our window by calling the AppBarMessages.QueryPos
and AppBarMessages.SetPos
messages. This notification can be sent for example when another appbar
is attached to our appbar
edge or when an appbar
that was on our edge has changed its size or position.
Next, we might get the notification AppBarNotifications.StateChange
. This notification is sent when the state of the taskbar
is changing. Again, the state
means the AlwaysOnTop
or AutoHide
settings. A possible response to this notification is to get the state of the taskbar
using the message AppBarMessages.GetState
and changing our window accordingly.
Another notification we might get is AppBarNotifications.FullScreenApp
. This notification is sent when a new window enters the full window mode or when the last full window closes. According to the lParam
, we can tell which case it is. The normal response to this notification is to change our z-order when a new full window opens and restore our z-order when the last full window closes. If you ask yourself what this full window mode is, try pressing F11 on your Internet Explorer.
Finally, the last notification, AppBarNotifications.WindowArrange
. This notification is sent when the user commands the shell to rearrange the opened windows (Tile
, Cascade
). This notification is sent twice. Once before the arrangement and once after the arrangement. According to the lParam
, you can tell whether it is before or after the arrangement. A normal response to this message is to hide our window before the arrangement and to show our window after the arrangement, so our window will not participate in the arrangement and hold its position.
These are all the notifications that are available for us. I've also explained some possible uses of these notifications, and later, you will see code that fits their explanations.
Section 7: Mixing Our Knowledge
So, now that we know what the operating system supplies us and it is all wrapped up nicely, all that is left to do is mix it up and create our ApplicationDesktopToolbar
class.
Like every class, we will begin with its constructor. In the constructor, we want the class to register our unique message id. Another thing we want is that the FormBorderStyle
will be set to SizableToolWindow
. You might wonder why we do that. Well, it turns out that when the operating system is managing the windows in the system, it takes care that all the windows that appears in the taskbar
cannot enter the area that is occupied by the appbar
s, which in our case will cause that even though we declared that we are an appbar
, the system will not let us position our window in the requested position. To solve this issue, we need to set our window to be a toolbar
, so it will get the WS_EX_TOOLBAR
style, and then it should work. Now pay close attention: This answer is what Microsoft published in MSDN in response to this problem. Unfortunately, this is not accurate. If it was accurate, then all I would have to do is to set the property ShowInTask
and that was enough. Also, setting the FormBorderStyle
as SizableToolWindow
still show our window in the taskbar
! I don't have a good answer for it. I assume that it was once correct, but today the reality has changed a bit. Finally, I know that it works only if the FormBorderStyle
is SizableToolWindow
or FixedToolWindow
, regardless of the ShowInTask
property.
Here is how the constructor looks like:
public ApplicationDesktopToolbar()
{
FormBorderStyle = FormBorderStyle.SizableToolWindow;
CallbackMessageID = RegisterCallbackMessage();
if (CallbackMessageID == 0)
throw new Exception("RegisterCallbackMessage failed");
}
Also, we need some private
variables to store some information:
private AppBarEdges m_Edge = AppBarEdges.Float;
private UInt32 CallbackMessageID = 0;
private Boolean IsAppbarMode = false;
private Size m_PrevSize;
private Point m_PrevLocation;
Before we will talk about the window procedure, here are some small functions I should explain:
On load of the form, I need to save the previous size and location of the form:
protected override void OnLoad(EventArgs e)
{
m_PrevSize = Size;
m_PrevLocation = Location;
base.OnLoad(e);
}
On closing the form, we should call AppbarRemove
to remove the window from the internal list:
protected override void OnClosing(CancelEventArgs e)
{
AppbarRemove();
base.OnClosing(e);
}
If the size changes while we are docked to an edge, I want to save only some of size information, because if we are aligned to the bottom or top, I don't need to save the width info because it is set to the screen width:
protected override void OnSizeChanged(EventArgs e)
{
if (IsAppbarMode)
{
if (m_Edge == AppBarEdges.Top || m_Edge == AppBarEdges.Bottom)
m_PrevSize.Height = Size.Height;
else
m_PrevSize.Width = Size.Width;
SizeAppBar();
}
base.OnSizeChanged(e);
}
One last code before talking about message handling, the public
property Edge
. When it is set, it should call AppBarNew
or AppBarRemove
, depending on whether it changes to float or a screen edge:
public AppBarEdges Edge
{
get
{
return m_Edge;
}
set
{
m_Edge = value;
if (value == AppBarEdges.Float)
AppbarRemove();
else
AppbarNew();
if (IsAppbarMode)
SizeAppBar();
}
}
Section 8: Window Procedure
At last, the window procedure, the place where all fits in. In our window procedure, we will take care of four messages. The first message is the message that we receive when a notification arrives. The second message is WM_ACTIVATE
, in this message, we need to call AppBarActivate
. The third message is WM_WINDOWPOSCHANGED
, Here, we need to call AppBarWindowPosChanged
. and the last message is WM_NCHITTEST
. Of course, I'll explain why I respond to this last message. First, you need to remember that the window can be sized, but I want that if the window is docked, then sizing will be allowed in only one direction instead of the normal 4 directions. What I mean is that if our window is docked to the top edge, then I want to be able to size only the bottom border! So what we do is according to the current docking edge we allow only one border to be sized. Soon you will see the code to do it.
So this is the window procedure code:
protected override void WndProc(ref Message msg)
{
if (IsAppbarMode)
{
if (msg.Msg == CallbackMessageID)
{
OnAppbarNotification(ref msg);
}
else if (msg.Msg == (int)WM.ACTIVATE)
{
AppbarActivate();
}
else if (msg.Msg == (int)WM.WINDOWPOSCHANGED)
{
AppbarWindowPosChanged();
}
else if (msg.Msg == (int)WM.NCHITTEST)
{
OnNcHitTest(ref msg);
return;
}
}
base.WndProc(ref msg);
}
The AppbarActivate
and AppbarWindowPosChanged
functions are familiar. We will now review the code for OnNcHitTest
:
void OnNcHitTest(ref Message msg)
{
DefWndProc(ref msg);
if ((m_Edge == AppBarEdges.Top) &&
((int)msg.Result == (int)MousePositionCodes.HTBOTTOM))
0.ToString();
else if ((m_Edge == AppBarEdges.Bottom) &&
((int)msg.Result == (int)MousePositionCodes.HTTOP))
0.ToString();
else if ((m_Edge == AppBarEdges.Left) &&
((int)msg.Result == (int)MousePositionCodes.HTRIGHT))
0.ToString();
else if ((m_Edge == AppBarEdges.Right) &&
((int)msg.Result == (int)MousePositionCodes.HTLEFT))
0.ToString();
else if ((int)msg.Result == (int)MousePositionCodes.HTCLOSE)
0.ToString();
else
{
msg.Result = (IntPtr)MousePositionCodes.HTCLIENT;
return;
}
base.WndProc(ref msg);
}
Well, actually, it's not very complicated. First, I call the default window procedure to get the mouse position. Then I check if the edge is top and the mouse position is the bottom border then I do nothing. The same is for the rest of the edges. If the mouse position is the close button, I also do nothing so the user can press the close button even if it's docked. Finally, in all other cases, I return HTCLIENT
which means that the mouse position is on the client area. So, for example, if the user position the mouse over the top border when the window is docked to the top edge, the window will think that the user is pointing on the client area and will do nothing instead of showing the sizeable icon and let it size it. One thing to notice about this code is that every time I need to do nothing, I do 0.ToString()
, this is because we can't put an empty statement in C# and every other way to form the code will result in less readable code, So don't be confused, the 0.ToString()
does nothing.
Finally, the last function, OnAppbarNotification
, this is where we handle the notifications we receive from the OS:
void OnAppbarNotification(ref Message msg)
{
AppBarStates state;
AppBarNotifications msgType = (AppBarNotifications)(Int32)msg.WParam;
switch (msgType)
{
case AppBarNotifications.PosChanged:
SizeAppBar();
break;
case AppBarNotifications.StateChange:
state = AppbarGetTaskbarState();
if ((state & AppBarStates.AlwaysOnTop) !=0)
{
TopMost = true;
BringToFront();
}
else
{
TopMost = false;
SendToBack();
}
break;
case AppBarNotifications.FullScreenApp:
if ((int)msg.LParam !=0)
{
TopMost = false;
SendToBack();
}
else
{
state = AppbarGetTaskbarState();
if ((state & AppBarStates.AlwaysOnTop) !=0)
{
TopMost = true;
BringToFront();
}
else
{
TopMost = false;
SendToBack();
}
}
break;
case AppBarNotifications.WindowArrange:
if ((int)msg.LParam != 0)
Visible = false;
else
Visible = true;
break;
}
}
I'm not going to explain this function at all! If you want explanation, read section 6 again: OS Notifications.
We have finished developing the ApplicationDesktopToolbar
class.
Using the Class
This should be your favorite part, using the class is very simple. All you need to do is change this line:
public class frmMain : Form
to this line:
public class frmMain : ShellLib.ApplicationDesktopToolbar
Then when you want to dock your application to an edge, just set the Edge
property like this:
private void frmMain_Load(object sender, System.EventArgs e)
{
this.Edge = AppBarEdges.Left;
}
I recommend you try the test program for this article, so you could see the result for yourself.
That's it!
Hope you like it. Don't forget to vote.
History
- 3rd March, 2003: Initial version
Arik Poznanski is a senior software developer at Verint. He completed two B.Sc. degrees in Mathematics & Computer Science, summa cum laude, from the Technion in Israel.
Arik has extensive knowledge and experience in many Microsoft technologies, including .NET with C#, WPF, Silverlight, WinForms, Interop, COM/ATL programming, C++ Win32 programming and reverse engineering (assembly, IL).