Painted Border Runs In Certain Situations - c#

In my tool I use a a panel to change pages. Each page has it's own panel and when I change a page I send the panel with the controls. On the panel I use as the canvas I have the following paint event:
private void panelContent_Paint(object sender, PaintEventArgs e)
{
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
// Paints a border around the panel to match the treeview control
e.Graphics.DrawRectangle(Pens.CornflowerBlue,
e.ClipRectangle.Left,
e.ClipRectangle.Top,
e.ClipRectangle.Width - 1,
e.ClipRectangle.Height - 1);
e.Graphics.Flush();
base.OnPaint(e);
}
This method basically draws a nice border around the panel so it look better. For some reason when I move a another form above this panel the lines that make up the border start to run a little. Occasionally small lines will be drawn from the border too. The problem only happens for a few seconds before the entire panel redraws again. Is there anything I can do to prevent this from happening?

ClipRectangle tells you what part of the control needs to be repainted. If you're moving something over it, this is probably going to be the intersection of your object and the one being moved. You can use this information to more efficiently repaint your control.
You probably want to draw the rectangle from (0, 0) to (panelContent.Width-1, panelContent.Height-1).

Related

PictureBox background equal to other PictureBox while moving?

C# Beginner here.
I'm making a 2D Tanks game, and everything's working out nicely so far.
Both of my tanks are Picture Boxes, and so is my Missile.
The image of the missile and tanks in the PictureBoxes have transparent BackColour properties. The problem is, the background of the missile & tanks are not transparent while on top of the other picturebox (pbBackground). It looks like this.
I'm aware that using different PB's is an inefficient way of going about it, but I've come pretty far and I don't really know any better. Anyways, as you can see, the Missile and Tank PB backgrounds show the form colour. When I downloaded the images, the backgrounds were transparent, I'm sure of it. How do I go about making the background of my PB's truly transparent? (Matching the background of the Overlapped PB?)
I saw this but it doesn't really match my scenario and I don't understand the solution.
UPDATE: Okay, I followed Tommy's advice, is this the right way to go about moving it along pbBackground in a timer constantly changing MissileX and MissileY? Currently this does nothing.
using (Graphics drawmissile = Graphics.FromImage(pbBackground.Image))
{
drawmissile.DrawImage(pbMissile.Image, new Point(MissileX,Convert.ToInt32(MissileY)));
}
PictureBox is opaque. And PictureBox is not efficient.
For making games, you should study Paint event which directly draws on your form.
Bitmap backgroundBitmap = new Bitmap("background");
Bitmap tankBitmap = new Bitmap("tank");
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(backgroundBitmap, 0, 0);
e.Graphics.DrawImage(tankBitmap, 30, 30);
}
private void timer1_Tick(object sender, EventArgs e)
{
this.Invalidate(); //trigger Form1_Paint to draw next frame
}
Don't layer multiple PictureBox instances on top of each other. It will get very confusing, very quickly.
Instead, use one single PictureBox and use Paint to draw your images to it. In this way, you can have much more control over the graphics operations happening.
Have a look at this
private void DrawIt()
{
System.Drawing.Graphics graphics = this.CreateGraphics();
System.Drawing.Rectangle rectangle = new System.Drawing.Rectangle(
50, 50, 150, 150);
graphics.DrawEllipse(System.Drawing.Pens.Black, rectangle);
graphics.DrawRectangle(System.Drawing.Pens.Red, rectangle);
}
In this example they demonstrate how to draw shapes directly onto a Form. You would use your PictureBox there instead. You can also draw images.
There's lots of ways to draw shapes onto a form using a System.Drawing.Graphics object. Try taking a look at this question for a comparison.
Tommy's answer is right, however, if you're dead set on using pictureboxes (a bad idea), set the overlapping picturebox backgroundcolour to Transparent and the Form's Background to whatever image. TIL the Transparent BackColour just uses the form colour / image. Tommy actually has the right answer here, but this is what I did to fix my problem (the lazy way).

Create Form fade out transition

