VB6 Label max string length? - vb6

I have a project for fun in VB6, I'm using quite long labels and would like them to stretch the entire length of my monitor however they seem to be capped at 256 chars per line. It let's me set their caption to as long as I like but after the 256th character, the rest does not appear on the screen.
If I change it to multiline however, it will display the full text but again will automatically take a new line at the 256th character meaning it doesn't utilise the full width of my monitor.
I was wondering if anyone knows why this is, a way around it or what my options are?
Edit: After testing, using a text box and making it look like a label is an alright work around, as text boxes don't seem to have this same restriction.
Edit 2: Text boxes lack an autosize function which is crucial to my project so any further advice is appreciated.

According to the MSDN documentation:
A Label control’s caption size is unlimited. For forms and all other
controls that have captions, the limit is 255 characters.
However, as you have seen this statement may not be correct. It appears to apply to Label controls as well, and the limit is actually 256 characters in my experimentation.
I think your idea of a TextBox control should work for what you need. Since there is no AutoSize property, simply change the width of the control in the Form Resize event.
Private Sub Form_Resize()
Text1.Width = Me.ScaleWidth
End Sub

Adding onto Brian's answer, yes, you will need to use a TextBox if you want your "label" to be more than 255 characters. You can make a TextBox look and act like a label if you set a few things.
First, set the BorderStyle property to vbBSNone (or 0, if you prefer). Then, you don't want users entering text into and otherwise changing your "label." If you're not fussy, you can set the Locked property to true. This isn't perfect, because setting the Locked property still allows users to click on the text and move around in it.
If you're really not fussy, you can set Enabled to false. This can confuse users, because everything gets greyed out and users are conditioned to understand that to mean that something is disabled. However, a disabled control can't be landed on or tabbed to, which is the behavior you want for a label.
If you want to get disabled behavior without altering the appearance, you need to use the API:
Private Const WS_DISABLED = &H8000000
Private Const GWL_STYLE = -16
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Sub Form_Load()
Dim theStyle As Long
theStyle = GetWindowLong(myTextBox.hwnd, GWL_STYLE) Or WS_DISABLED
Call SetWindowLong(myTextBox.hwnd, GWL_STYLE, theStyle)
End Sub
Pretty straightforward. GWL_STYLE is the index for the window's style properties. It's a hex value that amounts to a series of flags. If you Or the WS_DISABLED hex value with it, the result is to set the disabled flag. Which gets set when you set the window with the new value for GWL_STYLE.
Here are the different settings handled by GW_STYLE.

Related

How can it be possible to steal focus so easily?

Using Outlook 2013 on Windows 7, I created this Macro, just to test it:
Private Sub Application_Reminder(ByVal Item As Object)
Activeexplorer.Activate
End Sub
Whenever I am working on a different application and an Outlook reminder fires, Outlook becomes the active window stealing focus from the application I am working at.
How can it be possible??
I mean, I think that the “Activeexplorer.Activate” method uses some Windows api like “SetForegroundWindow” or maybe “SetActiveWindow” or some other api.
All these apis forbid to steal focus, so my question is how the Outlook vba method can so easily and horrifyingly able to steal focus?
The alarm is partially disarmed, as stated at https://msdn.microsoft.com/it-it/library/windows/desktop/ms633539(v=vs.85).aspx, SetForegroundWindow can be used by other processes not in the foreground if “The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).”
I changed the value of the registry key 'ForegroundLockTimeout' at 'HKCU:\Control Panel\Desktop' from zero to 20000 and now, in my specific case, Outlook won’t steal the focus.
I wonder which is the maximum value for the 'ForegroundLockTimeout' registry key, or, in other words, if it is possible to permanently disable any other process not in the foreground stealing focus from the active application.
Here's the reason Windows was not behaving as expected:
I changed the value of the registry key 'ForegroundLockTimeout' at 'HKCU:\Control Panel\Desktop' from zero to 20000
The value shouldn't have been zero in the first place. Something on your system, possibly a long time ago, must have explicitly changed this setting in order to disable the foreground lock. This has nothing to do with Outlook per se.
I recommend you set it back to the default, which is 200,000, i.e., 200 seconds.
As for the maximum, well, it has to fit into a DWORD, so probably about 49 days. If it is treated as a signed value internally, about 24 days. There's probably little point in setting it longer than a day.
Yes, SetForegroundWindow is supposed to respect the foreground window and just flash the taskbar for background applications but there are various hacks people use to trick Windows and steal the focus.
My preferred method of notifying the user about something important is to bring the window to the top without stealing the keyboard focus. The tricky bit would be to figure out which HWND to pass, I could not really find the HWND property of the ActiveExplorer form just by looking on MSDN.
Const HWND_TOP = 0
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOACTIVATE = &H10
Const SWP_SHOWWINDOW = &H40
Private Declare Sub 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)
...
SetWindowPos ??.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE Or SWP_SHOWWINDOW Or SWP_NOMOVE Or SWP_NOSIZE
Another alternative is to call FlashWindow to flash the taskbar button which is what SetForegroundWindow would do if you don't have the right to grab focus.

