Draw Border around ListBox - c#

How would I go about drawing a border with a specified width and color around a listbox?
Can this be done without overriding the OnPaint method?

Following Neutone's suggestion, here is a handy function to add and remove a Panel-based border around any control, even if it is nested..
Simply pass in the Color and size you want, and if you want a BorderStyle. To remove it again pass in Color.Transparent!
void setBorder(Control ctl, Color col, int width, BorderStyle style)
{
if (col == Color.Transparent)
{
Panel pan = ctl.Parent as Panel;
if (pan == null) { throw new Exception("control not in border panel!");}
ctl.Location = new Point(pan.Left + width, pan.Top + width);
ctl.Parent = pan.Parent;
pan.Dispose();
}
else
{
Panel pan = new Panel();
pan.BorderStyle = style;
pan.Size = new Size(ctl.Width + width * 2, ctl.Height + width * 2);
pan.Location = new Point(ctl.Left - width, ctl.Top - width);
pan.BackColor = col;
pan.Parent = ctl.Parent;
ctl.Parent = pan;
ctl.Location = new Point(width, width);
}
}

You can place a list box within a panel and have the panel serve as a border. The panel backcolor can be used to create a colored border. This doesn't require much code. Having a colored border around a form component can be an effective way of conveying status.

The problem with the ListBox control is that it does not raise the OnPaint method so you can not use it to draw a border around the control.
There are two methods to paint a custom border around the ListBox:
Use SetStyle(ControlStyles.UserPaint, True) in the constructor, then you can use the OnPaint method to draw the border.
Override WndProc method that handles operating system messages identified in the Message structure.
I used the last method to paint a custom border around the control, this will eliminate the need to use a Panel to provide a custom border for the ListBox.
public partial class MyListBox : ListBox
{
public MyListBox()
{
// SetStyle(ControlStyles.UserPaint, True)
BorderStyle = BorderStyle.None;
}
protected override void WndProc(ref Message m)
{
base.WndProc(m);
var switchExpr = m.Msg;
switch (switchExpr)
{
case 0xF:
{
Graphics g = this.CreateGraphics;
g.SmoothingMode = Drawing2D.SmoothingMode.Default;
int borderWidth = 4;
Rectangle rect = ClientRectangle;
using (var p = new Pen(Color.Red, borderWidth) { Alignment = Drawing2D.PenAlignment.Center })
{
g.DrawRectangle(p, rect);
}
break;
}
default:
{
break;
}
}
}
}

Related

Datagridview to draw wafer map

Currently I'm using C# datagridview to draw a wafer map which contains >500 rows and >700 columns.
However, there are a few issues:
Slow performance. As i need to adjust the column width, I have to loop through and assign individually.
for (int i = 0; i < this.dgvCompleteMapGrid.Columns.Count; i++)
{
this.dgvCompleteMapGrid.Columns[i].Width = 8;
}
I only need to draw the cell border for cells with value as a wafer map has an almost round shape. I'm using cellpainting event:
if (e.Value != null) {
if (e.Value.ToString() == "")
{
e.AdvancedBorderStyle.All = DataGridViewAdvancedCellBorderStyle.None;
}
else
{
if (e.Value.ToString() == "1")
{
e.CellStyle.BackColor = Color.Lime;
}
else
{
e.CellStyle.BackColor = Color.Red;
}
//check if the top border set None for neighbor empty value cell
if (e.AdvancedBorderStyle.Top.ToString() == "None")
{
e.AdvancedBorderStyle.Top = DataGridViewAdvancedCellBorderStyle.Single;
}
//repeat the same for left right and bottom
}
}
However, it seems like it will draw multiple border duplicate for most of the cells.
Is Datagridview recommended? I tried to draw rectangle on a panel and the performance is even worse.
Here's my Picturebox that allows for pixellated resizing (versus interpolated). E.g. similar to zooming in on a Microsoft Paint picture.
using System.Windows.Forms;
namespace WireWorld
{
public class ZoomablePicturebox : PictureBox
{
public ZoomablePicturebox()
{
}
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
pe.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
pe.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
base.OnPaint(pe);
}
}
}
To generate the bitmap, I use something like this:
// Pick width and height for your case
Bitmap MyBitmap = new Bitmap(width, height);
using (var g = Graphics.FromImage(retVal))
g.Clear(BackColor); // Pick a background fill color
// Draw your points, whatever your loop needs to be and your point definition is
foreach (MyPointDef point in _Points)
{
MyBitmap.SetPixel(point.X, point.Y, point.Color);
}
Then I put a picture box in a panel on my form. The panel then provides the scrolling. I can set the picture and zoom as follows:
canvasPB.SizeMode = PictureBoxSizeMode.Zoom; // Ensures picture is resized as we resize picturebox
canvasPB.Image = MyBitmap;
canvasPB.Height = canvasPB.Image.Height * Zoom; // Zoom is 1, 2, 3, etc, for 1X, 2X, ...
canvasPB.Width = canvasPB.Image.Width * Zoom;
canvasPB being the name of the Picturebox on my form I'm trying to use as my canvas.

