Related
Introduction
I am developing custom controls [Here: Form] with custom 3D shape and PathGradient colors.
everything is working very smoothly and even I had achieved what I exactly want
And the whole code to generate this form is given below :-
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.ComponentModel;
namespace CustomControls
{
public class SplashFORM : Form
{
[Description("If True then user can close the form by pressing Alt + F4 while it is focused.")]
public bool CanClose { get; set; } = false;
private readonly Timer Drawer = new Timer();
public SplashFORM()
{
FormBorderStyle = FormBorderStyle.None;
StartPosition = FormStartPosition.CenterScreen;
}
protected override void OnFormClosing(FormClosingEventArgs FCEArgs)
{
if (!CanClose) { FCEArgs.Cancel = true; return; }
base.OnFormClosing(FCEArgs);
}
protected override void OnResize(EventArgs e)
{
Invalidate();
base.OnResize(e);
}
protected override void OnLoad(EventArgs EArgs)
{
if (!DesignMode)
{
DrawForm(null, null);
}
base.OnLoad(EArgs);
}
private void DrawForm(object _, EventArgs __)
{
using (Bitmap BackIMG = new Bitmap(Width, Height))
{
using (Graphics Gfx = Graphics.FromImage(BackIMG))
{
Gfx.SmoothingMode = SmoothingMode.HighQuality;
FillRoundedRectangle(Gfx);
foreach (Control C in Controls)
{
using (Bitmap BitMP = new Bitmap(C.Width, C.Height))
{
Rectangle Rect = new Rectangle(0, 0, C.Width, C.Height);
C.DrawToBitmap(BitMP, Rect);
Gfx.DrawImage(BitMP, C.Location);
}
}
SetBitmap(BackIMG, Left, Top, Handle);
}
}
}
protected override void OnPaint(PaintEventArgs PEArgs)
{
if (DesignMode)
{
Graphics Gfx = PEArgs.Graphics;
Gfx.SmoothingMode = SmoothingMode.HighQuality;
FillRoundedRectangle(Gfx);
}
base.OnPaint(PEArgs);
}
protected override CreateParams CreateParams
{
get
{
CreateParams Params = base.CreateParams;
Params.ClassStyle = 0x00020000;
Params.Style |= 0x00020000;
if (!DesignMode) { Params.ExStyle |= 0x00080000; }
return Params;
}
}
protected override void OnPaintBackground(PaintEventArgs PEArgs)
{
using (Brush GPBrush = Helper.Get_SplashBrush(new Rectangle(0, 0, Width - 1, Height - 1)))
{ PEArgs.Graphics.FillRectangle(GPBrush, ClientRectangle); }
}
private void SetBitmap(Bitmap BitMP, int CLeft, int CTop, IntPtr CHndl)
{
if (BitMP.PixelFormat != PixelFormat.Format32bppArgb) throw new ApplicationException("The BitMP must be 32ppp with alpha-channel.");
IntPtr ScrnDC = Win32.GetDC(IntPtr.Zero);
IntPtr MemDC = Win32.CreateCompatibleDC(ScrnDC);
IntPtr HBitMP = IntPtr.Zero;
IntPtr OBitMP = IntPtr.Zero;
byte OPCity = 255;
try
{
HBitMP = BitMP.GetHbitmap(Color.FromArgb(0));
OBitMP = Win32.SelectObject(MemDC, HBitMP);
Win32.Size _Size = new Win32.Size(BitMP.Width, BitMP.Height);
Win32.Point _PointSource = new Win32.Point(0, 0);
Win32.Point _TopPos = new Win32.Point(CLeft, CTop);
Win32.BLENDFUNCTION _Blend = new Win32.BLENDFUNCTION
{
BlendOp = Win32.AC_SRC_OVER,
BlendFlags = 0,
SourceConstantAlpha = OPCity,
AlphaFormat = Win32.AC_SRC_ALPHA
};
Win32.UpdateLayeredWindow(CHndl, ScrnDC, ref _TopPos, ref _Size, MemDC, ref _PointSource, 0, ref _Blend, Win32.ULW_ALPHA);
}
finally
{
Win32.ReleaseDC(IntPtr.Zero, ScrnDC);
if (HBitMP != IntPtr.Zero)
{
Win32.SelectObject(MemDC, OBitMP);
Win32.DeleteObject(HBitMP);
}
Win32.DeleteDC(MemDC);
}
}
private GraphicsPath RoundedRect()
{
Rectangle _2DSize = new Rectangle(0, 0, Width - 1, Height - 1);
int Diameter = 50 * 2;
Size _Size = new Size(Diameter, Diameter);
Rectangle _Arc = new Rectangle(_2DSize.Location, _Size);
GraphicsPath _Path = new GraphicsPath();
_Path.AddArc(_Arc, 180, 90);
_Arc.X = _2DSize.Right - 50;
_Path.AddArc(_Arc, 270, 90);
_Arc.Y = _2DSize.Bottom - 5;
_Path.AddArc(_Arc, 0, 90);
_Arc.X = _2DSize.Left - Diameter;
_Path.AddArc(_Arc, 90, 90);
_Path.CloseFigure();
return _Path;
}
private void FillRoundedRectangle(Graphics Gfx)
{
if (Gfx == null) throw new ArgumentNullException("Graphics supplied is null");
using (GraphicsPath GPth = RoundedRect())
{
var Bnds = new Rectangle(0, 0, Width - 1, Height - 1);
PointF[] PT = new PointF[]
{
new PointF(-50, -50),
new PointF(Bnds.Right, 0),
new PointF(Bnds.Right, Bnds.Bottom),
new PointF(40, Bnds.Bottom),
new PointF((float)((float)Bnds.Right / 2 - ((float)Bnds.Bottom * 0.15)), (float)((float)Bnds.Bottom / 2 + ((float)Bnds.Bottom * 0.35)))
};
Brush GPBrush = Helper.Get_SplashBrush(Bnds);
//
Gfx.FillPath(GPBrush, GPth);
Region = new Region(GPth);
//using (Brush GPBrush = Helper.Get_SplashBrush(new Rectangle(0, 0, Width - 1, Height - 1)))
//{ Gfx.FillPath(GPBrush, GPth); }
}
}
}
internal class Win32
{
public const Int32 ULW_COLORKEY = 0x00000001;
public const Int32 ULW_ALPHA = 0x00000002;
public const Int32 ULW_OPAQUE = 0x00000004;
public const byte AC_SRC_OVER = 0x00;
public const byte AC_SRC_ALPHA = 0x01;
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteObject(IntPtr hObject);
public enum Bool
{
False = 0,
True
};
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public Int32 x;
public Int32 y;
public Point(Int32 x, Int32 y) { this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
public struct Size
{
public Int32 cx;
public Int32 cy;
public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
}
internal class Helper
{
public static Brush Get_SplashBrush(Rectangle Bnds)
{
PointF[] PTsGH;
Color[] CLRsGH;
PathGradientBrush PGB;
PTsGH = new PointF[]
{
new PointF(-50, -50),
new PointF(Bnds.Right, 0),
new PointF(Bnds.Right, Bnds.Bottom),
new PointF(40, Bnds.Bottom),
new PointF((float)((float)Bnds.Right / 2 - ((float)Bnds.Bottom * 0.15)), (float)((float)Bnds.Bottom / 2 + ((float)Bnds.Bottom * 0.35)))
};
CLRsGH = new Color[]
{
Color.FromArgb(120,40,40),
Color.FromArgb(60, 100, 40),
Color.FromArgb(50, 50, 120),
Color.FromArgb(0, 60, 100),
Color.FromArgb(240, 120, 20, 40)
};
PGB = new PathGradientBrush(PTsGH)
{
SurroundColors = CLRsGH,
CenterColor = Color.FromArgb(160, 124, 20)
};
return PGB;
}
public enum BrushType
{
Linear, Path, Solid
}
public enum ShapeType
{
Rectangular, Circular, Triangular, SplashSpecial
}
}
}
The Problem
Everything is very fine till now but when I try to change the opacity of the form by using this code
protected override void OnClick(EventArgs e)
{
Opacity -= 0.05;
base.OnClick(e);
}
then the form becomes opaque and looks very dirty
I want to change the opacity of the form using a timer to show a fade in and fade out effect!
I tried to use Invalidate(), Update() and Refresh() on the form after changing the opacity but still no luck :(
Is there a way to solve this issue, or is there any alternative to what I want to achieve?
Im working with a c# application which captures the system events using the global c# mouse and keyboard hooks, But I couldn't get the current keyboard cursor position values while im typing.
Following is my code and it always returns GetCaretPos output as (0,0)
PointDetail curPoint = new PointDetail();
Point position = new Point();
IntPtr hwndFoc;
IntPtr hwndFG = WinApiDelegate.GetForegroundWindow();
uint processID = 0;
uint mainWindowProcessId = 0;
IntPtr activeWindowThreadProcess = WinApiDelegate.GetWindowThreadProcessId(hwndFG, IntPtr.Zero);
IntPtr currWindowThread = IntPtr.Zero;
int thisWindowThread = 0;
this.Invoke(new MethodInvoker(delegate
{
currWindowThread = WinApiDelegate.GetWindowThreadProcessId(this.Handle, IntPtr.Zero);
thisWindowThread = WinApiDelegate.GetWindowThreadProcessId(this.Handle, out mainWindowProcessId);
}));
int activeWindowThread = WinApiDelegate.GetWindowThreadProcessId(hwndFG, out processID);
if (activeWindowThread != Thread.CurrentThread.ManagedThreadId)
{
WinApiDelegate.AttachThreadInput(activeWindowThreadProcess, currWindowThread, true);
hwndFoc = WinApiDelegate.GetActiveWindow();
bool CaretPos = WinApiDelegate.GetCaretPos(ref position);
}
You can find the cursor position using the caretPosition. Try the below code.
[DllImport("user32.dll", EntryPoint = "GetGUIThreadInfo")]
public static extern bool GetGUIThreadInfo(uint tId, out GUITHREADINFO threadInfo);
[StructLayout(LayoutKind.Sequential)] // Required by user32.dll
public struct RECT
{
public uint Left;
public uint Top;
public uint Right;
public uint Bottom;
};
[StructLayout(LayoutKind.Sequential)] // Required by user32.dll
public struct GUITHREADINFO
{
public uint cbSize;
public uint flags;
public IntPtr hwndActive;
public IntPtr hwndFocus;
public IntPtr hwndCapture;
public IntPtr hwndMenuOwner;
public IntPtr hwndMoveSize;
public IntPtr hwndCaret;
public RECT rcCaret;
};
private System.Windows.Point EvaluateCaretPosition()
{
caretPosition = new Point();
try
{
// Fetch GUITHREADINFO
GetCaretPosition();
caretPosition.X = (int)guiInfo.rcCaret.Left; //+ 25;
caretPosition.Y = (int)guiInfo.rcCaret.Bottom; //+ 25;
WinApiDelegate.ClientToScreen(guiInfo.hwndCaret, ref caretPosition);
}
catch (Exception Ex)
{
GenerateConsolidatedErrorLog(Ex);
}
return new System.Windows.Point(caretPosition.X, caretPosition.Y);
}
public void GetCaretPosition()
{
try
{
guiInfo = new WinApiDelegate.GUITHREADINFO();
guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo);
// Get GuiThreadInfo into guiInfo
WinApiDelegate.GetGUIThreadInfo(0, out guiInfo);
}
catch (Exception Ex)
{
GenerateConsolidatedErrorLog(Ex);
}
}
I am uploading a cab file from a web form, and want to unpack it in memory. I've tried tackling the issue with a CabInfo file, but without success. I do know how to unpack a cab file to my local disk, but do not know how to apply this in memory.
Any assistance would be appreciated.
If using another library is possible, take a look at this. The description clearly states the library will allow you to extract into memoty.
WIX is an open source project. You could always ask for this feature, ask for a better solution on their forum or simply modify the code for your need.
Vadim
Your initial question seems to indicate that you are allowed to use the Microsoft.Deployment.Compression DLL. If true, the code below should get you close to what you want:
CabEngine engine = new CabEngine();
foreach (ArchiveFileInfo archiveFileInfo in engine.GetFileInfo(fileStream))
{
Stream stream = engine.Unpack(fileStream, archiveFileInfo.Name);
byte[] buffer = new byte[stream.Length];
//The (int) below is a dirty trick for demonstration purposes only;
//re-work for production code
stream.Read(buffer, 0, (int)stream.Length);
}
Later answers from you seem to indicate that you are not allowed to use 3rd party DLLs in which case you will want to use the FDI* API. This link has some code that you will be able to modify from using hard-coded paths to (memory) streams: Extract .cab file in C#
There is a ready to use project that you can download: Cabinet File (*.CAB) Compression and Extraction
According to version history since Sep 2008 "..you can extract directly to memory."
Here is a utility class that should work in memory (it supports x86 or x64 compilation). Here is out you would use it, if we suppose a .CAB file has been uploaded into ASP.NET using the standard upload protocol:
using (CabFile file = new CabFile(HttpContext.Current.Request.Files[0].InputStream))
{
file.EntryExtract += CabEntryExtract;
file.ExtractEntries();
}
static void CabEntryExtract(object sender, CabEntryExtractEventArgs e)
{
// e.Entry.Name contains the entry name
// e.Entry.Data contains a byte[] with the entry data
// e.Entry.LastWriteTime contains the entry last write time
// e.Entry.Size contains the entry uncompressed size
}
And here is the utility and associated classes:
public sealed class CabFile : IDisposable
{
private IntPtr _hfdi;
private ERF _erf;
private GCHandle _erfHandle;
private byte[] _data;
private Dictionary<IntPtr, object> _handles = new Dictionary<IntPtr, object>();
private MemoryStream _currentEntryData;
private FNALLOC _alloc;
private FNCLOSE _close;
private FNFREE _free;
private FNOPEN _open;
private FNREAD _read;
private FNWRITE _write;
private FNSEEK _seek;
private FNFDINOTIFY _extract;
public event EventHandler<CabEntryExtractEventArgs> EntryExtract;
public CabFile(string filePath)
: this(GetStream(filePath))
{
}
private static Stream GetStream(string filePath)
{
if (filePath == null)
throw new ArgumentNullException("filePath");
return new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
public CabFile(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
using (MemoryStream data = new MemoryStream())
{
stream.CopyTo(data);
_data = data.ToArray();
}
_erf = new ERF();
_alloc = new FNALLOC(FnAlloc);
_free = new FNFREE(FnFree);
_close = new FNCLOSE(FnClose);
_open = new FNOPEN(FnOpen);
_read = new FNREAD(FnRead);
_write = new FNWRITE(FnWrite);
_seek = new FNSEEK(FnSeek);
_extract = new FNFDINOTIFY(FnNotifyExtract);
_erfHandle = GCHandle.Alloc(_erf, GCHandleType.Pinned);
_hfdi = FDICreate(
Marshal.GetFunctionPointerForDelegate(_alloc),
Marshal.GetFunctionPointerForDelegate(_free),
Marshal.GetFunctionPointerForDelegate(_open),
Marshal.GetFunctionPointerForDelegate(_read),
Marshal.GetFunctionPointerForDelegate(_write),
Marshal.GetFunctionPointerForDelegate(_close),
Marshal.GetFunctionPointerForDelegate(_seek)
, -1, _erfHandle.AddrOfPinnedObject());
}
public void ExtractEntries()
{
FDICopy(_hfdi, string.Empty, string.Empty, 0, Marshal.GetFunctionPointerForDelegate(_extract), IntPtr.Zero, IntPtr.Zero);
}
public void Dispose()
{
if (_hfdi != IntPtr.Zero)
{
FDIDestroy(_hfdi);
_hfdi = IntPtr.Zero;
}
_erfHandle.Free();
}
private void OnEntryExtract(CabEntry entry)
{
EventHandler<CabEntryExtractEventArgs> handler = EntryExtract;
if (handler != null)
{
handler(this, new CabEntryExtractEventArgs(entry));
}
}
private IntPtr FnAlloc(int cb)
{
return Marshal.AllocHGlobal(cb);
}
private void FnFree(IntPtr pv)
{
Marshal.FreeHGlobal(pv);
}
private IntPtr FnOpen(string pszFile, int oflag, int pmode)
{
// only used for reading archive
IntPtr h = new IntPtr(_handles.Count + 1);
_handles.Add(h, 0);
return h;
}
private int FnRead(IntPtr hf, byte[] pv, int cb)
{
// only used for reading archive
int pos = (int)_handles[hf];
int left = _data.Length - pos;
int read = Math.Min(left, cb);
if (read > 0)
{
Array.Copy(_data, pos, pv, 0, read);
_handles[hf] = pos + read;
}
return read;
}
private int FnWrite(IntPtr hf, byte[] pv, int cb)
{
// only used for writing entries
_currentEntryData.Write(pv, 0, cb);
return cb;
}
private int FnClose(IntPtr hf)
{
object o = _handles[hf];
CabEntry entry = o as CabEntry;
if (entry != null)
{
entry.Data = _currentEntryData.ToArray();
_currentEntryData.Dispose();
}
_handles.Remove(hf);
return 0;
}
private int FnSeek(IntPtr hf, int dist, SeekOrigin seektype)
{
// only used for seeking archive
int pos;
switch (seektype)
{
case SeekOrigin.Begin:
pos = dist;
break;
case SeekOrigin.Current:
pos = (int)_handles[hf] + dist;
break;
//case SeekOrigin.End:
default:
pos = _data.Length + dist;
break;
}
_handles[hf] = pos;
return pos;
}
private IntPtr FnNotifyExtract(FDINOTIFICATIONTYPE fdint, FDINOTIFICATION fdin)
{
CabEntry entry;
switch (fdint)
{
case FDINOTIFICATIONTYPE.COPY_FILE:
entry = new CabEntry(fdin);
entry._handle = new IntPtr(_handles.Count + 1);
_handles.Add(entry._handle, entry);
_currentEntryData = new MemoryStream();
return entry._handle;
case FDINOTIFICATIONTYPE.CLOSE_FILE_INFO:
entry = (CabEntry)_handles[fdin.hf];
FnClose(fdin.hf);
OnEntryExtract(entry);
return new IntPtr(1);
default:
return IntPtr.Zero;
}
}
private enum FDINOTIFICATIONTYPE
{
CABINET_INFO = 0,
PARTIAL_FILE = 1,
COPY_FILE = 2,
CLOSE_FILE_INFO = 3,
NEXT_CABINET = 4,
ENUMERATE = 5,
}
[StructLayout(LayoutKind.Sequential)]
private struct ERF
{
public int erfOper;
public int erfType;
public int fError;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class FDINOTIFICATION
{
public int cb;
public IntPtr psz1;
public IntPtr psz2;
public IntPtr psz3;
public IntPtr pv;
public IntPtr hf;
public ushort date;
public ushort time;
public ushort attribs;
public ushort setID;
public ushort iCabinet;
public ushort iFolder;
public int fdie;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr FNALLOC(int cb);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void FNFREE(IntPtr pv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private delegate IntPtr FNOPEN(string pszFile, int oflag, int pmode);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNREAD(IntPtr hf, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pv, int cb);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNWRITE(IntPtr hf, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pv, int cb);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNCLOSE(IntPtr hf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNSEEK(IntPtr hf, int dist, SeekOrigin seektype);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr FNFDINOTIFY(FDINOTIFICATIONTYPE fdint, FDINOTIFICATION pfdin);
[DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern IntPtr FDICreate(IntPtr pfnalloc, IntPtr pfnfree, IntPtr pfnopen, IntPtr pfnread, IntPtr pfnwriter, IntPtr pfnclose, IntPtr pfnseek, int cpuType, IntPtr perf);
[DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr FDIDestroy(IntPtr hdfi);
[DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern IntPtr FDICopy(IntPtr hdfi, string pszCabinet, string pszCabPath, int flags, IntPtr fnNotify, IntPtr fnDecrypt, IntPtr userData);
}
public sealed class CabEntry
{
internal IntPtr _handle;
internal CabEntry(CabFile.FDINOTIFICATION fdin)
{
Name = Marshal.PtrToStringAnsi(fdin.psz1);
Size = fdin.cb;
LastWriteTime = new DateTime(1980 + GetMask(fdin.date, 9, 15), GetMask(fdin.date, 5, 8), GetMask(fdin.date, 0, 4),
GetMask(fdin.time, 11, 15), GetMask(fdin.time, 5, 10), 2 * GetMask(fdin.time, 0, 4));
}
private static int GetMask(int value, byte startByte, byte endByte)
{
int final = 0;
int v = 1;
for (byte b = startByte; b <= endByte; b++)
{
if ((value & (1 << b)) != 0)
{
final += v;
}
v = v * 2;
}
return final;
}
public string Name { get; private set; }
public int Size { get; private set; }
public DateTime LastWriteTime { get; private set; }
public byte[] Data { get; internal set; }
}
public sealed class CabEntryExtractEventArgs : EventArgs
{
public CabEntryExtractEventArgs(CabEntry entry)
{
if (entry == null)
throw new ArgumentNullException("entry");
Entry = entry;
}
public CabEntry Entry { get; private set; }
}
NOTE: this code allocates big byte[] chunks so it could be optimized to use things like ChunkedMemoryStream (available for example in this library: CodeFluent Runtime Client) instead of byte[] to avoid impacting the LOH (Large Object Heap) too much.
Using the topic Overview - Handle Enumeration, number 5, the attempt Close mutex of another process and and information from Mutex analysis, the canary in the coal mine and discovering new families of malware/, I have came up with:
Attempt 1: http://pastebin.com/QU0WBgE5
You must open Notepad first. Needless to say, this is not working for me. I need better error checking to figure out what's going on. I don't know how to get mutex pointers in the format I see them in Process Explorer.
My goal is to be able to delete/kill of the mutex handles created by a process so more than one instance can be open. I can do this manually using Process Explorer, but I want to do it programmatically.
(Based on Yahia's notes, I need more permissions.)
Attempt 2: http://pastebin.com/yyQLhesP
At least now I have some sort of error checking, most of the time DuplicateHandle returns 6 or 5, which is an invalid handle and access denied respectfully.
Working attempt (kind of):
I actually didn't require anything Yahia stated in the end. I was getting a "local" handle when I needed a remote one. Basically, what I mean is that you have to find the HandleValue using NtQuerySystemInformation and use that handle, not the one returned by OpenMutex / CreateMutex.
Granted, I can't get it to work on some applications (osk.exe -- on screen keyboard), but it worked for the application I was going for, posting code in case someone wants to take it further.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Security.AccessControl;
using System.Security.Principal;
namespace FileLockInfo
{
public class Win32API
{
[DllImport("ntdll.dll")]
public static extern int NtQueryObject(IntPtr ObjectHandle, int
ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength,
ref int returnLength);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
[DllImport("ntdll.dll")]
public static extern uint NtQuerySystemInformation(int
SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength,
ref int returnLength);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr OpenMutex(UInt32 desiredAccess, bool inheritHandle, string name);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern int CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle,
ushort hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle,
uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();
public enum ObjectInformationClass : int
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_BASIC_INFORMATION
{ // Information Class 0
public int Attributes;
public int GrantedAccess;
public int HandleCount;
public int PointerCount;
public int PagedPoolUsage;
public int NonPagedPoolUsage;
public int Reserved1;
public int Reserved2;
public int Reserved3;
public int NameInformationLength;
public int TypeInformationLength;
public int SecurityDescriptorLength;
public System.Runtime.InteropServices.ComTypes.FILETIME CreateTime;
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_TYPE_INFORMATION
{ // Information Class 2
public UNICODE_STRING Name;
public int ObjectCount;
public int HandleCount;
public int Reserved1;
public int Reserved2;
public int Reserved3;
public int Reserved4;
public int PeakObjectCount;
public int PeakHandleCount;
public int Reserved5;
public int Reserved6;
public int Reserved7;
public int Reserved8;
public int InvalidAttributes;
public GENERIC_MAPPING GenericMapping;
public int ValidAccess;
public byte Unknown;
public byte MaintainHandleDatabase;
public int PoolType;
public int PagedPoolUsage;
public int NonPagedPoolUsage;
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_NAME_INFORMATION
{ // Information Class 1
public UNICODE_STRING Name;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
public struct GENERIC_MAPPING
{
public int GenericRead;
public int GenericWrite;
public int GenericExecute;
public int GenericAll;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_HANDLE_INFORMATION
{ // Information Class 16
public int ProcessID;
public byte ObjectTypeNumber;
public byte Flags; // 0x01 = PROTECT_FROM_CLOSE, 0x02 = INHERIT
public ushort Handle;
public int Object_Pointer;
public UInt32 GrantedAccess;
}
public const int MAX_PATH = 260;
public const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
public const int DUPLICATE_SAME_ACCESS = 0x2;
public const int DUPLICATE_CLOSE_SOURCE = 0x1;
}
public class Win32Processes
{
const int CNST_SYSTEM_HANDLE_INFORMATION = 16;
const uint STATUS_INFO_LENGTH_MISMATCH = 0xc0000004;
public static string getObjectTypeName(Win32API.SYSTEM_HANDLE_INFORMATION shHandle, Process process)
{
IntPtr m_ipProcessHwnd = Win32API.OpenProcess(Win32API.ProcessAccessFlags.All, false, process.Id);
IntPtr ipHandle = IntPtr.Zero;
var objBasic = new Win32API.OBJECT_BASIC_INFORMATION();
IntPtr ipBasic = IntPtr.Zero;
var objObjectType = new Win32API.OBJECT_TYPE_INFORMATION();
IntPtr ipObjectType = IntPtr.Zero;
IntPtr ipObjectName = IntPtr.Zero;
string strObjectTypeName = "";
int nLength = 0;
int nReturn = 0;
IntPtr ipTemp = IntPtr.Zero;
if (!Win32API.DuplicateHandle(m_ipProcessHwnd, shHandle.Handle,
Win32API.GetCurrentProcess(), out ipHandle,
0, false, Win32API.DUPLICATE_SAME_ACCESS))
return null;
ipBasic = Marshal.AllocHGlobal(Marshal.SizeOf(objBasic));
Win32API.NtQueryObject(ipHandle, (int)Win32API.ObjectInformationClass.ObjectBasicInformation,
ipBasic, Marshal.SizeOf(objBasic), ref nLength);
objBasic = (Win32API.OBJECT_BASIC_INFORMATION)Marshal.PtrToStructure(ipBasic, objBasic.GetType());
Marshal.FreeHGlobal(ipBasic);
ipObjectType = Marshal.AllocHGlobal(objBasic.TypeInformationLength);
nLength = objBasic.TypeInformationLength;
while ((uint)(nReturn = Win32API.NtQueryObject(
ipHandle, (int)Win32API.ObjectInformationClass.ObjectTypeInformation, ipObjectType,
nLength, ref nLength)) ==
Win32API.STATUS_INFO_LENGTH_MISMATCH)
{
Marshal.FreeHGlobal(ipObjectType);
ipObjectType = Marshal.AllocHGlobal(nLength);
}
objObjectType = (Win32API.OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(ipObjectType, objObjectType.GetType());
if (Is64Bits())
{
ipTemp = new IntPtr(Convert.ToInt64(objObjectType.Name.Buffer.ToString(), 10) >> 32);
}
else
{
ipTemp = objObjectType.Name.Buffer;
}
strObjectTypeName = Marshal.PtrToStringUni(ipTemp, objObjectType.Name.Length >> 1);
Marshal.FreeHGlobal(ipObjectType);
return strObjectTypeName;
}
public static string getObjectName(Win32API.SYSTEM_HANDLE_INFORMATION shHandle, Process process)
{
IntPtr m_ipProcessHwnd = Win32API.OpenProcess(Win32API.ProcessAccessFlags.All, false, process.Id);
IntPtr ipHandle = IntPtr.Zero;
var objBasic = new Win32API.OBJECT_BASIC_INFORMATION();
IntPtr ipBasic = IntPtr.Zero;
IntPtr ipObjectType = IntPtr.Zero;
var objObjectName = new Win32API.OBJECT_NAME_INFORMATION();
IntPtr ipObjectName = IntPtr.Zero;
string strObjectName = "";
int nLength = 0;
int nReturn = 0;
IntPtr ipTemp = IntPtr.Zero;
if (!Win32API.DuplicateHandle(m_ipProcessHwnd, shHandle.Handle, Win32API.GetCurrentProcess(),
out ipHandle, 0, false, Win32API.DUPLICATE_SAME_ACCESS))
return null;
ipBasic = Marshal.AllocHGlobal(Marshal.SizeOf(objBasic));
Win32API.NtQueryObject(ipHandle, (int)Win32API.ObjectInformationClass.ObjectBasicInformation,
ipBasic, Marshal.SizeOf(objBasic), ref nLength);
objBasic = (Win32API.OBJECT_BASIC_INFORMATION)Marshal.PtrToStructure(ipBasic, objBasic.GetType());
Marshal.FreeHGlobal(ipBasic);
nLength = objBasic.NameInformationLength;
ipObjectName = Marshal.AllocHGlobal(nLength);
while ((uint)(nReturn = Win32API.NtQueryObject(
ipHandle, (int)Win32API.ObjectInformationClass.ObjectNameInformation,
ipObjectName, nLength, ref nLength))
== Win32API.STATUS_INFO_LENGTH_MISMATCH)
{
Marshal.FreeHGlobal(ipObjectName);
ipObjectName = Marshal.AllocHGlobal(nLength);
}
objObjectName = (Win32API.OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(ipObjectName, objObjectName.GetType());
if (Is64Bits())
{
ipTemp = new IntPtr(Convert.ToInt64(objObjectName.Name.Buffer.ToString(), 10) >> 32);
}
else
{
ipTemp = objObjectName.Name.Buffer;
}
if (ipTemp != IntPtr.Zero)
{
byte[] baTemp2 = new byte[nLength];
try
{
Marshal.Copy(ipTemp, baTemp2, 0, nLength);
strObjectName = Marshal.PtrToStringUni(Is64Bits() ?
new IntPtr(ipTemp.ToInt64()) :
new IntPtr(ipTemp.ToInt32()));
return strObjectName;
}
catch (AccessViolationException)
{
return null;
}
finally
{
Marshal.FreeHGlobal(ipObjectName);
Win32API.CloseHandle(ipHandle);
}
}
return null;
}
public static List<Win32API.SYSTEM_HANDLE_INFORMATION>
GetHandles(Process process = null, string IN_strObjectTypeName = null, string IN_strObjectName = null)
{
uint nStatus;
int nHandleInfoSize = 0x10000;
IntPtr ipHandlePointer = Marshal.AllocHGlobal(nHandleInfoSize);
int nLength = 0;
IntPtr ipHandle = IntPtr.Zero;
while ((nStatus = Win32API.NtQuerySystemInformation(CNST_SYSTEM_HANDLE_INFORMATION, ipHandlePointer,
nHandleInfoSize, ref nLength)) ==
STATUS_INFO_LENGTH_MISMATCH)
{
nHandleInfoSize = nLength;
Marshal.FreeHGlobal(ipHandlePointer);
ipHandlePointer = Marshal.AllocHGlobal(nLength);
}
byte[] baTemp = new byte[nLength];
Marshal.Copy(ipHandlePointer, baTemp, 0, nLength);
long lHandleCount = 0;
if (Is64Bits())
{
lHandleCount = Marshal.ReadInt64(ipHandlePointer);
ipHandle = new IntPtr(ipHandlePointer.ToInt64() + 8);
}
else
{
lHandleCount = Marshal.ReadInt32(ipHandlePointer);
ipHandle = new IntPtr(ipHandlePointer.ToInt32() + 4);
}
Win32API.SYSTEM_HANDLE_INFORMATION shHandle;
List<Win32API.SYSTEM_HANDLE_INFORMATION> lstHandles = new List<Win32API.SYSTEM_HANDLE_INFORMATION>();
for (long lIndex = 0; lIndex < lHandleCount; lIndex++)
{
shHandle = new Win32API.SYSTEM_HANDLE_INFORMATION();
if (Is64Bits())
{
shHandle = (Win32API.SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(ipHandle, shHandle.GetType());
ipHandle = new IntPtr(ipHandle.ToInt64() + Marshal.SizeOf(shHandle) + 8);
}
else
{
ipHandle = new IntPtr(ipHandle.ToInt64() + Marshal.SizeOf(shHandle));
shHandle = (Win32API.SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(ipHandle, shHandle.GetType());
}
if (process != null)
{
if (shHandle.ProcessID != process.Id) continue;
}
string strObjectTypeName = "";
if (IN_strObjectTypeName != null){
strObjectTypeName = getObjectTypeName(shHandle, Process.GetProcessById(shHandle.ProcessID));
if (strObjectTypeName != IN_strObjectTypeName) continue;
}
string strObjectName = "";
if (IN_strObjectName != null){
strObjectName = getObjectName(shHandle, Process.GetProcessById(shHandle.ProcessID));
if (strObjectName != IN_strObjectName) continue;
}
string strObjectTypeName2 = getObjectTypeName(shHandle, Process.GetProcessById(shHandle.ProcessID));
string strObjectName2 = getObjectName(shHandle, Process.GetProcessById(shHandle.ProcessID));
Console.WriteLine("{0} {1} {2}", shHandle.ProcessID, strObjectTypeName2, strObjectName2);
lstHandles.Add(shHandle);
}
return lstHandles;
}
public static bool Is64Bits()
{
return Marshal.SizeOf(typeof(IntPtr)) == 8 ? true : false;
}
}
class Program
{
static void Main(string[] args)
{
String MutexName = "MSCTF.Asm.MutexDefault1";
String ProcessName = "notepad";
try
{
Process process = Process.GetProcessesByName(ProcessName)[0];
var handles = Win32Processes.GetHandles(process, "Mutant", "\\Sessions\\1\\BaseNamedObjects\\" + MutexName);
if (handles.Count == 0) throw new System.ArgumentException("NoMutex", "original");
foreach (var handle in handles)
{
IntPtr ipHandle = IntPtr.Zero;
if (!Win32API.DuplicateHandle(Process.GetProcessById(handle.ProcessID).Handle, handle.Handle, Win32API.GetCurrentProcess(), out ipHandle, 0, false, Win32API.DUPLICATE_CLOSE_SOURCE))
Console.WriteLine("DuplicateHandle() failed, error = {0}", Marshal.GetLastWin32Error());
Console.WriteLine("Mutex was killed");
}
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("The process name '{0}' is not currently running", ProcessName);
}
catch (ArgumentException)
{
Console.WriteLine("The Mutex '{0}' was not found in the process '{1}'", MutexName, ProcessName);
}
Console.ReadLine();
}
}
}
You need elevated privileges for that - especially DEBUG privilege.
See:
Manipulate Privileges in Managed Code Reliably, Securely, and Efficiently (MSDN)
Debug Privilege (MSDN)
The Windows Access Control Model Part 3
Simon Mourier's answer to Stack Overflow question How to enable the SeCreateGlobalPrivilege in .NET without resorting to P/Invoke or reflection?
ObjectSecurity Methods (MSDN)
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.