Reputation: 841
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?
Upvotes: 1
Views: 586
Reputation: 6738
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.
Upvotes: 2