61

是否有关于在 WPF 中进行 AppBar 对接(例如锁定到屏幕边缘)的完整指南?我知道需要进行 InterOp 调用,但我正在寻找基于简单 WPF 表单的概念证明或可以使用的组件化版本。

相关资源:

4

8 回答 8

89

请注意:这个问题收集了大量的反馈,下面的一些人提出了很好的观点或修复。因此,虽然我将代码保留在这里(并可能更新它),但我还在github 上创建了一个WpfAppBar 项目。随时发送拉取请求。

同一个项目也构建到WpfAppBar nuget 包


我从问题中提供的第一个链接(http://www.codeproject.com/KB/dotnet/AppBar.aspx)中获取了代码,并对其进行了修改以做两件事:

  1. 使用 WPF
  2. 成为“独立” - 如果您将这个单个文件放在您的项目中,您可以调用 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);
        }
    }
}
于 2008-09-17T16:23:06.967 回答
6

有一篇 1996 年的优秀 MSDN 文章是最新的,有趣的是:Extend the Windows 95 Shell with Application Desktop Toolbars。遵循其指导会生成一个基于 WPF 的应用栏,该应用栏可以处理此页面上的其他答案无法处理的许多场景:

  • 允许停靠到屏幕的任何一侧
  • 允许停靠到特定显示器
  • 允许调整应用栏的大小(如果需要)
  • 处理屏幕布局更改和监视器断开连接
  • 处理Win++ShiftLeft尝试最小化或移动窗口
  • 处理与其他应用栏的合作(OneNote 等)
  • 处理每个显示器的 DPI 缩放

我有一个演示应用程序和AppBarWindowGitHub 上的实现。

示例使用:

<apb:AppBarWindow x:Class="WpfAppBarDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:apb="clr-namespace:WpfAppBar;assembly=WpfAppBar"
    DataContext="{Binding RelativeSource={RelativeSource Self}}" Title="MainWindow" 
    DockedWidthOrHeight="200" MinHeight="100" MinWidth="100">
    <Grid>
        <Button x:Name="btClose" Content="Close" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="23" Margin="10,10,0,0" Click="btClose_Click"/>
        <ComboBox x:Name="cbMonitor" SelectedItem="{Binding Path=Monitor, Mode=TwoWay}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Margin="10,38,0,0"/>
        <ComboBox x:Name="cbEdge" SelectedItem="{Binding Path=DockMode, Mode=TwoWay}" HorizontalAlignment="Left" Margin="10,65,0,0" VerticalAlignment="Top" Width="120"/>

        <Thumb Width="5" HorizontalAlignment="Right" Background="Gray" x:Name="rzThumb" Cursor="SizeWE" DragCompleted="rzThumb_DragCompleted" />
    </Grid>
</apb:AppBarWindow>

代码隐藏:

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();

        this.cbEdge.ItemsSource = new[]
        {
            AppBarDockMode.Left,
            AppBarDockMode.Right,
            AppBarDockMode.Top,
            AppBarDockMode.Bottom
        };
        this.cbMonitor.ItemsSource = MonitorInfo.GetAllMonitors();
    }

    private void btClose_Click(object sender, RoutedEventArgs e)
    {
        Close();
    }

    private void rzThumb_DragCompleted(object sender, DragCompletedEventArgs e)
    {
        this.DockedWidthOrHeight += (int)(e.HorizontalChange / VisualTreeHelper.GetDpi(this).PixelsPerDip);
    }
}

更改停靠位置:

AppBar 停靠在边缘

用拇指调整大小:

调整大小

与其他应用栏的合作:

协调

如果您想使用它,请从 GitHub克隆。库本身只有三个文件,可以很容易地放在一个项目中。

于 2017-03-26T02:05:26.480 回答
4

