A problem with overriding OnPaint when DoubleBuffered set to true - c#

I have created a custom control which derives from Panel. I use it to display an Image using BackgroundImage property. I override the OnClick method and set isSelected to true then call Invalidate method and draw a rectangle in overrided OnPaint.
Everything goes just fine until I set DoubleBuffered to true. The rectangle is drawn and then it is erased and I can't get why this is happening.
public CustomControl()
: base()
{
base.DoubleBuffered = true;
base.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
PaintSelection();
}
private void PaintSelection()
{
if (isSelected)
{
Graphics graphics = CreateGraphics();
graphics.DrawRectangle(SelectionPen, DisplayRectangle.Left, DisplayRectangle.Top, DisplayRectangle.Width - 1, DisplayRectangle.Height - 1);
}
}

In your PaintSelection, you should not create a new Graphics object, because that object will draw to the front buffer, which is then promptly overdrawn by the contents of the back buffer.
Paint to the Graphics passed in the PaintEventArgs instead:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
PaintSelection(pe.Graphics);
}
private void PaintSelection(Graphics graphics)
{
if (isSelected)
{
graphics.DrawRectangle(SelectionPen, DisplayRectangle.Left, DisplayRectangle.Top, DisplayRectangle.Width - 1, DisplayRectangle.Height - 1);
}
}

Related

Transparent picturebox flickers

