Issue when Applying Region to a Form - c#

I am facing a strange behavior when I apply a non-rectangular region to a Windows Form (lets say an ellipse). The issue is that the form seems to disappear for a moment (as if the region is empty) when initially resized. It looks like a slight flicker whereas the contents of the desktop behind the form become visible for a fraction of the second. After the first resize this flicker is no longer observable.
This can be reproduced by simply creating a Windows Forms project and applying an ellipse region to the form by using the size of the form as a bounds rectangle for the ellipse (in this way you will be able to resize the form hence its borders will not be completely "eaten" by the region).
Note: I am updating the region of the Form in the OnResize event.
The code that I am using looks the following way:
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
GraphicsPath path = new GraphicsPath();
path.AddEllipse(new Rectangle(Point.Empty, this.Size));
this.Region = new Region(path);
}
Any ideas what might be causing this?
Quick follow-up:
I noticed that when I put the same code snippet in the OnSizeChanged event the flicker disappears or seems to happen rarely.
Thanks!

Handles Paint event
private void Form1_Paint(object sender, PaintEventArgs e)
{
GraphicsPath path = new GraphicsPath();
path.AddEllipse(new Rectangle(Point.Empty, this.Size));
this.Region = new Region(path);
}

You've fixed a massive problem for me in the same area.
I'm using this:
private void BorderedPanel_SizeChanged(object sender, EventArgs e)
{
this.Region = new Region(RoundedRectangle.CreatePlusOne(this.ClientRectangle, this.cornerRadius, this.RectangleCorners));
Refresh();
}
and it works without flickering. So it's worth giving a shot!

Related

C# WinForms, ToolTip with semi-transparent background

