Winforms Panel and Scrollbar issue - vb.net

This issue has bugged me for some time - when attempting to scroll down a Panel the bar will not move until the mouse is released (doesn't seem to be a problem in other controls).
In this forum http://csharpcode25.appspot.com/question/5080f1624f1eba38a4ca86bf the user has experienced the same problem and a possible solution is given by overriding WndProc - not sure if something got lost in translation from C# to VB but it just throws an error 'Type of argument 'Number' is 'System.IntPtr', which is not numeric.' at the first line. Any ideas?
Thanks
Public Class Panelx
Inherits Panel
Private Const WM_HSCROLL As Integer = &H114
Private Const WM_VSCROLL As Integer = &H115
Protected Overrides Sub WndProc(ByRef m As Message)
Try
If (m.Msg = WM_HSCROLL OrElse m.Msg = WM_VSCROLL) AndAlso ((CInt(Fix(m.WParam)) And &HFFFF) = 5) Then
' Change SB_THUMBTRACK to SB_THUMBPOSITION
m.WParam = CType((CInt(Fix(m.WParam)) And (Not &HFFFF)) Or 4, IntPtr)
End If
MyBase.WndProc(m)
Catch ex As Exception
EmailError(ex)
End Try
End Sub
End Class

Just remove the calls to the Fix() method:
If (m.Msg = WM_HSCROLL OrElse m.Msg = WM_VSCROLL) AndAlso ((CInt(m.WParam) And &HFFFF) = 5) Then
' Change SB_THUMBTRACK to SB_THUMBPOSITION
m.WParam = CType((CInt(m.WParam) And (Not &HFFFF)) Or 4, IntPtr)
End If

Related

How can I scroll a ListView by one row instead of the default three using the mouse wheel?

I have a modified ListView. When I scroll using the mouse wheel, it scrolls three rows.
I want it to scroll one row at a time using the mouse wheel.
Public Class listviewEx
Inherits ListView
Private Declare Function ShowScrollBar Lib "user32" (ByVal hwnd As IntPtr, ByVal wBar As Integer,
ByVal bShow As Boolean) As Integer
' Constants
Private Const SB_HORZ As Integer = 0
Private Const WM_HSCROLL As Integer = &H114
Private Const WM_VSCROLL As Integer = &H115
Public Event Scroll As ScrollEventHandler
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
ShowScrollBar(MyBase.Handle, SB_HORZ, False)
If m.Msg = &H115 Then
' Trap WM_VSCROLL
End If
End Sub
Public Sub New()
MyBase.New()
Me.SetStyle(ControlStyles.Opaque, True)
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
Me.SetStyle(ControlStyles.ResizeRedraw, True)
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.EnableNotifyMessage, True)
End Sub
End Class
You can add a behavior to your ListView, to make it scroll one row instead of the default three, using the Ctrl key (as this modifier is often used to change this kind of behaviors) in combination with the mouse wheel.
You can then have the standard three-rows scroll when Ctrl is not pressed and one-row scroll behavior when it's pressed.
Override WndProc (as you're already doing), to handle WM_MOUSEWHEEL and verify that the Ctrl key is pressed, checking whether the low-word of WParam is MK_CONTROL = &H08.
When it's pressed, determine whether the delta is positive or negative and increment the value returned by ListView.TopItem.Index to then set the TopItem based on the calculated offset (adding a minimum/maximum check to avoid overflows):
Imports System.Windows.Forms
Public Class ListViewEx
Inherits ListView
Private Const WM_MOUSEWHEEL As Integer = &H20A
Private Const MK_CONTROL As Integer = &H8
Public Sub New()
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
MyBase.WndProc(m)
Select Case m.Msg
Case WM_MOUSEWHEEL
If Items.Count > 0 AndAlso (m.WParam.ToInt64() And &HFF) = MK_CONTROL Then
Dim offset = If((m.WParam.ToInt64() >> 16) > 0, -1, 1) + TopItem.Index
offset = Math.Max(Math.Min(offset, Items.Count - 1), 0)
TopItem = Items(offset)
m.Result = IntPtr.Zero
End If
End Select
End Sub
End Class
Someone named Viorel did it:
Public Class listviewEx
Inherits ListView
Protected Overrides Sub WndProc(ByRef m As Message)
Const WM_MOUSEWHEEL = &H20A
Select Case m.Msg
Case WM_MOUSEWHEEL
If TopItem IsNot Nothing Then
Dim d As Int16 = (m.WParam.ToInt32 >> 16)
Dim i As Integer
If d > 0 Then
i = Math.Max(TopItem.Index - 1, 0)
Else
i = Math.Min(TopItem.Index + 1, Items.Count - 1)
End If
TopItem = Items(i)
End If
m.Result = IntPtr.Zero
Return
End Select
MyBase.WndProc(m)
End Sub
End Class

Hiding up/down buttons on NumericUpDown control

I am trying to subclass NumericUpDown in several ways to get better functionality and appearance.
Since NUD is construct of two controls I would like to hide up/down buttons in cases where property "Increment" is set to 0.
This code is in subclass:
Protected Overrides Sub OnTextBoxResize(ByVal source As Object, ByVal e As System.EventArgs)
Controls(0).Hide()
End Sub
... and it work OK.
But in that function I cannot check a value of Increment property like this:
Protected Overrides Sub OnTextBoxResize(ByVal source As Object, ByVal e As System.EventArgs)
If Me.Increment = 0 Then
Controls(0).Hide()
End if
End Sub
In scope of this function Me is not reachable.
I am also try with using local variables but can't find which event is fired before OnTextBoxResize to read value of Increment property.
What to do in such case to get desired functionality?
This seems to work fairly well. It Shadows the Increment property in order to set the visibility of the spinner controls when the Increment value is being changed. There is an underlying private method the base control calls called PositionControls which you cannot stop — that method could create some flicker, but on my test, it didn't.
Public Class MyNumBox
Inherits NumericUpDown
Shadows Property Increment As Decimal
Get
Return MyBase.Increment
End Get
Set(value As Decimal)
MyBase.Increment = value
OnTextBoxResize(Me, EventArgs.Empty)
End Set
End Property
Protected Overrides Sub OnHandleCreated(e As EventArgs)
MyBase.OnHandleCreated(e)
OnTextBoxResize(Me, EventArgs.Empty)
End Sub
Protected Overrides Sub OnTextBoxResize(source As Object, e As EventArgs)
If Me.IsHandleCreated Then
Me.Height = Me.PreferredHeight
Me.Controls(0).Visible = (MyBase.Increment > 0)
Dim borderWidth As Integer = 0
If Me.BorderStyle > BorderStyle.None Then
borderWidth = SystemInformation.Border3DSize.Width
End If
Dim textWidth As Integer
If Me.Increment = 0 Then
textWidth = Me.ClientSize.Width - (borderWidth * 2)
Else
textWidth = Me.ClientSize.Width - Me.Controls(0).Width - (borderWidth * 2)
End If
If Me.UpDownAlign = LeftRightAlignment.Left Then
If Me.Increment = 0 Then
Me.Controls(1).SetBounds(borderWidth, borderWidth, _
textWidth, Me.Controls(1).Height)
Else
Me.Controls(1).SetBounds(borderWidth + Me.Controls(0).Width, _
Me.Controls(1).Top, textWidth, Me.Controls(1).Height)
End If
Else
Me.Controls(1).SetBounds(borderWidth, Me.Controls(1).Top, _
textWidth, Me.Controls(1).Height)
End If
Me.Refresh()
End If
End Sub
End Class
In the OnTextBoxResize override, I am re-positioning the controls into their proper place, and this version does account for the UpDownAlign property.
If you can, read this thread over at EE where I answered a similar question. It resizes the edit portion so that the control is redrawn correctly when the buttons have been hidden and the control is resized. *Otherwise the portion of the control where the buttons used to be leaves ghosts behind.
One solution to your specific problem is to wait for the VisibleChanged() event and check the Increment() property from there. Here is a conversion of my previous answer with some minor changes:
Public Class NoArrowNumericUpDown
Inherits NumericUpDown
Private itb As InnerTextBox = Nothing
Protected Overrides Sub OnVisibleChanged(e As System.EventArgs)
If Me.Visible Then
If Me.Increment = 0 AndAlso IsNothing(itb) Then
Dim ctl As Control = Me.Controls(0) ' get the spinners
Me.Controls.Remove(ctl) ' remove the spinners
ctl = Me.Controls(0) ' get the edit control
itb = New InnerTextBox(Me, ctl)
End If
End If
MyBase.OnVisibleChanged(e)
End Sub
Public Class InnerTextBox
Inherits NativeWindow
Private parentControl As Control = Nothing
Const WM_WINDOWPOSCHANGING As Integer = &H46
Public Sub New(parentControl As Control, InnerTextBox As Control)
Me.parentControl = parentControl
Me.AssignHandle(InnerTextBox.Handle)
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case WM_WINDOWPOSCHANGING
Dim wp As WindowPos = CType(System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, GetType(WindowPos)), WindowPos)
If Me.parentControl IsNot Nothing Then
wp.cx = Me.parentControl.ClientSize.Width - 2 * wp.x
wp.cy = Me.parentControl.ClientSize.Height
System.Runtime.InteropServices.Marshal.StructureToPtr(wp, m.LParam, True)
End If
Exit Select
End Select
MyBase.WndProc(m)
End Sub
Public Structure WindowPos
Public hwnd As IntPtr
Public hwndInsertAfter As IntPtr
Public x As Integer
Public y As Integer
Public cx As Integer
Public cy As Integer
Public flags As UInteger
End Structure
End Class
End Class
EDIT: You could just enclose your code in a Try/Catch block?
Public Class NoArrowNumericUpDown
Inherits NumericUpDown
Protected Overrides Sub OnTextBoxResize(ByVal source As Object, ByVal e As System.EventArgs)
Try
If Me.Increment = 0 Then
Controls(0).Hide()
End If
Catch ex As Exception
End Try
End Sub
End Class

