Vertical ScrollBar does not repaint using EnableScrollBar api - vb.net

I am trying to disable a VScrollBar control using the EnableScrollBar api. When I call the api it returns as if no problems ocurred but the VScrollBar is not repainted.
To reproduce the problem create a Vb.Net windows forms project, drop a VScrollBar control and a button to the form and paste the following code:
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True, ExactSpelling:=True)>
Public Shared Function EnableScrollBar(ByVal hWnd As IntPtr, ByVal nBar As Integer, ByVal value As Integer) As Boolean
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim wSBflags As UInteger = 3UI 'SB_VERT
Dim wArrows As UInteger = 3UI 'ESB_DISABLE_BOTH
Dim result As Boolean = EnableScrollBar(Me.VScrollBar1.Handle, wSBflags, wArrows)
End Sub
I tried using SendMessage to send a redraw (WM_REDRAW) and a paint (WM_PAINT) but cant get it to work. Any ideias?
Ps: If you drop a multiline textbox and use the same code it works....

SB_VERT is for the vertical scrollbar as part of the non-client area of a window. For a scroll bar control, use the SB_CTL constant.

Related

Set the exact position of a Textbox's Scrollbar

I have a multi-line textbox with both its horizontal and vertical scrollbars visible, and WordWrap set to False. I load a large textfile in the textbox to allow the user to edit it.
The editing features automation which requires me to store the value of the entire textbox into a value, process it and then store it back into the textbox. This all is working great, but by setting a string into the textbox, it first clears it and then fills it again. This action sets the scrollbar position to the top-left corner.
I have tried to use Textbox1.scrolltocaret in my update routine which makes the cursor visible again, but it doesn't scroll to the exact position the control was set to before.
Also, I can't find out how to call textbox1.scrolltocaret every time the user moves using the arrowkey.
How can I store and restore the scrollbar locations?
Here's my code:
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
If Me.bPauseUpdate = True Then Exit Sub
Me.bPauseUpdate = True
Me.CursorLocation = TextBox1.SelectionStart
Dim aWorkingText() As String = Me.TextBox1.Lines
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Code that changes aWorkingText lives here, but is not relevant for this question
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
TextBox1.Lines = aWorkingText
Me.bPauseUpdate = False
TextBox1.SelectionStart = Me.CursorLocation
Textbox1.scrolltocaret
End Sub
I looked into using Textbox1.Scrollbars but they are only for controlling whether the scrollbars are visible.
I tried to make it work with the following code, but that does not allow me to actually set the value even though the IDE says I should be able to do so:
Me.ScrollbarX = TextBox1.AutoScrollOffset.X
Me.ScrollbarY = TextBox1.AutoScrollOffset.Y
.
.
.
TextBox1.AutoScrollOffset.X = Me.ScrollbarX
TextBox1.AutoScrollOffset.Y = Me.ScrollbarY
I even tried this, but it yields the same result.
TextBox1.AutoScrollOffset.X = New Point(Me.ScrollBarX)
TextBox1.AutoScrollOffset.Y = New Point(Me.ScrollBarY)
You can use the GetScrollPos Win32 API to get the current vertical and horizontal scrollbar position of the TextBox control.
Imports System.Runtime.InteropServices
<DllImport("user32.dll", CharSet:=CharSet.Auto)> Friend Shared Function GetScrollPos(hWnd As IntPtr, nBar As Integer) As Integer
End Function
Friend Enum SBOrientation As Integer
SB_HORZ = &H0
SB_VERT = &H1
End Enum
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Horizontal scroll position
GetScrollPos(TextBox1.Handle, SBOrientation.SB_HORZ)
'Vertical scroll position
GetScrollPos(TextBox1.Handle, SBOrientation.SB_VERT)
End Sub
Use the SendMessage Win32 API to set the TextBox scroll position
<DllImport("user32.dll")> Friend Shared Function SendMessage(hWnd As IntPtr, msg As UInteger, wParam As UInteger, lParam As UInteger) As IntPtr
End Function
Friend Const SB_THUMBPOSITION As UInteger = 4
Friend Enum WindowMessage As Integer
WM_HSCROLL = &H114
WM_VSCROLL = &H115
End Enum
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Set Vertical scroll position
Dim vPos = 16
Dim wparam1 = CUInt(vPos) << 16 Or (SB_THUMBPOSITION And &HFFFF)
SendMessage(TextBox1.Handle, CUInt(WindowMessage.WM_VSCROLL), CUInt(wparam1), CUInt(0))
'Set Horizontal scroll position
Dim hPos = 100
Dim wparam2 = CUInt(hPos) << 16 Or (SB_THUMBPOSITION And &HFFFF)
SendMessage(TextBox1.Handle, CUInt(WindowMessage.WM_HSCROLL), CUInt(wparam2), CUInt(0))
End Sub

