I am trying to create a program which gets the handle of the window under your cursor, show's some data about it and draws a filled rectangle (with very low alpha) on top of the whole window. I am using C# and winforms.
I have succeeded in doing so, but the problem is my draw method is in a BackgroundWorker's loop and it keeps making more and more rectangles (-> rectangle with higher alpha) on the window or when moving mouse to another window the old one still exists.
I haven't managed to find a method to clear the drawn rectangle as it just "is" on the screen and isn't bound to the graphics object or anything.
I have tried using certain native methods such as
[DllImport("User32.dll")]
public static extern Int64 SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
[DllImport("user32.dll")]
public static extern bool UpdateWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags);
but none of the above has worked correctly. Some of them do work but as the messages get in the queue the redrawing doesn't occur immediately or is very slow and glitched (flickering etc).
So, the question is, how would I "remove" the rectangle I have drawn using Graphics.FromHwnd(handleOfWindowUnderCursor)? I actually think it doesn't matter that it is drawn on other window as I have had the very same problem earlier when trying to get rid of the drawings on my own form too (never got that fixed either!).
Alternatively, any suggestions on how I could accomplish drawing and removing the rectangle on the window under cursor without using the methods I am now?
I noticed that drawing using
Graphics g = Graphics.FromHwnd(form.Handle);
draws on the form background, under its controls. Is it whatyou want to acomplish?
// draw the rectangle
Brush b = new SolidBrush(Color.FromArgb(20, 0, 0, 255));
g.FillRectangle(b, new Rectangle(5, 5, 200, 200));
// clear the rectangle
g.Clear(this.BackColor);
If I draw on the screen directly, with this:
Graphics g = Graphics.FromHwnd(IntPtr.Zero);
the rectangle disapears immediately after Windows refreshes the screen.
There is a third option, which is not realy strightforward.
Instead of drawing a rectangle, create a form with lowered opacity, TopMost property set to true and without borders. Then make it transparent to events:
protected override void WndProc(ref Message m)
{
const int WM_NCHITTEST = 0x0084;
const int HTTRANSPARENT = (-1);
if (m.Msg == WM_NCHITTEST)
{
m.Result = (IntPtr)HTTRANSPARENT;
}
else
{
base.WndProc(ref m);
}
}
The only things you have to take care after that is this form's Visible, Location and Size properties.
bool change = false;
private void timer1_Tick(object sender, EventArgs e)
{
try
{
if (change)
{
InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
change = false;
}
else
{
PaintRectangleToScreen();
change = true;
}
}
catch (System.Exception caught)
{
MessageBox.Show(caught.Message);
}
}
Related
So I created a simple form to test out using an SVG image to draw a custom shaped window. Inspiration found here
It seems to work fine, but no matter what I do my window size is too small to put any controls on.
Reasons for doing this: It's cool? Windows needs better themeing support. I was bored!
I am using Svg from nuget.com from within Visual Studio
Code:
using Svg;
public const int WM_NCLBUTTONDOWN = 0xA1; public const int HT_CAPTION = 0x2;
internal class NativeMethods
{
// Allows forms with Toolbox property set to false to be moved
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool ReleaseCapture();
}
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
System.Drawing.Drawing2D.GraphicsPath frmshp = new System.Drawing.Drawing2D.GraphicsPath();
//frmshp.AddEllipse(0, 0, this.Width, this.Height);
//SvgDocument.Open(#"TestWindowsshape.svg");
SvgDocument newshp = SvgDocument.Open(#"TestWindowsshape.svg");
frmshp = (newshp.Path);
this.Region = new Region(frmshp);
}
private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Make settings window movable without a titlebar
if (e.Button == MouseButtons.Left)
{
NativeMethods.ReleaseCapture();
NativeMethods.SendMessage(Handle, WM_NCLBUTTONDOWN (IntPtr)HT_CAPTION, (IntPtr)0);
}
}
I have tried to increase the svg size, played with the code some, but nothing I do will make the drawn window bigger. I know I can do this with a BMP, and the TransparancyKey option, but I would like to not do it that way, since the BMP & transparency method has the drawback of not being able to use one color in the bitmap itself. Any advice would be appreciated
Edit:
Matrix m = new Matrix();
m.Scale(100, 100, MatrixOrder.Append);
m.Translate(100, 100, MatrixOrder.Append);
newshp.Path.Transform(m);
Has been tried, with no effect. I would assume that this should have worked does that mean the problem is within my SVG?
The problem seems to be in the SVG file, i adjusted the size of a Rectangle in Notepad++ and got a bigger windows, however more complex shapes will be a hassle. It seems Inkscape cannot create SVGs of a reliable size... I have the "Document properties" set to my screen resolution, but the vectors all turn out too small. Perhaps Illustrator can do this properly.
I have developed an application in C# and I want to show it in full screen mode. It should also cover up the taskbar. To accomplish this I have used the Windows API. You can find the class below:
public sealed class WinAPI
{
[DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
public static extern int GetSystemMetrics(int which);
[DllImport("user32.dll")]
public static extern void
SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter,
int X, int Y, int width, int height, uint flags);
private const int SM_CXSCREEN = 0;
private const int SM_CYSCREEN = 1;
private static IntPtr HWND_TOP = IntPtr.Zero;
private const int SWP_SHOWWINDOW = 64; // 0×0040
public static int ScreenX
{
get { return GetSystemMetrics(SM_CXSCREEN); }
}
public static int ScreenY
{
get { return GetSystemMetrics(SM_CYSCREEN); }
}
public static void SetWinFullScreen(IntPtr hwnd)
{
SetWindowPos(hwnd, HWND_TOP, 0, 0, ScreenX, ScreenY, SWP_SHOWWINDOW);
}
}
I am using this class in conjunction with the following form settings to go in full screen mode:
private void terminalModeToolStripMenuItem_Click(object sender, EventArgs e)
{
// Remove the border.
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.WindowState = FormWindowState.Maximized;
this.Bounds = Screen.PrimaryScreen.Bounds;
// Full screen windows API hack.
WinAPI.SetWinFullScreen(this.Handle);
}
Now comes the funny part. If I click a button in my menu bar it will show up with a gap between the button and the menu as you can see in the image below:
Does anyone knows how to fix this issue? I would like it to show up like this:
And why does this happen anyway?
As Muraad pointed you to in his comment, try moving the following block of code into your Form load event:
// Remove the border.
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.WindowState = FormWindowState.Maximized;
this.Bounds = Screen.PrimaryScreen.Bounds;
And see if the issue still persists.
This problem is caused by having the task bar set to the top of the screen.
It can be resolved by either moving the task bar to the bottom of the screen, or by enabling the Auto-hide task bar check box in the Properties window of the task bar.
EDIT: As stated by #slashp 's comments. The root cause of this issue comes from some inner mechanics in drawing the menu. The menu has a safety to be always drawn within the working area. Which seems to be your screen - task bar. Because the application is placed over the task bar, the calculation is placing the menu below the task bar. (you can't see it, but it's still there)
How can I preserve the painting I drew on a picturebox?
I draw a circle, and fill it via the ExtFloodFill API.
This works fine.
When I resize the form (or minimize it) and resize it back to its original size part of the painting is gone.
When I refresh the picturebox the painting will be gone completely
I tried to repaint it in the Paint event, but this caused it to be repainted continuously as the painting itself triggered the Paint event as well.
See below for a test project.
When you click on the picturebox the painting will be drawn.
When you doubleclick the picturebox will be refreshed.
[1 form with 1 picturebox named pictureBox1]
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace FloodFill
{
public partial class Form1 : Form
{
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateSolidBrush(int crColor);
[DllImport("gdi32.dll")]
public static extern bool ExtFloodFill(IntPtr hdc, int nXStart, int nYStart, int crColor, uint fuFillType);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern int GetPixel(IntPtr hdc, int x, int y);
public static uint FLOODFILLSURFACE = 1;
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
DrawCircle();
FillGreen();
}
private void DrawCircle()
{
Graphics graBox = Graphics.FromHwnd(pictureBox1.Handle);
graBox.DrawEllipse(Pens.Red, 10, 10, 100, 100);
}
private void FillGreen()
{
Graphics graBox = Graphics.FromHwnd(pictureBox1.Handle);
IntPtr ptrHdc = graBox.GetHdc();
IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
SelectObject(ptrHdc, ptrBrush);
ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
DeleteObject(ptrBrush);
graBox.ReleaseHdc(ptrHdc);
}
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
pictureBox1.Refresh();
}
}
}
How can I preserve the painting I make when my form or the picturebox is resized or in any other way to be refreshed?
[EDIT]
I changed my Paint event to the following:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
DrawCircle();
FillGreen();
}
And now the circle is being redrawn after a resized, but the FloodFill isn't
(I also gave the picturebox a lightblue background for another test)
[EDIT2]
I changed the Paint event to use the Graphics g as follows:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
DrawCircle(g);
FillGreen(g);
}
private void DrawCircle(Graphics g)
{
g.DrawEllipse(Pens.Red, 10, 10, 100, 100);
}
private void FillGreen(Graphics g)
{
IntPtr ptrHdc = g.GetHdc();
IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
SelectObject(ptrHdc, ptrBrush);
ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
DeleteObject(ptrBrush);
g.ReleaseHdc(ptrHdc);
}
But when I resize back to the original size some lines of the FloodFill are skipped, especially when I resize slowly
Using GDI+ methods for drawing the code is simply:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Rectangle rect = new Rectangle(10, 10, 100, 100);
e.Graphics.FillEllipse(Brushes.Green, rect);
using (Pen pen = new Pen(Color.Red, 2f))
{
e.Graphics.DrawEllipse(pen , rect);
}
}
None of your DllImport or constants is needed at all.
Note that I chose to use a Pen with a width of 2f to demonstrate the use of the using clause to correctly create and dispose of the GDI+ (non-standard) object Pen
This will persist as it is drawn whenever needed. To draw it initially you must call pictureBox1.Invalidate(); once!
If you want to change the coordinates you should move the variable rect to class level, set it to new data and the call Invalidate on the PictureBox! The same goes for Colors or the Pen.Width: use class level variables and after each change trigger the Paint event by calling Invalidate()..
Once you understand that, the most important part of learning drawing in GDI+ is done..
For filling any drawings you have three options:
simple shapes draw with DrawXXX methods all have a FillXXX method.
complex shapes can be created with a GraphicsPath, which also has both a DrawXXX methods and a FillXXX method.
areas not created by shapes but by drawn pixels must be filled with a floodfill method. There is non buit-in in GDI+, but can simply write your own. maybe like the Fill4 in this answer..
Update: If you feel better using the FloodFill from gdi32.dll you can do so, but please change the code to use the Graphics object from the Paint event:
FillGreen(e.Graphics);
private void FillGreen(Graphics graBox)
{
IntPtr ptrHdc = graBox.GetHdc();
IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
SelectObject(ptrHdc, ptrBrush);
ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
DeleteObject(ptrBrush);
graBox.ReleaseHdc(ptrHdc);
}
for better flexibility you can probably make the params all dynamic, but I'm not used to that old library..
Note that the order of calls matters: FillXXX wil cover some of the pixels drawn by DrawXXX, so it must come first. FloodFill however depends on the bounding lines, in your case the circle, being drawn first, so it must come after..
I use the below code to pop-up a form on the screen on top of everything but it doesn't steal focus.
This works fine, but I now need to close the form, the form itself doesnt show up in Application.OpenForms
How do I go about doing this?
Setup and open the form
frmClientCall frm = new frmClientCall {StartPosition = FormStartPosition.Manual, Text = "Phone Call"};
frm.Location = new System.Drawing.Point(
Screen.PrimaryScreen.WorkingArea.Width - frm.Width,
Screen.PrimaryScreen.WorkingArea.Height - frm.Height - 202
);
frm.lblClient.Text = URI;
frm.ShowInactiveTopmost();
Code to prevent focus on the form
private const int SW_SHOWNOACTIVATE = 4;
private const int HWND_TOPMOST = -1;
private const uint SWP_NOACTIVATE = 0x0010;
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(
int hWnd, // Window handle
int hWndInsertAfter, // Placement-order handle
int X, // Horizontal position
int Y, // Vertical position
int cx, // Width
int cy, // Height
uint uFlags); // Window positioning flags
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
public void ShowInactiveTopmost()
{
ShowWindow(Handle, SW_SHOWNOACTIVATE);
SetWindowPos(Handle.ToInt32(), HWND_TOPMOST, Left, Top, Width, Height, SWP_NOACTIVATE);
}
Yes, this is not the only mishap. You for example can also see that the form's Load event never fires. Basic issue is that you are bypassing the normal logic, which is a pretty big deal in Winforms since it creates the native window lazily. In your case it happens when you use the Handle property. I think the underlying issue is that the Visible property was never set to true, that's the one that truly gets the ball rolling.
Well, don't do it this way, Winforms already supports showing a window without activating it. Paste this code into the form you want to display without activation:
protected override bool ShowWithoutActivation {
get { return true; }
}
The SetWindowPos() pinvoke to make it topmost is not necessary either, paste this code:
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
cp.ExStyle |= 8; // Turn on WS_EX_TOPMOST
return cp;
}
}
I am making an add on for a game, but I want it to reside as an overlay to the game's client area of the window.
Basically when I start the add on, i want it to show on top of the game. The catch is if you minimize or move the window, I want the the form to stick with it.
Anyone know of anything that can do the trick without having to hook directdraw?
Thanks.
Here is a simple way to do this. First, you'll need this line in your form's using statements:
using System.Runtime.InteropServices;
Next, add these declarations to your form:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int X;
public int Y;
public int Width;
public int Height;
}
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
Next, set your form's TopMost property to True. Finally, add a Timer control to your form, set its Interval property to 250 and its Enabled property to True, and put this code in its Tick event:
IntPtr hWnd = FindWindow(null, "Whatever is in the game's title bar");
RECT rect;
GetWindowRect(hWnd, out rect);
if (rect.X == -32000)
{
// the game is minimized
this.WindowState = FormWindowState.Minimized;
}
else
{
this.WindowState = FormWindowState.Normal;
this.Location = new Point(rect.X + 10, rect.Y + 10);
}
This code will keep your form positioned over the game's form if the game is not minimized, or it will minimize your form also if the game is minimized. To change the relative position of your application, just change the "+ 10" values in the last line.
The more complicated method would involve hooking windows messages to determine when the game form minimizes or moves or changes size, but this polling method will accomplish almost the same thing much more simply.
One last bit: FindWindow will return 0 if it finds no window with that title, so you could use this to close your own application when the game is closed.