I'm having some strange drawing artefacts that I'm hoping someone might be able to help me pin down.
Basically I have a left docked panel that is supposed to have a gradient background that hasn't been working right. I've changed the colours for debugging
screenshot http://img509.imageshack.us/img509/5650/18740614.png
The panel is docked left, and has a transparent control on it (hence you can see small stubs of red along the edges where the transparent control has correctly been painted, picking up the background colour).
When I resize the panel slowly however, I get red lines left at the bottom, which I would expected to be covered up by yellow fills. The code I'm using is as follows:
// Construction
sidePanel.Paint += new PaintEventHandler(OnPaint);
private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Panel pb = sender as Panel;
PaintControl(pb.Width, pb.Height, sender, e, true);
}
private static void PaintControl(int width, int height, object sender, PaintEventArgs e, bool sidebar)
{
Rectangle baseRectangle = new Rectangle(0, 0, width -1, height-1);
using (LinearGradientBrush gradientBrush = new LinearGradientBrush(baseRectangle, WizardBaseForm.StartColour, WizardBaseForm.EndColour, 90))
{
e.Graphics.Clear(Color.Yellow);
e.Graphics.DrawRectangle(Pens.Red, baseRectangle);
e.Graphics.FillRectangle(Brushes.Yellow, baseRectangle);
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Invalidate();
}
The overall effect of this is I'm not getting either a nice solid colour, or a nice gradient fill when I resize.
I would suggest creating your own panel, derive it from panel and place the following in the constructor:
this.SetStyle(ControlStyles.AllPaintingInWmPaint
| ControlStyles.OptimizedDoubleBuffer
| ControlStyles.ResizeRedraw
| ControlStyles.DoubleBuffer
| ControlStyles.UserPaint
, true);
Add following in New (after InitializeComponents):
this.SetStyle(ControlStyles.ResizeRedraw, true);
This is to ensure painting on resize.
There are few additional styles that you may try (AllPaintingInWmPaint, UserPaint...) but whether they should be set or not depends on your particular case and what exactly you want to do. In case that all painting is done by you in Paint event, I would set them.
Related
I'm learning how to draw in Winforms. I've created a Form, with a panel with scrollbars. Upone Event Paint I draw an ellipse. This is fairly straightforward:
this.panel1.AutoScroll = true;
this.panel1.AutoScrollMinSize = = new System.Drawing.Size(500, 300);
private void OnPaint(object sender, PaintEventArgs e)
{
Rectangle ellipse = new Rectangle(Point.Empty, new Size(400, 400));
ellipse.Offset(this.panel1.AutoScrollPosition);
using (Pen myPen = new System.Drawing.Pen(System.Drawing.Color.Red))
{
e.Graphics.DrawEllipse(myPen, ellipse);
}
}
private void OnPanelScroll(object sender, ScrollEventArgs e)
{
this.panel1.Invalidate();
}
This works fine, but the complete image is redrawn when resizing or scrolling the panel.
A long time ago, in the time of MFC there was the notion of ViewPort / SetViewPortOrg / mapping modes / etc.. Scrolling and resizing did not require a recalculation of the complete image. Once you had drawn the image you didn't have to redraw as long as the complete image was not changed. All you had to do was move the viewport, or change the mapping mode
Does .NET have something similar that can do the scrolling for me? Maybe I should not draw on a panel, but on another subclass of a ScrollableControl?
I am creating an application for an industrial touch screen computer with no hardware to brag about. The operator of this touch screen computer is among other things supposed to be able to unlock and drag buttons around on a form with a background image.
However, as many of you already might know, moving controls on a parent control with a background image isn't pretty. The dragging is slow and instead of experiencing a smooth dragging, the operator will see a button jumping after through hoops in the wake of the mouse pointer as you move the pointer across the screen.
This is the current code for moving the button:
private void btnButton_MouseMove(object sender, MouseEventArgs e)
{
// Do not proceed unless it is allowed to move buttons
if (!this._AllowButtonsToBeMoved)
return;
if (this._IsBeingDragged)
{
var btn = (sender as Button);
var newPoint = btn.PointToScreen(new Point(e.X, e.Y));
newPoint.Offset(this._ButtonOffset);
btn.Location = newPoint;
}
}
I am not looking to solve this exact problem, I'd rather eliminate it to save some time. What I wish to implement in order to eliminate this, is a more resource efficient way to move the box around. I'm thinking that moving a dotted rectangle instead of the button, then dropping it where I want the button must be way more efficient than dragging the button around the screen, causing who knows how many repaint operations.
Does anyone have any better suggestions? If not, then I would very much appreciate pointers on how to proceed with creating and moving this rectangle around the screen, as I am having some difficulty finding good sources of information regarding how to approach this on good ol' Google.
Update, 26/11/13
I'm attempting Luaan's suggestion regarding overriding the form's OnPaint, however I am unsure as to how exactly I can add the rendering of the button in this code. Any ideas?
protected override void OnPaint(PaintEventArgs e)
{
if (_IsBeingDragged)
{
e.Graphics.DrawImage(this._FormPaintBuffer, new Point(0, 0));
}
else
{
base.OnPaint(e);
}
}
This is a standard case of Winforms being too programmer-friendly. Details that any game programmer pays careful attention to but are way too easy to miss. It allows you to set a BackgroundImage and it will take anything you throw at it. That usually works just fine, except when you need the image to render quickly. Like you do in this case.
Two things you need to do to make it draw ~fifty times quicker:
Resize the bitmap yourself to fit the form's ClientSize so it doesn't have to be done repeatedly every time the image needs to be painted. The default Graphics.InterpolationMode property value produces very nice looking images but it is not cheap. Do note that this can take a significant memory hit, the reason it isn't done automatically.
Pay attention to the pixel format of the image. There's only one that draws fast, the one that can be blitted directly to the video adapter without having the value of every single pixel converted to the frame buffer format. That is PixelFormat.Format32bppPArgb on all video adapters in use in the past 10+ years. Big difference, it is ten times faster than all the other ones. You never get that format out of a bitmap that you created with a painting program so explicitly converting it is required. Again heed the memory hit.
It takes but a little scrap of code to get both:
private static Bitmap Resample(Image img, Size size) {
var bmp = new Bitmap(size.Width, size.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (var gr = Graphics.FromImage(bmp)) {
gr.DrawImage(img, new Rectangle(Point.Empty, size));
}
return bmp;
}
In the somewhat unlikely case you still have painting trails or stuttering you need to consider a very different approach. Like the one that's used in the Winforms designer. You use layers, an extra borderless transparent window on top of the original. You get one with the TransparencyKey property.
1) If you want to keep everything the way it is, you might want to implement some sort of double buffering for the background. When the form is redrawn, it always has to redraw pretty much the whole thing, while actually doing some logic (ie. JPG/PNG is slower to draw than a BMP). If you store the Paint canvas in a Bitmap, you can draw the whole form except for that one button in a Bitmap and draw only that as background while you're dragging the button - this way you get around all the draw logic of the form and its controls, which should be vastly faster.
2) You can only draw an outline of the button being moved. The trick is that you draw lines in XOR mode - the first time you draw the rectangle it adds the outline, and the next time you draw it in the same location, it disappears. This way you don't have to redraw the form all the time, just the few pixels that form the rectangle. The support for XOR lines in C# isn't the best, but you can use the ControlPaint.DrawReversibleLine method.
To drag a control you need to use the .DrawToBitmap function of the control and set the appropriate styles of the form. I haven't done it in the sample code but you'll need a "design mode" and a "normal mode". To drag the control you simply click it, drag it and click again. You can get fancy and make the Bitmap holding the control transparent so as to accommodate rounded edges.
For this example, make a standard C# Windows Forms application (Form1) and drop a button (button1) onto the form then place this code after the Form1 constructor in your Form class. Make sure to change the location of the background bitmap in code.
private Bitmap b = null;
private bool IsDragging = false;
private Point down = Point.Empty;
private Point offset = Point.Empty;
private void button1_MouseUp(object sender, MouseEventArgs e)
{
IsDragging = true;
button1.Visible = false;
down = button1.PointToScreen(e.Location);
offset = e.Location;
this.Invalidate();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (IsDragging)
{
IsDragging = false;
down = new Point(down.X - offset.X, down.Y - offset.Y);
button1.Location = down;
button1.Visible = true;
down = Point.Empty;
this.Invalidate();
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (IsDragging)
{
down.X += (e.X - down.X);
down.Y += (e.Y - down.Y);
this.Invalidate();
}
}
private void Form1_Load(object sender, EventArgs e)
{
try
{
b = new Bitmap(button1.Width, button1.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
button1.DrawToBitmap(b, new Rectangle(0, 0, button1.Width, button1.Height));
button1.MouseUp += new MouseEventHandler(button1_MouseUp);
this.MouseUp += new MouseEventHandler(Form1_MouseUp);
this.MouseMove += new MouseEventHandler(Form1_MouseMove);
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
this.UpdateStyles();
this.BackgroundImage = Image.FromFile(#"C:\Users\Public\Pictures\Sample Pictures\desert.jpg");
this.BackgroundImageLayout = ImageLayout.Stretch;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (IsDragging)
{
e.Graphics.DrawImage(b, new Point(down.X - offset.X, down.Y - offset.Y));
}
base.OnPaint(e);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (b != null)
{
b.Dispose();
}
}
I'm working on ImageButton, in which I paint every state(i've got several images for each state) of this button (like mouseOver, mouseDown etc.).
I've made control transparent using this code:
public ImageButton()
{
InitializeComponent();
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x20;
return parms;
}
}
But there's a problem, after few switches of state, corners becoming sharp and ugly, to solve this problem I need to clear background, but if my control is transparent then it's impossible.
I've tried this solution: Clearing the graphics of a transparent panel C#
but it's slow and makes control flickering.
Do you have any ideas how to clear this background and keep transparency of control?
Ok, I've solved this problem.
I've worked around it by setting control as not transparent and I draw canvas which is under my control, as background of my ImageButton.
Solution (in Paint event):
//gets position of button and transforms it to point on whole screen
//(because in next step we'll get screenshot of whole window [with borders etc])
Point btnpos = this.Parent.PointToScreen(new Point(Location.X, Location.Y));
//now our point will be relative to the edges of form
//[including borders, which we'll have on our bitmap]
if (this.Parent is Form)
{
btnpos.X -= this.Parent.Left;
btnpos.Y -= this.Parent.Top;
}
else
{
btnpos.X = this.Left;
btnpos.Y = this.Top;
}
//gets screenshot of whole form
Bitmap b = new Bitmap(this.Parent.Width, this.Parent.Height);
this.Parent.DrawToBitmap(b, new Rectangle(new Point(0, 0), this.Parent.Size));
//draws background (which simulates transparency)
e.Graphics.DrawImage(b,
new Rectangle(new Point(0, 0), this.Size),
new Rectangle(btnpos, this.Size),
GraphicsUnit.Pixel);
//do whatever you want to draw your stuff
PS. It doesn't work in designtime.
I want to show some graphics in a Winform app, it will be a stock's chart drawing tool. I think (but I am not sure...) I have to use a PictureBox, and using the System.Drawing.Graphics class' drawing primitives to draw the chart. I have started coding it, now it works more-or-less, but I have a problem with the resizing feature, as follows: when I resize the entire form, I see that the program shows the graphics then inmediately clear it. When I stop the mouse movement (without to release the mouse button) the graphics disappears!?!?
I made a small test environment to demo the bug:
Using VS2005, creating a new C# Windows Forms app, adding only a PictureBox to the form.
Setting the PictureBox's anchor to left, top, right and bottom. Add two event handler, the Resize to the PictureBox, and the Paint to the Form.
namespace PictureBox_Resize {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
Ellipse_Area = this.pictureBox1.Size;
}
private Pen penBlack = new Pen(Color.Black, 1.0f);
private Size Ellipse_Area;
private void Form1_Paint(object sender, PaintEventArgs e) {
Graphics g = this.pictureBox1.CreateGraphics();
g.DrawEllipse(penBlack, 0, 0, Ellipse_Area.Width, Ellipse_Area.Height);
}
private void pictureBox1_Resize(object sender, EventArgs e) {
Control control = (Control)sender;
Ellipse_Area = control.Size;
this.pictureBox1.Invalidate();
}
}
}
This small app shows the problem. It only draws an ellipse, but of course my drawing code is much more complicated one...
Any idea why the ellipse disappears when I resize the Form????
Why are you using a PictureBox? I would create a UserControl for your chart and draw the ellipse in its Paint method, just using its current size. In its constructor, set it up for double buffering and all painting in the paint method.
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
As far as I remember from my C++ days - where I did loads of such image-stuff - you need to call the repaint method - or override it to fit it for your behaviour.
I have derived a TabControl with the express purpose of enabling double buffering, except nothing is working as expected. Here is the TabControl code:
class DoubleBufferedTabControl : TabControl
{
public DoubleBufferedTabControl() : base()
{
this.DoubleBuffered = true;
this.SetStyle
(
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.SupportsTransparentBackColor,
false
);
}
}
This Tabcontrol is then set with it's draw mode as 'OwnerDrawnFixed' so i can changed the colours. Here is the custom drawing method:
private void Navigation_PageContent_DrawItem(object sender, DrawItemEventArgs e)
{
//Structure.
Graphics g = e.Graphics;
TabControl t = (TabControl)sender;
TabPage CurrentPage = t.TabPages[e.Index];
//Get the current tab
Rectangle CurrentTabRect = t.GetTabRect(e.Index);
//Get the last tab.
Rectangle LastTab = t.GetTabRect(t.TabPages.Count - 1);
//Main background rectangle.
Rectangle BackgroundRect = new Rectangle(LastTab.Width, t.Bounds.Y - 4, t.Width - (LastTab.Width), t.Height);
//Tab background rectangle.
Rectangle TabBackgroundRect = new Rectangle(0, LastTab.Y + LastTab.Height, LastTab.Width, t.Bounds.Height - (LastTab.Y + LastTab.Height));
//Set anitialiasing for the text.
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
//String format for the text.
StringFormat StringFormat = new StringFormat();
StringFormat.Alignment = StringAlignment.Center;
StringFormat.LineAlignment = StringAlignment.Center;
//Fill the background.
g.FillRectangle(Brushes.LightGray, BackgroundRect);
g.FillRectangle(Brushes.Bisque, TabBackgroundRect);
//Draw the selected tab.
if(e.State == DrawItemState.Selected)
{
g.FillRectangle(Brushes.White, e.Bounds);
Rectangle SelectedTabOutline = new Rectangle(e.Bounds.X + 2, e.Bounds.Y + 2, e.Bounds.Width, e.Bounds.Height - 4);
g.DrawRectangle(new Pen(Brushes.LightGray, 4f), SelectedTabOutline);
g.DrawString(CurrentPage.Text, new Font("Arial", 12f, FontStyle.Bold, GraphicsUnit.Point), new SolidBrush(Color.FromArgb(70, 70, 70)), CurrentTabRect, StringFormat);
}
else
{
g.FillRectangle(new SolidBrush(Color.FromArgb(230, 230, 230)), e.Bounds);
g.DrawString(CurrentPage.Text, new Font("Arial", 12f, FontStyle.Regular, GraphicsUnit.Point), Brushes.Gray, CurrentTabRect, StringFormat);
}
}
All to no avail however, as this control is not double buffered and still flickers when resized.
Any ideas?
If you read the documentation, it says, "This member is not meaningful for this control." If you want the control to be drawn utilizing double-buffering, you'll have to implement it yourself. Besides the fact that if you owner-draw the control, you would have to implement double-buffering yourself anyhow.
First of all, you can get rid of your TabControl code—you turn on buffering, and then immediately turn it off, so it's not actually doing anything useful.
Part of your problem is that you're trying to paint just part of the TabControl.
The easiest solution that gives about a 90% solution (it's still possible to get a flicker) is to add this to your form class:
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
If you want to be very sure of getting no flicker, you'll need to draw the entire TabControl yourself, and make sure to ignore background painting requests.
Edit: Note that this will only work in XP and later.
I've had problems with double buffering on controls in the past and the only way to stop the flicker was to ensure the inherited OnPaintBackground method was not being called. (See code below) You will also need to ensure the entire backgound is painted during your paint call.
protected override void OnPaintBackground( PaintEventArgs pevent )
{
//do not call base - I don't want the background re-painted!
}
Not sure, but you might try double-buffering the control that contains the tab control.
I looked around quite a bit, tried your code and whatever else I could think of, but I don't see a way to get rid of the flicker. Unfortunately, in my tests even a regular (non-owner-drawn) tab control flickers during resizing.
For what it's worth, turning off "Show window contents while dragging" will fix it, but I realize that may not be helpful.
I think it doesn't work because you are disabling double buffering!
All this.DoubleBuffered = true does is set ControlStyles.OptimizedDoubleBuffer to true. Since you are disabling that flag in the next line of your program, you are really doing nothing. Remove ControlStyles.OptimizedDoubleBuffer (and perhaps ControlStyles.AllPaintingInWmPaint) and it should work for you.