C# Transparent Form using UpdateLayeredWindow draw controls - c#

I'm developing an exe where I need to have a transparent background. I have made the image in Photoshop and it has all the neat stuff (shadows/opacity, reflection etc).
I have been struggling to get it working using TransparentColor+BackColor+Background Image, but I always end up with some pixel not being transparent. So I switched to UpdateLayeredWindow which works fine, but no control is being drawn now.
Here is some of my code
private void Form1_Load(object sender, EventArgs e)
{
UpdateFormDisplay(this.BackgroundImage);
}
protected override void OnPaint(PaintEventArgs e)
{
UpdateFormDisplay(this.BackgroundImage);
}
public void UpdateFormDisplay(Image backgroundImage)
{
IntPtr screenDc = API.GetDC(IntPtr.Zero);
IntPtr memDc = API.CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr oldBitmap = IntPtr.Zero;
try
{
//Display-image
Bitmap bmp = new Bitmap(backgroundImage);
hBitmap = bmp.GetHbitmap(Color.FromArgb(0)); //Set the fact that background is transparent
oldBitmap = API.SelectObject(memDc, hBitmap);
//Display-rectangle
Size size = bmp.Size;
Point pointSource = new Point(0, 0);
Point topPos = new Point(this.Left, this.Top);
//Set up blending options
API.BLENDFUNCTION blend = new API.BLENDFUNCTION();
blend.BlendOp = API.AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = API.AC_SRC_ALPHA;
API.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, API.ULW_ALPHA);
//Clean-up
bmp.Dispose();
API.ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
API.SelectObject(memDc, oldBitmap);
API.DeleteObject(hBitmap);
}
API.DeleteDC(memDc);
}
catch (Exception)
{
}
}
Here are some images to explain better

If you want to use regular control inside layered window that uses UpdateLayeredWindow API, you need to override control's OnPaint method to redirect drawing to off-screen bitmap which you later use with UpdateLayeredWindow method to update window look.
If you don't want to dig into controls code, or don't have much custom made controls, WM_PRINT message could be used to force controls to paint themselves into provided device context. Classic window subclassing (SetWindowLong/GetWindowLong) for catching moments when controls invalidate themselves is useful, but may be slightly dangerous - you have to watch for callback chain.
Finally, you can use lightweight (windowless) controls. They use form message queue to receive events and draw themselves, so only form drawing code modifications are necessary. Some of standard winforms controls support this mode.

Related

Confused about why two Panels work differently

I have a problem that is really confusing me. Let me lay some background. I am trying to develop my own Editor Control. I wish to have a blinking caret, I know I can do this using CreateCaret, ShowCaret ect but this is not how I wish to do it, I wish to implement this myself. My caret does not blink and I cant understand why.
The way I'm trying to implement this is by caching the area below the caret and then display the caret. Then half a second later I repaint the cached data back to the Editor Control therefore effecting a flashing caret. I have tried using just a Graphics Object and a Bitmap for the cache neither worked but I think I know why. So I decided to experiment. I set up one Panel on the Form itself through the Designer and one is handed coded into the Caret class itself. The Panel on the Form works but the Panel encapsulated within the class doesn't and I don't know why. My question is why?
Below is the Code. The BlinkTimer_Tick method just causes the Blink. Paint just Paints the caret, none of this should be hard to understand.
BackupBackground copies the area below the Caret to the cache, while RestoreBackground copies the cache to the Control. Now the problem is if you comment out the lines commented with "Works if this line is commented out" in both methods it all works but when these are not commented it doesn't work the caret does not blink. Both these Panels are set up the same.
private void BlinkTimer_Tick(object sender, EventArgs e)
{
Paint();
_BlinkTimer.Start();
}
private void BackupBackground(Graphics SrcGraph)
{
Form TF = _Parent.FindForm() as Form;
Panel P = TF.Controls["_TestPanel"] as Panel;
P = _Buffer; // Works if this line is Commentted out
Graphics DestGraph = P.CreateGraphics();
IntPtr SrcHDC = SrcGraph.GetHdc();
IntPtr DestHDC = DestGraph.GetHdc();
BitBlt(DestHDC, 0, 0, _Size.Width, _Size.Height,
SrcHDC, _Location.X, _Location.Y, TernaryRasterOperations.SRCCOPY);
DestGraph.ReleaseHdc(DestHDC);
SrcGraph.ReleaseHdc(SrcHDC);
}
private void RestoreBackground(Graphics DestGraph)
{
Form TF = _Parent.FindForm() as Form;
Panel P = TF.Controls["_TestPanel"] as Panel;
P = _Buffer; // Works if this line is Commentted out
Graphics SrcGraph = P.CreateGraphics();
IntPtr SrcHDC = SrcGraph.GetHdc();
IntPtr DestHDC = DestGraph.GetHdc();
BitBlt(DestHDC, _Location.X, _Location.Y, _Size.Width, _Size.Height,
SrcHDC, 0, 0, TernaryRasterOperations.SRCCOPY);
DestGraph.ReleaseHdc(DestHDC);
SrcGraph.ReleaseHdc(SrcHDC);
}
internal void Paint()
{
Graphics Graph = _Parent.CreateGraphics();
if (!_BlinkOn)
{
// Restore Graphics from Backup
RestoreBackground(Graph);
_BlinkOn = true;
}
else
{
// Backup Graphics
Graph.Flush();
BackupBackground(Graph);
// Draw Caret
using (SolidBrush P = new SolidBrush(Color.Black))
{
Graph.FillRectangle(P, new Rectangle(_Location, _Size));
}
_BlinkOn = false;
}
}
The Caret should flash in both circumstances as I change nothing really but it only flashes when I use the Panel on the Form.
Note: I do not intend to use a Panel for the cache I was just experimenting and found this behavior and its weird, so I need to know.
Thanks Danny.

