custom textboxes in VB.Net - vb.net

tldr - Made a subclass of Textbox, text looks screwy when it has focus. What's the proper way to handle it?
For my company's VB.Net application, I've been asked to make our textboxes behave like Google's textboxes, ie they need to have a blue-ish border around them when they have focus and a gray-ish border when they do not. I can already accomplish this by setting a textbox's BorderStyle to 'None', then drawing the appropriate rectangle within a form's Paint event. However, I have to do this for each and every single textbox that I use. And our application has quite a few of them. Needless to say, this is a pain and I'd rather have one piece of code that I can call upon.
So I figured that I have two options; I can either make a user control that contains a single textbox which uses the above method, or I can write my own class that inherits from the TextBox class and makes this behavior standard. I have elected to use the latter approach, and via overriding the OnPaint method I have achieved the desired behavior. But now I'm encountering some new pitfalls.
The main problem that I'm having is that text within the textbox is not rendered correctly when the textbox has focus. The text takes on a different font, appears bold, and highlighting looks wonky. If the textbox loses focus, the text looks correct. I suspect that I need to handle drawing for highlighted text differently, but I'm not sure what I need to do. Do I handle it in the OnPaint method or do I need to catch it somewhere else? Do I need to abandon this approach altogether and just make a user control?
Bonus question: for anyone with experience making custom textboxes, are there any tips or gotchas that I need to know about? This is my first time making a custom control, so I don't really know what all to expect.
edit: forgot to mention that I'm able to override OnPaint because I set the UserPaint flag to true. I'm guessing this was obvious, but I just want to be thorough.
edit2: Here's the class in its entirety.
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

As Hans mentioned, the TextBox doesn't even use the OnPaint method when it draws its text.
One way to do it is paint over the 3D border of the control in the WM_NCPAINT message. I won't claim it's completely flicker free:
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
I override the OnResize event to send the RedrawWindow message, which basically makes the control invalidate it's nonclient area.
Refactor as needed.

Related

Listview MouseLeave event: the Header is not included

