I have a winforms app which I have placed my own custom labels on using the following class
public class LabelWithBorder : Label
{
protected override void OnPaint(PaintEventArgs e)
{
ColorMe(e);
}
private void ColorMe(PaintEventArgs e)
{
Color myColor = Color.FromArgb(104, 195, 198);
Pen myPen = new Pen(myColor, 1);
e.Graphics.DrawRectangle(myPen,
e.ClipRectangle.Left,
e.ClipRectangle.Top,
e.ClipRectangle.Width - 1,
e.ClipRectangle.Height - 1);
base.OnPaint(e);
}
}
The resultant LabelWithBorder simply has a border with colors to match my clients own literature / website etc. The picture below shows (on the left) how it should / and does initially look like.
However the issue I have, is that whenever one window is dragged over another, the labels become distorted as per the picture on the right.
Can anyone advice what is causing this distortion and how I should rectify it.
This works and code is simple:
private Color myColor = Color.FromArgb(104, 195, 198);
protected override void OnPaint(PaintEventArgs e)
{
ControlPaint.DrawBorder(e.Graphics, ClientRectangle, myColor, ButtonBorderStyle.Solid);
base.OnPaint(e);
}
Try using double buffering as shown here:
https://web.archive.org/web/20140811193726/http://bobpowell.net/doublebuffer.aspx
Related
I've been asked to carry out some modernisation work on a C# WinForms application, and I'm using VS2019 with C#.Net 4.7.2.
The project owner wants to change the border colour of all the legacy Windows controls that were used originally. The initial idea was - using the MetroIT framework - to keep the original Windows controls in the Form Designer, but override them by defining new classes which extend the MetroIT equivalents but changing the class type of the declarations in the Designer's InitializeComponent() method.
That approach doesn't really work as a "drop-in" replacement because the MetroIT controls tend to subclass Control whereas existing code expects the legacy Windows properties/methods to be available.
Instead, therefore, I went down the route of trying to override the OnPaint method. That worked fine for CheckBox and RadioButton, but I'm now struggling to get it to work for a ListBox. The following is as far as I've got; it certainly isn't correct, as I'll explain, but it feels sort of close ?
public class MyListBox : ListBox
{
public MyListBox()
{
SetStyle(ControlStyles.UserPaint, true);
this.DrawMode = DrawMode.OwnerDrawVariable;
BorderStyle = BorderStyle.None;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush textBrush = new SolidBrush(Color.Black);
float y = 1;
foreach (String strItem in Items)
{
e.Graphics.DrawString(strItem, DefaultFont, textBrush, 1, y);
y += DefaultFont.Height;
}
Rectangle borderRectangle = this.ClientRectangle;
ControlPaint.DrawBorder(e.Graphics, borderRectangle, Color.Blue, ButtonBorderStyle.Solid);
}
}
Initially, with no data in the ListBox, the control is drawn correctly.
However, as soon as a scroll bar appears, the base Windows code draws the scroll bar over the top of my border instead of just inside it (whereas an unmodified ListBox re-draws the border around the top, bottom and right-hand side of the scroll bar):
Is there a way to change my code so that the border draws itself around the edges of the scroll bar instead of excluding it ?
The other way in which my code is wrong is that, as soon as I start to scroll, the border painting code is applying itself to some of the ListBox items:
Is there a way I can complete this, or am I wasting my time because the base scroll bar cannot be modified ?
Thanks
EDIT 1:
Further to the suggestions of #GuidoG:
Yes, to be clear, I really only want to change the border to blue. Happy to leave the listbox items as they would ordinarily be painted WITHOUT any border.
So, to achieve that, I have removed your suggested code to do DrawItem, and now I only have the code to paint the border - but clearly I must be doing something wrong, because the listbox has now reverted to looking like the standard one with the black border.
So here's what I have now:
In my Dialog.Designer.cs InitializeComponent():
this.FieldsListBox = new System.Windows.Forms.ListBox();
this.FieldsListBox.FormattingEnabled = true;
this.FieldsListBox.Location = new System.Drawing.Point(7, 98);
this.FieldsListBox.Name = "FieldsListBox";
this.FieldsListBox.Size = new System.Drawing.Size(188, 121);
this.FieldsListBox.Sorted = true;
this.FieldsListBox.TabIndex = 11;
this.FieldsListBox.SelectedIndexChanged += new System.EventHandler(this.FieldsListBox_SelectedIndexChanged);
In my Dialog.cs Form declaration:
public SelectByAttributesDialog()
{
InitializeComponent();
BlueThemAll(this);
}
private void BlueThemAll(Control parent)
{
foreach (Control item in parent.Controls)
{
Blue(item);
if (item.HasChildren)
BlueThemAll(item);
}
}
private void Blue(Control control)
{
Debug.WriteLine("Blue: " + control.Name.ToString());
Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);
using (Graphics g = control.Parent.CreateGraphics())
{
using (Pen selPen = new Pen(Color.Blue))
{
g.DrawRectangle(selPen, r);
}
}
}
I know that Blue() is being called for all the controls in this dialog, in the sense that the Debug.WriteLine() is outputting every control name, but no borders are being changed to blue (and certainly not the listbox).
I've been away from Windows Form programming for a very long time so I apologise for that, and I'm clearly doing something wrong, but have no idea what.
Solution 1: let the form drawn blue borders around everything:
This means you dont have to subclass any controls, but you put some code on the form that will draw the borders for you around each control.
Here is an example that works for me
// method that draws a blue border around a control
private void Blue(Control control)
{
Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);
using (Graphics g = control.Parent.CreateGraphics())
{
using (Pen selPen = new Pen(Color.Blue))
{
g.DrawRectangle(selPen, r);
}
}
}
// recursive method that finds all controls and call the method Blue on each found control
private void BlueThemAll(Control parent)
{
foreach (Control item in parent.Controls)
{
Blue(item);
if (item.HasChildren)
BlueThemAll(item);
}
}
where to call this ?
// draw the blue borders when the form is resized
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
panel3.Invalidate(); // on panel3 there is a control that moves position because it is anchored to the bottom
Update();
BlueThemAll(this);
}
// draw the borders when the form is moved, this is needed when the form moved off the screen and back
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
BlueThemAll(this);
}
// draw the borders for the first time
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
BlueThemAll(this);
}
If you also want blue borders around each item of the listbox:
To get some border around each item in your listbox use the DrawItem event in stead of the paint event, maybe this link could help.
I modified the code to get it drawing blue borders
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
listBox1.DrawItem += ListBox1_DrawItem;
private void ListBox1_DrawItem(object sender, DrawItemEventArgs e)
{
try
{
e.DrawBackground();
Brush myBrush = new SolidBrush(e.ForeColor);
Rectangle r = new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width - 1, e.Bounds.Height);
using (Pen selPen = new Pen(Color.Blue))
{
e.Graphics.DrawRectangle(selPen, r);
}
e.Graphics.DrawString(((ListBox)sender).Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
}
catch { }
}
and it looks like this
Solution 2: Subclassing the listbox:
If you need this in a subclassed listbox, then you can do this.
However, this will only work if the listbox has at least one pixel space left around it, because to get around the scrollbar you have to draw on the parent of the listbox, not on the listbox itself. The scrollbar will always draw itself over anything you draw there.
public class myListBox : ListBox
{
public myListBox(): base()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
BorderStyle = BorderStyle.None;
this.DrawItem += MyListBox_DrawItem;
}
private void MyListBox_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
Brush myBrush = new SolidBrush(e.ForeColor);
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
Rectangle r = new Rectangle(this.Left - 1, this.Top - 1, this.Width + 2, this.Height + 2);
using (Graphics g = this.Parent.CreateGraphics())
{
ControlPaint.DrawBorder(g, r, Color.Blue, ButtonBorderStyle.Solid);
}
}
}
and this will look like so
I created Panel class "GpanelBorder" which draw border in custom panel with code:
namespace GetterControlsLibary
{
public class GpanelBorder : Panel
{
private Color colorBorder;
public GpanelBorder()
{
SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(
new Pen(
new SolidBrush(colorBorder), 8),
e.ClipRectangle);
}
public Color BorderColor
{
get
{
return colorBorder;
}
set
{
colorBorder = value;
}
}
}
}
Works fine but when i in design mode, mouse click inside panel and move mouse or drag other control over this panel, artifacts are created (picture below)
How to fix it?
The .ClipRectangle parameter does NOT necessarily represent the entire area of your control to be painted within. It may represent a SMALLER portion of your control indicating just that portion needs to be repainted. You can use the "clip rectangle" to redraw only a portion of your control in situations where it would be too costly to compute and repaint the entire control. If that situation doesn't apply to you, then use the ClientRectangle to get the bounds of your entire control and use that to draw your border. Also, you are LEAKING a PEN and a SOLIDBRUSH. You need to .Dispose() of those resources when you're done with them. This is best done with a using block:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (SolidBrush sb = new SolidBrush(colorBorder), 8))
{
using (Pen p = new Pen(sb))
{
e.Graphics.DrawRectangle(p, this.ClientRectangle);
}
}
}
You may need to create a new Rectangle based on ClientRectangle and adjust that to your liking before drawing.
Thanks, Works great!
but corectly code is
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (SolidBrush sb = new SolidBrush(colorBorder))
{
using (Pen p = new Pen(colorBorder, 2))
{
e.Graphics.DrawRectangle(p, this.ClientRectangle);
}
}
}
I'm trying to understand the graphics, and in the Graphics.FromImage documentation, it has this as an example:
private void FromImageImage(PaintEventArgs e)
{
// Create image.
Image imageFile = Image.FromFile("SampImag.jpg");
// Create graphics object for alteration.
Graphics newGraphics = Graphics.FromImage(imageFile);
// Alter image.
newGraphics.FillRectangle(new SolidBrush(Color.Black), 100, 50, 100, 100);
// Draw image to screen.
e.Graphics.DrawImage(imageFile, new PointF(0.0F, 0.0F));
// Dispose of graphics object.
newGraphics.Dispose();
}
I'm new to C# and Windows Forms and am struggling to understand how this all fits together. How is this code run, say when the form first loads or when a button is pressed?
Maybe this will help. I have an example of both drawing on Paint events but also drawing on top of an existing Image. I created a form with two picture boxes. One for each case. pictureBox1 has an event handler for the .Paint event, while pictureBox2 is only drawn when a button is pressed.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
pictureBox1.BackColor=Color.Black;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
// The code below will draw on the surface of pictureBox1
// It gets triggered automatically by Windows, or by
// calling .Invalidate() or .Refresh() on the picture box.
DrawGraphics(e.Graphics, pictureBox1.ClientRectangle);
}
private void toolStripButton1_Click(object sender, EventArgs e)
{
// The code below will draw on an existing image shown in pictureBox2
var img = new Bitmap(pictureBox2.Image);
var g = Graphics.FromImage(img);
DrawGraphics(g, pictureBox2.ClientRectangle);
pictureBox2.Image=img;
}
void DrawGraphics(Graphics g, Rectangle target)
{
// draw a red rectangle around the moon
g.DrawRectangle(Pens.Red, 130, 69, 8, 8);
}
}
So when you launch the application a red rectangle appears on the left only, because the button hasn't been pressed yet.
and when the button is pressed, the red rectangle appears on top of the image displayed in pictureBox2.
Nothing dramatic, but it does the job. So depending on the mode of operation you need (user graphics, or image annotations) use the example code to understand how to do it.
I have developed an application in VB.NET that I'm now moving over to C#.
Everything is going smooth so far, but I'm facing one problem.
I have a pictureBox that has a picture in it. Over this picture box I want a gradient to be that goes from transparent at top down to the color "control" to blend in with the forms background color. I have allready done this in VB.net which works, but when im trying to do this in C# the gradient seems to be drawn, but behind the picture.
Here is what i have tried:
private void PictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Color top = Color.Transparent;
Color bottom = Color.FromKnownColor(KnownColor.Control);
GradientPictureBox(top, bottom, ref PictureBox1, e);
}
public void GradientPictureBox(Color topColor, Color bottomColor, ref PictureBox PictureBox1, System.Windows.Forms.PaintEventArgs e)
{
LinearGradientMode direction = LinearGradientMode.Vertical;
LinearGradientBrush brush = new LinearGradientBrush(PictureBox1.DisplayRectangle, topColor, bottomColor, direction);
e.Graphics.FillRectangle(brush, PictureBox1.DisplayRectangle);
brush.Dispose();
}
However this does in fact seem to work, but again it paints the gradient behind the picture. In VB.net it painted it on top of the picture without any extra code..
Do i need to add anything extra?
If it matters im coding in C# 2010 express.
The following code does it.
I would perhaps consider making this its own control, and using the following code as its Paint event.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(pictureBox1.Image, 0, 0, pictureBox1.ClientRectangle, GraphicsUnit.Pixel);
Color top = Color.FromArgb(128, Color.Blue);
Color bottom = Color.FromArgb(128, Color.Red);
LinearGradientMode direction = LinearGradientMode.Vertical;
LinearGradientBrush brush = new LinearGradientBrush(pictureBox1.ClientRectangle, top, bottom, direction);
e.Graphics.FillRectangle(brush, pictureBox1.ClientRectangle);
}
This code produces the following image
I am working on a program where I need to draw rectangle graphics onto the form itself, when I click the form. I created code to do this (below), but when I resize the form the rectangles are deleted.
How do I retain the drawn rectangles when the form is resized?
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Graphics g = this.CreateGraphics();
Pen Haitham = new Pen(Color.Silver, 2);
g.FillRectangle(Haitham.Brush, new Rectangle(e.X, e.Y, 50, 50));
}
You could do this instead:
private List<Point> _points = new List<Point>();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
foreach(Point point in _points)
{
using (Pen Haitham = new Pen(Color.Silver, 2))
{
e.Graphics.FillRectangle(Haitham.Brush, new Rectangle(point.X, point.Y, 50, 50));
}
}
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
_points.Add(new Point(e.X, e.Y));
Invalidate(); // could be optimized to invalidate only the future rectangle draw
}
In Windows with Winforms (or native Windows), you supposed to override OnPaint and do almost all your paint logic there.
Note with WPF, it would be different, you would compose a scene adding elements to it (here you would add a Rectangle shape to a Canvas for example).
You must do the "Graphics" things in the "Paint" event. Then you can see your rectangle always, because the event fires whenever windows needed to invalidate the paintings.
Cheers
I'm not too terribly familiar with graphics, but I am assuming that you would need to put all of your drawing objects into a container and have them redrawn when the form is sized. You might need to recall all of your paining objects in the sizeChanged event.