Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you do AppBar docking (to screen edge, like WinAmp) in WPF?

Is there any complete guidance on doing AppBar docking (such as locking to the screen edge) in WPF? I understand there are InterOp calls that need to be made, but I'm looking for either a proof of concept based on a simple WPF form, or a componentized version that can be consumed.

Related resources:

  • http://www.codeproject.com/KB/dotnet/AppBar.aspx
  • http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/05c73c9c-e85d-4ecd-b9b6-4c714a65e72b/
like image 937
paulwhit Avatar asked Sep 16 '08 19:09

paulwhit


2 Answers

There is an excellent MSDN article from 1996 which is entertainingly up to date: Extend the Windows 95 Shell with Application Desktop Toolbars. Following its guidance produces an WPF based appbar which handles a number of scenarios that the other answers on this page do not:

  • Allow dock to any side of the screen
  • Allow dock to a particular monitor
  • Allow resizing of the appbar (if desired)
  • Handle screen layout changes and monitor disconnections
  • Handle Win + Shift + Left and attempts to minimize or move the window
  • Handle co-operation with other appbars (OneNote et al.)
  • Handle per-monitor DPI scaling

I have both a demo app and the implementation of AppBarWindow on GitHub.

Example use:

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

Codebehind:

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

Changing docked position:

AppBar docked to edges

Resizing with thumb:

Resize

Cooperation with other appbars:

Coordination

Clone from GitHub if you want to use it. The library itself is only three files, and can easily be dropped in a project.

like image 23
Mitch Avatar answered Sep 20 '22 09:09

Mitch


Please Note: This question gathered a good amount of feedback, and some people below have made great points or fixes. Therefore, while I'll keep the code here (and possibly update it), I've also created a WpfAppBar project on github. Feel free to send pull requests.

That same project also builds to a WpfAppBar nuget package


I took the code from the first link provided in the question ( http://www.codeproject.com/KB/dotnet/AppBar.aspx ) and modified it to do two things:

  1. Work with WPF
  2. Be "standalone" - if you put this single file in your project, you can call AppBarFunctions.SetAppBar(...) without any further modification to the window.

This approach doesn't create a base class.

To use, just call this code from anywhere within a normal wpf window (say a button click or the initialize). Note that you can not call this until AFTER the window is initialized, if the HWND hasn't been created yet (like in the constructor), an error will occur.

Make the window an appbar:

AppBarFunctions.SetAppBar( this, ABEdge.Right ); 

Restore the window to a normal window:

AppBarFunctions.SetAppBar( this, ABEdge.None ); 

Here's the full code to the file - note you'll want to change the namespace on line 7 to something apropriate.

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);         }     } } 
like image 102
Philip Rieck Avatar answered Sep 20 '22 09:09

Philip Rieck