Is it possible to center windows forms with vba? - vba

My VBA macro is formatting a USB drive before it copies data on it.
The whole process is already working quite good.
So the problem is, that the Windows Format Tool window is opening somewhere on the top left corner of the screen and not like I want it, centered and pretty in the middle of the screen.
So is there a possibility to open them centered or if not, is there anything to center them afterwards?
This is how the window starts:
Private Sub format_drive(selected_volume)
Dim Result&, Drive&
varray = Split(selected_volume, ":")
Drive = Asc(varray(0)) - 65
Result = SHFormatDrive(0, Drive, SHFD_CAPACITY_DEFAULT, SHFD_FORMAT_QUICK)
End Sub

If it is a macro then there must be a parent application, most probably Excel. When a userform is launched from under Excel, the form will be positioned within the active window (that may be maximized). If you want to change the position of your form taylor the following sample to your needs:
Private Sub UserForm_initialize()
Me.Show vbModeless
With Me ' this must come after .Show
.Top = (Abs(Application.Top) + Application.Height - .Height)/2
.Left = (Abs(Application.Left) + ActiveWindow.Width - .Width)/2
End With
Application.Top can be negativ, Abs() is applied to handle it. It's not the most correct yet it's safe. Alternatively you can use ActiveWindows:
With Me
.Top = (Application.ActiveWindow.Height - .Height)/2
.Left = (Application.ActiveWindow.Width - .Width)/2
End With
At an advanced level you can use GetDeviceCaps(HDC hdc, HORZRES) and GetDeviceCaps(HDC hdc, VERTRES) to get the number of pixels of your screen. See this for more: GetDeviceCaps

Related

VB.NET - Change form size by screen resolution:

I'm presuming the answers very simple, but here's the issue, my option menu has two parts, normal settings and advanced settings, the advanced settings are accessed by pressing the advance button, fairly basic. This will make the form bigger unveiling the further settings, but the only problem I have is making a form bigger on different resolution does not make it bigger by the same factor - to put what i saying into context, if i increase the size of my form on a standard 1080p monitor, then run my program on a laptop resolution of 1366 by 768, the form will be much smaller than what i got it to draw on the 1080p resolution screen.
Heres the code I'm using to increase the form size:
Private Sub Advance_btn_Click(sender As Object, e As EventArgs) Handles Advance_btn.Click
If advanced = 0 Then
Me.Size = New Drawing.Size(262, 198) ' will increase form size
Advance_btn.Text = "<" ' text for button changes to know i have pressed the button - (to '<')
advanced = 1
ElseIf advanced = 1 Then
Me.Size = New Size(262, 85) ' will decrease form size
Advance_btn.Text = ">" ' text for button changes to know i have pressed the button - (to '>')
advanced = 0
End If
End Sub
The form is also without borders, if that makes somewhat a difference, thanks.
Me.Height = Screen.PrimaryScreen.Bounds.Height
Me.Width = Screen.PrimaryScreen.Bounds.Width * 0.6
In this example I use for years, the width is set to 60% of the screen width. This is in an IF statement, where I check if the user has a 4K monitor, if not, a full-screen mode is set at application start-up. You can, perpahs, do a similar thing with your drawing area.

Event handling class doesn't fire unless I break userform initialization

