在 WM_PAINT 中绘制的文本框在鼠标 enter/leave 上闪烁
TextBox drawn in WM_PAINT flickers on mouse enter/leave
我有一个自定义文本框,当它为空时我会在其中绘制一些占位符文本。
它工作得很好,但是当鼠标进入和离开 TextBox 时它会闪烁。这似乎与鼠标悬停在控件上时边框变蓝有关(我在 Windows 8.1)。
知道如何解决这个问题吗?
我试过各种 SetStyles 标志但都没有成功。
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);
}
}
}
}
}
我在覆盖 OnPaint 时遇到了其他问题。这是我想出的最佳解决方案:
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);
}
}
}
使用 WM_PAINT
而不是 OnPaint
是否有特殊原因?在 WM_PAINT 中,您从句柄获取绘图上下文,这始终是对控件的直接访问。在 OnPaint
中,事件参数中已经有一个 Graphics
,它可以是缓冲区或直接上下文,具体取决于样式。
您提到您尝试了几种样式但没有成功。首先我会说尝试这些 和 将你的绘画逻辑移动到 OnPaint
:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
如果它不起作用(聚焦控件在 Windows 中可能表现异常)并且您必须坚持使用 WM_PAINT,然后手动创建缓冲区。您的原始代码首先绘制一个白色矩形,然后绘制一些文本,这会导致闪烁。您可以通过使用缓冲区来避免这种情况:
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);
}
}
}
我有一个自定义文本框,当它为空时我会在其中绘制一些占位符文本。 它工作得很好,但是当鼠标进入和离开 TextBox 时它会闪烁。这似乎与鼠标悬停在控件上时边框变蓝有关(我在 Windows 8.1)。
知道如何解决这个问题吗?
我试过各种 SetStyles 标志但都没有成功。
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);
}
}
}
}
}
我在覆盖 OnPaint 时遇到了其他问题。这是我想出的最佳解决方案:
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);
}
}
}
使用 WM_PAINT
而不是 OnPaint
是否有特殊原因?在 WM_PAINT 中,您从句柄获取绘图上下文,这始终是对控件的直接访问。在 OnPaint
中,事件参数中已经有一个 Graphics
,它可以是缓冲区或直接上下文,具体取决于样式。
您提到您尝试了几种样式但没有成功。首先我会说尝试这些 和 将你的绘画逻辑移动到 OnPaint
:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
如果它不起作用(聚焦控件在 Windows 中可能表现异常)并且您必须坚持使用 WM_PAINT,然后手动创建缓冲区。您的原始代码首先绘制一个白色矩形,然后绘制一些文本,这会导致闪烁。您可以通过使用缓冲区来避免这种情况:
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);
}
}
}