How could you - in WinForms - create a black area that (over time) loses/gains transparency and is redrawn constantly so a kind of transition is created? I dont want to create a seperate form, so this would be very helpful and I could rearrange my new items as soon as the black area reaches a transparency of 0.
Left: What i have what at the moment:
Right: What the program looks like on startup
What I would want: A totally black screen
Override the OnPaint method of the form, and fill a rectangle the size of the form with the desired transparency. In this example, transparency keeps increasing:
var transparency = 0;
protected override OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// e.Graphics.DrawImage(Image, imageLeft, imageTop, imageWidth, imageHeight);
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(transparency, Black)), this.ClientRectangle);
}
If you want to do this from a Timer event, inside the Tick event Invalidate the form to force the paint.
protected void MyTimer_Tick(object sender, eventArgs e) {
transparency++;
MyForm.Invalidate();
}
If you need to draw on top of the PictureBox, that presents a problem. One way to solve it is to get rid of the PictureBox and draw the image using Graphics.DrawImage before you draw the fade. If you want to show the image but have the black go up against the edges of your animal, then make it's background color of the PictureBox transparent and convert your image to use a PNG or GIF with all around the animal transparent in the original image.
For more complex effects, Check out the dot-net-transitions project on Google Code. It supports a variety of linear/non-linear transitions.

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.

Refresh child controls in flowlayoutpanel when scrolling

In .NET 3.5 with winforms I'm making an image thumbnail viewer control.
The main control is derived from a FlowLayoutPanel which takes a list of images and displays them. The images which are displayed are made from a CustomControl on which I paint the and the accompanying label as well as the border of the control.
Images can be selected through clicking and yada yada as you would expect for that kind of control.
Here's a screenshote to illustrate:
That part works fine. The problem is then when I scroll the FlowLayoutPanel derived control the border doesn't redraw properly and there are lines remaining as shown in this screenshot:
I have set both the FlowLayoutPanel and the Images to double buffered. And the images and labels do not have the problem, so I suspect it is something else, but can't figure out what it is.
I think the method used to paint the border of the images might be at fault. Here's the code I use:
protected override void OnPaint(PaintEventArgs e)
{
Rectangle captionContainer;
captionContainer = new Rectangle();
if (!string.IsNullOrEmpty(this.Caption))
captionContainer = this.DrawCaption(e.Graphics);
if (this.Image != null)
this.DrawImage(e.Graphics, captionContainer);
this.Size = new Size(this.Padding.Horizontal + this.ImageSize.Width, this.Padding.Vertical + this.ImageSize.Height + captionContainer.Height);
ControlPaint.DrawBorder(e.Graphics, e.ClipRectangle, this.currentBorderColor, ButtonBorderStyle.Solid);
base.OnPaint(e);
}
I'll post more code if needed, but it is pretty lengthy, so I do not want to put too much code unless it actually is necessary.
Can anybody see where this is going wrong?
I have solved by also drawing the border using the Graphics object. Replacing
ControlPaint.DrawBorder(e.Graphics, e.ClipRectangle, this.currentBorderColor, ButtonBorderStyle.Solid);
with
e.Graphics.DrawRectangle(new Pen(this.currentBorderColor, 1F), new Rectangle(Point.Empty, new Size(this.Width - 1, this.Height - 1)));
does the trick. No idea why one works and not the other though...

c# - clear surface when resizing

I'm trying to build my own custom control for a windows forms application in C#.Net. Currently I paint some rectangles and other graphic elements using the paint event.
When I now resize the app form to fit the desktop size, all elements are repainted (which is exactly the behaviour I need) but the old one's are shown in the background.
Here's what I'm doing by now:
Pen penDefaultBorder = new Pen(Color.Wheat, 1);
int margin = 5;
private void CustomControl_Paint(object sender, PaintEventArgs e) {
CustomControl calendar = (CustomControl)sender;
Graphics graphics = e.Graphics;
graphics.Clear(Color.WhiteSmoke);
graphics.DrawRectangle(penDefaultBorder, margin, margin, calendar.Width - margin * 2, calendar.Height - margin * 2);
//...
}
Neither the graphics.Clear, nor adding a graphics.FillRectangle(...) will hide the old rectangle from the surface.
Ideas? Thank you all.
Paint events usually don't request an update for the entire canvas, just the area specified in the PaintEventArgs. I'm guessing what's happening is that only the newly-exposed regions of the canvas are being passed in the PaintEventArgs.
This one of the reasons that you shouldn't do any rendering in the Paint event. You should render to an offscreen bitmap - a buffer - and copy from that buffer to the control's canvas in the Paint event.
Searching for "double buffering" here or on Google will give you many examples of the technique.
Have you tried .Invalidate() to cause the form to redraw?

Categories