对不起我的英语......这是 Philip Rieck 的解决方案,有一些更正。它可以正确处理任务栏位置和大小的变化。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace wpf_appbar
{
    public enum ABEdge : int
    {
        Left,
        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;
            public RECT(Rect r)
            {
                Left = (int)r.Left;
                Right = (int)r.Right;
                Top = (int)r.Top;
                Bottom = (int)r.Bottom;
            }
            public static bool operator ==(RECT r1, RECT r2)
            {
                return r1.Bottom == r2.Bottom && r1.Left == r2.Left && r1.Right == r2.Right && r1.Top == r2.Top;
            }
            public static bool operator !=(RECT r1, RECT r2)
            {
                return !(r1 == r2);
            }
            public override bool Equals(object obj)
            {
                return base.Equals(obj);
            }
            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

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

        private enum TaskBarPosition : int
        {
            Left,
            Top,
            Right,
            Bottom
        }

        [StructLayout(LayoutKind.Sequential)]
        class TaskBar
        {
            public TaskBarPosition Position;
            public TaskBarPosition PreviousPosition;
            public RECT Rectangle;
            public RECT PreviousRectangle;
            public int Width;
            public int PreviousWidth;
            public int Height;
            public int PreviousHeight;
            public TaskBar()
            {
                Refresh();
            }
            public void Refresh()
            {
                APPBARDATA msgData = new APPBARDATA();
                msgData.cbSize = Marshal.SizeOf(msgData);
                SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref msgData);
                PreviousPosition = Position;
                PreviousRectangle = Rectangle;
                PreviousHeight = Height;
                PreviousWidth = Width;
                Rectangle = msgData.rc;
                Width = Rectangle.Right - Rectangle.Left;
                Height = Rectangle.Bottom - Rectangle.Top;
                int h = (int)SystemParameters.PrimaryScreenHeight;
                int w = (int)SystemParameters.PrimaryScreenWidth;
                if (Rectangle.Bottom == h && Rectangle.Top != 0) Position = TaskBarPosition.Bottom;
                else if (Rectangle.Top == 0 && Rectangle.Bottom != h) Position = TaskBarPosition.Top;
                else if (Rectangle.Right == w && Rectangle.Left != 0) Position = TaskBarPosition.Right;
                else if (Rectangle.Left == 0 && Rectangle.Right != w) Position = TaskBarPosition.Left;
            }
        }

        [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 ABEdge PreviousEdge { 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)
                    {
                        PreviousEdge = Edge;
                        ABSetPos(Edge, PreviousEdge, 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.None,
                    PreviousEdge = ABEdge.None,
                    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);
                info.PreviousEdge = info.Edge;
                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, info.PreviousEdge, 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;
        }

        static TaskBar tb = new TaskBar();

        private static void ABSetPos(ABEdge edge, ABEdge prevEdge, Window appbarWindow)
        {
            APPBARDATA barData = new APPBARDATA();
            barData.cbSize = Marshal.SizeOf(barData);
            barData.hWnd = new WindowInteropHelper(appbarWindow).Handle;
            barData.uEdge = (int)edge;
            RECT wa = new RECT(SystemParameters.WorkArea);
            tb.Refresh();
            switch (edge)
            {
                case ABEdge.Top:
                    barData.rc.Left = wa.Left - (prevEdge == ABEdge.Left ? (int)Math.Round(appbarWindow.ActualWidth) : 0);
                    barData.rc.Right = wa.Right + (prevEdge == ABEdge.Right ? (int)Math.Round(appbarWindow.ActualWidth) : 0);
                    barData.rc.Top = wa.Top - (prevEdge == ABEdge.Top ? (int)Math.Round(appbarWindow.ActualHeight) : 0) - ((tb.Position != TaskBarPosition.Top && tb.PreviousPosition == TaskBarPosition.Top) ? tb.Height : 0) + ((tb.Position == TaskBarPosition.Top && tb.PreviousPosition != TaskBarPosition.Top) ? tb.Height : 0);
                    barData.rc.Bottom = barData.rc.Top + (int)Math.Round(appbarWindow.ActualHeight);
                    break;
                case ABEdge.Bottom:
                    barData.rc.Left = wa.Left - (prevEdge == ABEdge.Left ? (int)Math.Round(appbarWindow.ActualWidth) : 0);
                    barData.rc.Right = wa.Right + (prevEdge == ABEdge.Right ? (int)Math.Round(appbarWindow.ActualWidth) : 0);
                    barData.rc.Bottom = wa.Bottom + (prevEdge == ABEdge.Bottom ? (int)Math.Round(appbarWindow.ActualHeight) : 0) - 1 + ((tb.Position != TaskBarPosition.Bottom && tb.PreviousPosition == TaskBarPosition.Bottom) ? tb.Height : 0) - ((tb.Position == TaskBarPosition.Bottom && tb.PreviousPosition != TaskBarPosition.Bottom) ? tb.Height : 0);
                    barData.rc.Top = barData.rc.Bottom - (int)Math.Round(appbarWindow.ActualHeight);
                    break;
            }

            SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref barData);
            switch (barData.uEdge)
            {
                case (int)ABEdge.Bottom:
                    if (tb.Position == TaskBarPosition.Bottom && tb.PreviousPosition == tb.Position)
                    {
                        barData.rc.Top += (tb.PreviousHeight - tb.Height);
                        barData.rc.Bottom = barData.rc.Top + (int)appbarWindow.ActualHeight;
                    }
                    break;
                case (int)ABEdge.Top:
                    if (tb.Position == TaskBarPosition.Top && tb.PreviousPosition == tb.Position)
                    {
                        if (tb.PreviousHeight - tb.Height > 0) barData.rc.Top -= (tb.PreviousHeight - tb.Height);
                        barData.rc.Bottom = barData.rc.Top + (int)appbarWindow.ActualHeight;
                    }
                    break;
            }
            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));
            appbarWindow.Dispatcher.BeginInvoke(new ResizeDelegate(DoResize), DispatcherPriority.ApplicationIdle, appbarWindow, rect);
        }
    }
}