How do I implement a TextChanging property on a TextBox?

I figured there would be a question like this already, but I didn't have any luck searching. I saw a question where someone asked the same thing but the answer was to use the TextChanged event. That's not what I want though.
TextBox has an event for TextChanged that occurs after the Text property has been changed. I need my control to raise an event before the Text property is actually changed to validate data. If it is valid the Text can be changed, if it is not valid the Text does not get changed.
Here's what I tried:
Public Class TextChangingEventArgs
Inherits System.ComponentModel.CancelEventArgs
Private p_sNewValue As String
Public Sub New()
p_sNewValue = String.Empty
End Sub
Public Sub New(sNewValue As String)
p_sNewValue = sNewValue
End Sub
Public Property NewValue As String
Get
Return p_sNewValue
End Get
Set(value As String)
p_sNewValue = value
End Set
End Property
End Class
Public Class BetterTextBox
Inherits TextBox
Public Event TextChanging(sender As Object, e As TextChangingEventArgs)
Public Overrides Property Text As String
Get
Return MyBase.Text
End Get
Set(value As String)
Dim e As New TextChangingEventArgs(value)
RaiseEvent TextChanging(Me, e)
If e.Cancel = False Then
MyBase.Text = value
End If
End Set
End Property
End Class
My in my Form I handle the TextChanging event:
Private Sub BetterTextBox1_TextChanging(sender As System.Object, e As TextChangingEventArgs) Handles BetterTextBox1.TextChanging
e.Cancel = Not Regex.IsMatch(e.NewValue, REGEX_PATTERN)
End Sub
This works for programmatically setting the Text value of the BetterTextBox control, but it does not work when you are typing into the text box.
Does anyone know what I need to do to get this to work?
Since you are already inheriting from TextBox, you can override WndProc and check for paste messages. This should resolve the right-click > Paste problem. You could then handle "regular," typed text in KeyPressed or KeyDown, as others have suggested.
Here's an example of what I'm talking about:
Private Const WM_PASTE As Integer = &H302
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WM_PASTE AndAlso Clipboard.ContainsText() AndAlso ShouldAllowPaste(Clipboard.GetText()) Then
MyBase.WndProc(m)
ElseIf m.Msg <> WM_PASTE Then
MyBase.WndProc(m)
End If
End Sub
Try this:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = 8465 Then
If HIWORD(m.WParam) = 1024 Then
' raise your TextChanging event
End If
End If
MyBase.WndProc(m)
End Sub
Public Function HIWORD(ByVal n As Integer) As UInteger
Return CUInt(n >> 16) And CUInt(&HFFFF)
End Function
I would suggest you should try with keydown event. It occurs before textchanged or even before
keypress.
Try the KeyPress event of the Textbox instead.
EDIT : to disable right-click in the Textbox, you can do something like this.
Put this code inside the Textbox's MouseDown event.
If e.Button = MouseButtons.Right Then
MessageBox.Show("can't paste!")
Exit Sub
End If
however I noticed you could still paste text using the key combination Ctrl + V. You can write this code to stop that in the KeyPress event.
If Keys.ControlKey And Keys.V Then
MessageBox.Show("can't paste!")
Exit Sub
End If

custom textboxes in 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.

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