I've subscribed to the MouseLeave event of my ListView. The event should be raised when the Mouse Pointer leaves the ListView bounds.
That works, but when the Mouse Pointer enters the ListView's Header and then leaves the ListView bounds, the event is not be raised.
Private Sub LV1_test_MouseLeave(sender As Object, e As EventArgs) Handles LV1_test.MouseLeave
// Not raised when the Pointer leaves the premises from the top of the ListView
End Sub
What can I do?
The ListView Header is actually a different object, its class name is SysHeader32.
The Header is shown in Details View, but it's created along with the ListView, so it's there even if you cannot see it (if you have added at least one Column, that is).
It's not a managed child Control of the ListView: the ListView.Controls collection is usually empty.
But it's a child control of the SysListView32 native control from which the managed class derives, thus, you can get its handle and read its messages; the WM_MOUSELEAVE message, in this case.
We can get its handle using FindWinDowEx or SendMessage (with LVM_GETHEADER), assign the handle to a NativeWindow class, override its WndProc and intercept the messages we need to handle. On WM_MOUSELEAVE, the NativeWindow class raises an event that the parent ListView can subscribes to, raising its own MouseLeave event as a result.
Since, as described, the Header is a distinct object, the ListView generates a MouseLeave event when the mouse pointer is moved over its Header. We need to override this behavior, so the MouseLeave event is only raised when the mouse Pointer leaves the ListView bounds completely.
We can override OnMouseLeave, verify whether the position returned by MousePosition (translated to client measures) falls within the ListView client bounds and let the method raise the MouseLeave event only when it doesn't.
EDIT:
Added WM_PARENTNOTIFY message check (for the WM_CREATE event notification) to handle the Header creation at run-time.
Custom ListView Control:
Now, if you subscribe to the MouseLeave event of this Custom Control, the event is raised only when the Mouse Pointer leaves the Client Area of the ListView, no matter where the Cursor is located.
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
<DesignerCategory("Code")>
Class ListViewCustom
Inherits ListView
Private Const LVM_GETHEADER As Integer = &H1000 + 31
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SendMessage(hWnd As IntPtr, uMsg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
Private sysHeader As SysHeader32 = Nothing
Private Sub AddSysHeaderHandler()
If DesignMode Then Return
If sysHeader Is Nothing Then
Dim sysHeaderHwnd = SendMessage(Me.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero)
If sysHeaderHwnd <> IntPtr.Zero Then
sysHeader = New SysHeader32(sysHeaderHwnd)
AddHandler sysHeader.SysHeaderMouseLeave,
Sub(s, evt)
Me.OnMouseLeave(evt)
End Sub
End If
End If
End Sub
Protected Overrides Sub OnHandleCreated(e As EventArgs)
MyBase.OnHandleCreated(e)
AddSysHeaderHandler()
End Sub
Protected Overrides Sub OnMouseLeave(e As EventArgs)
If Not Me.ClientRectangle.Contains(PointToClient(MousePosition)) Then
MyBase.OnMouseLeave(e)
End If
End Sub
' Handles the Header creation at run-time
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case &H210 'WM_PARENTNOTIFY
Dim msg As Integer = m.WParam.ToInt32() And &HFFFF
Select Case msg
Case &H1 ' WM_CREATE
AddSysHeaderHandler()
End Select
End Select
MyBase.WndProc(m)
End Sub
Protected Overrides Sub Dispose(disposing As Boolean)
If (disposing) Then sysHeader?.ReleaseHandle()
MyBase.Dispose(disposing)
End Sub
Private Class SysHeader32
Inherits NativeWindow
Public Event SysHeaderMouseLeave As EventHandler(Of EventArgs)
Public Sub New(handle As IntPtr)
AssignHandle(handle)
End Sub
Protected Friend Overridable Sub OnSysHeaderMouseLeave(e As EventArgs)
RaiseEvent SysHeaderMouseLeave(Me, e)
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case &H2A3 'WM_MOUSELEAVE
OnSysHeaderMouseLeave(EventArgs.Empty)
m.Result = IntPtr.Zero
Exit Select
Case Else
' NOP: Log other messages, add more cases...
End Select
MyBase.WndProc(m)
End Sub
End Class
End Class

Aero Effects on User-drawn TabControl

In my app, I would like to have my tabcontrol have an aero effect on the header. This works with the normal winforms tabcontrol using this code:
Imports System.Runtime.InteropServices
<DllImport("dwmapi.dll")> _
Private Shared Function DwmExtendFrameIntoClientArea(ByVal hwnd As IntPtr, ByRef pMarInset As MARGINS) As Integer
End Function
<StructLayout(LayoutKind.Sequential)> _
Private Structure MARGINS
Public cxLeftWidth As Integer
Public cxRightWidth As Integer
Public cyTopHeight As Integer
Public cyBottomHeight As Integer
End Structure
Problem is, I have a custom tabcontrol class (the actual one is much larger and has a much more complicated OnPaint() override than below)
Public Class CustomTab
Inherits TabControl
Public Sub New()
SetStyle(ControlStyles.UserPaint or ControlStyles.ResizeRedraw,True)
End Sub
Protected Overrides Sub OnPaint(e as PaintEventArgs)
Dim g as Graphics = e.Graphics
Dim r as Rectangle
For i=0 to TabCount - 1
If i = SelectedIndex
r = GetTabRect(i)
g.FillRectangle(New SolidBrush(My.Settings.TabColor),r)
Else
r = GetTabRect(i)
g.FillRectangle(New SolidBrush(SystemColors.Control),r)
End If
Next
End Sub
End Class
When I try the glass effects with this, it does not show in the header like normal.
Does anyone know why the Userdrawn tabcontrol will not have the transparent header?
Working (Normal tabcontrol):
Not working (my tabcontrol):
Never Mind- Found the Answer. In the OnPaint() Sub, write
g.Clear(Color.Black)
this paints the background black, so the glass effects show

vb.net gamepad support, partially working

I found the following class code on a forum. It works great for the gamepad (up, down, left, right) however all the code for the buttons is missing. Can anyone fill in the blanks?
This works:
Private Sub joystick1_Up() Handles joystick1.Up
moveUp()
End Sub
This does not:
Private Sub joystick1_buttonPressed() Handles joystick1.buttonPressed
MsgBox(joystick1.btnValue)
End Sub
because there is no "buttonPressed" event and I have no idea how to write it.
And here's the class:
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Public Class joystick
Inherits NativeWindow
Private parent As Form
Private Const MM_JOY1MOVE As Integer = &H3A0
' Public Event Move(ByVal joystickPosition As Point)
Public btnValue As String
Public Event Up()
Public Event Down()
Public Event Left()
Public Event Right()
<StructLayout(LayoutKind.Explicit)> _
Private Structure JoyPosition
<FieldOffset(0)> _
Public Raw As IntPtr
<FieldOffset(0)> _
Public XPos As UShort
<FieldOffset(2)> _
Public YPos As UShort
End Structure
Private Class NativeMethods
Private Sub New()
End Sub
' This is a "Stub" function - it has no code in its body.
' There is a similarly named function inside a dll that comes with windows called
' winmm.dll.
' The .Net framework will route calls to this function, through to the dll file.
<DllImport("winmm", CallingConvention:=CallingConvention.Winapi, EntryPoint:="joySetCapture", SetLastError:=True)> _
Public Shared Function JoySetCapture(ByVal hwnd As IntPtr, ByVal uJoyID As Integer, ByVal uPeriod As Integer, <MarshalAs(UnmanagedType.Bool)> ByVal changed As Boolean) As Integer
End Function
End Class
Public Sub New(ByVal parent As Form, ByVal joyId As Integer)
AddHandler parent.HandleCreated, AddressOf Me.OnHandleCreated
AddHandler parent.HandleDestroyed, AddressOf Me.OnHandleDestroyed
AssignHandle(parent.Handle)
Me.parent = parent
Dim result As Integer = NativeMethods.JoySetCapture(Me.Handle, joyId, 100, True)
End Sub
Private Sub OnHandleCreated(ByVal sender As Object, ByVal e As EventArgs)
AssignHandle(DirectCast(sender, Form).Handle)
End Sub
Private Sub OnHandleDestroyed(ByVal sender As Object, ByVal e As EventArgs)
ReleaseHandle()
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = MM_JOY1MOVE Then
' Joystick co-ords.
' (0,0) (32768,0) (65535, 0)
'
'
'
' (0, 32768) (32768, 32768) (65535, 32768)
'
'
'
'
' (0, 65535) (32768, 65535) (65535, 65535)
'
Dim p As JoyPosition
p.Raw = m.LParam
' RaiseEvent Move(New Point(p.XPos, p.YPos))
If p.XPos > 16384 AndAlso p.XPos < 49152 Then
' X is near the centre line.
If p.YPos < 6000 Then
' Y is near the top.
RaiseEvent Up()
ElseIf p.YPos > 59536 Then
' Y is near the bottom.
RaiseEvent Down()
End If
Else
If p.YPos > 16384 AndAlso p.YPos < 49152 Then
' Y is near the centre line
If p.XPos < 6000 Then
' X is near the left.
RaiseEvent Left()
ElseIf p.XPos > 59536 Then
' X is near the right
RaiseEvent Right()
End If
End If
End If
End If
If btnValue <> m.WParam.ToString Then
btnValue = m.WParam.ToString
End If
MyBase.WndProc(m)
End Sub
End Class
Instead of using the older winmm, I would use XInput instead (or if you can, use XNA).
There are a couple of ways you could go to do this. One step up is to use the XInput dlls directly as outlined by this question on the MSDN forums. That's still fairly ugly though. Probably the "easier" way to do this is using a wrapper library that exists out there like SlimDX or SharpDX.
One of the advantages of using XInput via SlimDX or SharpDX is that it will also work within a Windows Store app for Windows 8 :).
Here's a snippet from the GamePad sample in SharpDX:
C#
var controllers = new[] { new Controller(UserIndex.One), new Controller(UserIndex.Two), new Controller(UserIndex.Three), new Controller(UserIndex.Four) };
// Get 1st controller available
Controller controller = null;
foreach (var selectControler in controllers)
{
if (selectControler.IsConnected)
{
controller = selectControler;
break;
}
}
VB
Dim controllers As New List(Of Controller)
controllers.Add(New Controller(UserIndex.One))
controllers.Add(New Controller(UserIndex.Two))
controllers.Add(New Controller(UserIndex.Three))
controllers.Add(New Controller(UserIndex.Four))
Dim controller as Controller = Nothing;
For Each selectController In controllers
If selectController.IsConnected Then
controller = selectController
Exit For
End If
Next
Then you can get the state to work with using:
var state = controller.GetState();
You will notice that XInput uses more of a polling model, so you will need to occasionally check for a button press to detect this. If you need to poll continuously, you can probably spin up a new Task to do this on.

