【问题标题】:App Bar Window pops away from docking postion, then moves into the docking position应用栏窗口从停靠位置弹出,然后移动到停靠位置
【发布时间】:2013-08-31 17:16:56
【问题描述】:

我最近在我的 WPF 应用程序中添加了一个窗口,它可以作为“应用程序栏”停靠在桌面边缘。我用来做对接的代码来自thisstackoverflow 帖子。

程序定义了三个与此窗口相关的用户设置。一个是窗口停靠的边缘,另外两个是Left & Top 属性的值。这个想法是,当窗口关闭或程序关闭时,窗口将在程序重新启动时以相同的状态和位置重新打开。

我遇到的问题是,当程序打开时,窗口首先显示在屏幕上的随机位置(可能是创建窗口时由 Windows 分配给它的坐标),然后它移动到停靠位置。我见过的其他具有应用栏功能的程序,比如 Trillian,从一开始就被绘制在停靠位置。看到窗户这样移动有点令人不安。

这是窗口中的一些代码:

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();
}

我添加了在窗口被激活时调用SHAppBarrMessage 的函数,或者当它的位置和大小发生变化时,正如我在this acrticle 中所读到的那样。这些调用似乎对行为没有任何影响,所以我可能会删除它们。

我知道 SourceInitializedLoading 事件在窗口显示之前但在窗口句柄和布局和测量传递完成之后被调用。不过,看起来窗口是在调用 AppBarFunctions.SetAppBar 之前呈现的,这就是为什么我看到它出现然后移动到位的原因。

我还尝试通过将LeftTop 属性设置为保存在窗口构造函数设置中的值来将窗口移动到停靠位置。那也没用。事实上,情况更糟,因为窗口首先在停靠位置绘制,然后显然是从桌面边缘移开为它腾出空间,然后又移回停靠位置。

如何让这个窗口在启动时出现在停靠的位置,之后不再移动?

编辑:

我想我已经找到了问题的原因。 AppBarFunctions 类代码中有一条注释,在 ABSetPos 方法中,就在它安排对窗口的 Dispatcher(UI 线程)上的 DoResize 方法的调用之前。评论内容如下:

// 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.

显然 WPF 或 Windows 正在将窗口移出为窗口保留的空间,然后我将其移回。我在代码中添加了很多跟踪点,我可以看到窗口不是呈现直到进行该移动(代码中的注释中提到的那个)。窗口渲染后,我的代码将其移动到停靠位置。

AppBarFunctions 类已经添加了一个窗口过程挂钩,用于查看来自 shell 的消息。如果我添加对 WM_WINDOWPOSCHANGED 的检查,我可以以某种方式阻止消息被处理吗?或者,也许我可以更改由 Windows / WPF 完成的移动的 LeftTop 属性的值,以便窗口最终到达我想要的位置?

【问题讨论】:

    标签: c# wpf window taskbar


    【解决方案1】:

    我找到了一种方法来防止窗口从停靠区域移动。基本上,我使用的代码已经使用 Window Procedure Hook 方法来监视ABN_* 通知消息。我在这个方法中添加了代码来监视WM_WINDOWPOSCHANGING 消息。

    这是我写的代码:

    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;
    }
    

    当窗口停靠到边缘并注册到 shell 时,它会记住调用 SHAppBarMessage / ABM_SETPOS 返回的停靠矩形。当该方法接收到WM_WINDOWPOSCHANGED 消息时,它会检查窗口是否沿边缘停靠并且没有被拖动。如果是,它将WINDOWPOS 结构从非托管内存编组到托管对象,将窗口的位置和大小设置回停靠位置和大小,并将其编组回非托管内存。然后将handled设置为true并退出。

    这完美地工作并且防止窗口从其停靠位置弹回并返回。并且无需在窗口的Dispatcher线程上安排移动到停靠位置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-04
      • 2013-11-14
      • 2014-11-12
      相关资源
      最近更新 更多