Screenshot method generates black images

After failing to use the control.drawtobitmap in c#, my second option was to take screenshots of the desktop and crop out the desired sections.
My hiccup shows up once i switch user accounts, although the program does not crash, once the user is switched the program generates pure black images only.
I used this code as a reference:
WebBrowser.DrawToBitmap() or other methods?
I guess logically this makes sense as this would help windows save resources.
What options/ solutions do i have in my situation?
Edit 1
made a modification to the code for testing:
int c = 0;
while (true)
{
try
{
c++;
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height);
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
bmp.Save("picture" + c.ToString() + ".jpg");
Thread.Sleep(5000);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
this works perfectly while on the user account, but as soon as i switch users, it returns the exception: The handle is invalid.
Any ideas?
Edit 2:
The bug in DrawToBitmap is not exactly random...
if i used the code you supplied:
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
bmp.Save(".\\picture.jpg");
it works perfectly, example: http://oi61.tinypic.com/1z23ynp.jpg
However, the moment i right-click on the web-browser control, DrawToBitmap will return a blank image.
example: http://oi60.tinypic.com/9ay0yc.jpg
So i can easily overcome this bug by adding
((Control)webbrowser1).Enabled = false;
this makes any clicking impossible on the web-browser, but unfortunately to deactivate it would render my project useless as its main function is to emulate mouse clicks on a web-browser control.
although this might also be a problem if the window is hidden.
currently im looking at this post, where code is supplied to give you a window handle.
Simulate click into a hidden window
it seems it might be of some value... do have a look.
What were the problems you had with DrawToBitmap?
It works fine here, (W8.1, VS2013) even with a Webcontrol and also after switching users. (But see the edit at the end for the conditions!)
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
// Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
Here is code to take a screenshot of your window:
Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height );
using (Graphics g = Graphics.FromImage(bmp))
g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
//Clipboard.SetImage(bmp); for testing only
bmp.Dispose();
I can switch users like I want, the program keeps working.
BTW, the link you posted is really old, many things may have improved.
Edit:
With the updated question things are a lot clearer.
So you want to continuously get a screenshot of your program even when the user has changed, right?
and you want to display a WebControl, right?
A user can have three types of desktop: the logon/logoff screen, the screensaver screen and one or more normal desktop screen(s). But while the user is logged off he has no desktop screen at all.
Therefore the screenshot method will not work if the user has no active desktop, neither as g.CopyFromScreen, which will cause a GDI-error nor using a window handle like in the various solutions on the web, including the ones your link leads to. All these will, at best, show a blank or black screen.
So the DrawToBitmap method is the only one that works.
You wrote that it has random errors. That's not what I see.
The problems come in predictably when the user interacts with the WebBrowser in any way. This includes scrolling or clicking with or without navigation. After these interactions the WebBrowser will draw itself as an empty box until its URL is reloaded - not only refreshed - but really reloaded by webBrowser1.Uri = new Uri(uriPath). This can be done, see my other post
The WebBrowser also has another issue when doing a DrawToBitmap: It will fail (with the said empty box) for any pages that include an <input type="text" element. I'm not sure what's the best way to workaround this, let alone why it happends in the first place.. A screenshot method doesn't have that specific problem.
Edit 2:
The code the OP has dug up code which, using a call to PrintWindow, seems to solve all problems we had: It works while being logged off, works with Refeshing even after clicking in the WebBrowser and scrapes all pages, including those with textual input fields. Hoorah!
After cutting down the slack here is a version that can create a copy of the Form or just the WebBroser (or any other Control) with or without borders:
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
public Bitmap CaptureWindow(Control ctl)
{
//Bitmap bmp = new Bitmap(ctl.Width, ctl.Height); // includes borders
Bitmap bmp = new Bitmap(ctl.ClientRectangle.Width, ctl.ClientRectangle.Height); // content only
using (Graphics graphics = Graphics.FromImage(bmp))
{
IntPtr hDC = graphics.GetHdc();
try { PrintWindow(ctl.Handle, hDC, (uint)0); }
finally { graphics.ReleaseHdc(hDC); }
}
return bmp;
}
Finally this code seems to work even when i have switched users.
Code to take screen shot of any unsaved Notepad process ("Untitled - Notepad")
private void Form1_Load(object sender, EventArgs e)
{
//loop for debugging
int c = 0;
while(true)
{
c++;
System.Drawing.Bitmap image = CaptureWindow(FindWindow(null, "Untitled - Notepad"));
image.Save(".\\picture"+c.ToString()+".jpg");
Thread.Sleep(5000);
}
}
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public System.Drawing.Bitmap CaptureWindow(IntPtr hWnd)
{
System.Drawing.Rectangle rctForm = System.Drawing.Rectangle.Empty;
using (System.Drawing.Graphics grfx = System.Drawing.Graphics.FromHdc(GetWindowDC(hWnd)))
{
rctForm = System.Drawing.Rectangle.Round(grfx.VisibleClipBounds);
}
System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(rctForm.Width, rctForm.Height);
System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(pImage);
IntPtr hDC = graphics.GetHdc();
try
{
PrintWindow(hWnd, hDC, (uint)0);
}
finally
{
graphics.ReleaseHdc(hDC);
}
return pImage;
}
Note that the window may be hidden but it must still be maximized on the user account to get a complete screen shot.
Digital Rights Management may be stopping this, because Windows adds protection for digital media.
If, for example, you are attempting to create a screen capture of something in Media Player or Media Center using Microsoft's rendering of Graphics - yes, Microsoft is going to "protect you from any potential lawsuit."
Try this: Click "Print Screen" on your keyboard and then go into Microsoft Paint and try pasting your screen capture into it. Is anything there?
I faced with the same problem and I couldn't use CopyFromScreen method because my form with WebBrowser could be hidded. PrintWindow method also didn't work well generating images with black areas, especially when my MDI form partially covered by MDI parent form.
Finally I used OleDraw method as in this topic on SO, but integrated it in a class derived from WebBrowser. Basically, it just overrides standard control reaction to WM_PRINT message. This allows to do normal Control.DrawToBitmap not only for the WebBrowser, but for a form with WebBrowser in it as well. This also works if the form is hidden (covered by another form, including MDI parent form) and should work when user has locked session with Win+L (I haven't tested it).
public class WebBrowserEx : WebBrowser
{
private const uint DVASPECT_CONTENT = 1;
[DllImport("ole32.dll", PreserveSig = false)]
private static extern void OleDraw([MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwAspect,
IntPtr hdcDraw,
[In] ref System.Drawing.Rectangle lprcBounds
);
protected override void WndProc(ref Message m)
{
const int WM_PRINT = 0x0317;
switch (m.Msg)
{
case WM_PRINT:
Rectangle browserRect = new Rectangle(0, 0, this.Width, this.Height);
// Don't know why, but drawing with OleDraw directly on HDC from m.WParam.
// results in badly scaled (stretched) image of the browser.
// So, drawing to an intermediate bitmap first.
using (Bitmap browserBitmap = new Bitmap(browserRect.Width, browserRect.Height))
{
using (var graphics = Graphics.FromImage(browserBitmap))
{
var hdc = graphics.GetHdc();
OleDraw(this.ActiveXInstance, DVASPECT_CONTENT, hdc, ref browserRect);
graphics.ReleaseHdc(hdc);
}
using (var graphics = Graphics.FromHdc(m.WParam))
{
graphics.DrawImage(browserBitmap, Point.Empty);
}
}
// ignore default WndProc
return;
}
base.WndProc(ref m);
}
}

How to redraw only a region of a layered window?

I have a layered window which is normally drawn this way:
private void SelectBitmap(Bitmap bitmap)
{
IntPtr screenDc = GetDC(IntPtr.Zero);
IntPtr memDc = CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr hOldBitmap = IntPtr.Zero;
try
{
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
hOldBitmap = SelectObject(memDc, hBitmap);
POINT sourceLocation = new POINT(0, 0);
BLENDFUNCTION blend = new BLENDFUNCTION();
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
SIZE newSize = new SIZE(bitmap.Width, bitmap.Height);
POINT newLocation = new POINT(Location.X, Location.Y);
UpdateLayeredWindow(Handle, screenDc,
ref newLocation, ref newSize,
memDc,
ref sourceLocation, 0,
ref blend,
ULW_ALPHA);
}
finally
{
ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
SelectObject(memDc, hOldBitmap);
DeleteObject(hBitmap);
}
DeleteDC(memDc);
}
}
However, this obviously redraw the whole window every time it's called. It's quite a performance drain on large window. (even on my top of the line PC, which make me wonder how people could handle that in Win2K)
If I read the Microsoft paper on the layered window, it says: UpdateLayeredWindow always updates the entire window. To update part of a window, use the traditional WM_PAINT and set the blend value using SetLayeredWindowAttributes.
I just can't understand the above. How is WM_PAINT supposed to access the layered window bitmap and redraw only part of it on the window? From what I understood, layered windows simply disable the WM_PAINT message and expect the user to draw the window by himself. There's obviously no way to bind the WM_PAINT to the custom drawing done.
Am I missing something very obvious?
After long profiling, I found out it wasn't really the layered window update that was bottleneck. Refreshing the whole screen, the SelectBitmap method above, on a 1920*1200 was taking about 6-8ms. Sure, not very amazing, but plenty enough to refresh at 30 FPS+.
In my case, the performance drains was coming from some thread asking for refresh almost a hundred time per redraw, making everything sluggish. The solution was to break down the refresh/redraw and separate them. One would update (union) a region and the other, when not drawing, would take that region, draw it and then empty it.

How to do Free hand Image Cropping in C# window application?

How to do Free hand Image Cropping in C# window application??
Okay, you provided very small amount of information, but I'll assume that you are using winforms. There are some tasks dealing with freehand-technique such as:
Drawing
Drag-n-dropping
Cropping
Selecting
They all are very similar. Let's assume that you have a PictureBox and want to crop an image inside it.
// Current selection
private Rectangle _cropRectangle;
// Starting point
private Point _cropStart;
// Dragging flag
private bool _isDragging;
private void pBox_MouseDown(object sender, MouseEventArgs e)
{
_cropRectangle = new Rectangle(e.X, e.Y, 0, 0);
_isDragging = true;
}
private void pBox_MouseUp(object sender, MouseEventArgs e)
{
_isDragging = false;
}
private void pBox_MouseMove(object sender, MouseEventArgs e)
{
if (!_isDragging)
return;
_cropRectangle = new Rectangle(Math.Min(_cropStart.X, e.X),
Math.Min(_cropStart.Y, e.Y),
Math.Abs(e.X - _cropStart.X),
Math.Abs(e.Y - _cropStart.Y));
pBox.Invalidate();
}
private void pBox_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.Red, _cropRectangle);
}
What happens: I make use of three mouse events (MouseDown, MouseUp, MoseMove) and the Paint event. Basically, whenever you want to do anything from the above list, you'll have handle these four events.
I tried to keep the code short and self-explanatory. There are four event handlers working with three instance fields. The fields are used to store the current state of dragging process.
Feel free to customize the code, especially the pBox_Paint handler. Mine just draws a thin red rectangle around selected area. You might want to do something more elaborate here.
Whenever you're done with your rectangle, you can call the Crop method:
private Image Crop()
{
Bitmap croppedImage = new Bitmap(_cropRectangle.Width, _cropRectangle.Height);
using (Graphics g = Graphics.FromImage(croppedImage))
{
g.DrawImage(pBox.Image, 0, 0, _cropRectangle, GraphicsUnit.Pixel);
}
return croppedImage;
}
It creates a new Bitmap and put the selected portion of source image into it. The returned Image object might be used in any manner you like.
EDIT: trying to simplify the code I made some mistakes earlier, fixed now.
You can use Graphics.DrawImage to draw a cropped image onto the graphics object from a bitmap.
Rectangle cropRect = new Rectangle(...);
Bitmap src = Image.FromFile(fileName) as Bitmap;
Bitmap target = new Bitmap(cropRect.Width, cropRect.Height);
using(Graphics g = Graphics.FromImage(target))
{
g.DrawImage(src, cropRect,
new Rectangle(0, 0, target.Width, target.Height),
GraphicsUnit.Pixel);
}
You can also refer the full code for this....
Refer this *Link*