How to set TextBox hint label in VB .NET?

In my application I have one text box for entering user name. If text is empty i want to show "Enter User name here" in same text box in gray color. Is there any property like this for text box. Like in Firefox browser if URL field is empty it will show "Go to a web site" In gray color
Thanks
I know this may be an old thread, but I thought I'd answer it just in case anyone else stumbled across it.
First declare the following (you may need to import System.Runtime.InteropServices)
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As Int32
End Function
Then call the following (change as needed):
SendMessage(Me.textbox1.Handle, &H1501, 0, "Enter User name here")
Assuming you mean Windows Forms, take a look at this question.
Basically, you need to call a WinAPI SendMessage function for the control with EM_SETCUEBANNERvalue.
I really like this solution from CodeProject.com: http://www.codeproject.com/KB/edit/TextBoxHint.aspx?display=Print
What's really nice is the slick fade out as the user types in his/her text into the field. It's pretty darn easy to implement and looks great.
you can subclass TextBox and override WndProc
Public Class TextBoxPlaceHolder
Inherits TextBox
Private Const WM_PAINT As Int32 = &HF
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
If m.Msg = WM_PAINT AndAlso Me.TextLength = 0 Then
Using g = Me.CreateGraphics
g.DrawString("Enter User name here", Me.Font, Brushes.Gray, 1, 1)
End Using
End If
End Sub
End Class
Awesome work Ivan and cardmagik.
Create a usercontrol and use the below code.
Public Class RichTextBoxPlaceHolder
Inherits RichTextBox
Private Const WM_PAINT As Int32 = &HF
Private mstrHint As String = "Enter text"
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
If m.Msg = WM_PAINT AndAlso Me.TextLength = 0 Then
Using g = Me.CreateGraphics
g.DrawString(mstrHint, Me.Font, Brushes.Gray, 1, 1)
End Using
End If
End Sub
Public Property Hint() As String
Get
Return mstrHint
End Get
Set(ByVal value As String)
mstrHint = value
End Set
End Property
End Class
Screenshot:
Try something along the lines of this:
(onkeyup)
If TextBox.Text = "" Then
TextBox.Text = "Enter username here..."
TextBox.ForeColor = <your chosen color here>
Else
TextBox.ForeColor = <normal color here>
End If