How can I force a vb6 control to redraw itself?

I've got a VB6 program. I'm using the Mainfest to apply "XP Themes" and give it the modern (as of 8 years ago!) look.
However, for graphical style Command buttons, I have to use some special code to redraw the button. Therein lies the problem.
When I click on one of this Graphical buttons it gets the proper "highlighting" of the background, but when another button gets the focus or mouseover, etc. that former button keeps the background highlighting.
If I move another window in front of it, the form redraws itself and this "residual" background color disappears.
I'm trying to figure out how to force that to happen.
What I've tried:
button.refresh
form.refresh
Doevents
Here is a video demo of the problem
I don't have anything like your setup to try this in, but you can try using the API call InvalidateRect. I've shown the declarations and created a Sub that uses it. It should be a simple copy and paste to try.
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Declare Function InvalidateRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT, ByVal bErase As Long) As Long
Private Declare Function GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Sub RefreshMe()
Dim udtRect As RECT
Call GetClientRect(Me.hwnd, udtRect)
InvalidateRect Me.hwnd, udtRect, 1
DoEvents 'give windows a chance to handle the event
End Sub

How to reset color properties of Microsoft Heirarchical FlexGrid?

I have an MSHFlexgrid in a legacy VB6 application that the users want to set various colours in. I've sorted out applying the user colours but I need to add a reset option as well. However I'm getting an overflow error when attempting the following line:
grdUserData.BackColor = vbWindowBackground
The same code works fine on other other controls I've used it with but errors with the MSHFlexGrid. The actual value of vbWindowBackground is &H80000005 or -2147483643.
I tried reading the BackColor property of another control but that has the same underlying value and thus also causes the same error.
How can I get the actual BGR/RGB (or long) value used for the control rather than the preset which I'm assuming is part of a look up for the color at runtime?
While it's easy enough to estimate what the right color is for some controls for the current Windows theme I need to get the right color for the current theme regardless of what the current theme is.
How about this?
Private Declare Function GetSysColor Lib "user32" ( _
ByVal nIndex As Long) As Long
Private Function BgrColor(ByVal Color As Long) As Long
If Color >= 0 Then
BgrColor = Color
Else
BgrColor = GetSysColor(Color And &HFFFFFF)
End If
End Function

How do I programmatically change the "underline keyboard shortcuts" Control Panel setting?

In the "Control Panel > Ease of Access Centre > Make the keyboard easier to use" is an option to "Underline keyboard shortcuts and access keys."
Is there a way of programmatically switching this on and off?
I'm using Visual Basic Scripts, but can use .NET.
Run Registry Editor and go to HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Preference
Now create or modify a String Value (REG_SZ) called On and set its value to 1
Information is comming from:
http://www.windowsvalley.com/get-underlined-keyboard-shortcuts-and-access-keys-permanently/
AFAIK, there's no way to toggle this option programmatically except for automating the approproate GUI actions (opening the Control Panel, switching the option on/off and applying the changes). In this case, I'd recommend using AutoIt to automate the option switching.
It turns out you CAN programatically change the “underline keyboard shortcuts” option in your own application. You need to send the WM_UPDATEUISTATE message to your main form according to the documentation found at: https://learn.microsoft.com/en-us/windows/win32/menurc/wm-updateuistate
Since you mentioned Visual Basic, here's how to do it:
Private Const WM_UPDATEUISTATE = &H128
Private Const UIS_CLEAR = &H2
Private Const UISF_HIDEACCEL = &H2
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hWnd As
Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Then in the "Form_Load" event send the message and it will activate keyboard shortcuts underlining for all controls and menus present on that form:
Private Sub Form_Load()
PostMessage Me.hWnd, WM_UPDATEUISTATE, UIS_CLEAR + UISF_HIDEACCEL * 65536, 0
End Sub

