c# - 应用栏窗口从停靠位置弹出,然后移动到停靠位置

标签 c# wpf window taskbar

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

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

我遇到的问题是,当程序打开时,窗口首先显示在屏幕上的随机位置(可能是创建窗口时 Windows 分配给它的坐标),然后它移动到停靠位置。我见过的其他具有应用程序栏功能的程序,如 Trillian,从一开始就绘制在停靠位置。看到 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();
}

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

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

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

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

编辑:

我想我已经找到问题的原因了。 AppBarFunctions里面有评论类代码,在ABSetPos方法,就在它安排调用 DoResize 之前窗口上的方法 Dispatcher (用户界面线程)。评论如下:

// 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类已经添加了一个窗口过程 Hook ,用于查看来自 shell 的消息。如果我添加 WM_WINDOWPOSCHANGED 检查,我是否可以以某种方式停止处理消息?或者我可以更改 Left 的值和 Top Windows/WPF 完成的移动的属性,以便窗口最终到达我想要的位置?

最佳答案

我找到了一种方法来防止窗口从停靠区域移动。基本上,我使用的代码已经使用 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 线程上安排移动到停靠位置。

关于c# - 应用栏窗口从停靠位置弹出,然后移动到停靠位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18491178/

相关文章:

c# - 如何在一个元素(Gridview)上绑定(bind)多个数据?

go - 检查进程是否有窗口

swift - 如何显示然后隐藏占据全屏的覆盖窗口

c# - 将点击事件附加到 GridView 中的图像上

c# - ASP.NET 应用程序中任务的奇怪行为

c# - 防止 WPF 控件在 MouseMove 事件上重叠

c# - 如果模型未实现 INotifyPropertyChanged, View 模型如何从其底层模型传播更改通知?

c# - 在构成列表框项目的两个控件之间平均分配宽度?

.net - 我们可以从 dll 而不是 exe 启动 Windows 服务吗?

c# - 使用 ImmutableSortedSet<T> 进行线程安全缓存