我最近在我的WPF应用程序中添加了一个窗口,该窗口可以作为“应用栏”停靠在桌面的边缘。我用来进行对接的代码来自this stackoverflow post。
该程序有三个与此窗口相关的用户设置。一个是窗口停靠的边缘,另外两个是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中读取时其位置和大小发生变化时添加的函数。这些调用似乎对行为没有任何影响,所以我可能会删除它们。
我知道在显示窗口之前调用SourceInitialized
和Loading
事件,但在窗口句柄和布局&之后调用。措施通行证已经完成。但是,看起来窗口是在调用AppBarFunctions.SetAppBar
之前呈现的,这就是我看到它出现然后移动到位的原因。
我还尝试将Left
和Top
属性设置为窗口构造函数中设置中保存的值,从而将窗口移动到停靠位置。这也行不通。实际上,情况更糟,因为窗口首先在停靠位置绘制,然后显然被移离桌面边缘以腾出空间,然后移回到停靠位置。
如何让这个窗口在启动时出现在停靠位置而不是之后移动?
编辑:
我想我找到了问题的原因。 AppBarFunctions
类代码中的ABSetPos
类代码中有注释,就在它调度窗口DoResize
(UI线程)上的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
类已添加一个窗口过程挂钩,用于监视来自shell的消息。如果我为WM_WINDOWPOSCHANGED添加一个检查,我能否以某种方式阻止消息被处理?或者我可以为Windows / WPF完成的移动更改Left
和Top
属性的值,以便窗口最终到达我想要的位置?
答案 0 :(得分:5)
我找到了一种防止窗户从停靠区域移动的方法。基本上,我正在使用的代码已经使用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
结构从非托管内存编组到托管对象中,设置位置&窗户的大小回到停靠位置&大小,并将其编组回非托管内存。然后将处理设置为true&退出。
这完美无缺。防止窗户从其停靠位置反弹;返回。并且无需安排移动到窗口Dispatcher
线程上的停靠位置。