How to load a specific text in textbox with greyish colour and uneditable [duplicate]

This question already has answers here:
Watermark TextBox in WinForms
(11 answers)
Creating a TextBox with watermark using ControlStyles.UserPaint shows the watermark just once at component creation
(2 answers)
Closed 2 years ago.
I am creating a login form by using Visual Basic. Is it possible for me to load a specific text in a textbox with greyish color and uneditable? (Just like the effect in Youtube search bar) What I manage to found on the internet is just hide function and change the color of the text.
Sorry for my confusing title.
Add this Module (ModExtentions) to the Project: it adds an extension method, SetCueBanner(), to TextBox Controls.
Specifying True or False, changes the cue banner behavior:
False: the cue banner is visible until the control gets focus,
True, the cue banner is visible until the first char is entered.
This internal functionality is activated sending an EM_SETCUEBANNER message to the Control.
Use it like this:
' The Cue Banner is visible until the control gets focus
TextBox1.SetCueBanner("Some text...", False)
' The Cue Banner is visible until a character is entered
TextBox1.SetCueBanner("Some text...", True)
The Module where the extension method is defined:
Imports System.Runtime.InteropServices
Public Module ModExtentions
Private Const EM_SETCUEBANNER As Integer = &H1501
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As String) As Integer
End Function
<Extension()>
Public Sub SetCueBanner(tbox As TextBox, ByVal text As String, ByVal showOnFocus As Boolean)
SendMessage(tbox.Handle, EM_SETCUEBANNER, If(showOnFocus, 1, 0), text)
End Sub
End Module
Set the TextBox Enabled property to false. You can put in code from code but the user can't type in the box.
Set up your gray text in the Form.Load. The use the Enter and Leave events to change it to normal and back again.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1.ForeColor = Color.Gray
TextBox1.Text = "Search term here..."
End Sub
Private Sub TextBox1_Enter(sender As Object, e As EventArgs) Handles TextBox1.Enter
TextBox1.ForeColor = Color.Black
TextBox1.Text = ""
End Sub
Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave
If String.IsNullOrWhiteSpace(TextBox1.Text) Then
TextBox1.ForeColor = Color.Gray
TextBox1.Text = "Search term here..."
End If
End Sub

Button in TextBox disappears when selecting text while Windows media player plays music

This is my first question, please be nice. Oh, and I'm not a native english speaker. :)
I've discovered some weird bug in my application. I've created a TextBox control with a button in it (code below). Following these steps will make the button disappear.
Start windows media player with some music (or video)
Start the application
Click in TextBox control and hold down left mouse button
Move your cursor around like crazy
Woosh... Button disappears.
This will not happen when you closed or paused your windows media player. I was able to reproduce this bug on a different system (Windows 7 and Windows 10). This is totally weird, because it doesn't seem logical. Windows is doing crazy stuff with the windows media player...
I'm not sure if there's a workaround. Can anybody help me with this or should I ask on Microsoft forums? I've tried to call "UpdateButton" while selection changed, but I wasn't successful.
Public Class TextBoxEx
Inherits TextBox
Const BUTTON_WIDTH As Integer = 18
Const EM_SETMARGINS As Integer = &HD3
Const EC_RIGHTMARGIN As Integer = &H2
<Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As IntPtr
End Function
Private btnCommand As Button
Public Sub New()
btnCommand = New Button
btnCommand.Cursor = Cursors.Default
btnCommand.Image = My.Resources.iconCancel
Me.Controls.Add(btnCommand)
Call UpdateButton()
End Sub
Private Sub UpdateButton()
Dim rightMargin As Integer = (BUTTON_WIDTH + 1) << 16
btnCommand.Size = New Size(BUTTON_WIDTH, Me.ClientSize.Height)
btnCommand.Location = New Point(Me.ClientSize.Width - BUTTON_WIDTH, 0)
Call SendMessage(Me.Handle, EM_SETMARGINS, EC_RIGHTMARGIN, rightMargin)
End Sub
Protected Overrides Sub OnResize(e As System.EventArgs)
MyBase.OnResize(e)
Call UpdateButton()
End Sub
End Class