How to disable a User Control to draw it's background (or Region)

My question : How to disable a User Control to draw it's background (or Region)
Note : I already tried to override and empty OnPaintBackground or set background color to transparent.
I'm trying to bypass winform paint for my custom user controls in a custom container.
For that I thought to give a try to this : Beginners-Starting-a-2D-Game-with-GDIplus
My setup is :
A Form containing:
A User control (DrawingBoard)
A Container with elements I can drag and drop to this DrawingBoard (it's a listbox).
My render loop is inside the DrawingBoard with all elements specified in the previous link.
public DrawingBoard()
{
InitializeComponent();
//Resize event are ignored
SetStyle(ControlStyles.FixedHeight, true);
SetStyle(ControlStyles.FixedWidth, true);
SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, true);// True is better
SetStyle(System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, true); // True is better
// Disable the on built PAINT event. We dont need it with a renderloop.
// The form will no longer refresh itself
// we will raise the paint event ourselves from our renderloop.
SetStyle(System.Windows.Forms.ControlStyles.UserPaint, false); // False is better
}
#region GDI+ RENDERING
public Timer t = new Timer();
//This is your BackBuffer, a Bitmap:
Bitmap B_BUFFER = null;
//This is the surface that allows you to draw on your backbuffer bitmap.
Graphics G_BUFFER = null;
//This is the surface you will use to draw your backbuffer to your display.
Graphics G_TARGET = null;
Size DisplaySize = new Size(1120, 630);
bool Antialiasing = false;
const int MS_REDRAW = 32;
public void GDIInit()
{
B_BUFFER = new Bitmap(DisplaySize.Width, DisplaySize.Height);
G_BUFFER = Graphics.FromImage(B_BUFFER); //drawing surface
G_TARGET = CreateGraphics();
// Configure the display (target) graphics for the fastest rendering.
G_TARGET.CompositingMode = CompositingMode.SourceCopy;
G_TARGET.CompositingQuality = CompositingQuality.AssumeLinear;
G_TARGET.SmoothingMode = SmoothingMode.None;
G_TARGET.InterpolationMode = InterpolationMode.NearestNeighbor;
G_TARGET.TextRenderingHint = TextRenderingHint.SystemDefault;
G_TARGET.PixelOffsetMode = PixelOffsetMode.HighSpeed;
// Configure the backbuffer's drawing surface for optimal rendering with optional
// antialiasing for Text and Polygon Shapes
//Antialiasing is a boolean that tells us weather to enable antialiasing.
//It is declared somewhere else
if (Antialiasing)
{
G_BUFFER.SmoothingMode = SmoothingMode.AntiAlias;
G_BUFFER.TextRenderingHint = TextRenderingHint.AntiAlias;
}
else
{
// No Text or Polygon smoothing is applied by default
G_BUFFER.CompositingMode = CompositingMode.SourceOver;
G_BUFFER.CompositingQuality = CompositingQuality.HighSpeed;
G_BUFFER.InterpolationMode = InterpolationMode.Low;
G_BUFFER.PixelOffsetMode = PixelOffsetMode.Half;
}
t.Tick += RenderingLoop;
t.Interval = MS_REDRAW;
t.Start();
}
void RenderingLoop(object sender, EventArgs e)
{
try
{
G_BUFFER.Clear(Color.DarkSlateGray);
UIPaint(G_BUFFER);
G_TARGET.DrawImageUnscaled(B_BUFFER, 0, 0);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
#endregion
Then my elements get the event fired and try to draw what I would like:
public override void UIPaint(Graphics g)
{
Pen p = new Pen(Color.Blue,4);
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
g.DrawLines(p, new Point[] { new Point(Location.X, Location.Y), new Point(Location.X + Width, Location.Y), new Point(Location.X + Width, Location.Y + Height), new Point(Location.X, Location.Y + Height), new Point(Location.X, Location.Y - 2) });
g.DrawImageUnscaled(GDATA.GetWindowImage(), Location);
}
here is what happening on my DrawingBoard :
As I can't post image ... here is the link : http://s8.postimage.org/iqpxtaoht/Winform.jpg
The background is DarkSlateGray because my G_BUFFER state to clear each tick with that, Ok
The blue rectangle is what I paint, but it get cropped, KO
The Texture is cropped, KO
The region that crop my drawing is the control size.
So from there I've tried everything I could to disable WinForm to make some magic drawing in background. Tried to override and empty everything that got paint/update/refresh/invalidate/validate on Form/DrawingBoard/Elements but nothing allowed me to get my texture or drawing to not get cropped by the control background : (
I also tried to set the background of the Element as transparent and also to set Form.TransparencyKey = blabla with each element BackColor = blabla. But failed each time.
I'm certainly missing something : / But I don't know what.
Why don't you want to draw the background? To avoid problems with flickering in my custom control (derived from class Control), here's what I did:
(1) override OnPaintBackground:
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
(2) Draw in an offscreen Bitmap and then transfer it to the screen:
Bitmap _completeFrame;
protected override void OnPaint(PaintEventArgs pe)
{
DrawFrame(); // draws onto _completeFrame, calling new Bitmap()
// when _completeFrame is null or its Size is wrong
var g = pe.Graphics;
g.DrawImage(_completeFrame, new Point());
}

Custom control in a UserControl does not render correctly

In this picture...
... you can see next to each "Line Color" label there is a colored circle.
The colored circle is, in my project, a Swatch. Here is the entire code file for Swatch:
public class Swatch : System.Windows.Forms.Panel
{
/*private int _Radius = 20;
[System.ComponentModel.Category("Layout")]
public int Radius
{
get { return _Radius; }
set { _Radius = value; }
} */
private System.Drawing.Color _BorderColor = System.Drawing.Color.Transparent;
[System.ComponentModel.Category("Appearance")]
public System.Drawing.Color BorderColor
{
get { return _BorderColor; }
set { _BorderColor = value; }
}
private System.Drawing.Color _FillColor = System.Drawing.Color.Blue;
[System.ComponentModel.Category("Appearance")]
public System.Drawing.Color FillColor
{
get { return _FillColor; }
set { _FillColor = value; }
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
System.Drawing.Rectangle RealRect = new System.Drawing.Rectangle(e.ClipRectangle.Location, e.ClipRectangle.Size);
RealRect.Inflate(-1, -1);
int Radius = Math.Min(RealRect.Size.Height, RealRect.Size.Width);
System.Drawing.Rectangle SqRect = new System.Drawing.Rectangle();
SqRect.Location = RealRect.Location;
SqRect.Size = new System.Drawing.Size(Radius, Radius);
System.Drawing.Drawing2D.CompositingQuality PrevQual = e.Graphics.CompositingQuality;
using (System.Drawing.SolidBrush Back = new System.Drawing.SolidBrush(this.FillColor))
{
using (System.Drawing.Pen Pen = new System.Drawing.Pen(new System.Drawing.SolidBrush(this.BorderColor)))
{
//e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.FillEllipse(Back, SqRect);
e.Graphics.DrawEllipse(Pen, SqRect);
}
}
e.Graphics.CompositingQuality = PrevQual;
}
public Swatch()
{
this.SetStyle(System.Windows.Forms.ControlStyles.UserPaint, true);
this.SetStyle(System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(System.Windows.Forms.ControlStyles.ResizeRedraw, true);
this.SetStyle(System.Windows.Forms.ControlStyles.SupportsTransparentBackColor, true);
this.DoubleBuffered = true;
}
}
Each row is a UserControl which consists of a TableLayoutPanel, labels, a Swatch control, and a NumericUpDown box.
There about 10 rows and they are placed in TableLayoutPanel, which sits inside a TabPage on a tab control. The tab page has AutoScroll set to true so that overflow causes the tab page to scroll.
The problem is that whenever I run the application and scroll up and down, the Swatches (the colored circles) tear and show all sorts of artifacts, as seen in the picture above. I'd like to have clean scrolling with no rendering artifacts.
I've tried using SetStyle (as suggested here Painting problem in windows form) but it has had no effect.
The UserControl (each row) has DoubleBuffered set to true, and that too has had no effect either.
I fear I am missing something rather obvious.
The problem is that you calculate the radius of the circle based on the clipping rectangle. So when the line is only partially visible a bad value is resulted.
You should calculate it based on the real rectangle, the one provided by the base class, and let it being clipped normally.

Change the borderColor of the TextBox

How can I change the BorderColor of the Textbox when a user Clicks on it or focuses on it?
You can handle WM_NCPAINT message of TextBox and draw a border on the non-client area of control if the control has focus. You can use any color to draw border:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ExTextBox : TextBox
{
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
private const int WM_NCPAINT = 0x85;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NCPAINT && this.Focused)
{
var dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
g.DrawRectangle(Pens.Red, 0, 0, Width - 1, Height - 1);
}
}
}
}
Result
The painting of borders while the control is focused is completely flicker-free:
BorderColor property for TextBox
In the current post I just change the border color on focus. You can also add a BorderColor property to the control. Then you can change border-color based on your requirement at design-time or run-time. I've posted a more completed version of TextBox which has BorderColor property:
in the following post:
BorderColor property for TextBox
try this
bool focus = false;
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (focus)
{
textBox1.BorderStyle = BorderStyle.None;
Pen p = new Pen(Color.Red);
Graphics g = e.Graphics;
int variance = 3;
g.DrawRectangle(p, new Rectangle(textBox1.Location.X - variance, textBox1.Location.Y - variance, textBox1.Width + variance, textBox1.Height +variance ));
}
else
{
textBox1.BorderStyle = BorderStyle.FixedSingle;
}
}
private void textBox1_Enter(object sender, EventArgs e)
{
focus = true;
this.Refresh();
}
private void textBox1_Leave(object sender, EventArgs e)
{
focus = false;
this.Refresh();
}
This is an ultimate solution to set the border color of a TextBox:
public class BorderedTextBox : UserControl
{
TextBox textBox;
public BorderedTextBox()
{
textBox = new TextBox()
{
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(-1, -1),
Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
AnchorStyles.Left | AnchorStyles.Right
};
Control container = new ContainerControl()
{
Dock = DockStyle.Fill,
Padding = new Padding(-1)
};
container.Controls.Add(textBox);
this.Controls.Add(container);
DefaultBorderColor = SystemColors.ControlDark;
FocusedBorderColor = Color.Red;
BackColor = DefaultBorderColor;
Padding = new Padding(1);
Size = textBox.Size;
}
public Color DefaultBorderColor { get; set; }
public Color FocusedBorderColor { get; set; }
public override string Text
{
get { return textBox.Text; }
set { textBox.Text = value; }
}
protected override void OnEnter(EventArgs e)
{
BackColor = FocusedBorderColor;
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e)
{
BackColor = DefaultBorderColor;
base.OnLeave(e);
}
protected override void SetBoundsCore(int x, int y,
int width, int height, BoundsSpecified specified)
{
base.SetBoundsCore(x, y, width, textBox.PreferredHeight, specified);
}
}
WinForms was never good at this and it's a bit of a pain.
One way you can try is by embedding a TextBox in a Panel and then manage the drawing based on focus from there:
public class BorderTextBox : Panel {
private Color _NormalBorderColor = Color.Gray;
private Color _FocusBorderColor = Color.Blue;
public TextBox EditBox;
public BorderTextBox() {
this.DoubleBuffered = true;
this.Padding = new Padding(2);
EditBox = new TextBox();
EditBox.AutoSize = false;
EditBox.BorderStyle = BorderStyle.None;
EditBox.Dock = DockStyle.Fill;
EditBox.Enter += new EventHandler(EditBox_Refresh);
EditBox.Leave += new EventHandler(EditBox_Refresh);
EditBox.Resize += new EventHandler(EditBox_Refresh);
this.Controls.Add(EditBox);
}
private void EditBox_Refresh(object sender, EventArgs e) {
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.Clear(SystemColors.Window);
using (Pen borderPen = new Pen(this.EditBox.Focused ? _FocusBorderColor : _NormalBorderColor)) {
e.Graphics.DrawRectangle(borderPen, new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1));
}
base.OnPaint(e);
}
}
Using OnPaint to draw a custom border on your controls is fine. But know how to use OnPaint to keep efficiency up, and render time to a minimum. Read this if you are experiencing a laggy GUI while using custom paint routines: What is the right way to use OnPaint in .Net applications?
Because the accepted answer of PraVn may seem simple, but is actually inefficient. Using a custom control, like the ones posted in the answers above is way better.
Maybe the performance is not an issue in your application, because it is small, but for larger applications with a lot of custom OnPaint routines it is a wrong approach to use the way PraVn showed.
set Text box Border style to None
then write this code to container form "paint" event
private void Form1_Paint(object sender, PaintEventArgs e)
{
System.Drawing.Rectangle rect = new Rectangle(TextBox1.Location.X,
TextBox1.Location.Y, TextBox1.ClientSize.Width, TextBox1.ClientSize.Height);
rect.Inflate(1, 1); // border thickness
System.Windows.Forms.ControlPaint.DrawBorder(e.Graphics, rect,
Color.DeepSkyBlue, ButtonBorderStyle.Solid);
}
With PictureBox1
.Visible = False
.Width = TextBox1.Width + 4
.Height = TextBox1.Height + 4
.Left = TextBox1.Left - 2
.Top = TextBox1.Top - 2
.SendToBack()
.Visible = True
End With
Here is my complete Flat TextBox control that supports themes including custom border colors in normal and focused states.
The control uses the same concept mentioned by Reza Aghaei https://stackoverflow.com/a/38405319/5514131 ,however the FlatTextBox control is more customizable and flicker-free.
The control handles the WM_NCPAINT window message in a better way to help eliminate flicker.
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WindowMessage.WM_NCPAINT AndAlso _drawBorder AndAlso Not DesignMode Then 'Draw the control border
Dim w As Integer
Dim h As Integer
Dim clip As Rectangle
Dim hdc As IntPtr
Dim clientRect As RECT = Nothing
GetClientRect(Handle, clientRect)
Dim windowRect As RECT = Nothing
GetWindowRect(Handle, windowRect)
w = windowRect.Right - windowRect.Left
h = windowRect.Bottom - windowRect.Top
clip = New Rectangle(CInt((w - clientRect.Right) / 2), CInt((h - clientRect.Bottom) / 2), clientRect.Right, clientRect.Bottom)
hdc = GetWindowDC(Handle)
Using g As Graphics = Graphics.FromHdc(hdc)
g.SetClip(clip, CombineMode.Exclude)
Using sb = New SolidBrush(BackColor)
g.FillRectangle(sb, 0, 0, w, h)
End Using
Using p = New Pen(If(Focused, _borderActiveColor, _borderNormalColor), BORDER_WIDTH)
g.DrawRectangle(p, 0, 0, w - 1, h - 1)
End Using
End Using
ReleaseDC(Handle, hdc)
Return
End If
MyBase.WndProc(m)
End Sub
I have removed the default BorderStyle property and replaced it with a simple boolean DrawBorder property that controls whether to draw a border around the control or not.
Use the BorderNormalColor property to specify the border color when the TextBox has no focus, and the BorderActiveColor property to specify the border color when the control receives focus.
The FlatTextBox comes with two themes VS2019 Dark and VS2019 Light, use the Theme property to switch between them.
Complete FlatTextBox control code written in VB.NET
https://gist.github.com/ahmedosama007/37fe2004183a51a4ea0b4a6dcb554176