i'm working on this small WinForm app and decided to use custom drawn tooltip with semi-transparent background. So i started with setting the OwnerDraw property on ToolTip to True, created event handlers for Draw and Popup events (see the example code bellow. The commented version isn't working either).
private void toolTip_Popup(object sender, PopupEventArgs e)
{
e.ToolTipSize = new Size(400, 400);
}
private void toolTip_Draw(object sender, DrawToolTipEventArgs e)
{
//e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(120, Color.Red)), new Rectangle(e.Bounds.Location, e.Bounds.Size));
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(120, Color.Red)), new Rectangle(0, 0, 400, 400));
}
Now when the ToolTip is shown for the first time for a specific control everything works as intended. See the picture bellow (400x400 tooltip with semi-transparent red background).
But when i hover over the same control for the second time ToolTip loses its semi-transparency. See the picture bellow. Why is that so?
Thank you all for your help. I'm pretty sure that Ben Voigts answer, or Jimis comments could solve this problem somehow too (i'll try them out later and update the answer if i'll be able to utilise them).
I based my solution on the first comment made by Hans Passant where he suggested to use Graphics.CopyFromScreen() in Popup event handler(toolTipDay_Popup), to capture the image underneath the ToolTip and then in Draw event handler(toolTipDay_Draw) i just drew the captured image.
(There is a problem with different DPI scalings as noted by Hans Passant, but that can be +- solved by Farshid T answer in How to get Windows Display settings?, i didn't include it in code bellow).
So the solution i'm using right now is as follows:
Bitmap dayToolTipBackground = new Bitmap(200, 200);
private void toolTipDay_Popup(object sender, PopupEventArgs e)
{
e.ToolTipSize = new Size(200, 200);
var backGraphics = Graphics.FromImage(dayToolTipBackground);
var cursorPosition = Cursor.Position;
backGraphics.CopyFromScreen(new Point(Cursor.Position.X, Cursor.Position.Y + 21), new Point(0, 0), new Size((200, 200)));
}
private void toolTipDay_Draw(object sender, DrawToolTipEventArgs e)
{
e.Graphics.DrawImage(dayToolTipBackground, new Point(0, 0));
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(120, Color.Red)), new Rectangle(e.Bounds.Location, e.Bounds.Size));
}
The first time you hover over the control, a new instance of tooltip is created with the transparent color (ARGB.120). When you click outside the tooltip, whether the tooltip object is fully disposed or are you simply setting the instance as invisible?
When you are hovering over the instance for the second time, then, if the old tooltip object is not disposed, there is a chance that the same object is called again. So now when you do Graphics.FillRectangle() on an existing tooltip with color = ARGB.120, you are just overlaying another layer of ARGB.120 color on it, which will darken it further because the color levels are changed.
Therefore when you click outside the tooltip after you call it for the first time, you might need to dispose the tooltip object(or the e.Graphics object, if that doesn't affect other parts of your application) and create new tooltip objects every time you hover over the control.
Windows needs to be told that the windows beneath the popup need to be redrawn. This is done via a "layered window" style. With layering, the content gets drawn in z-order and transparency blending works. Without layering, only the top window gets sent a repaint and it draws on top of meaningless leftover data in the DC's screen buffer.
You can try p/invoking SetLayeredWindowAttributes
I strongly recommend reading the MSDN documentation on Layered Windows:
Here and here

How to begin draw in one event and continue in another?

I am using Visual Studio 2013 to write a Windows Forms C# application. I want to draw game board on Form1_Load and draw pawns on button click. I have written two methods: InitDraw() and Draw(). When both method are in Form1_Load() or button1_Click() it's OK, but if InitDraw() is in Form1_Load() and Draw() is in button1_Click() - it draws only if I press Alt or move windows out of screen and move back to screen. I added Invalidate() but this does not help.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Bitmap drawBitmap;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
InitDraw();
}
private void button1_Click(object sender, EventArgs e)
{
Draw();
}
private void InitDraw()
{
drawBitmap = new Bitmap(500, 500);
pictureBox1.Image = drawBitmap;
}
private void Draw()
{
Graphics g = Graphics.FromImage(pictureBox1.Image);
Pen myPen = new Pen(Color.Black);
g.DrawLine(myPen, 0, 0, 100, 100);
myPen.Dispose();
g.Dispose();
Invalidate();
}
}
}
Nobody's stopping you from drawing wherever you want, the thing to remember is to do it on a bitmap image instead of trying to force it to screen. By this I mean you need to create your own Bitmap object, and any draw functions you call you call them on this.
To actually get this to show on screen, call the Invalidate function on the host control -- not a PictureBox by the way. Something lightweight like a panel will do. And in that control's Paint event simply draw your image.
You were sort of close, but you calling Invalidate on the form, besides the fact that it's horribly inefficient to redraw everything when you know exactly what needs to be redrawn, simply won't do anything. You don't rely on the Paint event, but on the intrinsic binding a PictureBox has with a Bitmap -- Bitmap who's handle you never change. As far as the PictureBox is concerned, everything is the same so it won't actually paint itself again. When you actually force it to paint itself by dragging the window outside the screen bounds and then back in it will read the bitmap and draw what you expect.
You must draw in the Paint event of the picture box. Windows might trigger the Paint event at any moment, e.g. if a window in front of it is removed or the form containing the picturebox is moved.
What you draw on the screen is volatile; therefore, let Windows decide when to (re-)draw. You can also trigger redrawing with
picturebox1.Invalidate();
or
picturebox1.Refresh();
Difference: Invalidate waits until the window is idle. Refresh draws immediately.
If you only draw in Form_Load or Button_Click, your drawing might get lost, when windows triggers a paint on its own. Try calling picturebox1.Invalidate in these events instead.

Drawing glitches when using CreateGraphics rather than Paint event handler for custom drawing