So I'm coding a project that is Plants vs. Zombies made of pure c# using no game engines and here I have a graphic problem.
I need to render a transparent picturebox over another transparent picturebox and I had to define a new control that is really transparent and everything goes fine with the transparency aspect but here is a problem:
Flicker :|
I have so much of them because of the RecreateHandle(); method I use when I change the image of my control to make animations and when it moves to have the real transparency.
Here is my code I wonder if any one could help !
public class TransparentControl : Control
{
private Image _image;
public TransparentControl()
{
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.SupportsTransparentBackColor, true);
base.BackColor = Color.FromArgb(0, 0, 0, 0);
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnMove(EventArgs e)
{
//RecreateHandle();
}
protected override void OnPaint(PaintEventArgs e)
{
if (_image != null)
{
e.Graphics.DrawImage(_image, (Width / 2) - (_image.Width / 2), (Height / 2) - (_image.Height / 2));
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//Do not paint background
}
//Hack
public void Redraw()
{
//RecreateHandle();
}
public Image Image
{
get
{
return _image;
}
set
{
_image = value;
//RecreateHandle();
}
}
}
One way to reduce flickering is to create two different bitmaps. One to draw to, and one to display.
Image BackBuffer;
Image BrontBuffer;
private void RotateImages()
{
lock (this.BackBuffer)
{
var temp = this.BackBuffer;
this.BackBuffer = this.FrontBuffer;
this.FrontBuffer = temp;
}
}
Draw everything into your BackBuffer, then show the FrontBuffer.
Note that you should declare the FrontBuffer with the same width/height values as the BackBuffer, in pretty much exactly the same spot that you declare the BackBuffer.
Use the method RotateImages() immediately after (before should work, too) you display the front buffer.

How do I set the background image to None, or some other default value, in an image button?

I'm building off this question and creating a simple ImageButton class that represents a button containing only an image. I implemented (at least I believe I did) the suggestions in this answer, but I'm still getting an exception with this code:
public class ImageButton : Button
{
// Overrides the property
public override Image BackgroundImage
{
get { return base.BackgroundImage; }
set
{
base.BackgroundImage = value;
if (value != null) this.Size = value.Size;
}
}
// Shadows the property (note the -new- keyword)
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Size Size
{
get
{
return base.Size;
}
set
{
base.Size = value;
}
}
public ImageButton()
{
this.BackgroundImage = base.BackgroundImage;
this.BackgroundImageChanged += new EventHandler(ImageButton_BackgroundImageChanged);
}
void ImageButton_BackgroundImageChanged(object sender, EventArgs e)
{
this.Size = this.BackgroundImage.Size;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(BackgroundImage, 0, 0); // <-- Error occurs here
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// Do nothing
}
}
When I try to add this control to the designer, I get
The control ImageButton has thrown an unhandled exception in the
designer and has been disabled.
Exception: Value cannot be null. Parameter name: image
Stack trace: ImageButton.OnPaint(PaintEventArgs e) in
ImageButton.cs:line48
Line 48 is this line:
e.Graphics.DrawImage(BackgroundImage, 0, 0);
I realise that this error is thrown because BackgroundImage is not set to a value, but I'm unsure how to do so in the code. In the actual application, this class will never be added to the designer, but rather added programmatically. How can I fix this exception?
this.BackgroundImage = base.BackgroundImage;
Yes, sure, guaranteed exception. You hope that somebody has set the BackgroundImage property before the constructor runs. That's not possible, the constructor runs before any property on the control can be set.
The next thing that goes wrong is that the Paint event will be raised in the designer as well. Which happens immediately after you drop the control on the form. That's a Kaboom, neither the user nor your code has given the BackgroundImage property a value yet. So just fix the method:
protected override void OnPaint(PaintEventArgs e)
{
if (BackgroundImage != null) e.Graphics.DrawImage(BackgroundImage, 0, 0);
}
to remove the background image just write:
this.BackgroundImage = null;
I don't know C#, but my guess for this would be to have a transparent 1x1px image stored somewhere and assign that on init. That way, there'd be an image but nothing will be seen.
Or you could try adding some check around line 48, like this:
if(BackgroundImage!=null){
e.Graphics.DrawImage(BackGroundImage,0,0);
}
Maybe add something like this:
else{ //No background image
//Draw some dummy/placeholder
}
Yet another method would be to encapsulate the DrawImage call within a try{} block and catch the resulting expression. You should be able to safely use an empty catch{}block if it's that one.
What if you don't draw when there is no image set, also call base in the beginning of the function.
base.OnPaint(e);
There is a lot of redundant code in your implementation: try this revised version...
public class ImageButton : Button
{
public ImageButton()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs pevent)
{
//base.OnPaint(pevent); <-- NO LONGER NEEDED (WE DO NOT WANT THE SYSTEM TO PAINT THE BUTTON);
if (this.BackgroundImage != null)
{
pevent.Graphics.DrawImage(this.BackgroundImage, 0, 0);
}
else
{
//Just fill in black (for example)
pevent.Graphics.FillRectangle(new SolidBrush(Color.Black), this.ClientRectangle);
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
base.OnPaintBackground(pevent);
pevent.Graphics.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
}
public override Image BackgroundImage
{
get
{
return base.BackgroundImage;
}
set
{
base.BackgroundImage = value;
this.Size = base.BackgroundImage.Size;
this.Refresh();
}
}
}

Coloring a margin on Avalonedit

I have added a margin (For adding breakpoints) to the left side of my TextEditor in the following manner:
public partial class LogicSimViewCodeWPFCtrl : UserControl
{
private class BreakPointMargin : AbstractMargin
{
private const int margin = 20;
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
protected override Size MeasureOverride(Size availableSize)
{
return new Size(margin, 0);
}
}
}
private void LogicCodeInit()
{
try
{
TxtEditCodeViewer.TextArea.LeftMargins.Insert(0, new BreakPointMargin());
...
The margin is added successfully but now I'd like to color the background of the margin. How can I accomplish this?
https://web.archive.org/web/20190716171503/http://community.sharpdevelop.net/forums/t/16047.aspx
You would have to override OnRender:
protected override void OnRender(DrawingContext drawingContext)
{
Size renderSize = this.RenderSize;
drawingContext.DrawRectangle(SystemColors.ControlBrush, null,
new Rect(0, 0, renderSize.Width, renderSize.Height));
Also, you aren't required to derived from AbstractMargin - you can use any WPF control you want. AbstractMargin just provides the TextView and Document properties and keeps them up to date. If you don't need those or can implement them yourself, you can just use another base class.

Transparent label control is not refreshing properly

I've created a transparent label control and the transparency works great. However, I find when I update the control's Text field, the original Text doesn't clear before it paints the new text. So if I change the control's Text field several times, it soon becomes unreadable.
Any clues? Thanks!
public partial class TransLabel : Label
{
public TransLabel()
{
InitializeComponent();
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
this.Font = new Font("Franklin Gothic Book", 12f, FontStyle.Regular);
this.ForeColor = Color.White;
this.BackColor = Color.Transparent;
}
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
this.Invalidate(); // seems to have no effect
this.Refresh(); // seems to have no effect
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//do nothing
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
}
Try changing your Text property to this:
public override string Text {
get {
return base.Text;
}
set {
base.Text = value;
if (this.Parent != null)
this.Parent.Invalidate(this.Bounds, false);
}
}
Since WinForms doesn't have true support for transparency, I think you have to invalidate the parent container.
Also, when inheriting a control, you usually don't have an InitializeComponent() method.

graphics panel in c#

Instead of setting image using 'background' property, I would like to draw the image using Graphics class on a panel. How can I do it in C#.Net?
you can try following piece of code.
public class ImagePanel:Panel
{
private Image image;
public Image Image
{
get { return image; }
set
{
image = value;
Refresh();
}
}
protected override void OnPaint(PaintEventArgs e)
{
if(Image!=null)
{
e.Graphics.DrawImage(this.Image,Point.Empty);
}
base.OnPaint(e);
}
}
Make use of System.Drawing.Graphics class to draw the things.
Details : http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx related to drawing
example : http://www.techotopia.com/index.php/Drawing_Graphics_in_C_Sharp

Categories

Resources