How would I go about creating a textbox help label (cuebanner/cuetext)?

I wanted to create a textbox which has a help label inside the box which then disappears when the box has characters entered into it. I found one way of doing it which involves loading the form with text inside the textbox in the colour grey and then removing it when the user clicks on the box... The problem with this is i wanted to use a string.IsNullOrEmpty(textboxIP) but when the user hasn't typed anything into the box, the program sees the box as not empty as it has the pre-loaded writing in it. This is the code I used to remove the text on user click...
Dim WatermarkIP As String = "Yes"
Dim WatermarkPing As String = "Yes"
Private Sub textboxIP_Enter(sender As Object, e As EventArgs) Handles textboxIP.Enter
If WatermarkIP = "Yes" Then
textboxIP.Clear()
textboxIP.ForeColor = Color.Black
WatermarkIP = "No"
End If
End Sub
Private Sub textboxPing_Enter(sender As Object, e As EventArgs) Handles textboxPing.Enter
If WatermarkPing = "Yes" Then
textboxPing.Clear()
textboxPing.ForeColor = Color.Black
WatermarkPing = "No"
End If
End Sub
Does anyone know of a better way of creating a greyed out help/hint label inside the textbox which IS NOT counted as text inside the box, does not have to be deleted by the user before they can type in the box and is maybe a bit simpler?
I've found it finally!
This will give you that watermark text on your textbox controls:
Imports:
Imports System.Runtime.InteropServices
Global Declarations in your main class:
Private Const EM_SETCUEBANNER As Integer = &H1501
<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
Functions in your main class:
Private Sub SetCueText(ByVal control As Control, ByVal text As String)
SendMessage(control.Handle, EM_SETCUEBANNER, 0, text)
End Sub
Usage (usually in form_load event):
SetCueText(TextBox1, "blah")
SetCueText(TextBox2, "blahblah")
Hope this helps :)
Credit: http://www.vbforums.com/showthread.php?638105-CueBanner-Watermark-text-for-Textboxes

Creating a progress bar that runs on another thread, while keeping calculation in main thread

