I am using the following code to generate a bespoke cursor dependent on the mouse position inside a win forms control. The cursor becomes a line that points towards the center of the control. Everything works very well for a few seconds and then I get the very unhelpful message :-
Exception thrown: 'System.Runtime.InteropServices.ExternalException' in System.Drawing.Common.dll
An exception of type 'System.Runtime.InteropServices.ExternalException' occurred in System.Drawing.Common.dll but was not handled in user code
A generic error occurred in GDI+.
This error appears to be connected to the garbage collector trying to clean up the pointer while it is still in use (though it could be something else). As you will see I have tried to make the pointer a property so that it isn't cleaned up but that doesn't seem to help.
Any ideas on how to avoid this error would be very welcome.
public struct IconInfo
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
public partial class CursorTest : UserControl
{
public CursorTest()
{
InitializeComponent();
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
[DllImport("user32.dll")]
static extern IntPtr CreateIconIndirect(ref IconInfo icon);
[DllImport("user32.dll")]
private static extern bool DestroyIcon(IntPtr hIcon);
bool IsBusy { get; set; } = false;
IntPtr ptr { get; set; }
/// <summary>
/// Create a 32x32 cursor from a bitmap, with the hot spot in the middle
/// </summary>
public void CreateCursor(Bitmap bmp)
{
ptr = bmp.GetHicon();
IconInfo tmp = new IconInfo();
GetIconInfo(ptr, ref tmp);
tmp.xHotspot = 16;
tmp.yHotspot = 16;
tmp.fIcon = false;
ptr = CreateIconIndirect(ref tmp);
this.Cursor = new Cursor(ptr);// Error Happens here
DestroyIcon(ptr);
}
private void GenerateCursorFromPostion(Point e)
{
if (IsBusy)
{
System.Diagnostics.Trace.WriteLine("Busy!!!");
return;
}
IsBusy = true;
float x = 16 * (Width / 2.0f - e.X);
float y = 16 * (Height / 2.0f - e.Y);
PointF st = new PointF(x + 16, y + 16);
PointF ed = new PointF(16 - x, 16 - y);
Bitmap bmp = new Bitmap(32, 32);
Graphics g = Graphics.FromImage(bmp);
g.DrawLine(Pens.Black, st, ed);
CreateCursor(bmp);
g.Dispose();
bmp.Dispose();
IsBusy = false;
}
private void CursorTest_MouseMove(object sender, MouseEventArgs e)
{
GenerateCursorFromPostion(e.Location);
}
}
You have a number of issues with your current code.
Your primary issue appears to be that you are destroying the icon for the cursor. When you create a Cursor using a handle, the handle is not copied, but used directly. Instead, you need to dispose it either when the control is being disposed, or when the cursor is replaced with another.
You also need to dispose the handle from GetHicon, and the Bitmap and Graphics needs a using, and you need to handle various errors.
public partial class CursorTest : UserControl
{
public CursorTest()
{
InitializeComponent();
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetIconInfo(IntPtr hIcon, out IconInfo pIconInfo);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr CreateIconIndirect(in IconInfo icon);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);
bool IsBusy { get; set; } = false;
IntPtr ptr { get; set; }
protected override Dispose(bool disposing)
{
base.Dispose(disposing);
DestroyIcon(ptr);
}
/// <summary>
/// Create a 32x32 cursor from a bitmap, with the hot spot in the middle
/// </summary>
public void CreateCursor(Bitmap bmp)
{
var original = IntPtr.Zero;
try
{
original = bmp.GetHicon();
if(!GetIconInfo(original, out var tmp))
throw new Win32Exception(Marshal.GetLastWin32Error());
tmp.xHotspot = 16;
tmp.yHotspot = 16;
tmp.fIcon = false;
var newPtr = CreateIconIndirect(in tmp);
if (newPtr != IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
this.Cursor = new Cursor(newPtr);
DestroyIcon(ptr);
ptr = newPtr;
}
finally
{
if(original != IntPtr.Zero)
DestroyIcon(original);
}
}
private void GenerateCursorFromPostion(Point e)
{
if (IsBusy)
{
System.Diagnostics.Trace.WriteLine("Busy!!!");
return;
}
IsBusy = true;
float x = 16 * (Width / 2.0f - e.X);
float y = 16 * (Height / 2.0f - e.Y);
PointF st = new PointF(x + 16, y + 16);
PointF ed = new PointF(16 - x, 16 - y);
using (Bitmap bmp = new Bitmap(32, 32))
{
using (Graphics g = Graphics.FromImage(bmp))
g.DrawLine(Pens.Black, st, ed);
CreateCursor(bmp);
}
IsBusy = false;
}
private void CursorTest_MouseMove(object sender, MouseEventArgs e)
{
GenerateCursorFromPostion(e.Location);
}
}
I have two monitors, my main (laptop) monitor with resolution set to 3840x2160 with 250% scaling and an external monitor at 2560x1440 with 100% scaling. When I maximize my border-less (WindowStyle="None") WPF window on my external monitor the window becomes larger than the display.
If I set the scaling on my main monitor (the laptop monitor) to 100% the window on the secondary monitor will be maximized correctly.
Why doesn't GetMonitorInfo return the correct size of the secondary display when the main display is scaled?
Thanks!
MainWindow XAML:
<Window x:Class="WpfMaximize.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStyle="None">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="60" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="Maximize window" Click="MaximizeWindow" />
<Button Content="Restore window" Click="RestoreWindow" />
</StackPanel>
</Window>
MainWindow code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SourceInitialized += new EventHandler(FormSourceInitialized);
}
private void FormSourceInitialized(object sender, EventArgs e)
{
IntPtr handle = new WindowInteropHelper(this).Handle;
HwndSource.FromHwnd(handle).AddHook(new HwndSourceHook(FormMaximize.WindowProc));
}
private void RestoreWindow(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Normal;
}
private void MaximizeWindow(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Maximized;
}
}
FormMaximize logic (from here)
public class FormMaximize
{
private const int WM_GETMINMAXINFO = 0x0024;
public static IntPtr WindowProc(IntPtr handle, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (message)
{
case WM_GETMINMAXINFO:
WmGetMinMaxInfo(handle, lParam);
handled = true;
break;
}
return (IntPtr)0;
}
private static void WmGetMinMaxInfo(IntPtr handle, IntPtr lParam)
{
MinMaxInfo minmaxInfo = (MinMaxInfo)Marshal.PtrToStructure(lParam, typeof(MinMaxInfo));
// Get current monitor information
int MONITOR_DEFAULT_TO_NEAREST = 0x00000002;
IntPtr monitor = MonitorFromWindow(handle, MONITOR_DEFAULT_TO_NEAREST);
if (monitor != IntPtr.Zero) // Not null pointer
{
MonitorInfo monitorInfo = new MonitorInfo();
GetMonitorInfo(monitor, monitorInfo);
Rectangle workArea = monitorInfo.WorkArea;
Rectangle monitorArea = monitorInfo.MonitorArea;
minmaxInfo.MaxPosition.X = Math.Abs(workArea.Left - monitorArea.Left);
minmaxInfo.MaxPosition.Y = Math.Abs(workArea.Top - monitorArea.Top);
minmaxInfo.MaxSize.X = Math.Abs(workArea.Right - workArea.Left);
minmaxInfo.MaxSize.Y = Math.Abs(workArea.Bottom - workArea.Top);
}
Marshal.StructureToPtr(minmaxInfo, lParam, true);
}
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MinMaxInfo
{
public Point Reserved;
public Point MaxSize;
public Point MaxPosition;
public Point MinTrackSize;
public Point MaxTrackSize;
};
[StructLayout(LayoutKind.Sequential)]
public class MonitorInfo
{
public int Size = Marshal.SizeOf(typeof(MonitorInfo));
public Rectangle MonitorArea = new Rectangle();
public Rectangle WorkArea = new Rectangle();
public int Flags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct Rectangle
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
// Windows API external reference
[DllImport("User32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MonitorInfo lpMonitorInfo);
[DllImport("User32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
}
I came across this as well. What I found was the WmGetMinMaxInfo was getting hit 2x. The first pass the handle was correct so MonitorFromWindow would return the expected info, the second pass it would return the default/primary monitor and cause the behavior you are seeing. As a work around I used MonitorFromPoint from the window's center point which persists when minimized.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SourceInitialized += new EventHandler(FormSourceInitialized);
}
private void FormSourceInitialized(object sender, EventArgs e)
{
IntPtr handle = new WindowInteropHelper(this).Handle;
HwndSource.FromHwnd(handle).AddHook(new HwndSourceHook(WindowProc));
}
private void RestoreWindow(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Normal;
}
private void MaximizeWindow(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Maximized;
}
private const int WM_GETMINMAXINFO = 0x0024;
private IntPtr WindowProc(IntPtr handle, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (message)
{
case WM_GETMINMAXINFO:
var xCenter = (int)(Left + (Width / 2));
var yCenter = (int)(Top + (Height / 2));
var windowCenter = new POINT(xCenter, yCenter);
FormMaximize.WmGetMinMaxInfo(handle, lParam, windowCenter);
handled = true;
break;
}
return (IntPtr)0;
}
}
public class FormMaximize
{
private static void WmGetMinMaxInfo(IntPtr handle, IntPtr lParam, POINT windowCenter)
{
MinMaxInfo minmaxInfo = (MinMaxInfo)Marshal.PtrToStructure(lParam, typeof(MinMaxInfo));
// Get current monitor information
IntPtr monitor = MonitorFromPoint(windowCenter, MONITORINFO.MonitorOptions.MONITOR_DEFAULTTONEAREST);
if (monitor != IntPtr.Zero) // Not null pointer
{
MonitorInfo monitorInfo = new MonitorInfo();
GetMonitorInfo(monitor, monitorInfo);
Rectangle workArea = monitorInfo.WorkArea;
Rectangle monitorArea = monitorInfo.MonitorArea;
minmaxInfo.MaxPosition.X = Math.Abs(workArea.Left - monitorArea.Left);
minmaxInfo.MaxPosition.Y = Math.Abs(workArea.Top - monitorArea.Top);
minmaxInfo.MaxSize.X = Math.Abs(workArea.Right - workArea.Left);
minmaxInfo.MaxSize.Y = Math.Abs(workArea.Bottom - workArea.Top);
}
Marshal.StructureToPtr(minmaxInfo, lParam, true);
}
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MinMaxInfo
{
public Point Reserved;
public Point MaxSize;
public Point MaxPosition;
public Point MinTrackSize;
public Point MaxTrackSize;
};
[StructLayout(LayoutKind.Sequential)]
public class MonitorInfo
{
public int Size = Marshal.SizeOf(typeof(MonitorInfo));
public Rectangle MonitorArea = new Rectangle();
public Rectangle WorkArea = new Rectangle();
public int Flags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct Rectangle
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
// Windows API external reference
[DllImport("User32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MonitorInfo lpMonitorInfo);
[DllImport("User32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
}
I'm trying to disable most of the touch gestures that can effect my WebBrowser control in my winforms project and i've been unsuccessfull so far.
Edge Gestures have been disabled with PKEY_EdgeGesture_DisableTouchWhenFullScreen => working
I'm using a PreFilterMessage but it seems that WM_GESTURE can't be filtered from there :
public bool PreFilterMessage(ref Message m)
{
// Messages to be filtered
const int WM_MOUSEWHEEL = 0x20A;
const int WM_SYSCOMMAND = 0x0112;
const int WM_GESTURE = 0x0119;
const int WM_GESTURENOTIFY = 0x011A;
const int SC_MOVE = 0xF010;
switch (m.Msg)
{
case WM_MOUSEWHEEL:
Debug.WriteLine("Filtering WM_MOUSEWHEEL !"); // Working
return true;
case WM_GESTURE:
Debug.WriteLine("Filtering WM_GESTURE !"); // Not working
return true;
case WM_GESTURENOTIFY:
Debug.WriteLine("Filtering WM_GESTURENOTIFY !"); // Not working
return true;
case WM_SYSCOMMAND:
int command = m.WParam.ToInt32() & 0xfff0;
if (command == SC_MOVE) return true;
break;
}
Next i've moved to WndProc override and i see the messages in debug log but so far i haven't figured out how to filter message from there: the gesture got executed anyway.
protected override void WndProc(ref Message m)
{
const int WM_GESTURE = 0x0119;
const int WM_GESTURENOTIFY = 0x011A;
switch (m.Msg)
{
case WM_GESTURE:
Debug.WriteLine("WM_GESTURE go away !!!");
return;
case WM_GESTURENOTIFY:
Debug.WriteLine("WM_GESTURENOTIFY begone with thee !!");
return;
}
base.WndProc(ref m);
}
i try to avoid CSS injection in the html page or setting a registry value to globally disable zoom in internet explorer, anyone has an idea ?
Answering my own question :)
Filtering WM_GESUTURE and WM_TOUCH messages out have led to nowhere (never noticed a change in the app touch behavior)
WM_POINTER is the way to go... Filetering WM_POINTERUP and WM_POINTERDOWN messages has an effect on my application.
Working with WM_POINTER messages gives low level datas and you have to build your own gesture interpretation (which looks like a real pain, if your goal is only to disable theses)
The workaround : since WM_POINTER carries info about primary, secondary, ternary pointers, i filter out all the messages not related to primary pointer, thus disabling two fingers
public partial class Form1 : Form, IMessageFilter
{
internal static int HIWORD(IntPtr wParam)
{
return (int)((wParam.ToInt64() >> 16) & 0xffff);
}
internal static int LOWORD(IntPtr wParam)
{
return (int)(wParam.ToInt64() & 0xffff);
}
[Flags]
internal enum VIRTUAL_KEY_STATES
{
NONE = 0x0000,
LBUTTON = 0x0001,
RBUTTON = 0x0002,
SHIFT = 0x0004,
CTRL = 0x0008,
MBUTTON = 0x0010,
XBUTTON1 = 0x0020,
XBUTTON2 = 0x0040
}
[StructLayout(LayoutKind.Sequential)]
internal struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
public POINT(Point pt)
{
X = pt.X;
Y = pt.Y;
}
public Point ToPoint()
{
return new Point(X, Y);
}
public void AssignTo(ref Point destination)
{
destination.X = X;
destination.Y = Y;
}
public void CopyFrom(Point source)
{
X = source.X;
Y = source.Y;
}
public void CopyFrom(POINT source)
{
X = source.X;
Y = source.Y;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(Rectangle source)
{
Left = source.Left;
Top = source.Top;
Right = source.Right;
Bottom = source.Bottom;
}
public RECT(int x, int y, int width, int height)
{
Left = x;
Top = y;
Right = Left + width;
Bottom = Top + height;
}
public int Width
{
get { return Right - Left; }
}
public int Height
{
get { return Bottom - Top; }
}
public Rectangle ToRectangle()
{
return new Rectangle(Left, Top, Width, Height);
}
public void Inflate(int dx, int dy)
{
Left -= dx;
Top -= dy;
Right += dx;
Bottom += dy;
}
public void Deflate(int leftMargin, int topMargin, int rightMargin, int bottomMargin)
{
Left += leftMargin;
Top += topMargin;
Right -= rightMargin;
Bottom -= bottomMargin;
if (Bottom < Top)
{
Bottom = Top;
}
if (Right < Left)
{
Right = Left;
}
}
public void Offset(int dx, int dy)
{
Left += dx;
Top += dy;
Right += dx;
Bottom += dy;
}
}
[Flags]
internal enum TOUCH_FLAGS
{
NONE = 0x00000000
}
[Flags]
internal enum TOUCH_MASK
{
NONE = 0x00000000,
CONTACTAREA = 0x00000001,
ORIENTATION = 0x00000002,
PRESSURE = 0x00000004,
}
internal enum POINTER_INPUT_TYPE
{
POINTER = 0x00000001,
TOUCH = 0x00000002,
PEN = 0x00000003,
MOUSE = 0x00000004
}
[Flags]
internal enum POINTER_FLAGS
{
NONE = 0x00000000,
NEW = 0x00000001,
INRANGE = 0x00000002,
INCONTACT = 0x00000004,
FIRSTBUTTON = 0x00000010,
SECONDBUTTON = 0x00000020,
THIRDBUTTON = 0x00000040,
FOURTHBUTTON = 0x00000080,
FIFTHBUTTON = 0x00000100,
PRIMARY = 0x00002000,
CONFIDENCE = 0x00004000,
CANCELED = 0x00008000,
DOWN = 0x00010000,
UPDATE = 0x00020000,
UP = 0x00040000,
WHEEL = 0x00080000,
HWHEEL = 0x00100000,
CAPTURECHANGED = 0x00200000,
}
[StructLayout(LayoutKind.Sequential)]
internal struct POINTER_TOUCH_INFO
{
[MarshalAs(UnmanagedType.Struct)]
public POINTER_INFO PointerInfo;
public TOUCH_FLAGS TouchFlags;
public TOUCH_MASK TouchMask;
[MarshalAs(UnmanagedType.Struct)]
public RECT ContactArea;
[MarshalAs(UnmanagedType.Struct)]
public RECT ContactAreaRaw;
public uint Orientation;
public uint Pressure;
}
[StructLayout(LayoutKind.Sequential)]
internal struct POINTER_INFO
{
public POINTER_INPUT_TYPE pointerType;
public int PointerID;
public int FrameID;
public POINTER_FLAGS PointerFlags;
public IntPtr SourceDevice;
public IntPtr WindowTarget;
[MarshalAs(UnmanagedType.Struct)]
public POINT PtPixelLocation;
[MarshalAs(UnmanagedType.Struct)]
public POINT PtPixelLocationRaw;
[MarshalAs(UnmanagedType.Struct)]
public POINT PtHimetricLocation;
[MarshalAs(UnmanagedType.Struct)]
public POINT PtHimetricLocationRaw;
public uint Time;
public uint HistoryCount;
public uint InputData;
public VIRTUAL_KEY_STATES KeyStates;
public long PerformanceCount;
public int ButtonChangeType;
}
internal const int
WM_PARENTNOTIFY = 0x0210,
WM_NCPOINTERUPDATE = 0x0241,
WM_NCPOINTERDOWN = 0x0242,
WM_NCPOINTERUP = 0x0243,
WM_POINTERUPDATE = 0x0245,
WM_POINTERDOWN = 0x0246,
WM_POINTERUP = 0x0247,
WM_POINTERENTER = 0x0249,
WM_POINTERLEAVE = 0x024A,
WM_POINTERACTIVATE = 0x024B,
WM_POINTERCAPTURECHANGED = 0x024C,
WM_POINTERWHEEL = 0x024E,
WM_POINTERHWHEEL = 0x024F,
// WM_POINTERACTIVATE return codes
PA_ACTIVATE = 1,
PA_NOACTIVATE = 3,
MAX_TOUCH_COUNT = 256;
internal static int GET_POINTER_ID(IntPtr wParam)
{
return LOWORD(wParam);
}
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool GetPointerInfo(int pointerID, ref POINTER_INFO pointerInfo);
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_POINTERDOWN:
case WM_POINTERUP:
case WM_POINTERUPDATE:
case WM_POINTERCAPTURECHANGED:
int pointerID = GET_POINTER_ID(m.WParam);
POINTER_INFO pi = new POINTER_INFO();
if (GetPointerInfo(pointerID, ref pi))
{
// Not a primary pointer => filter !
if ((pi.PointerFlags & POINTER_FLAGS.PRIMARY) == 0)
{
return true;
}
}
break;
}
return false;
}
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/
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:
Work with WPF
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);
}
}
}
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:
Resizing with thumb:
Cooperation with other appbars:
Clone from GitHub if you want to use it. The library itself is only three files, and can easily be dropped in a project.
Very happy to have found this question. Above class is really useful, but doesnt quite cover all the bases of AppBar implementation.
To fully implement all the behaviour of an AppBar (cope with fullscreen apps etc) you're going to want to read this MSDN article too.
http://msdn.microsoft.com/en-us/library/bb776821.aspx
Sorry for my English... Here is the Philip Rieck's solution with some corrects. It correctly works with Taskbar position and size changes.
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);
}
}
}
The same code you can write for the Left and Right edges.
Good job, Philip Rieck, thank you!
I modified code from Philip Rieck (btw. Thanks a lot) to work in multiple display settings. Here's my solution.
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);
}
}
}
As a commercial alternative, see the ready-to-use ShellAppBar component for WPF which supports all cases and secnarios such as taskbar docked to left,right,top,bottom edge, support for multiple monitors, drag-docking, autohide , etc etc. It may save you time and money over trying to handle all these cases yourself.
DISCLAIMER: I work for LogicNP Software, the developer of ShellAppBar.
Sorry, the last code I posted didn't work when the Taskbar is resized. The following code change seems to work better:
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);
I've spent some weeks exploring this challenge and finally created a very solid NuGet package delivering this functionality in very friendly way. Simply create a new WPF app then change the main window's class from Window to DockWindow (in the XAML) and that's it!
Get the package here and see the Git repo for a demonstration app.