I'm trying to override the OnPaint method of a label in my own custom control with FillPath.
Here is my code for the control:
public partial class GlassLabel : Label
{
public GlassLabel()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = this.CreateGraphics();
GraphicsPath path = new GraphicsPath();
SolidBrush br = new SolidBrush(Color.Black);
path.AddString("LLOOOOLL", new FontFamily("Microsoft Sans Serif"), (int)FontStyle.Regular, 12, new Point(55, 55), StringFormat.GenericDefault);
g.SmoothingMode = SmoothingMode.HighQuality;
g.FillPath(br, path);
}
}
When I run it the text of the label is just the same, it doesn't draw with FillPath.
The reason I'm trying to override the label is I want to use it on Aero glass, which needs FillPath. If I could turn a graphics(the FillPath) in to an object so I can attach events to it, I would like info on that too.
Thanks.
Just tried:
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.FillPath(br, path);
Didnt work.
Do not create a new Graphics object, but use e.Graphics provided in the PaintEventArgs argument.
I am not sure what you are trying to achieve with the GraphicsPath. Probably you could just use TextRenderer instead.
protected override void OnPaint(PaintEventArgs e)
{
TextRenderer.DrawText(e.Graphics, "LLOOOOLL", Font, ClientRectangle, ForeColor,
TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
UPDATE:
I switched a form to Aero Glass and made some tests. Both approaches with TextRenderer and with GraphicsPath work, however the TextRenderer does not perform very well because ClearType produces artifacts on glass.
These API declarations are required
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public int Left;
public int Right;
public int Top;
public int Bottom;
}
[DllImport("dwmapi.dll", PreserveSig = false)]
public static extern void DwmExtendFrameIntoClientArea (IntPtr hwnd,
ref MARGINS margins);
[DllImport("dwmapi.dll", PreserveSig = false)]
public static extern bool DwmIsCompositionEnabled();
In the form's constructor I have this code
InitializeComponent();
if (DwmIsCompositionEnabled()) {
// Stretch the margins into the form for the glass effect.
MARGINS margins = new MARGINS();
margins.Top = 300;
DwmExtendFrameIntoClientArea(this.Handle, ref margins);
}
The custom Label must have a black background. Black parts will display as glass. It must have a minimum size of about (125, 70) to fit your text because you start drawing at (55, 55). (Was your label too small?) You have to change the AutoSize to false in order to be able to change the size of the label. Here is the code for the custom label
protected override void OnPaint(PaintEventArgs e)
{
GraphicsPath path = new GraphicsPath();
SolidBrush br = new SolidBrush(Color.FromArgb(1, 0, 0));
path.AddString("LLOOOOLL", Font.FontFamily, (int)Font.Style, Font.SizeInPoints,
new Point(55, 55), StringFormat.GenericDefault);
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.FillPath(br, path);
}
With a few differences, it is the same code as yours. An important difference is that the text color must be different from black; otherwise, it would appear as glass. I just take the font properties of the actual label font. This way you can change its appearance in the properties window.
I found the code for the glass effect in this article of TheCodeKing.
with a single cs file you can create your own label control
using System;
using System.Windows.Forms;
namespace GujControls
{
public partial class LABEL_BIRTHDATE : Label
{
public LABEL_BIRTHDATE()
{
this.SuspendLayout();
this.Font = GujWords.gujfont;
this.Size = new System.Drawing.Size(70, 23);
this.ResumeLayout();
}
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
protected override void OnPaint(PaintEventArgs e)
{
TextRenderer.DrawText(e.Graphics, "NAME", Font, ClientRectangle, ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
}
}
Related
I'm trying to create a custom RichTextBox with border color, but i have a problem...
My border color not showing
Here's my code :
public partial class AlXRichTextBox : RichTextBox
{
private RichTextBox textBox;
private Color borderColor;
public AlXRichTextBox()
{
InitializeComponent();
}
public Color BorderColor
{
get { return borderColor; }
set { borderColor = value; Invalidate(); }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen p = new Pen(borderColor);
Graphics g = e.Graphics;
int variance = 3;
//g.DrawRectangle(p, new Rectangle(base.Location.X - variance, base.Location.Y - variance, base.Width + variance, base.Height + variance));
ControlPaint.DrawBorder(e.Graphics, base.ClientRectangle, borderColor, ButtonBorderStyle.Solid);
}
private void InitializeComponent()
{
this.textBox = new System.Windows.Forms.RichTextBox();
this.SuspendLayout();
//
// richTextBox1
//
this.textBox.Location = new System.Drawing.Point(0, 0);
this.textBox.Name = "richTextBox1";
this.textBox.Size = new System.Drawing.Size(100, 96);
this.textBox.TabIndex = 0;
this.textBox.Text = "";
this.textBox.Multiline = true;
this.textBox.BorderStyle = BorderStyle.None;
//
// AlXRichTextBox
//
this.Size = new System.Drawing.Size(278, 123);
this.ResumeLayout(false);
}
}
What's problem with this ?
Referring to MSDN article :
Overriding OnPaint will not allow you to modify the appearance of all controls. Those controls that have all of their painting done by Windows (for example, TextBox) never call their OnPaint method, and thus will never use the custom code. Refer to the Help documentation for the particular control you want to modify to see if the OnPaint method is available. For a list of all the Windows Form Controls, see Controls to Use on Windows Forms. If a control does not have OnPaint listed as a member method, you cannot alter its appearance by overriding this method. For more information about custom painting, see Custom Control Painting and Rendering.
However there is a "hack", You can achieve calling the Paint method by calling following code:
private const int WM_PAINT = 15;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT && !inhibitPaint)
{
// raise the paint event
using (Graphics graphic = base.CreateGraphics())
OnPaint(new PaintEventArgs(graphic,
base.ClientRectangle));
}
}
private bool inhibitPaint = false;
public bool InhibitPaint
{
set { inhibitPaint = value; }
}
Src: RichTextBox and UserPaint
Other point is that You cannot draw outside of the Rectangle (which is total size of Your RichTB component. So You actually want to provide him with different Coords (smaller inner ones) and You will draw to the outside.
Your class will look like:
public partial class AlXRichTextBox : RichTextBox
{
private Color borderColor = Color.Red;
public Color BorderColor
{
get { return borderColor; }
set { borderColor = value; Invalidate(); }
}
protected override void OnPaint(PaintEventArgs e)
{
int variance = 3;
e = new PaintEventArgs(e.Graphics, new Rectangle(e.ClipRectangle.X + variance, e.ClipRectangle.Y + variance, e.ClipRectangle.Width - variance, e.ClipRectangle.Height - variance));
base.OnPaint(e);
Pen p = new Pen(borderColor, variance);
Graphics g = e.Graphics;
g.DrawRectangle(p, new Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width, e.ClipRectangle.Height));
}
private const int WM_PAINT = 15;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT && !inhibitPaint)
{
// raise the paint event
using (Graphics graphic = base.CreateGraphics())
OnPaint(new PaintEventArgs(graphic,
base.ClientRectangle));
}
}
private bool inhibitPaint = false;
public bool InhibitPaint
{
set { inhibitPaint = value; }
}
}
IMPORTANT
As it is not expected for this control to be changed by Paint, You will get "not nice" behavior in regards to the drawing changes like border, new elements etc.. If You would like to use such element, consider using WPF - Windows Presentation Foundation. They are much more nicer to templating the items and modifying design.
A little late answer, but I was on same path as you these days and It got me to this solution, It works for me:
using System;
using System.Drawing;
using System.Windows.Forms;
public class MyRichTextBox : RichTextBox
{
private const UInt32 WM_PAINT = 0x000F;
private const UInt32 WM_USER = 0x0400;
private const UInt32 EM_SETBKGNDCOLOR = (WM_USER + 67);
private const UInt32 WM_KILLFOCUS = 0x0008;
public MyRichTextBox()
{
this.BorderStyle = System.Windows.Forms.BorderStyle.None;
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
Graphics g = Graphics.FromHwnd(Handle);
Rectangle bounds = new Rectangle(0, 0, Width - 1, Height - 1);
Pen p = new Pen(SystemColors.Highlight, 3);
if (m.Msg == WM_PAINT)
{
if (this.Enabled == true)
{
if (this.Focused)
{
g.DrawRectangle(p, bounds);
}
else
{
g.DrawRectangle(SystemPens.ControlDark, bounds);
}
}
else
{
g.FillRectangle(Brushes.White, bounds);
g.DrawRectangle(SystemPens.Control, bounds);
}
}
if (m.Msg == EM_SETBKGNDCOLOR) //color disabled background
{
Invalidate();
}
if (m.Msg == WM_KILLFOCUS) //set border back to normal on lost focus
{
Invalidate();
}
}
}
This Rich Textbox changes 3 border colors - enabled, focused and disabled with disabled background. As you see the code is simple and short. The only trick is to overide KILL_FOCUS and EM_SETBKGNDCOLOR messages (this one is for changing disabled background), and RichTextbox should be BorderStyle=None. Cheers !
I've got a WinForms app and I want to programatically draw circles on top of certain areas. I'm running into a couple problems and any insight would be appreciated!
1) I've got code for drawing and clearing the circles (see below), but the circles are being drawn behind all my controls. I want them to be drawn as "top-most" in every case. How do I do this?
2) When my app starts up, I'll have some circles that need to be drawn right away. I tried drawing them on the Form Load event to no avail. But as to here (Form graphics not set when form loads) I'm now drawing it on the Paint event. While this works reasonably well (with a bool to make sure it only does it the first time), It seems to have problems with the this.Invalidate(); (as no circles are getting drawn). Is there a better way? Here's my code (parseText runs on a the index change of a comboBox):
private void parseText()
{
this.Invalidate();
List<string> lines = new List<string>(richTextBoxRaw.Text.Split(new string[] { Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries));
foreach (string s in lines)
{
switch (s)
{
case "<draw1>":
drawCircle(107, 26, 25);
break;
default:
break;
}
}
}
private void drawCircle(int x, int y, int transparency)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
SolidBrush brush = new SolidBrush(Color.FromArgb(transparency, 255,0,0));
Graphics graphics = this.CreateGraphics();
graphics.FillEllipse(brush, new Rectangle(x, y, 25, 25));
brush.Dispose();
graphics.Dispose();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (starting)
parseText();
starting = false;
}
One of not so complicated and yet working approaches of accomplishing your requirement could be to create custom transparent panel and place it on top of the controls where the red circles will be drawn.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void DrawCircle(int x, int y, int transparency, Graphics graphics)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
SolidBrush brush = new SolidBrush(Color.FromArgb(transparency, 255, 0, 0));
graphics.FillEllipse(brush, new Rectangle(x, y, 25, 25));
brush.Dispose();
graphics.Dispose();
}
private void TransparentPanel1_Paint(object sender, PaintEventArgs e)
{
DrawCircle(10, 10, 255, e.Graphics);
}
private void Form1_Load(object sender, EventArgs e)
{
transparentPanel1.Enabled = false;
transparentPanel1.Paint += TransparentPanel1_Paint;
transparentPanel1.BringToFront();
}
}
public class TransparentPanel : Panel
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e);
}
}
The problem isn't that I don't know how to make a border-less form re-sizable, or to how to draw a border. The problem is what happens when you re-size the form with that custom border.
Here is a screenshot, because I don't know how to explain it:
Here is how I created the border (currently):
private void Form1_Paint(object sender, PaintEventArgs e)
{
int width = 1;
Rectangle rec = this.ClientRectangle;
ButtonBorderStyle bbs = ButtonBorderStyle.Solid;
Color clr = Color.Gray;
ControlPaint.DrawBorder(e.Graphics, rec, clr, width, bbs, clr, width, bbs, clr, width, bbs, clr, width, bbs);
}
As for re-sizing a border-less form; I created a repository for the project.
Resize Custom Border - Bitbucket
I don't have any idea as to why this happens, so I wouldn't know where to begin. I just need to draw a border without it doing this. I have tried other ways of drawing one, but the results were the same.
Hopefully this and the repository becomes useful for anyone trying to do the same.
Thank you for taking your time to read if you did.
Try to use Graphics.DrawRectangle instead of DrawBorder
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Single fWidth = 5.0f;
Rectangle r = new Rectangle(0,0,this.ClientRectangle.Width-1,this.ClientRectangle.Height-1);
e.Graphics.DrawRectangle(new Pen(Color.Gray, fWidth), r);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Invalidate();
}
Use Graphic library :
Step 1: Override the OnPaint handler for your main form
Step 2: Define a rectangle that covers your current form
Step 3: Draw the defined rectangle
protected override void OnPaint(PaintEventArgs e)
{
Rectangle r = new Rectangle(0,0,this.ClientRectangle.Width-1,this.ClientRectangle.Height-1);
e.Graphics.DrawRectangle(new Pen(Color.Gray, 1.0f), r);
}
You may also implement this using a condition statement like:
this.form.Resize += // some handler1
//in hadler1
{
this.form.Paint += // Your new paint handler2
}
//in handler2
{
Rectangle r = new Rectangle(0,0,this.ClientRectangle.Width-1,this.ClientRectangle.Height-1);
e.Graphics.DrawRectangle(new Pen(Color.Gray, 1.0f), r);
}
I created a custom control which I want to use to overlay part of my form with status information on demand. It should display a text and have a background color depending on the type of information. Here is the still incomplete code.
public partial class StatusPanel : UserControl
{
public enum PanelStyle
{
Info,
Warning,
Error
}
public PanelStyle Style { get; set; }
public StatusPanel()
{
InitializeComponent();
this.imgGreen = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgGreen))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(200, Color.Green));
g.FillRectangle(bg, 0, 0, 256, 256);
}
this.imgYellow = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgYellow))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(10, Color.Yellow));
g.FillRectangle(bg, 0, 0, 256, 256);
}
}
protected readonly Font font = new Font("Arial", 12.0F);
protected readonly Brush textBrush = new SolidBrush(Color.Black);
protected readonly Image imgGreen;
protected readonly Image imgYellow;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do not draw background
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
Image img = GetImage();
e.Graphics.DrawImage(img, 0, 0, this.Width, this.Height);
e.Graphics.DrawString("ABC", this.font, this.textBrush, 1.0F, 1.0F);
}
protected Image GetImage()
{
return (this.Style == PanelStyle.Info) ? this.imgGreen : this.imgYellow;
}
}
This works quite fine. But when I put some buttons on a form and one of this controls infront of them, they will "overdraw" when the mouse moves over the button and the highlight effects redraws them.
How will my component be notified that it needs to redraw because underlying control have redrawn?
register a paint-handler to all controls under Parent.Control and a handler for Parent.ControlAdded that registers your paint-handler
something like this:
private void myDummyUserControl_Load(object sender, EventArgs e)
{
var uc = (DummyUserControl)sender;
uc.Parent.ControlAdded += new ControlEventHandler(Parent_ControlAdded);
foreach (Control c in uc.Parent.Controls)
{
if (uc == c)
continue;
c.Paint += new PaintEventHandler(c_Paint);
}
}
void c_Paint(object sender, PaintEventArgs e)
{
//checks & paint stuff here
}
void Parent_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Paint += new PaintEventHandler(c_Paint);
}
//edit
not sure if you need to use recursion to go down the tree of child controls and need to add handlers on those too ...
Hey, I've been trying to paint my own TabControl to get rid of the 3D Shadow but I am not having much success. The DrawItem event isn't firing at the moment. Do I have to shoot it myself? How do I do that?
Code:
namespace NCPad
{
public partial class NCE_TabControl : TabControl
{
Rectangle TabBoundary;
RectangleF TabTextBoundary;
public NCE_TabControl()
{
InitializeComponent();
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true);
this.DrawMode = TabDrawMode.OwnerDrawFixed;
this.Paint += new PaintEventHandler(this.OnPaint);
this.DrawItem += new DrawItemEventHandler(this.OnDrawItem);
}
protected void OnPaint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.Red), e.ClipRectangle);
}
protected void OnDrawItem(object sender, DrawItemEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.Blue), this.TabBoundary);
MessageBox.Show("hi");
}
protected override void OnLayout(LayoutEventArgs levent)
{
base.OnLayout(levent);
this.TabBoundary = this.GetTabRect(0);
this.TabTextBoundary = (RectangleF)this.GetTabRect(0);
}
}
}
I'm not sure on this, but I believe if you specify the ControlStyles.UserPaint bit to true, then the DrawItem won't fire. The other ControlStyles (AllPaintingInWmPaint and DoubleBuffer) have dependencies on each other, though, so you would need to leave them off as well. However, not setting the UserPaint bit to true will result in the Paint event not getting fired. What I have been doing is overriding the OnPaintBackground method:
public partial class NCE_TabControl : TabControl
{
Rectangle TabBoundary;
RectangleF TabTextBoundary;
StringFormat format = new StringFormat(); //for tab header text
public NCE_TabControl()
{ InitializeComponent();
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true);
this.DrawMode = TabDrawMode.OwnerDrawFixed;
this.format.Alignment = StringAlignment.Center;
this.format.LineAlignment = StringAlignment.Center;
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
Graphics g = pevent.Graphics;
g.FillRectangle(new SolidBrush(Color.Red), 0, 0, this.Size.Width, this.Size.Height);
foreach (TabPage tp in this.TabPages)
{
//drawItem
int index = this.TabPages.IndexOf(tp);
this.TabBoundary = this.GetTabRect(index);
this.TabTextBoundary = (RectangleF)this.GetTabRect(index);
g.FillRectangle(new SolidBrush(Color.LightBlue), this.TabBoundary);
g.DrawString("tabPage " + index.ToString(), this.Font, new SolidBrush(Color.Black), this.TabTextBoundary, format);
}
}
}
I think this will work for you, but there may be other methods of doing it as well.
In order to get DrawItem event fired, set DrawMode = OwnerDrawFixed on the Tab Control
http://msdn.microsoft.com/en-us/library/system.windows.forms.tabcontrol.drawitem.aspx
One simple way is to change the TabControl's Appearance property to either Buttons or FlatButtons and set the DrawMode badk to Normal.