您可以为左右边缘编写相同的代码。干得好,菲利普·里克,谢谢!

于 2011-04-10T06:39:13.887 回答
4

我修改了 Philip Rieck 的代码(顺便说一句。非常感谢)以在多个显示设置中工作。这是我的解决方案。

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

        [StructLayout(LayoutKind.Sequential)]
        private struct MONITORINFO
        {
          public int cbSize;
          public RECT  rcMonitor;
          public RECT  rcWork;
          public int dwFlags;
        }

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

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO mi);


        private const int MONITOR_DEFAULTTONEAREST = 0x2;
        private const int MONITORINFOF_PRIMARY = 0x1;

        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 GetActualScreenData(ABEdge edge, Window appbarWindow, ref int leftOffset, ref int topOffset, ref int actualScreenWidth, ref int actualScreenHeight)
        {
            IntPtr handle = new WindowInteropHelper(appbarWindow).Handle;
            IntPtr monitorHandle = MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST);

            MONITORINFO mi = new MONITORINFO();
            mi.cbSize = Marshal.SizeOf(mi);

            if (GetMonitorInfo(monitorHandle, ref mi))
            {
                if (mi.dwFlags == MONITORINFOF_PRIMARY)
                {
                    return;
                }
                leftOffset = mi.rcWork.left;
                topOffset = mi.rcWork.top;
                actualScreenWidth = mi.rcWork.right - leftOffset;
                actualScreenHeight = mi.rcWork.bottom - mi.rcWork.top;
            }
        }

        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;

            int leftOffset = 0;
            int topOffset = 0;
            int actualScreenWidth = (int)SystemParameters.PrimaryScreenWidth;
            int actualScreenHeight = (int)SystemParameters.PrimaryScreenHeight;

            GetActualScreenData(edge, appbarWindow, ref leftOffset, ref topOffset, ref actualScreenWidth, ref actualScreenHeight);

            if (barData.uEdge == (int)ABEdge.Left || barData.uEdge == (int)ABEdge.Right)
            {
                barData.rc.top = topOffset;
                barData.rc.bottom = actualScreenHeight;
                if (barData.uEdge == (int)ABEdge.Left)
                {
                    barData.rc.left = leftOffset;
                    barData.rc.right = (int)Math.Round(appbarWindow.ActualWidth) + leftOffset;
                }
                else
                {
                    barData.rc.right = actualScreenWidth + leftOffset;
                    barData.rc.left = barData.rc.right - (int)Math.Round(appbarWindow.ActualWidth);
                }
            }
            else
            {
                barData.rc.left = leftOffset;
                barData.rc.right = actualScreenWidth + leftOffset;
                if (barData.uEdge == (int)ABEdge.Top)
                {
                    barData.rc.top = topOffset;
                    barData.rc.bottom = (int)Math.Round(appbarWindow.ActualHeight) + topOffset;
                }
                else
                {
                    barData.rc.bottom = actualScreenHeight + topOffset;
                    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);
        }
    }
}
于 2013-09-19T13:53:31.957 回答
3

很高兴找到这个问题。上面的类确实很有用,但并没有完全涵盖 AppBar 实现的所有基础。

要完全实现 AppBar 的所有行为(应对全屏应用等),您也需要阅读这篇 MSDN 文章。

http://msdn.microsoft.com/en-us/library/bb776821.aspx

于 2009-03-08T20:34:13.403 回答
1

抱歉,我发布的最后一个代码在调整任务栏大小时不起作用。以下代码更改似乎效果更好:

  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);
于 2009-04-22T20:50:19.757 回答
1

作为商业替代品,请参阅 WPF 的即用型 ShellAppBar组件,它支持所有案例和条件,例如停靠在左侧、右侧、顶部、底部边缘的任务栏、支持多显示器、拖放停靠、自动隐藏等。与尝试自己处理所有这些案件相比,它可能会节省您的时间和金钱。

免责声明:我为 ShellAppBar 的开发人员 LogicNP Software 工作。

于 2009-07-13T04:58:28.683 回答
1

我花了几个星期来探索这个挑战,最后创建了一个非常可靠的 NuGet 包,以非常友好的方式提供了这个功能。只需创建一个新的 WPF 应用程序,然后将主窗口的类从 Window 更改为 DockWindow(在 XAML 中),就是这样!

在此处获取包并查看演示应用程序的 Git 存储库。

于 2015-10-22T02:16:20.700 回答