TextBox, нарисованный в WM_PAINT, мерцает при входе / выходе мыши

У меня есть пользовательский TextBox, в котором я рисую некоторый текст заполнителя, когда он пуст. Он работает довольно хорошо, но мигает, когда мышь входит и покидает 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);
     }
  }
}

1 ответ

Решение

Есть ли особая причина для использования 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);
        }
    }
}
Другие вопросы по тегам