This is a follow-up to the following question:
Can't set Userform.KeyPreview to true
To recap: the goal is to build a form with some command buttons and a frame containing check boxes. The check boxes are dynamically populated at userform_initialize in the frame so the user can scroll through them. My problem was with keyboard shortcuts. It wasn't possible to brute force write KeyDown handlers for each of the checkboxes because I don't know which ones will exist. Unfortunately, Excel doesn't support KeyPreview so I had to mock up my own version. Thank you to #UGP for giving me promising avenues that seem to work, but not quite...
First, this is my class module called clsReasonPickKP. I create a new instance for each checkbox to listen for KeyDown events:
Option Explicit
Dim WithEvents vChkBx As MSForms.CheckBox
Friend Sub initializeListener(cControl As control)
Set vChkBx = cControl
End Sub
Private Sub vChkBx_KeyDown(ByVal keyCode As MSForms.ReturnInteger, ByVal shift As Integer)
frm2.keyChooser keyCode
End Sub
The line frm2.keyChooser keyCode launches a quick sub located in the userform code module. Code below:
Public Sub keyChooser(ByVal keyCode As MSForms.ReturnInteger)
Select Case keyCode
Case vbKeyEscape: cancelBtn_Click
Case vbKeyReturn: completeDecision_Click
Case vbKeyN: customizeNote_Click
Case vbKeyS: resetDecisionNote_Click
Case vbKeyR: chkRefGrnds_Click
End Select
End Sub
I've copied the relevant part of the UserForm_Initialize sub below. The loop creates the checkboxes and an event listener for each.
Sub UserForm_Initialize()
Dim x As Long, maxWidth as Long
Dim cControl As control
Dim keyPreviewCollection As New Collection
Dim keyPreviewer As clsReasonPickKP
For x = 1 To dTbl.Rows.Count - 1
Set cControl = chkBoxFrame.Controls.Add("Forms.CheckBox.1", "chkBox" & x, True)
With cControl
.AutoSize = True
.WordWrap = False
.Left = 10
.Top = 16 * x - 12
.Caption = dTbl(x, 1).Value
If .Width > maxWidth Then maxWidth = .Width
End With
Set keyPreviewer = New clsReasonPickKP
keyPreviewer.initializeListener cControl
keyPreviewCollection.Add keyPreviewer
Next x
'Additional initialization code here
End Sub
The odd thing is that unless I break code some time after keyPreviewCollection.Add keyPreviewer, the listener doesn't seem to handle the event. For example, if I set a break point at Next x or for x > 1 and then complete initialization, then when the form is finished initializing and appears the listener calls keyChooser and all is well; if I don't break code like that, it doesn't trap the event or call the sub, etc.
To trouble-shoot, I've tried not adding keyPreviewer to the collection, and then the listener also doesn't work, no matter if or when I break. It seems adding the object to the collection, and being in code break mode after adding it to the collection, somehow makes the listener trap the event.
Also interesting, if I put a breakpoint in the vChkBx_KeyDown module, it breaks when the event is raised (assuming an appropriate break as described above). After I then run the code, however, it stops handling the KeyDown event when its raised.
In case it helps, I'm currently working in Excel 2010.
Does anybody have any idea what's going on? Any idea how to solve this, even with a different code approach?
Thank you as always for everybody's help.
It turns out that the problem was so simple and right in front of my eyes. I just had to make the keyPreviewer and keyPreviewCollection variables public in my userform code module.
That still doesn't answer why breaking code execution after adding the object to the collection made VBA treat it as public, but just happy that it all works.
DoEvents might be the ticket. See the article below:
https://www.automateexcel.com/vba/doevents/

Control Tip Text on dual monitors?

I have a small user community for an application that contains a lot of user forms and fields.
I have added ControlTipText on most of the fields as helper/reminder.
The problem occurs when the user form is opened/displayed on the secondary monitor, the yellow stuff
(ControlTipText) is popping up on the right edge of the primary monitor.
Not a huge issue, but annoying.
Anyone who has a fix for this?
Same issue, resolved by:
Creating a label with a distinctive background colour positioned over the control that you want to write tip text for
Initialise the UF with the label visible = false, e.g. Userform1.label1.visible = false
use the "mousemove" event to write code to show the label
Private Sub TextBox1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
label1.visible = true
'position label as desired
End Sub
use the click or mousemove event on the label this time to banish the tool tip. You will have lots of labels over controls in the VB editor.
As an alternative I creatie a label with an "Information" icon that is clickable to unhide the tooltip label rather than mousemove event sometimes
As ControlTipText is a property of the Text Box it doesn't have any members which would allow you to adjust it's position.
It seems like the only way the issue can be resolved is by viewing Excel on your primary monitor. If it's useful, you can force Excel to move to the primary monitor with something like this:
With Application
.WindowState = xlNormal
.Left = 0
.Top = 0
.WindowState = xlMaximized
End With

How to get Winforms Panel to correctly layout many items?