Transparency around non-rectangular (owner draw) ToolTip control?

I'm customizing the appearance of a WinForms ToolTip control by responding to the Draw event. I just want some of the ToolTip's corners to be rounded. I've got everything working such that the first time the ToolTip is displayed, everything looks perfect. On subsequent displays, however, the unfilled areas of my rounded rectangle continue to have what was in the background the first time the ToolTip was displayed.
Screen shot of problem (I don't have rights to put inline apparently):
http://tinypic.com/r/30xa3w9/3
In the picture, you can see the left-over artifacts in the upper-left corner where I would like it to just be transparent (showing the gray background), like this:
tinypic.com/r/mvn8eo/3 (nor rights to add more than one link)
Here is the drawing code:
private void ToolTip_Draw(object sender, DrawToolTipEventArgs args)
{
args.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var rect = new RectangleF(0, 0, args.Bounds.Width, args.Bounds.Height);
using (var backBrush = new LinearGradientBrush(rect, Color.Silver, this.BackColor, 90))
{
using (var path = GetRoundedRectangle(rect, 10, 4, 4, 1))
{
args.Graphics.FillPath(backBrush, path);
args.DrawText();
}
}
}
The GetRoundedRectangle function (not included) just calculates the appropriate GraphicsPath for the rounded geometry that I want.
I tried adding a call to args.DrawBackground after setting the BackColor to Color.Transparent, but that just filled in the area with the dark gray of the form's background rather than actually being transparent, which I think is the typical "simulated" transparency of WinForms.
As a side note, an non-customized ToolTip with IsBalloon set to true is non-rectangular with correct transparency.
Can anyone suggest a fix for this problem?
Control.Region is what you are looking for. You need to tell the window manager the shape of the tooltip, so background is properly redrawn.
Here is a solution, though imperfect. It uses Graphics.CopyFromScreen to copy the area under the tooltip into the background. Of course, getting the location of the tooltip isn't simple -- hence the reflection and PInvoke call to GetWindowRect.
A remaining glitch is that the background might be wrong while the the tooltip fades out. For example, if you have a button that is colored when the mouse is over it, the tooltip will still have that colored background when you move the mouse off while it fades away. Setting ToolTip.UseFading to false seems to change the frequency of background paints such that it is worse than the fading problem. If the user has disabled eye candy at the OS level, that might also trigger the same paint glitches as having UseFading set to false.
private void ToolTip_Draw2(object sender, DrawToolTipEventArgs args)
{
var graphics = args.Graphics;
var bounds = args.Bounds;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var windowRect = GetWindowRect();
graphics.CopyFromScreen(windowRect.Left, windowRect.Top, 0, 0, new Size(bounds.Width, bounds.Height));
using (var backBrush = new LinearGradientBrush(bounds, C.Color_LogitechGray2, this.BackColor, 90))
{
using (var path = GetRoundedRectangle(bounds, 10, 4, 4, 1))
{
args.Graphics.FillPath(backBrush, path);
args.DrawText();
}
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
private Rectangle GetWindowRect()
{
RECT rect = new RECT();
var window = typeof(ToolTip).GetField("window", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this) as NativeWindow;
GetWindowRect(window.Handle, ref rect);
return rect;
}

Categories

Resources