How can I catch the autosize double-click event on a listview in VB.NET?

I am using Visual Studio 2008 and VB.NET. I've got a listview control on my form and I've added columns using the windows forms designer. As you know, if you double-click on the sizer or divider or whatever you want to call it between two columns, the column on the left will autosize (unless you disable that). How can I catch this specific event? The ColumnWidthChanged event and the DoubleClick event are likely candidates, but in the ColumnWidthChanged event, there's no way I can see to determine if it was an autosize. Similarly, there's no simple way to catch what was clicked exactly with the DoubleClick event. Does anyone have any ideas how I can catch this specific event type?
Detecting events on a listview's header is quite tricky.
You will need to create your own header to replace the one that it normally uses, and then listen to the appropriate messages. There aren't any specific ones for column resize handles, as far as I know.
The following class subclasses ListView and adds a handler that detects a double-click between columns. That is as close as it gets, I think.
I hope it will help you out somewhat.
Class MyListView
Inherits ListView
Protected Overrides Sub CreateHandle()
MyBase.CreateHandle()
New HeaderControl(Me)
End Sub
Private Class HeaderControl
Inherits NativeWindow
Private _parent As ListView = Nothing
<DllImport("User32.dll", CharSet := CharSet.Auto, SetLastError := True)> _
Public Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
Public Sub New(parent As ListView)
_parent = parent
Dim header As IntPtr = SendMessage(parent.Handle, (&H1000 + 31), IntPtr.Zero, IntPtr.Zero)
Me.AssignHandle(header)
End Sub
Protected Overrides Sub WndProc(ByRef message As Message)
Const WM_LBUTTONDBLCLK As Integer = &H203
Select Case message.Msg
Case WM_LBUTTONDBLCLK
Dim position As Point = Control.MousePosition
Dim relative As Point = _parent.PointToClient(position)
Dim rightBorder As Integer = 0
For Each c As ColumnHeader In _parent.Columns
rightBorder += c.Width
If relative.X > (rightBorder - 6) AndAlso relative.X < (rightBorder + 6) Then
MessageBox.Show([String].Format("Double-click after column '{0}'", c.Text))
End If
Next
Exit Select
End Select
MyBase.WndProc(message)
End Sub
End Class
End Class
You will need to include a using System.Runtime.InteropServices; statement for this to work.