VB.Net CheckedListBox automatically wrap text - vb.net

This is more a design than a programming question (well, maybe not). I have 4 CheckedListBoxes which are filled with Data from an SQLite-database (Visual Studio 2010), and some of the entries exceed the width of the Box. I know that I can include a horizontal scrollbar in a CheckedListBox but everybody hates horizontal scrollbars (very ugly), so I tried to find an option to automatically wrap the text that doesn't fit.
So if there is any solution to have the text wrapped when the width of the box is too small it would be awesome.
I could extend the window size but it is already over 1000px in width and some of the users use computers made of wood with 1024x768 solution, so that's not really an option.
Datagrid would be another option but I thought there must be an easier solution. Any hints?
Edit: Sorry, it's Windows Forms.

You can write your own CheckedListBox pretty easily using a panel with some actual CheckBoxes on it so you can do the other things you'd expect such as disable certain ones, fix the way it cuts off drop characters, iterate them and so forth.
The problem with wrap, is a) determining the Text Extent of long text so you know how tall to make each checkbox, and b) having to keep a cumulative items height so you know where to add the next one. Of course, once you support wrap, you have to be able to adjust them all which involves moving them when a text change causes one in the middle to grow/shrink.
The panel's AutoScroll handles all the scrolling for you, which includes adding a HSCroll as needed, which is not always desirable. One way to overcome this, which might work for the actual CheckedListBox you are using, is to eat the HScroll instead.
<DllImport("user32.dll")> _
Private Shared Function ShowScrollBar(hWnd As IntPtr,
wBar As Integer,
bShow As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Then in form load or perhaps after you have populated it:
ShowScrollBar(myControl.Handle, ScrollBarDirection.SB_HORZ, False)
You can also just subclass the existing CheckedListBox to eat the scrollbar in OnClientSizeChanged
Public Class CheckedListBox2
Inherits CheckedListBox
' optionally remove the scroll bar
Public Property VerticalScrollOnly As Boolean
' PInvokes
<DllImport("user32.dll", SetLastError:=True)>
Public Shared Function GetWindowLong(ByVal hWnd As IntPtr,
ByVal nIndex As Integer) As Integer
End Function
<DllImport("user32.dll")>
Private Shared Function ShowScrollBar(hWnd As IntPtr,
wBar As Integer,
bShow As Boolean) _
As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
'// window style constants for scrollbars
Private Const WS_VSCROLL As Integer = &H200000
Private Const WS_HSCROLL As Integer = &H100000
Private Const GWL_STYLE As Integer = -16
Private Enum ScrollBarDirection
SB_HORZ = 0
SB_VERT = 1
SB_CTL = 2
SB_BOTH = 3
End Enum
' eat the HScroll when it shows up
Protected Overrides Sub OnClientSizeChanged(e As EventArgs)
Dim HScrollVis As Boolean
HScrollVis = IsHScrollVisible(Me)
If VerticalScrollOnly AndAlso HScrollVis Then
ShowScrollBar(MyBase.Handle, ScrollBarDirection.SB_HORZ, False)
End If
MyBase.OnClientSizeChanged(e)
End Sub
Friend Shared Function IsHScrollVisible(ByVal ctl As Control) As Boolean
Dim wndStyle As Integer = GetWindowLong(ctl.Handle, GWL_STYLE)
Return ((wndStyle And WS_HSCROLL) <> 0)
End Function
End Class

Related

WM_SETREDRAW in RichTextBox fails in .NET Framework 4.7.2 but works in 4.6.2

I have a control that inherits from RichTextBox and the idea is, that the user types in numbers per line and the control colors each line read or green depending on if the text in the line is a valid number.
On TextChanged each line is checked and by applying SelectionColor the color of each line is adjusted accordingly. Please find the full code below.
In order to keep that somewhat speedy I suspend redrawing of the control before updating the colors by sending the WM_SETREDRAW message, as is described in various questions/answers on this site, e.g. this one about RichTextBox in particular.
I have used this for quite a while without issue but recently reused the control in a project that targets .NET Framework 4.7.2 instead of older frameworks like 4.5 or 4.6.
Here I run into the problem of graphical glitches as soon as the control contains enough lines to cause scrolling. The colors are not applied correctly, ghost cursors appear and so on. I tried to show that in the picture:
The only thing I changed between the second and third picture was to change the target framework to 4.6.2 and recompile.
Does someone have an idea what causes this and how to work around the problem?
Please feel free to use VB or C# to your taste, I don't judge :-)
Here is the code of the control I used to reproduce the behavior:
Public Class RichtextNumberlist
Inherits RichTextBox
Private Sub tbValues_TextChanged(sender As Object, e As EventArgs) Handles Me.TextChanged
SuspendDrawing(Me)
UpdateColor(Me)
ResumeDrawing(Me)
End Sub
<DllImport("user32.dll")>
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
End Function
Private Const WM_SETREDRAW As Integer = 11
Public Sub SuspendDrawing(ByVal Target As Control)
SendMessage(Me.Handle, WM_SETREDRAW, False, 0)
End Sub
Public Sub ResumeDrawing(ByVal Target As Control)
SendMessage(Me.Handle, WM_SETREDRAW, True, 0)
Target.Invalidate()
End Sub
Private Function CheckLine(line As String) As Boolean
Dim d As Double
Return Double.TryParse(line, d)
End Function
Private Sub UpdateColor(tbValues As RichTextBox)
Dim t = Threading.Thread.CurrentThread
Console.WriteLine($"Updating color from thread STA={t.GetApartmentState().ToString()}, BG={t.IsBackground}")
'Save selection parameters to reset them afterwards
Dim oldsel As Integer = tbValues.SelectionStart
Dim oldsell As Integer = tbValues.SelectionLength
Dim pos As Integer = 0
Dim lCount = tbValues.Lines.Count
Dim lines = tbValues.Lines
For i = 0 To lCount - 1
'Set selection on the ith line
tbValues.SelectionStart = pos
tbValues.SelectionLength = lines(i).Length
Dim lineok = CheckLine(lines(i))
'Adjust the color accordingly
If lineok Then
tbValues.SelectionColor = Color.ForestGreen
Else
tbValues.SelectionColor = Color.Red
End If
'Move forward
pos += lines(i).Length + 1
Next
'Reset the selection
tbValues.SelectionStart = oldsel
tbValues.SelectionLength = oldsell
End Sub
End Class

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

VB 2012 TextBox.Clear() not working

I'm working on an encryption program and it uses a "PIN" to calculate some stuff for the encryption. I have a textbox where the user can insert the "PIN". I'd like to prevent people from entering anything but numbers. I added this on the KeyPress event:
If Not Char.IsControl(e.KeyChar) Then
If Not Char.IsNumber(e.KeyChar) Then
MsgBox("Invalid character", , "WARNING!")
TextBox3.Clear()
End If
End If
It shows the msgbox and it doesn't write to the textbox until i close th emsgbox. The typed character appears in the textbox. When I write another one it works the same as before, but it only replaces the last character instead of writing another one. Is there something I'm missing because that looks like a bug to me?
Set the ES_NUMBER windows style for your TextBox:
Public Class Form1
Public Const GWL_STYLE As Integer = (-16)
Public Const ES_NUMBER As Integer = &H2000
Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
(ByVal handle As IntPtr, ByVal nIndex As Integer) As Integer
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal handle As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer
Public Sub SetNumbersOnlyTextBox(ByVal TB As TextBox)
SetWindowLong(TB.Handle, GWL_STYLE, GetWindowLong(TB.Handle, GWL_STYLE) Or ES_NUMBER)
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
SetNumbersOnlyTextBox(TextBox3)
End Sub
End Class
It shows the msgbox and it doesn't write to the textbox until i close th emsgbox.
Yes, that's what modal dialogs do. They block the caller from updates until closed. That's the point; the user cannot interact with the parent until they clear the modal child.
Why not simply clear the textbox first? Better yet; don't show an annoying dialog at all. Simply disallow the user from entering invalid characters by setting e.Handled to true. However, it's a bit trickier than it sounds as you need to allow for the backspace and delete keys, disable pasting, etc.
Here's an example of a NumericTextbox: http://msdn.microsoft.com/en-us/library/ms229644(v=vs.80).aspx
You just need to set the Handled property to true instead of clear:
e.Handled = True
As MarkPM notes above, if its a key you don't want you can set e.handle=true (as you intercept the key on the keypress event) to have the system eat it.
Along with this, in stead of a pop-up, you can have a label on the form that says "Only numbers can be entered here" or something like that. Set it up so that the color of the text is red. Also set it up so the label is not normally visible.
Finally, also in the keypress event, beyond setting e.handle=true for unwanted keys, when an unwanted key comes along make the label that says "Only numbers can be entered here" visible - you can also set up a timed event to turn the label's visibility off after a few seconds. You can also throw a Beep() into the mix if you like :-)
This is less invasive then a pop-up and moves things along nicely for the user.

Vertical ScrollBar does not repaint using EnableScrollBar api

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.

Outlook VBA Macro: Best way to indicate 'please wait'

What's the best practice for indicating to the user that a Macro is running within Outlook ?
The macro can take around 1-30 seconds to complete.
I want to avoid a modal 'msgbox' popping up before the macro is run, as this can be annoying.
I would rather avoid the hourglass cursor if possible, and wondered if there was a better way.
Is there a way of placing a non-modal 'status' message up, whilst the macro is running?
(The macro I have runs against the currently selected mailItem - and it launched by a button on the Quick Access Toolbar).
This article (also this) on best practice says use the status bar.
This article on Outlook says:
Changing the Status Bar
There is no
way to change the status bar text in
Microsoft Outlook. The status bar is
not exposed as it is in other
Microsoft Office object models.
Outlook.com provides code for a progress box.
Couple of things that string to mind, I am sure other will have ideas as well.
1.Show a form with a progress bar on it that reports progress or has the progress bar in marque mode if you can’t report progress
2.Show a form with a picture box with your favourite animated gif inside(spinny pizza etc.). You can turn off the buttons etc.
3. Use win api to get play with the outlook staus bar
Not knowing what you are doing in your macro you may have to deal with keeping the form “On top” and pumping async progress into it.
Cheers
Marcus
Expanding on #76mel's answer, a nice way to do this is with a non-modal userform. Make something really simple with just a label and caption like this:
What I like to do is have the userform set as:
Non modal (in properties F4, set ShowModal to false)
This means you can click outside the status bar and it doesn't stop you.
I set the StartupPosition to 0-Manual and Top and Left to something like 100 so that the Status form appears in the top left corner of the screen (out of the way of any other messages which appear in centre by default)
Set the label's value to some default text for when the Userform first loads
Public strStatus As String
Public Const defaultStatus As String = "Default status text" 'set this to whatever you want
Sub statusReporter()
frmStatus.Show
'''
'Your code here
'''
frmStatus.lblStatus = "Step 1"
'...
frmStatus.lblStatus = "Step 2"
'...
'''
'Unload the form
'''
frmStatus.lblStatus = defaultStatus
frmStatus.Hide
End Sub
Note, like with Excel's Application.Statusbar you must reset the userform to its default value if you plan to use it later on in the same instance of Excel
Optionally use this too
'Written By RobDog888 - VB/Office Guru™
'Add a Command Button so you can toggle the userform's topmost effect
Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function SetWindowPos Lib "user32" ( _
ByVal hwnd As Long, _
ByVal hWndInsertAfter As Long, _
ByVal X As Long, _
ByVal Y As Long, _
ByVal cx As Long, _
ByVal cy As Long, _
ByVal wFlags As Long) As Long
Private Const HWND_TOPMOST = -1
Private Const HWND_NOTOPMOST = -2
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOSIZE = &H1
Private mlHwnd As Long
Private Sub UserForm_Initialize()
Dim overTim As Single
overTim = Timer
mlHwnd = FindWindow("ThunderDFrame", "Status") 'Change "Status" to match your userforms caption
Do While mlHwnd = 0 And Timer - overTim < 5
mlHwnd = FindWindow("ThunderDFrame", "Status")
DoEvents
Loop
'Set topmost
SetWindowPos mlHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE
End Sub
in the userform code itself to keep it on top always