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?
Related
I hope you can help me with this problem, attached videos to explain in a simpler way.
First example
Panel (has a textured background) with labels (the labels have a png image without background)
Events: MouseDown, MouseUp and MouseMove.
As you will notice in the video to drag the label the background turns white panel and regains its background image when I stop dragging the label
Panel controls have a transparent background as property, but changing the background with any color, let the problem occurred related to the substance, I do not understand why this happens and how to fix less.
Second Example
Contains the above, with the only difference that the panel controls instead of having transparent background, I chose black color for that property
You have to use double buffer and you don't have to stop using an image on the background, you can have everything running smoothly.
You have a couple of ways to do this, the fast way (not enough most of the time) is to enable doublebuffer of the panel.
The "slow" but better way is to do your own Double Buffer using a Bitmap object as a buffer.
This example creates a "side buffer" and accepts an image as parameter and draws it using created buffer.
public void DrawSomething(Graphics graphics, Bitmap yourimage)
{
Graphics g;
Bitmap buffer = new Bitmap(yourimage.Width, yourimage.Height, graphics);
g = Graphics.FromImage(buffer);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.DrawImage(yourimage, 0, 0);
graphics.DrawImage(buffer, 0, 0);
g.Dispose();
}
Call this on your OnPaint event.
BTW... this is just a double buffer example.
Cheers
Change DoubleBuffered to true for both form and panel. I think that should solve your problem.
this is totally normal, because System.Windows.Forms.Control based items were not designed to do this kind of advanced Graphics operations.
in fact the reason that this effect happens here, is that when you assign any value other than 255 to the alpha component of a control BackColor, the form does the following when you change the control size or position:
it sets the new control position
it redraws the parent control
it gets the background of the control's parent as an image
it draws acquired image into the control body to seem as if the control is transparent
the control body gets drawn on top of the previously drawn background
the control children are drawn
* this is is a simplified explanation for the sake of illustration to deliver the idea
steps 1, 2 are responsible for the flickering effect that you see.
but you have two ways to solve this,
-the first is some kinda advanced solution but it's very powerful, which is you would have to create a double buffered custom control that would be your viewport.
the second is to use WPF instead of windows forms, as WPF was designed exactly to do this kind of things.
if you can kindly provide some code, i can show you how to do both.
So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.
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...
I am trying to write a small interactive game-like application, where I need to have a Draw method that's gonna draw on screen, but can't figure out how to structure the method for WPF.
If this was Winforms, I could use:
public void Draw (Graphics g)
{
}
But for a WPF Window, what should I have on it in the xaml (currently only have a Grid), and what should this Draw method receive as an argument?
First I want to do it like this to get it working, then I can think about how to make it more WPF, etc. But now I am more interested in getting this to work.
Typically, you "draw" in WPF in a completely different manner.
In Windows Forms/GDI, the graphics API is an immediate mode graphics API. Each time the window is refreshed/invalidated, you explicitly draw the contents using Graphics.
In WPF, however, things work differently. You rarely ever directly draw - instead, it's a retained mode graphics API. You tell WPF where you want the objects, and it takes care of the drawing for you.
The best way to think of it is, in Windows Forms, you'd say "Draw a line from X1 to Y1. Then draw a line from X2 to Y2. Then ...". And you repeat this every time you need to "redraw" since the screen is invalidated.
In WPF, instead, you say "I want a line from X1 to Y1. I want a line from X2 to Y2." WPF then decides when and how to draw it for you.
This is done by placing the shapes on a Canvas, and then letting WPF do all of the hard work.
When there are just too many objects to be drawn very quickly (huge Visual Tree) another option would be to use a WriteableBitmap. Just use the Pixels property to set the pixels and/or use the Render method to draw UIElements.
I preffer to use OnRender method like in this example:
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawRectangle(null, new Pen(Brushes.Black, 2), new Rect(0, 0, ActualWidth, Height));
}
To Implement a Draw loop type behavior in WPF you can use the CompositionTarget.Rendering event. This is raised once per frame when the WPF drawing system is painting frames.
As others have pointed out this is not very WPF friendly but it will work and can be used to get more immediate drawing behavior out of a WPF app.
In most cases you would use a single root canvas and update say the Canvas position of an element on the CompositionTarget.Rendering event.
For example to make a ellipse fly all over the screen do this:
In your XAML (For a Window that is 640 by 480 in size):
<Canvas x:Name="theCanvas">
<Ellipse x:Name="theEllipse" Height="10" Width="10" Fill="Black" />
</Canvas>
In your Code behind for the Window that the above XAML is in (Make sure to add a reference to System.Windows.Media in order to see the CompsitionTarget object :
public static Random rand = new Random();
public View()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, System.EventArgs e)
{
double newLeft = rand.Next(0, 640);
double newTop = rand.Next(0, 480);
theEllipse.SetValue(Canvas.LeftProperty,newLeft);
theEllipse.SetValue(Canvas.TopProperty, newTop);
}
Yow should add a Canvas (or change the Grid for a Canvas) and then draw over it. Here is Microsoft tut on drawing over a canvas
Also, I don't know how related is this other question to yours, but you might want to check it out.
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).