Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App Bar Window pops away from docking postion, then moves into the docking position

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?

like image 894
Tony Vitabile Avatar asked Aug 28 '13 14:08

Tony Vitabile


1 Answers

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.

like image 190
Tony Vitabile Avatar answered Sep 27 '22 22:09

Tony Vitabile