I've written a Windows Forms app where I do custom drawing on a Panel using Control.CreateGraphics(). Here's what my Form looks like at startup:
The custom drawing is performed on the top panel in the Click event handler of the "Draw!" button. Here's my button click handler:
private void drawButton_Click(object sender, EventArgs e)
{
using (Graphics g = drawPanel.CreateGraphics())
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
After a click on drawButton, the form looks like this:
Success!
But when I shrink the form by dragging a corner...
...and expand it back to its original size,
part of what I drew is gone!
This also happens when I drag part of the window offscreen...
...and drag it back onscreen:
If I minimize the window and restore it, the whole image is erased:
What is causing this? How can I make it so the graphics I draw are persistent?
Note: I've created this self-answered question so I have a canonical Q/A to direct users to, as this is a common scenario that's hard to search for if you don't already know the cause of the problem.
TL;DR:
Don't do your drawing in response to a one-time UI event with Control.CreateGraphics. Instead, register a Paint event handler for the control on which you want to paint, and do your drawing with the Graphics object passed via the PaintEventArgs.
If you want to paint only after a button click (for example), in your Click handler, set a boolean flag indicating that the button has been clicked and then call Control.Invalidate(). Then do your rendering conditionally in the Paint handler.
Finally, if your control's contents should change with the size of the control, register a Resize event handler and call Invalidate() there too.
Example code:
private bool _doCustomDrawing = false;
private void drawPanel_Paint(object sender, PaintEventArgs e)
{
if (_doCustomDrawing)
{
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
private void drawButton_Click(object sender, EventArgs e)
{
_doCustomDrawing = true;
drawPanel.Invalidate();
}
private void drawPanel_Resize(object sender, EventArgs e)
{
drawPanel.Invalidate();
}
But why? What was I doing wrong, and how does this fix it?
Take a look at the documentation for Control.CreateGraphics:
The Graphics object that you retrieve through the CreateGraphics method should not normally be retained after the current Windows message has been processed, because anything painted with that object will be erased with the next WM_PAINT message.
Windows doesn't take responsibility for retaining the graphics you draw to your Control. Rather, it identifies situations in which your control will require a repaint and informs it with a WM_PAINT message. Then it's up to your control to repaint itself. This happens in the OnPaint method, which you can override if you subclass Control or one of its subclasses. If you're not subclassing, you can still do custom drawing by handling the public Paint event, which a control will fire near the end of its OnPaint method. This is where you want to hook in, to make sure your graphics get redrawn every time the Control is told to repaint. Otherwise, part or all of your control will be painted over to the control's default appearance.
Repainting happens when all or part of a control is invalidated. You can invalidate the entire control, requesting a full repaint, by calling Control.Invalidate(). Other situations may require only a partial repaint. If Windows determines that only part of a Control needs to be repainted, the PaintEventArgs you receive will have a non-empty ClipRegion. In this situation, your drawing will only affect the area in the ClipRegion, even if you try to draw to areas outside that region. This is why the call to drawPanel.Invalidate() was required in the above example. Because the appearance of drawPanel needs to change with the size of the control and only the new parts of the control are invalidated when the window is expanded, it's necessary to request a full repaint with each resize.

GDI+ Problem drawing a table

I try to draw a table on a panel in C# Windows Form by using GDI+. The problem is that when I minimize the application, my drawing disappears. How can I avoid this and why acting this way?
Everything disappears when you minimize a window. I assume you mean that the table isn't there anymore when you restore the window. That will happen when you don't use the Paint event to draw the table but draw the screen directly. Implement the panel's Paint event:
private void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.DrawLine(Pens.Black, 0, 0, panel1.Width, 0);
// etc...
}

Problem with using Paint event in c#

I have used this below code before and was working perfectly. When I use the same
in one of my window form, the color of the form is not changing.
I mean after the page loads it shows the default color of the form. But when I try to debug the code below, it changes the color of the form perfectly. The problem is, after executing the last line of the code the color of the form goes back to the default color.
Am I missing something?
The form looks like a windows taskbar, and it has one tab control in it.
private void TaskBar_Paint(object sender, PaintEventArgs e)
{
Graphics mGraphics = e.Graphics;
Pen pen1 = new Pen(Color.FromArgb(96, 155, 173), 1);
Rectangle Area1 = new Rectangle(0, 0, this.Width - 2, this.Height - 2);
LinearGradientBrush LGB = new LinearGradientBrush(Area1,
Color.FromArgb(96, 155, 173),
Color.FromArgb(245, 251, 251),
LinearGradientMode.Vertical);
mGraphics.FillRectangle(LGB, Area1);
mGraphics.DrawRectangle(pen1, Area1);
}
There isn't much to go on here. What is this handler attached to? The Paint event of the Form? If so, you should override OnPaint() instead of attaching to the handler. My guess is that some other method is doing some painting too. You need to track that down. Without more code, it is not very likely that anyone here can help you. Sorry.

Categories