пользовательские текстовые поля в VB.Net - PullRequest
1 голос
/ 29 ноября 2011

tldr - сделал подкласс Textbox, текст выглядит странно, когда он имеет фокус. Какой правильный способ справиться с этим?

Для приложения моей компании VB.Net меня попросили заставить наши текстовые поля вести себя как текстовые поля Google, то есть они должны иметь рамку синего цвета вокруг них, когда они имеют фокус, и границу серого цвета, когда они делают не. Я уже могу сделать это, установив для BorderStyle текстового поля значение «Нет», а затем нарисовав соответствующий прямоугольник в событии Paint формы. Тем не менее, я должен сделать это для каждого текстового поля, которое я использую. И в нашем приложении их немало. Излишне говорить, что это боль, и я предпочел бы иметь один кусок кода, к которому я могу обратиться.

Итак, я решил, что у меня есть два варианта; Я могу либо создать пользовательский элемент управления, который содержит одно текстовое поле, которое использует вышеупомянутый метод, либо я могу написать свой собственный класс, который наследуется от класса TextBox и делает это поведение стандартным. Я решил использовать последний подход, и, переопределив метод OnPaint, я достиг желаемого поведения. Но теперь я сталкиваюсь с некоторыми новыми подводными камнями.

Основная проблема, с которой я сталкиваюсь, заключается в том, что текст в текстовом поле отображается неправильно, когда текстовое поле имеет фокус. Текст приобретает другой шрифт, выглядит жирным, а выделение выглядит шатким. Если текстовое поле теряет фокус, текст выглядит правильно. Я подозреваю, что мне нужно по-разному обрабатывать рисование для выделенного текста, но я не уверен, что мне нужно делать. Я обращаюсь с этим в методе OnPaint или мне нужно поймать это где-нибудь еще? Нужно ли вообще отказываться от этого подхода и просто создавать пользовательский элемент управления?

Бонусный вопрос: для любого, у кого есть опыт создания пользовательских текстовых полей, есть какие-нибудь советы или хитрости, о которых мне нужно знать? Я впервые делаю пользовательский элемент управления, поэтому я не знаю, чего ожидать.

edit: забыл упомянуть, что я могу переопределить OnPaint, потому что для флага UserPaint установлено значение true. Я предполагаю, что это было очевидно, но я просто хочу быть тщательным.

edit2: вот класс целиком.

Imports System.Drawing

Public Class MyCustomTextBox
   Inherits TextBox

   Public Sub New()
      MyBase.New()
      Me.BorderStyle = BorderStyle.None
      SetStyle(ControlStyles.UserPaint, True)
   End Sub

   Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
      'I want these textboxes to highlight all text by default
      Me.SelectAll()
      MyBase.OnGotFocus(e)
   End Sub

   Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
      Me.SelectionLength = 0
      MyBase.OnLostFocus(e)
   End Sub

   Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
      Dim p As Pen = Nothing

      'MyBase.OnPaint(e)

      e.Graphics.FillRectangle(Brushes.White, Me.ClientRectangle)

      If Me.Focused Then
         p = New Pen(Brushes.CornflowerBlue)
      Else
         p = New Pen(Brushes.Gainsboro)
      End If

      e.Graphics.DrawRectangle(p, 0, 0, Me.ClientSize.Width - 1, Me.ClientSize.Height - 1)
      e.Graphics.DrawString(Me.Text, Me.Font, New SolidBrush(Me.ForeColor), Me.ClientRectangle)
   End Sub

End Class

1 Ответ

2 голосов
/ 30 ноября 2011

Как уже упоминал Ганс, TextBox даже не использует метод OnPaint при рисовании текста.

Один из способов сделать это - закрасить трехмерную границу элемента управления в сообщении WM_NCPAINT.Я не буду утверждать, что он полностью свободен от мерцания:

Imports System.Runtime.InteropServices

Public Class TextBoxWithBorder
  Inherits TextBox

  Public Const WM_NCPAINT As Integer = &H85

  <Flags()> _
  Private Enum RedrawWindowFlags As UInteger
    Invalidate = &H1
    InternalPaint = &H2
    [Erase] = &H4
    Validate = &H8
    NoInternalPaint = &H10
    NoErase = &H20
    NoChildren = &H40
    AllChildren = &H80
    UpdateNow = &H100
    EraseNow = &H200
    Frame = &H400
    NoFrame = &H800
  End Enum

  <DllImport("User32.dll")> _
  Public Shared Function GetWindowDC(ByVal hWnd As IntPtr) As IntPtr
  End Function

  <DllImport("user32.dll")> _
  Private Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Boolean
  End Function

  <DllImport("user32.dll")> _
  Private Shared Function RedrawWindow(hWnd As IntPtr, lprcUpdate As IntPtr, hrgnUpdate As IntPtr, flags As RedrawWindowFlags) As Boolean
  End Function

  Public Sub New()
    MyBase.BorderStyle = Windows.Forms.BorderStyle.Fixed3D
  End Sub

  Protected Overrides Sub OnResize(e As System.EventArgs)
    MyBase.OnResize(e)
    RedrawWindow(Me.Handle, IntPtr.Zero, IntPtr.Zero, RedrawWindowFlags.Frame Or RedrawWindowFlags.UpdateNow Or RedrawWindowFlags.Invalidate)
  End Sub

  Protected Overrides Sub WndProc(ByRef m As Message)
    MyBase.WndProc(m)

    If m.Msg = WM_NCPAINT Then
      Dim hDC As IntPtr = GetWindowDC(m.HWnd)
      Using g As Graphics = Graphics.FromHdc(hDC)
        If Me.Focused Then
          g.DrawRectangle(Pens.CornflowerBlue, New Rectangle(0, 0, Me.Width - 1, Me.Height - 1))
        Else
          g.DrawRectangle(Pens.Gainsboro, New Rectangle(0, 0, Me.Width - 1, Me.Height - 1))
        End If
        g.DrawRectangle(SystemPens.Window, New Rectangle(1, 1, Me.Width - 3, Me.Height - 3))
      End Using
      ReleaseDC(m.HWnd, hDC)
    End If

  End Sub
End Class

Я перезаписываю событие OnResize, чтобы отправить сообщение RedrawWindow, что в основном делает элемент управления недействительным в его неклиентской области.

Рефакторинг по мере необходимости.

...