Preface: I know this is an unusual/improper way to do this. I can do this with a "real" ShowDialog(), background worker/thread, and so on. I'm not looking for help doing it that way; I am trying to do specifically what I describe here, even if it is ugly. If this is impossible for X reason, please let me know though.
I have created a fancy progress dialog for some of our long running operations. I need to have this dialog shown on a new thread while having processing continue on the calling (UI in most cases) thread.
This has 3 real requirements:
Prevent user interaction with the calling form (similar to ShowDialog(this))
Keep the progress dialog above the main window (it can fall behind now)
Allow the main thread to continue processing
What I have looks like this (and works just fine so far, as far as running goes, except for those issues above):
Using ... ShowNewProgressDialogOnNewThread() ...
Logic
UpdateProgress() //static
Logic
UpdateProgress() //static, uses Invoke() to call dialog
...
End Using // destroys the form, etc
I have tried a few ways to do this:
ShowDialog() on BackgroundWorker / Thread
Action.BeginInvoke() which calls a function
ProgressForm.BeginInvoke(... method that calls ShowDialog... )
Wrapping main form in a class that implements IWin32Window so it can be called cross-threaded and passed to ShowDialog() - this one failed somewhere later one, but at least causes ShowDialog() to not barf immediately.
Any clues or wisdom on how to make this work?
Solution (For Now)
The call to EnableWindow is what did what I was looking for.
I do not experience any crashes at all
Changed to use ManualResetEvent
I set TopMost, because I couldn't always guarantee the form would end up on top otherwise. Perhaps there is a better way.
My progress form is like a splash screen (no sizing, no toolbar, etc), perhaps that accounts for the lack of crashes (mentioned in answer)
Here is another thread on the EnableWindow topic (didn't reference for this fix, tho)
Getting the progress window consistently displayed on top of the (dead) form is the difficult requirement. This is normally handled by using the Form.Show(owner) overload. It causes trouble in your case, WF isn't going to appreciate the owner form belonging to another thread. That can be worked around by P/Invoking SetWindowLong() to set the owner.
But now a new problem emerges, the progress window goes belly-up as soon as it tries to send a message to its owner. Somewhat surprisingly, this problem kinda disappears when you use Invoke() instead of BeginInvoke() to update progress. Kinda, you can still trip the problem by moving the mouse over the border of the disabled owner. Realistically, you'll have to use TopMost to nail down the Z-order. More realistically, Windows just doesn't support what you are trying to do. You know the real fix, it is at the top of your question.
Here's some code to experiment with. It assumes you progress form is called dlgProgress:
Imports System.Threading
Public Class ShowProgress
Implements IDisposable
Private Delegate Sub UpdateProgressDelegate(ByVal pct As Integer)
Private mOwnerHandle As IntPtr
Private mOwnerRect As Rectangle
Private mProgress As dlgProgress
Private mInterlock As ManualResetEvent
Public Sub New(ByVal owner As Form)
Debug.Assert(owner.Created)
mOwnerHandle = owner.Handle
mOwnerRect = owner.Bounds
mInterlock = New ManualResetEvent(False)
Dim t As Thread = New Thread(AddressOf dlgStart)
t.SetApartmentState(ApartmentState.STA)
t.Start()
mInterlock.WaitOne()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
mProgress.BeginInvoke(New MethodInvoker(AddressOf dlgClose))
End Sub
Public Sub UpdateProgress(ByVal pct As Integer)
mProgress.Invoke(New UpdateProgressDelegate(AddressOf dlgUpdate), pct)
End Sub
Private Sub dlgStart()
mProgress = New dlgProgress
mProgress.StartPosition = FormStartPosition.Manual
mProgress.ShowInTaskbar = False
AddHandler mProgress.Load, AddressOf dlgLoad
AddHandler mProgress.FormClosing, AddressOf dlgClosing
EnableWindow(mOwnerHandle, False)
SetWindowLong(mProgress.Handle, -8, mOwnerHandle)
Application.Run(mProgress)
End Sub
Private Sub dlgLoad(ByVal sender As Object, ByVal e As EventArgs)
mProgress.Location = New Point( _
mOwnerRect.Left + (mOwnerRect.Width - mProgress.Width) \ 2, _
mOwnerRect.Top + (mOwnerRect.Height - mProgress.Height) \ 2)
mInterlock.Set()
End Sub
Private Sub dlgUpdate(ByVal pct As Integer)
mProgress.ProgressBar1.Value = pct
End Sub
Private Sub dlgClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs)
EnableWindow(mOwnerHandle, True)
End Sub
Private Sub dlgClose()
mProgress.Close()
mProgress = Nothing
End Sub
'--- P/Invoke
Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
If IntPtr.Size = 4 Then
Return SetWindowLongPtr32(hWnd, nIndex, dwNewLong)
Else
Return SetWindowLongPtr64(hWnd, nIndex, dwNewLong)
End If
End Function
Private Declare Function EnableWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal enabled As Boolean) As Boolean
Private Declare Function SetWindowLongPtr32 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
Private Declare Function SetWindowLongPtr64 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
End Class
Sample usage:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Using dlg As New ShowProgress(Me)
For ix As Integer = 1 To 100
dlg.UpdateProgress(ix)
System.Threading.Thread.Sleep(50)
Next
End Using
End Sub
I know it's a bit dirty but can't you just do the work in the dialog??
I mean something like
Dialog.MyShowDialog(callback);
and do all the work in callback as well as the UI update.
That way you'll retain the ShowDialog behaivour while allowing different code to be called.
I wrote a blog post on this topic a while ago (dealing with splash forms, but the idea is the same). The code is in C#, but I will try to convert it an post it here (coming...).