I have recently added a window to my WPF application which can be docked to an edge of the desktop as an "app bar". The code I'm using to do the docking came from this stackoverflow post.
The program has three user settings defined related to this window. One is the edge where the window is docked, the other two are the values of the Left
& Top
properties. The idea is that when the window is closed, or the program is shut down, the window will open back in the same state and location when the program restarts.
The problem I'm having is that when the program opens up, the window is first displayed at a random location on the screen (probably the coordinates assigned to it by Windows when the window is created) and then it moves into the docked position. Other programs I've seen that have the app bar functionality, like Trillian, are drawn in the docked position from the beginning. It's a little disconcerting to see the window move like that.
Here is some code from the window:
private void AppBarWindow_Activated( object sender, EventArgs e ) {
if ( Settings.Default.AppBarWindowEdge != ABEdge.None ) {
AppBarFunctions.SendShellActivated( this );
}
}
private void AppBarWindow_Closing( object sender, CancelEventArgs e ) {
Settings.Default.AppBarWindowLeft = Left;
Settings.Default.AppBarWindowTop = Top;
Settings.Default.Save();
AppBarFunctions.SetAppBar( this, ABEdge.None );
// Other, app specific code . . .
}
private void AppBarWindow_LocationChanged( object sender, EventArgs e ) {
if ( Settings.Default.AppBarWindowEdge != ABEdge.None ) {
AppBarFunctions.SendShellWindowPosChanged( this );
}
}
private void AppBarWindow_SourceInitialized( object sender, EventArgs e ) {
if ( Settings.Default.AppBarWindowEdge != ABEdge.None ) {
SizeWindow( Settings.Default.AppBarWindowEdge == ABEdge.None ? ABEdge.Left : ABEdge.None );
}
}
private void AppBarWindow_SizeChanged( object sender, SizeChangedEventArgs e ) {
if ( Settings.Default.AppBarWindowEdge != ABEdge.None ) {
AppBarFunctions.SendShellWindowPosChanged( this );
}
}
private void SizeWindow( ABEdge originalEdge ) {
// App specific code to compute the window's size . . .
if ( originalEdge != Settings.Default.AppBarWindowEdge ) {
AppBarFunctions.SetAppBar( this, Settings.Default.AppBarWindowEdge );
}
Settings.Default.AppBarWindowLeft = Left;
Settings.Default.AppBarWindowTop = Top;
Settings.Default.Save();
}
I have added functions to call SHAppBarrMessage
when the window is activated, or when its position and size change, as I read in this acrticle. The calls don't seem to have any effect on the behavior, so I might remove them.
I know that the SourceInitialized
and Loading
events are called before the window is displayed but after the window handle and the layout & measure passes have been completed. It appears, though, that the window is rendered before the call to AppBarFunctions.SetAppBar
is made, which is why I see it appear and then move into place.
I've also tried to move the window into the docked position by setting the Left
and Top
properties to the values saved in the settings in the window's constructor. That didn't work, either. In fact, it was worse, as the window was first drawn in the docked position, then apparently was moved away from that desktop edge to make room for it, and then moved back into the docked location.
How do I get this window to appear in the docked position upon start up and not move afterward?
Edit:
I think I have found the cause of the problem. There is a comment in the AppBarFunctions
class code, in the ABSetPos
method, just before it schedules a call to the DoResize
method on the window's Dispatcher
(UI thread). The comment reads:
// This is done async, because WPF will send a resize after a new appbar is added.
// if we size right away, WPFs resize comes last and overrides us.
So apparently WPF or Windows is moving the window out of the space being reserved for the window, and I then move it back in. I added a lot of trace points in my code & I can see that the window isn't rendered until after that move is made (the one mentioned in the comment in the code). After the window is rendered, it is moved into the docked position by my code.
The AppBarFunctions
class already adds a window procedure hook for wathcing for messages from the shell. If I add a check for WM_WINDOWPOSCHANGED, could I somehow stop the message from being processed? Or maybe I can change the values for the Left
and Top
properties for the move done by Windows / WPF so the window ends up where I want it to be?
I've found a way to keep the window from moving from the docked area. Basically, the code I'm using already uses a Window Procedure Hook method to watch for ABN_*
notification messages. I added code in this method to watch for WM_WINDOWPOSCHANGING
messages.
Here's the code I wrote:
public IntPtr WindowProcedureHook( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled ) {
if ( msg == (int) WinMessages.WM_WINDOWPOSCHANGING ) {
if ( IsDocked && !IsDragging ) {
WindowPos pos = (WindowPos) Marshal.PtrToStructure( lParam, typeof( WindowPos ) );
// Keep this window in its docked position.
pos.x = (int) DockedPosition.X;
pos.y = (int) DockedPosition.Y;
pos.cx = (int) DockedSize.Width;
pos.cy = (int) DockedSize.Height;
Marshal.StructureToPtr( pos, lParam, false );
handled = true;
}
} else if ( msg == CallbackId ) {
if ( wParam.ToInt32() == (int) ABNotify.ABN_WINDOWPOSCHANGED ) {
SetDockedPosition( Window, this, true );
handled = true;
}
}
return IntPtr.Zero;
}
When the window is docked to an edge & registered with the shell, it remembers the docking rectangle returned from the call to SHAppBarMessage / ABM_SETPOS
. When the method receives a WM_WINDOWPOSCHANGED
message, it checks to see if the window is docked along an edge and is not being dragged. If it is, it marshals the WINDOWPOS
structure from unmanaged memory into a managed object, sets the position & size of the window back to the docked position & size, and marshals it back to unmanaged memory. It then sets handled to true & exits.
This works perfectly & keeps the window from bouncing away from its docked position & back in. And there is no need to schedule a move into the docked position on the window's Dispatcher
thread.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With