I've got a WinForms Panel control which holds a large number of child controls. Each child is left docked, causing the horizontal width of the contents to grow. The containing Panel has its AutoScroll property set so that you can get to all the contents.
I'm running into a problem when the total width of the contents gets too large. Once you've hit this maximum width, additional content elements are placed on top of existing contents instead of being placed to the right. But, if I resize the Panel after it has done its initial layout, it corrects itself by expanding its logical width and placing each content element in the correct location. How do I get it to layout correctly before the user resizes the window?
Here's a simple example:
Form1.vb
Public Class Form1
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
For i As Integer = 1 To 200
Dim gb As New GroupBox
gb.Text = "Box " & i.ToString
gb.Width = 250
gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
gb.BringToFront()
Next
End Sub
End Class
Form1.Designer.vb
Partial Class Form1
Inherits System.Windows.Forms.Form
Private Sub InitializeComponent()
Me.Panel1 = New System.Windows.Forms.Panel()
Me.SuspendLayout()
'
'Panel1
'
Me.Panel1.AutoScroll = True
Me.Panel1.Dock = System.Windows.Forms.DockStyle.Fill
Me.Panel1.Location = New System.Drawing.Point(0, 0)
Me.Panel1.Name = "Panel1"
Me.Panel1.Size = New System.Drawing.Size(284, 262)
Me.Panel1.TabIndex = 0
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 262)
Me.Controls.Add(Me.Panel1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents Panel1 As System.Windows.Forms.Panel
End Class
This is what the window looks like when it first comes up, scrolled nearly to the end so you can see the problem area. Notice that Box 183 to 199 are missing because they are placed on top of each other. This is not right.
This is what the window looks like after you manually resize it, scrolled nearly to the end. The panel fixed itself in response to the resize; the total logical width of the panel was automatically extended enough to hold all the contents. This is what I want it to look like when it first comes up.
I've tried manually setting the location of each box, and I've tried calling PerformLayout() and several other functions. Nothing seems to work. So far I haven't found the magic combination to get the good layout. Does anyone know how to fix this?
Edit:
Here's a screenshot that might make the issue more obvious. I adjusted the box widths and the number of boxes to show the problem better. See how the last box overlaps box 656? Every box from 657 to 700 has the same incorrect location. Turning off docking and setting the location myself doesn't help.
Looks like a bug with the scrolling information. If you call PerformLayout when the Panel is scrolled all the way to the right, it correctly places the controls in the proper place. That requires some code in the OnShown method:
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
Panel1.AutoScroll = True
Panel1.SuspendLayout()
For i As Integer = 1 To 200
Dim gb As New GroupBox
gb.Text = "Box " & i.ToString
gb.Width = 250
gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
gb.BringToFront()
Next
Panel1.ResumeLayout(False)
End Sub
Protected Overrides Sub OnShown(e As EventArgs)
MyBase.OnShown(e)
Panel1.AutoScrollPosition = New Point(Panel1.HorizontalScroll.Maximum - _
Panel1.HorizontalScroll.LargeChange, 0)
Panel1.PerformLayout()
Panel1.AutoScrollPosition = Point.Empty
End Sub
Of course, having over 200 container controls on the form is never recommended.
AutoScroll is not AutoPositionMyChildren. From MSDN:
When adding controls programmatically to a form, use the AutoScrollPosition property to position the control either inside or outside of the current viewable scroll area.
If you looped thru the controls, to print their location, you's see at some point (probably around 130) that Location.Y becomes fixed at 32767 probably some default unscrolled max. This is also the point they start stacking because they in fact have the same initial location. Some of the code you have makes up for that but it isnt quite right. Once you scroll it, the panel fixes the coords on the child controls.
First, I would suggest that you set Panel1.AutoScrollMinSize to something like {480, 0} so that the HScroll bar appears at design time; this allows you to calc a good height for the boxes which wont cause a VScroll as you add controls.
Dim gb As GroupBox
' only 150 because problem is when (i * width) > 32k
For i As Integer = 0 To 150
gb = New GroupBox
gb.Name = i.ToString ' added
gb.Text = "Box " & i.ToString
gb.Width = 250
' no docking so set the height
gb.Height = Panel1.Bounds.Height - 30 ' trying to avoid the VSCroll
' set location explicitly
gb.Location = NewCtlLocation(Panel1.Controls.Count,
Panel1.AutoScrollPosition.X)
' Dock and Anchor mess up the AutoScroll
'gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
' allow panel to update its scroll positions
Panel1.ScrollControlIntoView(gb)
' not needed; seems to offset something with Dock
' changing ZOrder may not always be desirable
'gb.BringToFront()
' debug illumination
Console.WriteLine("{0} {1} {2}", i.ToString,
Panel1.AutoScrollPosition.X.ToString,
gb.Location.X.ToString)
Next
'Go back to start
Panel1.ScrollControlIntoView(Panel1.Controls("0"))
Location helper so you can tweak gutters or margins (dock replacement):
Friend Function NewCtlLocation(ByVal n As Integer,
ByVal ScrollPosX As Integer) As Point
Const TopMargin As Integer = 5
Const LeftMargin As Integer = 5
Return New Point((n * 250) + ScrollPosX, 0)
End Function
Notes:
I have a vertical scroller which repeatedly adds up to 120 user controls which works well but it does not need/use ScrollControlIntoView and they never stack up like yours do. I suspect maybe because they are smaller. There is also at least a second or two before the next one can be added, which may matter. But, good to know.
It might be possible to use the ControlAdded event of the panel to do something, but it would likely amount to ScrollControlIntoView. Doing it once at the end only doesnt work, so using it as they are added is allowing something to get updated as you go.
With the right fiddling, you might be able to get Dock to work, but I suspect it may be part of the problem such as Height and Left set this way dont update the panel's internal scroll map.
Your boxes actually look narrower than 250 - is autosize on?
Suspend/Resume Layout hurt rather than help - they prevent the control from doing anything about the virtual area being populated. It should happen fast enough that no one will see anything. Results:
Works on My SystemTM

VB.NET panel scrolling without showing scrollbars

I am trying to create a custom UI element: a panel, derived from the original panel, which is scrollable with special scrollbars (not the integrated ones) and has some other special abilities.
The actual problem is the scrolling. When I change the value of the custom scrollbar (e.g. scrolling), the panel-integrated scrollbars show up suddenly, although autoScroll = false.
Leading to the unintended state where both scrollbars are visible, the integrated and my custom one.
Private Sub ScrollB_EvValueChanged(NewVal As Integer) Handles ScrollB.EvValueChanged
Me.CleanPanel1.VerticalScroll.Value = NewVal
End Sub
How can I assign the new scrolling position (the new offset), determined by the custom scrollbar, to the panel without showing up panel-integrated scrollbars?
Sadly a panel (or usercontrol) with another panel on it and playing with the .Top and .Left properties of the inner panel to simulate scrolling is not an appropriate solution in my case.
Thank you for all your hints!
'I've been looking for methods of doing this all over, most of it is way more complicated than it needs to be, or you gotta write a whole dang program just to remove the bars to scroll. Anyhow, here's a quick, effective, neat method of doing this (since I was having trouble finding anything, I'll post it.
'e.delta detects a mouse wheel tick, if greater than 0, scroll up, less than 0 scroll down.
' the -91 part deducts some of the panel overhang (adjust as needed)
'I have buttons in this project that are 50 tall, so this scrolls perfectly for me.
'the nested if statements where there is no code (just else), tells the program to stop 'do nothing if a border is near by. The bottom of my panel, while scrolling, doesn't 'stop when the bottom of the panel (which is hanging off the bottom of the form quite 'some ways) reaches the bottom of the form, this can be adjusted by altering the 700 constant.
Private Sub DaddyPanel_MouseWheel(sender As Object, e As MouseEventArgs) Handles DaddyPanel.MouseWheel
If e.Delta < 0 Then
If (-DaddyPanel.Height - 91) > (DaddyPanel.Location.Y - 700) Then
Else
TextBox1.Text = DaddyPanel.Height & " " & DaddyPanel.Location.Y
DaddyPanel.Location = DaddyPanel.Location + New Point(0, -50)
End If
Else
If DaddyPanel.Location = New Point(0, 0) Then
Else
DaddyPanel.Location = DaddyPanel.Location + New Point(0, 50)
End If
End If
End Sub