How to Repaint When Drawing Outside the Form Area - c#

I am writing an application that needs to draw outside of it's main window area. I already have to code to actually do the drawing:
[DllImport("User32.dll")]
public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("User32.dll")]
public static extern void ReleaseDC(IntPtr hwnd, IntPtr dc);
IntPtr desktopPtr = GetDC(IntPtr.Zero);
Graphics g = Graphics.FromHdc(desktopPtr);
g.DrawLine(Pens.White, 0, 0, Screen.FromControl(this).WorkingArea.Width, Screen.FromControl(this).WorkingArea.Height);
g.Dispose();
ReleaseDC(IntPtr.Zero, desktopPtr);
However the on paint event is an unsuitable place to put the code because it's not called when something outside of the form is redrawn. So my question is, where could this code be placed so it is called whenever part of the screen is redrawn?

If you want content painted on the screen, you should always create a window to hold that content. Painting on the desktop (a window that you don't own) is a bad idea.
The solution is to create a window, with the extended style WS_EX_NOACTIVATE and draw on that in response to WM_PAINT messages. For a WinForms application, the runtime calls Form.OnPaint when you get a WM_PAINT so you can handle that event and do the painting there. To demonstrate:
[DllImport("User32.dll")]
private static extern IntPtr GetWindowLong(IntPtr hWnd, int index);
[DllImport("User32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int index, IntPtr value);
[DllImport("User32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
private const int WS_EX_NOACTIVATE = 0x08000000;
private const int GWL_EXSTYLE = -20;
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_FRAMECHANGED = 0x0020;
private const uint StyleUpdateFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED;
public Form1()
{
InitializeComponent();
this.FormBorderStyle = FormBorderStyle.None;
this.Paint += Form1_Paint;
this.Shown += Form1_Shown;
}
private void Form1_Shown(object sender, EventArgs e)
{
IntPtr currentStyle = GetWindowLong(this.Handle, GWL_EXSTYLE);
int current = currentStyle.ToInt32();
current |= WS_EX_NOACTIVATE;
SetWindowLong(this.Handle, GWL_EXSTYLE, new IntPtr(current));
SetWindowPos(this.Handle, IntPtr.Zero, 0, 0, 0, 0, StyleUpdateFlags);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
If you want your window to float on top set the form's TopMost property to true. If you want your window to stick to the bottom of the Z-Order (the exact opposite of TopMost) then add the following logic to your form:
private struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public uint flags;
}
private const int WM_WINDOWPOSCHANGING = 0x0046;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_WINDOWPOSCHANGING)
{
if (m.LParam != IntPtr.Zero)
{
WINDOWPOS posInfo = Marshal.PtrToStructure<WINDOWPOS>(m.LParam);
posInfo.hwndInsertAfter = HWND_BOTTOM;
Marshal.StructureToPtr(posInfo, m.LParam, true);
m.Result = IntPtr.Zero;
return;
}
}
base.WndProc(ref m);
}
This handles the WM_WINDOWPOSCHANGING window message, and prevents the window from moving up in the Z-Order by telling the window manager to put it at the bottom.

Related

Hide and Show taskbar on windows 10

I have a wpf application which is in maximized state always without showing taskbar.
Here is the code for Hiding and showing taskbar.
[DllImport("user32.dll")]
private static extern int FindWindow(string className, string windowText);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hwnd, int command);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
static int hwnd = FindWindow("Shell_TrayWnd", "");
public static new void Hide()
{
ShowWindow(hwnd, SW_HIDE);
}
public static new void Show()
{
ShowWindow(hwnd, SW_SHOW);
}
This is working fine on windows 7. But when application runs on Windows 10.. taskbar didnt show up again by calling show().
Here is the part where I am calling show()
#region Show Desktop
private void Desktop_MouseUp(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left )
{
this.WindowState = System.Windows.WindowState.Minimized;
Shell32.Shell objShel = new Shell32.Shell();
objShel.MinimizeAll();
Show();
}
}
#endregion
This works on the main display and is taken from here and converted to c#.
public static class Taskbar
{
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr FindWindow(
string lpClassName,
string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int x,
int y,
int cx,
int cy,
uint uFlags
);
[Flags]
private enum SetWindowPosFlags : uint
{
HideWindow = 128,
ShowWindow = 64
}
public static void Show()
{
var window = FindWindow("Shell_traywnd", "");
SetWindowPos(window, IntPtr.Zero, 0, 0, 0, 0, (uint) SetWindowPosFlags.ShowWindow);
}
public static void Hide()
{
var window = FindWindow("Shell_traywnd", "");
SetWindowPos(window, IntPtr.Zero, 0, 0, 0, 0, (uint)SetWindowPosFlags.HideWindow);
}
}

Disable Close Button In Title Bar of a WPF Window (C#)

I'd like to know how to disable (not remove/hide) the Close button in a WPF window. I know how to hide it which makes the window's title bar look like this:
But I want to disable it meaning it should look like this:
I'm scripting in C# and using WPF (Windows Presentation Foundation).
Try this:
public partial class MainWindow : Window
{
[DllImport("user32.dll")]
static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
const uint MF_BYCOMMAND = 0x00000000;
const uint MF_GRAYED = 0x00000001;
const uint SC_CLOSE = 0xF060;
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// Disable close button
IntPtr hwnd = new WindowInteropHelper(this).Handle;
IntPtr hMenu = GetSystemMenu(hwnd, false);
if (hMenu != IntPtr.Zero)
{
EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
}
}
}
Taken from here.
Make sure you set the ResizeMode to NoResize.
You have to override and in OnCLosing event set e.cancel=true
public MyWindow()
{
InitializeComponent();
this.Closing += new System.ComponentModel.CancelEventHandler(MyWindow_Closing);
}
void MyWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
}
This post which an answer using Behavior, GetWindowLong and SetWindowLong:
public class HideCloseButtonOnWindow : System.Windows.Interactivity.Behavior<Window>
{
#region bunch of native methods
private const int GWL_STYLE = -16;
private const int WS_SYSMENU = 0x80000;
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= OnLoaded;
base.OnDetaching();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var hwnd = new System.Windows.Interop.WindowInteropHelper(AssociatedObject).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
}
}
How to use it:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:w="clr-namespace:WpfApplication2">
<i:Interaction.Behaviors>
<w:HideCloseButtonOnWindow />
</i:Interaction.Behaviors>
</Window>
You can probably do it with win32 hackery.
I have done it this way: Get CustomChromeWindow(which will eventually look exactly like the one in picture), and just bind Command() property to viewmodel, and then set CanExecuteCommand=false, which will make the button disabled(How does one "disable" a button in WPF using the MVVM pattern?).
There might me this way too: How to disable close button on a window in another process with C++?
Basically, call that code with pInvoke. You can obtain WPF window handle easily.
If you would like a more generic version of Yoav's accepted answer that doesn't require adding Win API calls to your Window class, here's a extension class and method:
namespace WinApi
{
using System.Runtime.InteropServices;
using System.Windows.Interop;
public static class WinApi
{
[DllImport("user32.dll")]
public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
const uint MF_BYCOMMAND = 0x00000000;
const uint MF_GRAYED = 0x00000001;
const uint SC_CLOSE = 0xF060;
public static void DisableCloseButton(this System.Windows.Window window)
{
// Disable close button
IntPtr hwnd = new WindowInteropHelper(window).EnsureHandle();
IntPtr hMenu = GetSystemMenu(hwnd, false);
if (hMenu != IntPtr.Zero)
EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
}
}
}
Then call it from your Window like so:
this.DisableCloseButton();
// or
WinApi.DisableCloseButton(this);
Since the extension uses EnsureHandle() you don't need to hook OnSourceInitialized() in your Window.
Be aware that EnsureHandle() raises OnSourceInitialized(), so don't call this until after you have done anything you want to happen prior to that call.
You can call new WindowInteropHelper(this).Handle() in your Window code if you need to check whether the handle has already been created.