C# Win Forms tab Control tab width error

I have custom tab control where OnPaint method is override.
Then strange growth of tabs occurs. Tabs getting bigger (padding getting bigger) and they width depends on length of the text.
When I use default Tab Control - padding is OK. How to avoid this situation when I use UserPaint?
partial class Tab : TabControl
{
public Tab()
{
InitializeComponent();
Init();
}
private void Init()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void OnPaint(PaintEventArgs e)
{
DrawTabPane(e.Graphics);
}
private void DrawTabPane(Graphics g)
{
if (!Visible)
return;
// here we draw our tabs
for (int i = 0; i < this.TabCount; i++)
DrawTab(g, this.TabPages[i], i);
}
internal void DrawTab(Graphics g, TabPage tabPage, int nIndex)
{
Rectangle recBounds = this.GetTabRect(nIndex);
RectangleF tabTextArea = recBounds;
Point[] pt = new Point[4];
pt[0] = new Point(recBounds.Left + 1, recBounds.Bottom);
pt[1] = new Point(recBounds.Left + 1, recBounds.Top + 1);
pt[2] = new Point(recBounds.Right - 1, recBounds.Top + 1);
pt[3] = new Point(recBounds.Right - 1, recBounds.Bottom);
Brush br = new SolidBrush(clr_tab_norm);
g.FillPolygon(br, pt);
br.Dispose();
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
br = new SolidBrush(clr_txt);
g.DrawString(tabPage.Text, Font, br, tabTextArea, stringFormat);
}
}
Turning on ControlStyles.UserPaint for controls that are built into Windows, like TabControl, is not the proper thing to do. I assume the bug is in GetTabRect(), it isn't visible in the snippet.
Instead, you should use the TabControl.DrawMode property and implement the DrawItem event. There's a good example in the MSDN Library.
It would appear from the image that your code is setting the size of the tabs to be wider than they need to be. The extra padding is present in all your tabs but it is just more visible in the tabs with longer text.
I can't be sure why this is but I'd guess that the code to calculate the size of the tabs (based on font metrics) is using a different font from that used to draw the tabs.

Categories