我最近在我的 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 中读到的那样文章。这些调用似乎对行为没有任何影响,所以我可能会删除它们。
我知道 SourceInitialized
和 Loading
事件在窗口显示之前调用,但在窗口句柄和布局和测量 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/