在WPF中,如何将AppBar停靠到屏幕边缘,例如WinAmp?
在WPF中进行AppBar停靠(例如锁定到屏幕边缘)是否有任何完整指南?我知道需要进行InterOp调用,但是我正在寻找基于简单WPF表单的概念证明或者可以使用的组件化版本。
相关资源:
- http://www.codeproject.com/KB/dotnet/AppBar.aspx
- http://social.msdn.microsoft.com/Forums/zh-CN/wpf/thread/05c73c9c-e85d-4ecd-b9b6-4c714a65e72b/
解决方案
回答
请注意:这个问题收集了很多反馈,下面的一些人提出了要点或者解决方法。因此,尽管我将代码保存在这里(并可能进行更新),但我还在github上创建了一个WpfAppBar项目。随时发送请求请求。
该项目还构建到WpfAppBar nuget包中
我从问题中提供的第一个链接中获取了代码(http://www.codeproject.com/KB/dotnet/AppBar.aspx),并对其进行了修改以完成两件事:
- 使用WPF
- 是"独立的"-如果将此单个文件放在项目中,则可以调用AppBarFunctions.SetAppBar(...),而无需对窗口进行任何进一步的修改。
这种方法不会创建基类。
若要使用,只需在普通的wpf窗口中的任何位置调用此代码即可(例如单击按钮或者初始化)。请注意,只有在初始化窗口之后才能调用此函数,如果尚未创建HWND(如在构造函数中),则会发生错误。
将窗口设为应用栏:
AppBarFunctions.SetAppBar( this, ABEdge.Right );
将窗口还原为普通窗口:
AppBarFunctions.SetAppBar( this, ABEdge.None );
这是文件注释的完整代码,我们需要将第7行的名称空间更改为适当的名称。
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; using System.Windows.Threading; namespace AppBarApplication { public enum ABEdge : int { Left = 0, Top, Right, Bottom, None } internal static class AppBarFunctions { [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } [StructLayout(LayoutKind.Sequential)] private struct APPBARDATA { public int cbSize; public IntPtr hWnd; public int uCallbackMessage; public int uEdge; public RECT rc; public IntPtr lParam; } private enum ABMsg : int { ABM_NEW = 0, ABM_REMOVE, ABM_QUERYPOS, ABM_SETPOS, ABM_GETSTATE, ABM_GETTASKBARPOS, ABM_ACTIVATE, ABM_GETAUTOHIDEBAR, ABM_SETAUTOHIDEBAR, ABM_WINDOWPOSCHANGED, ABM_SETSTATE } private enum ABNotify : int { ABN_STATECHANGE = 0, ABN_POSCHANGED, ABN_FULLSCREENAPP, ABN_WINDOWARRANGE } [DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)] private static extern uint SHAppBarMessage(int dwMessage, ref APPBARDATA pData); [DllImport("User32.dll", CharSet = CharSet.Auto)] private static extern int RegisterWindowMessage(string msg); private class RegisterInfo { public int CallbackId { get; set; } public bool IsRegistered { get; set; } public Window Window { get; set; } public ABEdge Edge { get; set; } public WindowStyle OriginalStyle { get; set; } public Point OriginalPosition { get; set; } public Size OriginalSize { get; set; } public ResizeMode OriginalResizeMode { get; set; } public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == CallbackId) { if (wParam.ToInt32() == (int)ABNotify.ABN_POSCHANGED) { ABSetPos(Edge, Window); handled = true; } } return IntPtr.Zero; } } private static Dictionary<Window, RegisterInfo> s_RegisteredWindowInfo = new Dictionary<Window, RegisterInfo>(); private static RegisterInfo GetRegisterInfo(Window appbarWindow) { RegisterInfo reg; if( s_RegisteredWindowInfo.ContainsKey(appbarWindow)) { reg = s_RegisteredWindowInfo[appbarWindow]; } else { reg = new RegisterInfo() { CallbackId = 0, Window = appbarWindow, IsRegistered = false, Edge = ABEdge.Top, OriginalStyle = appbarWindow.WindowStyle, OriginalPosition =new Point( appbarWindow.Left, appbarWindow.Top), OriginalSize = new Size( appbarWindow.ActualWidth, appbarWindow.ActualHeight), OriginalResizeMode = appbarWindow.ResizeMode, }; s_RegisteredWindowInfo.Add(appbarWindow, reg); } return reg; } private static void RestoreWindow(Window appbarWindow) { RegisterInfo info = GetRegisterInfo(appbarWindow); appbarWindow.WindowStyle = info.OriginalStyle; appbarWindow.ResizeMode = info.OriginalResizeMode; appbarWindow.Topmost = false; Rect rect = new Rect(info.OriginalPosition.X, info.OriginalPosition.Y, info.OriginalSize.Width, info.OriginalSize.Height); appbarWindow.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new ResizeDelegate(DoResize), appbarWindow, rect); } public static void SetAppBar(Window appbarWindow, ABEdge edge) { RegisterInfo info = GetRegisterInfo(appbarWindow); info.Edge = edge; APPBARDATA abd = new APPBARDATA(); abd.cbSize = Marshal.SizeOf(abd); abd.hWnd = new WindowInteropHelper(appbarWindow).Handle; if( edge == ABEdge.None) { if( info.IsRegistered) { SHAppBarMessage((int)ABMsg.ABM_REMOVE, ref abd); info.IsRegistered = false; } RestoreWindow(appbarWindow); return; } if (!info.IsRegistered) { info.IsRegistered = true; info.CallbackId = RegisterWindowMessage("AppBarMessage"); abd.uCallbackMessage = info.CallbackId; uint ret = SHAppBarMessage((int)ABMsg.ABM_NEW, ref abd); HwndSource source = HwndSource.FromHwnd(abd.hWnd); source.AddHook(new HwndSourceHook(info.WndProc)); } appbarWindow.WindowStyle = WindowStyle.None; appbarWindow.ResizeMode = ResizeMode.NoResize; appbarWindow.Topmost = true; ABSetPos(info.Edge, appbarWindow); } private delegate void ResizeDelegate(Window appbarWindow, Rect rect); private static void DoResize(Window appbarWindow, Rect rect) { appbarWindow.Width = rect.Width; appbarWindow.Height = rect.Height; appbarWindow.Top = rect.Top; appbarWindow.Left = rect.Left; } private static void ABSetPos(ABEdge edge, Window appbarWindow) { APPBARDATA barData = new APPBARDATA(); barData.cbSize = Marshal.SizeOf(barData); barData.hWnd = new WindowInteropHelper(appbarWindow).Handle; barData.uEdge = (int)edge; if (barData.uEdge == (int)ABEdge.Left || barData.uEdge == (int)ABEdge.Right) { barData.rc.top = 0; barData.rc.bottom = (int)SystemParameters.PrimaryScreenHeight; if (barData.uEdge == (int)ABEdge.Left) { barData.rc.left = 0; barData.rc.right = (int)Math.Round(appbarWindow.ActualWidth); } else { barData.rc.right = (int)SystemParameters.PrimaryScreenWidth; barData.rc.left = barData.rc.right - (int)Math.Round(appbarWindow.ActualWidth); } } else { barData.rc.left = 0; barData.rc.right = (int)SystemParameters.PrimaryScreenWidth; if (barData.uEdge == (int)ABEdge.Top) { barData.rc.top = 0; barData.rc.bottom = (int)Math.Round(appbarWindow.ActualHeight); } else { barData.rc.bottom = (int)SystemParameters.PrimaryScreenHeight; barData.rc.top = barData.rc.bottom - (int)Math.Round(appbarWindow.ActualHeight); } } SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref barData); SHAppBarMessage((int)ABMsg.ABM_SETPOS, ref barData); Rect rect = new Rect((double)barData.rc.left, (double)barData.rc.top, (double)(barData.rc.right - barData.rc.left), (double)(barData.rc.bottom - barData.rc.top)); //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. appbarWindow.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new ResizeDelegate(DoResize), appbarWindow, rect); } } }
回答
很高兴找到这个问题。上面的类确实有用,但是并不能完全覆盖AppBar实现的所有基础。
为了完全实现AppBar的所有行为(应对全屏应用程序等),我们也将要阅读此MSDN文章。
http://msdn.microsoft.com/en-us/library/bb776821.aspx
回答
抱歉,调整任务栏大小时,我发布的最后一个代码不起作用。以下代码更改似乎更好地起作用:
SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref barData); if (barData.uEdge == (int)ABEdge.Top) barData.rc.bottom = barData.rc.top + (int)Math.Round(appbarWindow.ActualHeight); else if (barData.uEdge == (int)ABEdge.Bottom) barData.rc.top = barData.rc.bottom - (int)Math.Round(appbarWindow.ActualHeight); SHAppBarMessage((int)ABMsg.ABM_SETPOS, ref barData);
回答
作为一种商业替代方案,请参阅WPF的即用型ShellAppBar组件,该组件支持所有情况和场景,例如停靠在左侧,右侧,顶部,底部边缘的任务栏,支持多个监视器,拖放,自动隐藏等。尝试自己处理所有这些情况可能会节省时间和金钱。
免责声明:我为LogicNP Software(ShellAppBar的开发人员)工作。