Allow vertical scrolling in listbox, when it is disabled (VB6)

I need to allow the vertical scrollbar in a multiselect listbox (VB6) however, when the control is disabled, I can't scroll.
I would think there is an API to allow this, but my favorite VB6 site (MVPS VB.NET) does not have a way.
I toyed with pretending it was disabled, and ignore the clicks... but to do that with VB6 code is really ugly... so if this is a solution, I need an API to ignore the clicks.
Thanks for your help.
I came up with the following code, which hides all of the gnarly details behind a class. Basically, I implemented greg's idea of using overlaying another scrollbar on top of the disabled list box's scrollbar. In my code, I dynamically create another ListBox control (resized so that only its scrollbar is visible), and use its scrollbar to scroll the actual ListBox. I also specifically avoided using the Windows API (except for the call to GetSystemMetrics that I used to figure how how wide a scroll bar is on the system). The nice thing about using another ListBox's scrollbar is that it will be themed properly (a ListBox uses the OS's theme when it displays it's scrollbar, but a VB.Scrollbar doesn't, so it would look out-of-place). Another advantage of using a second ListBox to scroll the first list box is that it's really easy to implement the scrolling logic (just set the first ListBox's TopIndex property to the second ListBox's TopIndex property whenever the second one is scrolled).
I also set it up to be as low-impact as possible (you only have to call a single function in your Form_Load event to make it work).
Usage
Add CustomScrollingSupport.cls and ListBoxExtras.bas to your project.
In your form's Form_Load event, add the following line:
AddCustomListBoxScrolling Me
This will make every VB.ListBox on the form support scrolling even while they are disabled. If you only want to add this functionality to a select number of ListBox's, you can call AddCustomScrollingSupport instead, passing in a specific ListBox control.
Interesting Note
In an older version of this code, I wasn't calling the ZOrder method on the second listbox (the one that provides the scrollbar) to make sure it would appear on top of the first listbox. This meant the second listbox was actually behind the first listbox; the interesting thing is that the scrolling on the second ListBox still worked when the first ListBox was disabled! Apparently, when the first ListBox is disabled, any mouse and keyboard events that would have gone to that ListBox "bleed through" to the second ListBox, so scrolling support still does work. I'm not sure if this is a bug or by design (I mean, you could argue that it makes sense that controls behind a disabled control would be able to receive events...). However, I found the scrolling to be slightly jerky at times, so I decided to add .ZOrder 0 to make the second listbox render on top of the first one. This has the drawback that you see the frame border for the second listbox (to the left of the scroll bar), which you wouldn't see if it was hidden behind the first listbox, but the scrolling is smoother.
CustomScrollingSupport.cls
This class wraps up the logic necessary to add "custom scrolling support" (for lack of a better name) to a VB.ListBox control. It should not be used directly, instead use the one of the Add* methods in the ListBoxExtras.bas module (I'll provide the code for that module later in the post).
Option Explicit
Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Private Const SM_CXVSCROLL = 2
Private Const SM_CXFRAME = 32
Private m_runningScrollers As Collection
Private WithEvents m_list As VB.listbox
Private WithEvents m_listScroller As VB.listbox
'--------------------------------------------------------------'
' Bind '
' '
' Adds custom scrolling support to a ListBox control. '
' Specifically, it allows the ListBox to be '
' scrolled even when it is disabled. '
' '
' Parameters: '
' '
' + list '
' the ListBox control to add custom scrolling support to '
' '
' + runningScrollers '
' a Collection of CustomScrollingSupport objects. Passed '
' in so that this object can remove itself from the list '
' when it is terminated. '
' '
'--------------------------------------------------------------'
Public Sub Bind(ByVal list As VB.listbox, runningScrollers As Collection)
Set m_list = list
Set m_runningScrollers = runningScrollers
'Create another ListBox loaded with the same number of entries as the real listbox'
Set m_listScroller = m_list.Container.Controls.Add("VB.ListBox", list.Name & "_scroller")
LoadScrollerList
Dim nScrollbarWidth As Long
nScrollbarWidth = GetSystemMetricScaled(SM_CXVSCROLL, m_list) + _
GetSystemMetricScaled(SM_CXFRAME, m_list)
'Display the other listbox (the "scroller"), just wide enough so that only its scrollbar is visible'
'and place it over the real listboxs scroll bar'
With m_listScroller
.Left = m_list.Left + m_list.Width - nScrollbarWidth
.Top = m_list.Top
.Height = m_list.Height
.Width = nScrollbarWidth
.Enabled = True
.Visible = True
.ZOrder 0
End With
End Sub
Private Sub m_listScroller_Scroll()
'If the master list has changed, need to reload scrollers list'
'(not ideal, but there is no ItemAdded event that we could use to keep the lists in sync)'
If m_list.ListCount <> m_listScroller.ListCount Then
LoadScrollerList
End If
'Make any scrolling done on the scroller listbox occur in the real listbox'
m_list.TopIndex = m_listScroller.TopIndex
End Sub
Private Sub Class_Terminate()
Dim scroller As CustomScrollingSupport
Dim nCurrIndex As Long
If m_runningScrollers Is Nothing Then
Exit Sub
End If
'Remove ourselves from the list of running scrollers'
For Each scroller In m_runningScrollers
nCurrIndex = nCurrIndex + 1
If scroller Is Me Then
m_runningScrollers.Remove nCurrIndex
Debug.Print m_runningScrollers.Count & " scrollers are running"
Exit Sub
End If
Next
End Sub
Private Sub LoadScrollerList()
Dim i As Long
m_listScroller.Clear
For i = 1 To m_list.ListCount
m_listScroller.AddItem ""
Next
End Sub
Private Function GetSystemMetricScaled(ByVal nIndex As Long, ByVal ctrl As Control)
GetSystemMetricScaled = ctrl.Container.ScaleX(GetSystemMetrics(nIndex), vbPixels, ctrl.Container.ScaleMode)
End Function
ListBoxExtras.bas
This module contains two utility methods:
AddCustomScrollingSupport adds custom scrolling functionality
to an individual VB.ListBox
control
AddCustomListBoxScrolling adds custom scrolling
functionality to every VB.ListBox
control on a given Form
Option Explicit
Public Sub AddCustomScrollingSupport(ByVal list As VB.listbox)
Static runningScrollers As New Collection
Dim newScroller As CustomScrollingSupport
Set newScroller = New CustomScrollingSupport
runningScrollers.Add newScroller
newScroller.Bind list, runningScrollers
End Sub
Public Sub AddCustomListBoxScrolling(ByVal frm As Form)
Dim ctrl As Control
For Each ctrl In frm.Controls
If TypeOf ctrl Is VB.listbox Then
AddCustomScrollingSupport ctrl
End If
Next
End Sub
Rather than looking for the API to ignore clicks, can't you just ignore the events? (i.e. just don't do when the user clicks/selects something).
And, I think there is a SelectionMode property to disable multiselect and make it single select.
If you don't want the user to be able to select anything at all, you can try hooking into the SelectionIndexChanged event and set the SelectionIndex to -1.
My VB6 is a bit rustly, so sorry if the event/property name don't match exactly.
Speaking of hacks, what if you enable the scrollbar when the mouse is moving over the scroll bar?
Or maybe ... place another scroll bar over the ListBox's SB, and use APIs to scoll the disabled LB.
Enabling just the scrollbar on a disabled listbox is possible (I think), but you'd have to dig into the Windows API and do SendMessage and some other grisly stuff.
I just checked it out, and when a listbox is disabled you can still scroll it up and down programatically by changing the control's ListIndex property. So you could do something like what greg suggests, and "float" an enabled vertical scroll bar over the one on the list box, and use this scrollbar's Value_Changed event (I think that's what it's called) to change the listbox's ListIndex property.
This is a total VB hack, but I think you can leave the listbox enabled, and then drag a transparent label (with blank text) over all of the listbox except the scrollbar. The label will intercept any mouse clicks (although this won't affect keystrokes).
This will only work if the label is transparent like I remember (it may be the image control - without a loaded image - that works like this in VB).

Resources