Splash screen with layered window in c#

In the multilayered window I have the click through working but I still see some gray behind my background picture in my window how do I remove that? I have looked on stack overflow and codeproject but I can't find a solution.
This is my code:
private Margins marg;
internal struct Margins
{
public int Left, Right, Top, Bottom;
}
public enum GWL
{
ExStyle = -20
}
public enum WS_EX
{
Transparent = 0x20,
Layered = 0x80000
}
public enum LWA
{
ColorKey = 0x1,
Alpha = 0x2
}
const Int32 HTCAPTION = 0x02;
const Int32 WM_NCHITTEST = 0x84;
const byte AC_SRC_OVER = 0x00;
const byte AC_SRC_ALPHA = 0x01;
[DllImport("user32.dll", SetLastError = true)]
private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, int crKey, byte bAlpha, LWA dwFlags);
[DllImport("dwmapi.dll")]
static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMargins);
public Form1() {
InitializeComponent();
//BackColor = Color.Aqua;
//TransparencyKey = Color.Aqua;
StartPosition = FormStartPosition.CenterScreen;
BackgroundImage = new Bitmap(mypicture);
BackgroundImageLayout = ImageLayout.Stretch;
this.Size = mypicture.Size;
this.FormBorderStyle = FormBorderStyle.None;
TopMost = true;
Visible = true;
int initialsytle = (int) GetWindowLong(this.Handle,-20);
initialsytle = initialsytle |0x80000 | 0x20;
IntPtr pointer = (IntPtr) initialsytle;
SetWindowLong(this.Handle, -20, pointer);
SetLayeredWindowAttributes(this.Handle, 0, 128, LWA.ColorKey);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
marg.Left = 0;
marg.Top = 0;
marg.Right = this.Width;
marg.Bottom = this.Height;
DwmExtendFrameIntoClientArea(this.Handle, ref marg);
}
}

