|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionHello. 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 application that can be aligned to one of the screen edges. for example ICQ is an appbar, cause 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 tell 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 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: C# does Shell, Part 1C# does Shell, Part 2 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: BasicsSo now that we know what is an Application Desktop Toolbar (I told you, like taskbar but can also float!) lets get busy. There are good news and bad news when we come to develop such an application. The bad news is that there is no magical API that do 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.. Lets begin from the good news. What does the operating system supply us? The OS supply us information about windows movements that might affect our window, so we could act accordingly. Image 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 supply us is the 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 appbars 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 appbars in the system and when something happens that might affect an appbar the OS send messages to the appbars in this list, so that the appbars can respond (move, size, disappear etc). You might wonder how this registration is done, or how the OS notify us about the changes. This is explained in the following section. Section 2: Communicating with the OSHow does the OS and our program communicates? 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 Here is the original declaration of the UINT_PTR SHAppBarMessage(
DWORD dwMessage, // Appbar message value to send.
PAPPBARDATA pData); // Address of an APPBARDATA structure. typedef struct _AppBarData {
DWORD cbSize;
HWND hWnd;
UINT uCallbackMessage;
UINT uEdge;
RECT rc;
LPARAM lParam;
} APPBARDATA, *PAPPBARDATA;
and the C# equivalent: // Sends an appbar message to the system.
[DllImport("shell32.dll")]
public static extern UInt32 SHAppBarMessage(
UInt32 dwMessage, // Appbar message value to send.
ref APPBARDATA pData); // Address of an APPBARDATA structure. The
Lets explain some of this. The first parameter of the public enum AppBarMessages
{
// Registers a new appbar and specifies the message identifier
// that the system should use to send notification messages to
// the appbar.
New = 0x00000000,
// Unregisters an appbar, removing the bar from the system's
// internal list.
Remove = 0x00000001,
// Requests a size and screen position for an appbar.
QueryPos = 0x00000002,
// Sets the size and screen position of an appbar.
SetPos = 0x00000003,
// Retrieves the autohide and always-on-top states of the
// Microsoft® Windows® taskbar.
GetState = 0x00000004,
// Retrieves the bounding rectangle of the Windows taskbar.
GetTaskBarPos = 0x00000005,
// Notifies the system that an appbar has been activated.
Activate = 0x00000006,
// Retrieves the handle to the autohide appbar associated with
// a particular edge of the screen.
GetAutoHideBar = 0x00000007,
// Registers or unregisters an autohide appbar for an edge of
// the screen.
SetAutoHideBar = 0x00000008,
// Notifies the system when an appbar's position has changed.
WindowPosChanged = 0x00000009,
// Sets the state of the appbar's autohide and always-on-top
// attributes.
SetState = 0x0000000a
}
the messages will be explained later. The second parameter Section 3: Registering our AppBarThe first step in making our program an appbar is register it in the OS appbars list. the registration is done by sending the message Lets review this one more time cause I know its a little fuzzy. We want to register our window as an appbar, so we use the SendMessage([window handle],[unique message id],[notify code],
So in my window procedure I need to respond to the unique message id, and according to the Why is the message id should be unique? because I need to respond to it in my window procedure, I cant just give a random value cause 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 UINT RegisterWindowMessage(
LPCTSTR lpString); // message string
and the C# equivalent:
// The RegisterWindowMessage function defines a new window message that is
Now that we know how to do the registration of an appbar lets see some code that does it. First here is how we get a new unique message id for latter 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 do the registration: private Boolean AppbarNew()
{
// prepare data structure of message
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
msgData.uCallbackMessage = RegisterCallbackMessage();
// install new appbar
UInt32 retVal = ShellApi.SHAppBarMessage((UInt32)AppBarMessages.New,
Here I create a new Section 4: Setting the AppBar sizeThe next step to to pay notice 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: 1) 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 2) After doing repairs to the returning rectangle we need to send the OS the message 3) After the 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)
{
// prepare data structure of message
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
msgData.uEdge = (UInt32)m_Edge;
msgData.rc = appRect;
// query postion for the appbar
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.QueryPos, ref msgData);
appRect = msgData.rc;
}
private void AppbarSetPos(ref ShellApi.RECT appRect)
{
// prepare data structure of message
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
msgData.uEdge = (UInt32)m_Edge;
msgData.rc = appRect;
// set postion for the appbar
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.SetPos, ref msgData);
appRect = msgData.rc;
}
These two functions are very simple. Both accept the proposed rectangle, creates an 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()
{
// prepare the proposed rectangle
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;
}
}
// Step 1: check the proposed rectangle using QueryPos
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;
}
// Step 2: after fixing the rectangle, set the proposed rectangle using
First we build our first proposition for the rectangle. Then we send the Section 5: Other MessagesSo, until now we saw several uses of the We will start with the private Boolean AppbarRemove()
{
// prepare data structure of message
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
// remove appbar
UInt32 retVal = ShellApi.SHAppBarMessage((UInt32)AppBarMessages.Remove,
The function creates an On to the next message. The rules of the Appbars says that when an appbar application receive the message private void AppbarActivate()
{
// prepare data structure of message
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
// send activate to the system
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.Activate, ref msgData);
}
Nothing to explain. Again, according to the rules, when our application receive the message private void AppbarWindowPosChanged()
{
// prepare data structure of message
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
msgData.hWnd = Handle;
// send windowposchanged to the system
ShellApi.SHAppBarMessage((UInt32)AppBarMessages.WindowPosChanged,
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 reappear 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 helps with the autohide stuff are private Boolean AppbarSetAutoHideBar(Boolean hideValue)
{
// prepare data structure of message
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;
// set auto hide
UInt32 retVal = ShellApi.SHAppBarMessage(
Finally we have three more simple messages that might help us, those messages are used when working with the taksbar. The first two: private AppBarStates AppbarGetTaskbarState()
{
// prepare data structure of message
ShellApi.APPBARDATA msgData = new ShellApi.APPBARDATA();
msgData.cbSize = (UInt32)Marshal.SizeOf(msgData);
// get taskbar state
UInt32 retVal = ShellApi.SHAppBarMessage(
Quite simple. So this is 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 NotificationsRemember we have send the public enum AppBarNotifications
{
// Notifies an appbar that the taskbar's autohide or
// always-on-top state has changed—that is, the user has selected
// or cleared the "Always on top" or "Auto hide" check box on the
// taskbar's property sheet.
StateChange = 0x00000000,
// Notifies an appbar when an event has occurred that may affect
// the appbar's size and position. Events include changes in the
// taskbar's size, position, and visibility state, as well as the
// addition, removal, or resizing of another appbar on the same
// side of the screen.
PosChanged = 0x00000001,
// Notifies an appbar when a full-screen application is opening or
// closing. This notification is sent in the form of an
// application-defined message that is set by the ABM_NEW message.
FullScreenApp = 0x00000002,
// Notifies an appbar that the user has selected the Cascade,
// Tile Horizontally, or Tile Vertically command from the
// taskbar's shortcut menu.
WindowArrange = 0x00000003
}
We will start with Next, we might get the notification Another notification we might get is Finally, the last notification, This is all the notifications that are available for us. I've explained also some possible uses of these notifications, and later you will see code that fits there explanations. Section 7: Mixing our knowledgeSo, now that we know what the operating system supply us and it is all wrapped up nicely, all that is left to do is mix it up and create our 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 Here is how the constructor looks like: public ApplicationDesktopToolbar()
{
FormBorderStyle = FormBorderStyle.SizableToolWindow;
// Register a unique message as our callback message
CallbackMessageID = RegisterCallbackMessage();
if (CallbackMessageID == 0)
throw new Exception("RegisterCallbackMessage failed");
}
also we need some private variables to store some information: // saves the current edge
private AppBarEdges m_Edge = AppBarEdges.Float;
// saves the callback message id
private UInt32 CallbackMessageID = 0;
// are we in dock mode?
private Boolean IsAppbarMode = false;
// save the floating size and location
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 cause 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 public AppBarEdges Edge
{
get
{
return m_Edge;
}
set
{
m_Edge = value;
if (value == AppBarEdges.Float)
AppbarRemove();
else
AppbarNew();
if (IsAppbarMode)
SizeAppBar();
}
}
Section 8: Window ProcedureAt 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 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 void OnNcHitTest(ref Message msg)
{
DefWndProc(ref msg);
if ((m_Edge == AppBarEdges.Top) &&
Well, actually its 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 its docked. finally In all other cases I return Finally, the last function, 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) // before
Visible = false;
else // after
Visible = true;
break;
}
}
I'm not going to explain this function at all! If you want explanation read again section 6: OS Notifications. We have finished developing the Using the ClassThis should be you favorite part, using the class is very simple. All you need to do is changing this line: public class frmMain : Form
to this line: public class frmMain : ShellLib.ApplicationDesktopToolbar
then when you want to dock you application to an edge just set the 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.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||