I have been having an issue when using the OnPaint event on a TextBox.
Whenever I click on the text area with the mouse or when I start to type, the background of the text box changes back to white. Any idea what can cause that?
Here is my code:
protected override void OnCreateControl() {
base.OnCreateControl();
SetStyle(ControlStyles.OptimizedDoubleBuffer
| ControlStyles.DoubleBuffer
| ControlStyles.AllPaintingInWmPaint
| ControlStyles.ResizeRedraw
| ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs e) {
SolidBrush bBrush = new SolidBrush(Enabled ? BackColor : Color.LightGray);
e.Graphics.FillRectangle(bBrush, ClientRectangle);
bBrush.Dispose();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
using (Brush aBrush = new SolidBrush(ForeColor)) {
StringFormat sf = new StringFormat();
switch (TextAlign) {
case HorizontalAlignment.Center:
sf.Alignment = StringAlignment.Center;
break;
case HorizontalAlignment.Left:
sf.Alignment = StringAlignment.Near;
break;
case HorizontalAlignment.Right:
sf.Alignment = StringAlignment.Far;
break;
}
string text = UseSystemPasswordChar && !Enabled
? new StringBuilder(Text.Length).Insert(0, PasswordChar.ToString(), Text.Length).ToString()
: Text;
e.Graphics.DrawString(text, (Font)Font.Clone(), aBrush, ClientRectangle, sf);
aBrush.Dispose();
}
}
Related
I have a custom control, a Hover button, that simply shows borders whenever a mouse moves over it or got focus.
Everything is fine but I am having problem handling a situation where I click the button and it shows another form (ShowDialog) or in case of (MessageBox), and then return back to the parent form of the control.
This is the control code:
namespace CustomControlTutorial
{
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public partial class HoverButton : Button
{
byte eventPaintMode = 0;
//Color borderClr = Color.Transparent;
public HoverButton()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
//base.OnPaint(pe);
// render background
Color clr = this.Parent.BackColor;
Brush b = new SolidBrush(clr);
pe.Graphics.FillRectangle(b, ClientRectangle);
// Draw borders depending on event case
Point p00 = new Point(0, 0);
Point p01 = new Point(0, ClientSize.Height - 1);
Point p10 = new Point(ClientSize.Width - 1, 0);
Point p11 = new Point(ClientSize.Width - 1, ClientSize.Height - 1);
Pen pen1;
Pen pen2;
switch (eventPaintMode)
{
case 1:
{
// draw borders (using pen)
pen1 = new Pen(new SolidBrush(Color.DimGray), 1);
pen2 = new Pen(new SolidBrush(Color.White), 1);
pe.Graphics.DrawLine(pen1, p10, p11);
pe.Graphics.DrawLine(pen2, p00, p10);
pe.Graphics.DrawLine(pen1, p01, p11);
pe.Graphics.DrawLine(pen2, p00, p01);
break;
}
case 2:
{
pen2 = new Pen(new SolidBrush(Color.DimGray), 1);
pen1 = new Pen(new SolidBrush(Color.White), 1);
pe.Graphics.DrawLine(pen1, p10, p11);
pe.Graphics.DrawLine(pen2, p00, p10);
pe.Graphics.DrawLine(pen1, p01, p11);
pe.Graphics.DrawLine(pen2, p00, p01);
break;
}
case 3:
{
// draw borders (using pen)
pen1 = new Pen(new SolidBrush(Color.DimGray), 1);
pen2 = new Pen(new SolidBrush(Color.White), 1);
pe.Graphics.DrawLine(pen1, p10, p11);
pe.Graphics.DrawLine(pen2, p00, p10);
pe.Graphics.DrawLine(pen1, p01, p11);
pe.Graphics.DrawLine(pen2, p00, p01);
// draw focus lines
pen1 = new Pen(new SolidBrush(Color.Black), 1);
pen1.DashCap = DashCap.Round;
pen1.DashPattern = new float[] { 1, 1, 1, 1 };
p00 = new Point(5, 5);
p01 = new Point(5, ClientSize.Height - 6);
p10 = new Point(ClientSize.Width - 6, 5);
p11 = new Point(ClientSize.Width - 6, ClientSize.Height - 6);
pe.Graphics.DrawLine(pen1, p10, p11);
pe.Graphics.DrawLine(pen1, p00, p10);
pe.Graphics.DrawLine(pen1, p01, p11);
pe.Graphics.DrawLine(pen1, p00, p01);
pe.Graphics.SmoothingMode = SmoothingMode.None;
break;
}
default:
break;
}
// render text
String drawString = this.Text;
SizeF size = pe.Graphics.MeasureString(drawString, this.Font);
float pointX = ((float)ClientSize.Width - size.Width) / 2;
float pointY = ((float)ClientSize.Height - size.Height) / 2;
pe.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), (int)pointX, (int)pointY);
b.Dispose();
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
eventPaintMode = 1;
Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
eventPaintMode = 0;
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left)
{
eventPaintMode = 2;
Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (e.Button == MouseButtons.Left)
{
eventPaintMode = 1;
Invalidate();
}
}
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
eventPaintMode = 3;
Invalidate();
}
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
eventPaintMode = 0;
Invalidate();
}
}
}
And this is where I show a test message box:
private void hoverButton1_Click(object sender, EventArgs e)
{
MessageBox.Show("Test");
}
I can't find an event like, OnParentLostFocus or something like that.
The problem is that when I set the button to show a message, the button's parent (Form) can't be accessed until the message is closed. So there is no connection between the 2 forms that I can use to make any changes to the button..
This is how it works. The parent From and its children won't receive the windows paint messages to redraw themselves as long as the modal dialog is shown.
Your code creates:
As you can see, the button draws two different effects on tab stops. A hover and focus effects. While it draws the hover effect and ignores the focus one on the MouseEnter even if the control is currently has the focus. Another thing is, the button doesn't show or draw the normal/default state when the modal dialog is closed.
IMHO, the two things shouldn't combine, interact with the mouse events to draw the hover and down effects, and draw the focus rectangle whenever the control gains the focus and the mouse effects are not applied. Just like how the default System.Windows.Forms.Button works.
For example:
// Your namespace...
public enum MouseState : int
{
None, Over, Down
}
[DesignerCategory("Code")]
public class HoverButton : Button
{
private MouseState state = MouseState.None;
public HoverButton() : base()
{
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw, true);
UpdateStyles();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
var r = ClientRectangle;
g.Clear(Parent.BackColor);
var p00 = new Point(0, 0);
var p01 = new Point(0, r.Height - 1);
var p10 = new Point(r.Width - 1, 0);
var p11 = new Point(r.Width - 1, r.Height - 1);
switch (state)
{
case MouseState.Over:
if (r.Contains(PointToClient(MousePosition)))
{
g.DrawLine(Pens.DimGray, p10, p11);
g.DrawLine(Pens.White, p00, p10);
g.DrawLine(Pens.DimGray, p01, p11);
g.DrawLine(Pens.White, p00, p01);
}
break;
case MouseState.Down:
g.DrawLine(Pens.White, p10, p11);
g.DrawLine(Pens.DimGray, p00, p10);
g.DrawLine(Pens.White, p01, p11);
g.DrawLine(Pens.DimGray, p00, p01);
break;
default:
break;
}
TextRenderer.DrawText(g, Text, Font, r, ForeColor,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
if (Focused && MouseButtons == MouseButtons.None &&
!r.Contains(PointToClient(MousePosition)))
{
r.Inflate(-2, -2);
ControlPaint.DrawFocusRectangle(g, r);
}
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
state = MouseState.Over;
Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
state = MouseState.None;
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left)
{
state = MouseState.Down;
Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
state = MouseState.Over;
Invalidate();
}
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
state = MouseState.None;
Invalidate();
}
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
state = MouseState.None;
Invalidate();
}
}
Which creates:
Side Notes
Call the e.Graphics.Clear(..) method to clear the drawing canvas with the desired color.
Use the predefined Brushes and Pens whenever it's possible instead of creating new graphics objects.
The TextRenderer is better choice to draw strings over controls, while the e.Graphics.DrawString(..) is better one to draw over images.
To center the text in the control, draw it in a rectangle and use an overload where you can pass the TextFormatFlags (or the StringFormat with the Graphics.DrawString(..) method) to dictate that along with the other text layout information.
Don't forget to dispose the graphics objects (pen1 and pen2 in your code).
I made custom combobox that have an integrated button to add new item and it works good in case when DropDownStyle = ComboBoxStyle.DropDownList but there is a problem when the combobox DropDownStyle = ComboBoxStyle.DropDown the text of the combobox is cover the button that I made.
Is there away to make space before text in case when the DropDownStyle for the combobox is set to DropDown? You can see the problem in image.
[Image showing the spacing issue on the combobox]1
public class ComboBoxButton3 : ComboBox
{
public ComboBoxButton3()
{
myButton = new Button();
this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
this.DropDownStyle = ComboBoxStyle.DropDownList;
}
protected override void OnCreateControl()
{
this.myButton.Size = new Size(23, this.ClientSize.Height);
this.myButton.Location = new Point(0, 0);
this.myButton.Cursor = Cursors.Default;
this.Button.BackgroundImage = global::RibbonMenuControlTest.Properties.Resources.add1;
this.Button.BackgroundImageLayout = ImageLayout.Stretch;
this.Button.FlatStyle = FlatStyle.Flat;
this.Controls.Add(this.myButton);
base.OnCreateControl();
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (this != null)
{
e.DrawBackground();
if (e.Index >= 0)
{
StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Center;
sf.Alignment = StringAlignment.Center;
Brush brush = new SolidBrush(this.ForeColor);
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
brush = SystemBrushes.HighlightText;
e.Graphics.DrawString(this.Items[e.Index].ToString(), this.Font, brush, e.Bounds, sf);
}
}
base.OnDrawItem(e);
}
public Button myButton;
public Button Button
{
get
{
return myButton;
}
set
{
myButton = value;
}
}
}
Generally, if a control is not designed to do a certain function with ease, there is normally a reason for it.
I would suggest re-considering your design before going forward.
Regardless, if you insist and continue - then these threads should give you the desired result / point you in the right direction (at least with moving the text around).
Align Text in Combobox
http://blog.michaelgillson.org/2010/05/18/left-right-center-where-do-you-align/
Snippet:
I have a custom TextBox in which I draw some place holder text when it's empty.
It works pretty well, but it flickers when the mouse enters and leaves the TextBox. It seems related to the border becoming blue when the mouse hovers the control (I'm on Windows 8.1).
Any idea how I could fix this ?
I've tried various SetStyles flags without success.
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center
};
private Font mPlaceHolderFont;
[DllImport("user32")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0F)
{
if (string.IsNullOrEmpty(Text) && !Focused)
{
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var rect = new RectangleF(2, 2, Width - 4, Height - 4);
g.FillRectangle(Brushes.White, rect);
g.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
}
}
}
}
I had other problems with overriding OnPaint. Here is the best solution I came up with :
class MyTextBox : TextBox
{
public string PlaceHolder { get; set; }
static readonly Brush sPlaceHolderBrush = new SolidBrush(Color.FromArgb(70, 70, 78));
static readonly StringFormat sFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
private Font mPlaceHolderFont;
private Brush mForegroundBrush;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var bounds = new Rectangle(-2, -2, Width, Height);
var rect = new RectangleF(1, 0, Width - 2, Height - 2);
e.Graphics.FillRectangle(Brushes.White, rect);
if (string.IsNullOrEmpty(Text) && !Focused)
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
if (mForegroundBrush == null)
mForegroundBrush = new SolidBrush(ForeColor);
e.Graphics.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
}
else
{
var flags = TextFormatFlags.Default | TextFormatFlags.TextBoxControl;
if (!Multiline)
flags |= TextFormatFlags.SingleLine | TextFormatFlags.NoPadding;
TextBoxRenderer.DrawTextBox(e.Graphics, bounds, Text, Font, flags, TextBoxState.Selected);
}
}
}
Is there a special reason for using WM_PAINT instead of OnPaint? In WM_PAINT you obtain a drawing context from the handle, which is always a direct access to the control. In OnPaint you already have a Graphics in the event args, which can be either a buffer or a direct context, depending on the styles.
You mentioned that you have tried a few styles with no success. Firstly I would say try these and move your paint logic into OnPaint:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
If it does not work (a focused control may behave strangely in Windows) and you must stick to WM_PAINT, then create a buffer manually. Your original code draws a white rectangle first, then some text, which causes flickering. You can avoid this by using a buffer:
IntPtr dc = GetWindowDC(Handle);
using (Graphics g = Graphics.FromHdc(dc))
{
// creating a buffered context
using (BufferedGraphicsContext context = new BufferedGraphicsContext())
{
// creating a buffer for the original Graphics
using (BufferedGraphics bg = context.Allocate(e.Graphics, ClientRectangle))
{
if (mPlaceHolderFont == null)
mPlaceHolderFont = new Font(Font, FontStyle.Italic);
var gBuf = bg.Graphics;
var rect = ClientRectangle;
rect.Inflate(-1, -1);
gBuf.FillRectangle(Brushes.White, rect);
gBuf.DrawString(PlaceHolder, mPlaceHolderFont, sPlaceHolderBrush, rect, sFormat);
// copying the buffer onto the original Graphics
bg.Render(e.Graphics);
}
}
}
I am making a height-adjustable and vertically alignable textbox with following code. The reason why I should do this is because although I can make winform textbox height-adjustable, I still can't vertically align the text in the textbox. So I decided I have to draw the text OnPaint event. The textbox is showing correct alignment now, but cursor is still located on top of textbox. Is there any way to control this position as well?
public class TextBoxHeightAdjustable : System.Windows.Forms.TextBox
{
public TextBoxHeightAdjustable()
{
this.AutoSize = false;
this.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
}
protected override void OnPaint(PaintEventArgs e)
{
// This never runs no matter what I try!
base.OnPaint(e);
// Create a StringFormat object with the each line of text, and the block
// of text centered on the page.
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), ClientRectangle, stringFormat);
}
}
In spite of Hans' advice, I proceeded since the textbox will contain simple numeric-like text. As Hans said, there is not much option for us to deal with original cursor and lively displayed text. So I hided them with following extension method and by disabling double-clicking and keypress update.
[DllImport("user32.dll")]
static extern bool HideCaret(IntPtr hWnd);
public static void HideCaret(this TextBox textBox)
{
HideCaret(textBox.Handle);
}
So eventually following code works for my purpose but I can't say it's complete. I may still figure out how to draw my own cursor. Hope this helps others with similar issue.
public class TextBoxHeightAdjustable : System.Windows.Forms.TextBox
{
const int WM_DBLCLICK = 0xA3;
const int WM_LBUTTONDBLCLK = 0x203;
public TextBoxHeightAdjustable() : base()
{
this.AutoSize = false;
this.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
//base.OnKeyPress(e);
if (e.KeyChar == (char)Keys.Back)
{
Text = Text.Remove(Text.Length-1);
}
else
{
Text += e.KeyChar;
}
e.Handled = true;
}
protected override void WndProc(ref Message m)
{
if ((m.Msg == WM_DBLCLICK) || (m.Msg == WM_LBUTTONDBLCLK))
{
}
else
{
base.WndProc(ref m);
}
}
protected override void OnTextChanged(System.EventArgs args)
{
//KeyEventArgs kpe = (KeyEventArgs) args;
//this.Font = new Font(this.Font.FontFamily, 0);
using (Graphics g = this.CreateGraphics())
{
g.FillRectangle(Brushes.White, ClientRectangle);
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
g.DrawString(Text, Font, new SolidBrush(ForeColor), ClientRectangle, stringFormat);
}
}
protected override void OnPaint(PaintEventArgs e)
{
this.HideCaret();
e.Graphics.FillRectangle(Brushes.White, ClientRectangle);
// This never runs no matter what I try!
//base.OnPaint(e);
// Create a StringFormat object with the each line of text, and the block
// of text centered on the page.
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), ClientRectangle, stringFormat);
}
}
I've been searching all morning and unfortunately I'm not sure what the techincal term is for this issue, so I'm unable to find a resolution.
When I derive from a GroupBox and override the onPaint function the groupboxes are redrawing themselves over-top the previous groupboxes. The child controls paint correctly, just the groupbox is affected..
class ExtendedComponents
{
public partial class extendedGroupBox : GroupBox
{
private Color borderColor;
public extendedGroupBox()
{
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ContainerControl, true);
this.borderColor = Color.Black;
}
[NotifyParentProperty(true)]
public Color BorderColor
{
get { return this.borderColor; }
set { this.borderColor = value; Invalidate(); }
}
protected override void OnPaint(PaintEventArgs e)
{
Size tSize = TextRenderer.MeasureText(this.Text, this.Font);
Rectangle borderRect = e.ClipRectangle;
borderRect.Y += tSize.Height / 2;
borderRect.Height -= tSize.Height / 2;
ControlPaint.DrawBorder(e.Graphics, borderRect, this.borderColor, ButtonBorderStyle.Dotted);
Rectangle textRect = e.ClipRectangle;
textRect.X += 6;
textRect.Width = tSize.Width + 5;
textRect.Height = tSize.Height;
e.Graphics.FillRectangle(new SolidBrush(this.BackColor), textRect);
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), textRect);
}
}
}
Any help would be much appreciated!
The easy answer is to not use the GroupBox control-- it's inherently flicky.
Try using a Panel control instead with your DoubleBuffer SetStyles, etc.
For your current implementation, don't use the e.ClipRectangle:
//Rectangle borderRect = e.ClipRectangle;
Rectangle borderRect = this.ClientRectangle;
//Rectangle textRect = e.ClipRectangle;
Rectangle textRect = this.ClientRectangle;
Another thing to note is that you should override OnPaintBackground to avoid flicker. There you either do nothing or draw the control fore color.