Moving window by click-drag on a control

I have a WinForms project. I have a panel on the top of my window. I want that panel to be able to move the window, when the user clicks on it and then drags.
How can I do this?
Add the following declerations to your class:
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HTCAPTION = 0x2;
[DllImport("User32.dll")]
public static extern bool ReleaseCapture();
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
Put this in your panel's MouseDown event:
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}
}

Window "on desktop"

I've been using Rainlendar for some time and I noticed that it has an option to put the window "on desktop". It's like a bottomMost window (as against topmost).
How could I do this on a WPF app?
Thanks
My answer is in terms of the Win32 API, not specific to WPF (and probably requiring P/Invoke from C#):
Rainlendar has two options:
"On Desktop", it becomes a child of the Explorer desktop window ("Program Manager"). You could achieve this with the SetParent API.
"On Bottom" is what you describe - its windows stay at the bottom of the Z-order, just in front of the desktop. It's easy enough to put them there to begin with (see SetWindowPos) - the trick is to stop them coming to the front when clicked. I would suggest handling the WM_WINDOWPOSCHANGING message.
This is what I used so the window is always "on bottom":
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
...
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
public static void SetBottom(Window window)
{
IntPtr hWnd = new WindowInteropHelper(window).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
I was trying to do the same...
i've used a lot of ideas arround, but i was able to to it and prevent the flickering.
I managed to override WndProc, used one setwindowpos before to put it in the background, and another to prevent it from getting the focus...
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_NOZORDER = 0x0004;
const int WM_ACTIVATEAPP = 0x001C;
const int WM_ACTIVATE = 0x0006;
const int WM_SETFOCUS = 0x0007;
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const int WM_WINDOWPOSCHANGING = 0x0046;
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr DeferWindowPos(IntPtr hWinPosInfo, IntPtr hWnd,
IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr BeginDeferWindowPos(int nNumWindows);
[DllImport("user32.dll")]
static extern bool EndDeferWindowPos(IntPtr hWinPosInfo);
private void Window_Loaded(object sender, RoutedEventArgs e)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SETFOCUS)
{
IntPtr hWnd = new WindowInteropHelper(this).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
handled = true;
}
return IntPtr.Zero;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.RemoveHook(new HwndSourceHook(this.WndProc));
}
The OnDesktop version that Im using:
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static void SetOnDesktop(Window window)
{
IntPtr hWnd = new WindowInteropHelper(window).Handle;
IntPtr hWndProgMan = FindWindow("Progman", "Program Manager");
SetParent(hWnd, hWndProgMan);
}
I was having some trouble finding the Program Manager window, but Kimmo, the creator from Rainlendar gave me a link to the code:
http://www.ipi.fi/~rainy/legacy.html
If anybody needs more detail just look in library/rainwindow.cpp for the function SetWindowZPos.
The attached property version of #HrejWaltz's answer:
Update (12/28/2016)
public class WindowSinker
{
#region Properties
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_NOZORDER = 0x0004;
const int WM_ACTIVATEAPP = 0x001C;
const int WM_ACTIVATE = 0x0006;
const int WM_SETFOCUS = 0x0007;
const int WM_WINDOWPOSCHANGING = 0x0046;
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
Window Window = null;
#endregion
#region WindowSinker
public WindowSinker(Window Window)
{
this.Window = Window;
}
#endregion
#region Methods
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr DeferWindowPos(IntPtr hWinPosInfo, IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr BeginDeferWindowPos(int nNumWindows);
[DllImport("user32.dll")]
static extern bool EndDeferWindowPos(IntPtr hWinPosInfo);
void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
var Handle = (new WindowInteropHelper(Window)).Handle;
var Source = HwndSource.FromHwnd(Handle);
Source.RemoveHook(new HwndSourceHook(WndProc));
}
void OnLoaded(object sender, RoutedEventArgs e)
{
var Hwnd = new WindowInteropHelper(Window).Handle;
SetWindowPos(Hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
var Handle = (new WindowInteropHelper(Window)).Handle;
var Source = HwndSource.FromHwnd(Handle);
Source.AddHook(new HwndSourceHook(WndProc));
}
IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SETFOCUS)
{
hWnd = new WindowInteropHelper(Window).Handle;
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
handled = true;
}
return IntPtr.Zero;
}
public void Sink()
{
Window.Loaded += OnLoaded;
Window.Closing += OnClosing;
}
public void Unsink()
{
Window.Loaded -= OnLoaded;
Window.Closing -= OnClosing;
}
#endregion
}
public static class WindowExtensions
{
#region Always On Bottom
public static readonly DependencyProperty SinkerProperty = DependencyProperty.RegisterAttached("Sinker", typeof(WindowSinker), typeof(WindowExtensions), new UIPropertyMetadata(null));
public static WindowSinker GetSinker(DependencyObject obj)
{
return (WindowSinker)obj.GetValue(SinkerProperty);
}
public static void SetSinker(DependencyObject obj, WindowSinker value)
{
obj.SetValue(SinkerProperty, value);
}
public static readonly DependencyProperty AlwaysOnBottomProperty = DependencyProperty.RegisterAttached("AlwaysOnBottom", typeof(bool), typeof(WindowExtensions), new UIPropertyMetadata(false, OnAlwaysOnBottomChanged));
public static bool GetAlwaysOnBottom(DependencyObject obj)
{
return (bool)obj.GetValue(AlwaysOnBottomProperty);
}
public static void SetAlwaysOnBottom(DependencyObject obj, bool value)
{
obj.SetValue(AlwaysOnBottomProperty, value);
}
static void OnAlwaysOnBottomChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var Window = sender as Window;
if (Window != null)
{
if ((bool)e.NewValue)
{
var Sinker = new WindowSinker(Window);
Sinker.Sink();
SetSinker(Window, Sinker);
}
else
{
var Sinker = GetSinker(Window);
Sinker.Unsink();
SetSinker(Window, null);
}
}
}
#endregion
}
Based on the answers of HrejWaltz and James M, I want to provide a modified solution that intercepts and modifies incoming WM_WINDOWPOSCHANGING messages by setting the SWP_NOZORDER flag instead of calling SetWindowPos everytime a WM_SETFOCUS message is received.
The class offers attached properties to directly add to a WPF Window using WindowSinker.AlwaysOnBottom="True".
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
public class WindowSinker
{
#region Windows API
// ReSharper disable InconsistentNaming
private const int WM_WINDOWPOSCHANGING = 0x0046;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_NOACTIVATE = 0x0010;
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public uint flags;
}
private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
// ReSharper restore InconsistentNaming
#endregion
#region WindowSinker
private readonly Window window;
private bool disposed;
public WindowSinker(Window window)
{
this.window = window;
if (window.IsLoaded)
{
OnWindowLoaded(window, null);
}
else
{
window.Loaded += OnWindowLoaded;
}
window.Closing += OnWindowClosing;
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
window.Loaded -= OnWindowLoaded;
window.Closing -= OnWindowClosing;
disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~WindowSinker()
{
Dispose(false);
}
#endregion
#region Event Handlers
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy,
uint uFlags);
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
SetWindowPos(new WindowInteropHelper(window).Handle, HWND_BOTTOM, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
var source = HwndSource.FromHwnd(new WindowInteropHelper(window).Handle);
source?.AddHook(WndProc);
}
private void OnWindowClosing(object sender, CancelEventArgs e)
{
var source = HwndSource.FromHwnd(new WindowInteropHelper(window).Handle);
source?.RemoveHook(WndProc);
}
private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_WINDOWPOSCHANGING)
{
var windowPos = Marshal.PtrToStructure<WINDOWPOS>(lParam);
windowPos.flags |= SWP_NOZORDER;
Marshal.StructureToPtr(windowPos, lParam, false);
}
return IntPtr.Zero;
}
#endregion
#region Attached Properties
private static readonly DependencyProperty SinkerProperty = DependencyProperty.RegisterAttached(
"Sinker",
typeof(WindowSinker),
typeof(WindowSinker),
null);
public static readonly DependencyProperty AlwaysOnBottomProperty = DependencyProperty.RegisterAttached(
"AlwaysOnBottom",
typeof(bool),
typeof(WindowSinker),
new UIPropertyMetadata(false, OnAlwaysOnBottomChanged));
public static WindowSinker GetSinker(DependencyObject d)
{
return (WindowSinker) d.GetValue(SinkerProperty);
}
private static void SetSinker(DependencyObject d, WindowSinker value)
{
d.SetValue(SinkerProperty, value);
}
public static bool GetAlwaysOnBottom(DependencyObject d)
{
return (bool) d.GetValue(AlwaysOnBottomProperty);
}
public static void SetAlwaysOnBottom(DependencyObject d, bool value)
{
d.SetValue(AlwaysOnBottomProperty, value);
}
private static void OnAlwaysOnBottomChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is Window window)
{
if ((bool) e.NewValue)
{
SetSinker(window, new WindowSinker(window));
}
else
{
GetSinker(window)?.Dispose();
SetSinker(window, null);
}
}
}
#endregion
}

Categories