deactivate Excel VBA userform - excel

I am having some macro in Excel vba and in that I am performing some functions on the excel sheets which takes around 30 seconds to complete. So I want to show a user form with a progress bar during that span of time.
I tried using userform.show in very start of the function and userform.hide at the end but I found that No action can be performed in background.
So just want to know if there is any turn around to let the processing be done in the background while the form is being displayed.
Many thanks :)
Private Sub CommandButton1_Click()
'--------------Initialize the global variables----------------
UserForm1.Show
nameOfSheet2 = "Resource Level view"
nameOfSheet3 = "Billable Hours"
nameOfSheet4 = "Utilization"
'-------------------------------------------------------------
Dim lastRow, projectTime, nonProjectTime, leaveAndOther
Dim loopCounter, resourceCounter
lastRow = 0
projectTime = 0
nonProjectTime = 0
leaveAndOther = 0
resourceCounter = 2
Set workbook1 = Workbooks.Open(File1.Value)
Sheet3Creation
Sheet2Creation
Sheet4Creation
UserForm1.Hide
End Sub

The usage of Progress Bar is to show the progress of currently running code. And I wouldn't know if anyone want to do anything with the sheet while the code is running...
Anyway if you want to interact with the sheet while Form is displaying you may try to add the following code:
UserForm.Show vbvModeless
And to update a Modeless form you must add DoEvents within your subroutine.
When you want to close the form at the end, do this:
UserForm.Unload
Here is what I would do:
Click a button to run your macro
Private Sub Button1_Click()
Call userform.show vbMmodeless
End Sub
Private Sub UserForm_activate()
Call Main '-- your macro name
End Sub
Sub Main()
'-- your code
DoEvents '-- to update the form *** important
useroform.Unload
End Sub
After OP showed his code:
Why do we need a progress bar?
When macros take a long time to run, people get nervous. Did it crash? How much longer will it take? Do I have time to run to the bathroom? Relax...
In your case I do not really see that you are using any sort of heaving codes running at the background. So adding a progress bar could make your code slow as to update it, you may be calling an extra loop... check this reference article if you really want to have the progress bar :),
You can also use Application.StatusBar to display a message.
The other is to use Timer or a littel bit more technical way would be to wrap system timer ticks and refresh/update form accordingly. In VBA Excel we don't get that lucky as for C# or VB..
VBA Macro On Timer style to run code every set number of seconds, i.e. 120 seconds.
How do I show a running clock in Excel?
How do you test running time of VBA code?

Related

VBA best practices for modules relative to modeless userforms

I came across this similar issue and read the replies: Modeless form that still pauses code execution
I have been attempting to apply in my own situation the suggestion provided by David Zemens. In my situation, I cannot seem to find an approach that incorporates Mr. Zemen's suggestion without also utilizing a GoTo.
I am wondering if there is a better or more elegant solution.
Here is an outline of what I am doing:
I have a UserForm with a Command Button that begins the code execution that will perform several actions on multiple Excel workbooks. As such, there are a number of blocks of code and the successful completion of one block of code allows for the execution of the subsequent block of code.
At a certain point, depending on the situation, the code might require User input; in other situations, the needed data is obtainable from an Excel. If input is needed from the User, another UserForm is displayed.
The User may need to view several different Excel sheets before entering the input, so the UserForm is modeless. So the code comes to a stop until the User enters the needed input and clicks another Command Button.
It is at this point I am having trouble: how to resume the program flow. Is the only way to 'pick-up where it left-off' is by using a GoTo statement? Or is there some way to organize the modules so there is a single consistent program flow, defined in one spot and not duplicated from the point at which User input might be needed?
Here is my take on the problem . Hope I understood the problem correctly.
Assumptions:
There are two user forms.
UserForm1 with a button to start the processing.
UserForm2 with a button to supply intermediate input.
A sub inside a module to start/ launch UserForm1.
VBA Code (for the sub routine)
Sub LaunchUserForm1()
Dim frm As New UserForm1
'/ Launch the main userform.
frm.Show vbModeless
End Sub
VBA Code (for UserForm1)
Private Sub cmdStart_Click()
Dim i As Long
Dim linc As Long
Dim bCancel As Boolean
Dim frm As UserForm2
'/ Prints 1 to 5 plus the value returned from UserForm2.
For i = 1 To 5
If i = 2 Then
Set frm = New UserForm2
'/ Launch supplementary form.
frm.Show vbModeless
'<< This is just a PoC. If you have large number of inputs, better way will be
' to create another prop such as Waiting(Boolean Type) and then manipulate it as and when User
' supplies valid input. Then validate the same in While loop>>
'/ Wait till we get the value from UserForm2.
'/ Or the User Cancels the Form with out any input.
Do While linc < 1 And (linc < 1 And bCancel = False)
linc = frm.Prop1
bCancel = frm.Cancel
DoEvents
Loop
Set frm = Nothing
End If
Debug.Print i + linc
Next
MsgBox "User Form1's ops finished."
End Sub
VBA Code (for UserForm2)
Dim m_Cancel As Boolean
Dim m_prop1 As Long
Public Property Let Prop1(lVal As Long)
m_prop1 = lVal
End Property
Public Property Get Prop1() As Long
Prop1 = m_prop1
End Property
Public Property Let Cancel(bVal As Boolean)
m_Cancel = bVal
End Property
Public Property Get Cancel() As Boolean
Cancel = m_Cancel
End Property
Private Sub cmdlinc_Click()
'/Set the Property Value to 10
Me.Prop1 = 10
Me.Hide
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'/ Diasble X button
Me.Cancel = True
Me.Hide
Cancel = True
End Sub
OK so here are my thoughts.
You have a userform frmSelectUpdateSheet which you wish to use in order to allow the user to select the sheet, when the sheet can't be determined programmatically. The problem is that if you do .Show vbModeless (which allows the user to navigate the worksheet/s), then code continues to execute which either leads to errors or otherwise undesired output.
I think it's possible to adapt the method I described in the previous answer. However, that's out of the question here unless you're paying me to reverse engineer all of your code :P
Assuming you have a Worksheet object variable (or a string representing the sheet name, etc.) which needs to be assigned at this point (and that this variable is Public in scope), just use the CommandButton on the form to assign this based on the selected item in the frmSelectUpdateSheet list box.
This is probably a superior approach for a number of reasons (not the least of which is trying to avoid application redesign for this sort of fringe case), such as:
This keeps your form vbModal, and does prevent the user from inadvertently tampering with the worksheet during the process, etc.
Using this approach, the thread remains with the vbModal displayed frmSelectUpdateSheet, and you rely on the form's event procedures for control of process flow/code execution.
It should be easier (and hence, cheaper) to implement; whether you're doing it yourself or outsourcing it.
It should be easier (and hence, cheaper) to maintain.
NOW, on closer inspection, it looks like you're already doing this sort of approach with the cmdbtnSelect_Click event handler, which leads me to believe there's a related/follow-up problem:
The sheet names (in listbox) are not sufficient for user to identify the correct worksheet. So if the user needs the ability to "scroll" the sheet (e.g., to review data which does not fit in the window, etc.), then add some spinner buttons or other form controls to allow them to navigate the sheet.

Declaring a UserForm listbox as a variable in a module? Excel VBA

Alright this is my first post on here so be gentle.
I have a Userform with a listbox containing x amount of values. I have a separate module that calls this Userform and then performs some procedures based on the values chosen in the listbox. I have the following:
Dim lb as ListBox
lb = Userform1.Listbox1
For x = 0 to lb.ListCount - 1 Then
'Do the stuff I need it to
Next x
The problem I am getting is that lb returns a "Nothing".
I am sure this is something simple but I can't figure it out. Appreciate the help.
Are you closing your userform before you are running your code, as if this the case then your listbox is effectively non existent in memory and you listcount will not return anything. You can also remove the line where you are assigning your lb variable and directly get the listcount of the listbox like the code below. Also if you dont want the userform to show when you code is doing the loop just hide the userform run the loop and then unload the userform after that. This way you list box is still in memory long enough for the loop to get the correct value
for x = 1 to userform.listbox.listcount
'' Do you stuff
next x
I tried the code and it gave me the correct value. If there is anything else let me know and hope this helps :)
Declare the module just like that.
Sub listboxpopulate(listb as string,usf as userform)
for i=0 Usf.controls(listb).listcount-1
Do ur stuff
Next
End sub
This will help

Lock Select Box Size In Excel

This seems like a simple thing, but I cannot figure it out, or find it online.
If I select 5 cells in a column(say A1:A5), and I would like to move this selection shape(column 1:5) over (to B1:B5); Is there shortcut to do this? Currently I hit the left arrow, and the select box changes size to just B1, and I have to hit shift and select B2:B5. Ideally I would like to discover a hot key that "locks" the shape of the select box.
It has been suggested by colleagues to write a macro, but this is ineffective in many cases. For example what if instead of a column I wanted to do the same thing with a row, or with a different sized shape. It seems likely that excel has this feature built in.
I'm not sure how a macro would be ineffective. I would write procedures similar to what's below, then assign them to hotkeys. Let me know if it works for you.
Option Explicit
Public rowDim As Long
Public colDim As Long
Public putSel as Boolean
Sub togglePutSel()
putSel = Not putSel
End Sub
Sub GetSelShape()
rowDim = Selection.Rows.Count
colDim = Selection.Columns.Count
putSel = True
End Sub
Sub PutSelShape()
Selection.Resize(rowDim, colDim).Select
End Sub
If you want to make it work for whenever you hit the arrow keys, then in your Sheet code, you can use this. You may want to do a quick check that rowDim and colDim aren't 0. The only issue with this is that you'd be stuck with that behavior unless you create a trigger to stop calling PutSelShape. So, I'd suggest one macro (hotkeyed to GetSelShape) to toggle it, and another hotkey for togglePutSel.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If putSel Then
Call PutSelShape
End If
End Sub

Excel VBA Calling the Calendar via Command Button in a form

I have just added in Calendar 12.0 from the tools > Additional Controls. Calendar works fine and I have it spitting the date out to the right cells. What I would like, however, is to really make the calendar visible from a command button as my form contains a bunch of fields and I don't want to bog up the form with this calendar. I have tried Calendar1.show but the .show isn't an option.
So ultimately I need a command button to show the calendar, allow the user to select (I have that) and then close the calendar. Any help? I thank you in advance!!
bdubb
In this snippet, CommandButton1 is from the ActiveX controls, not the form controls. It requires that you click the button to show the calendar (which pops up next to the button you clicked), and click the button again to hide the calendar.
Private Sub CommandButton1_Click()
If Not Calendar1.Visible Then
Calendar1.LinkedCell = "A1"
Calendar1.Top = Sheet1.CommandButton1.Top
Calendar1.Left = Sheet1.CommandButton1.Left + Sheet1.CommandButton1.Width + 1
Calendar1.Visible = True
Else
Calendar1.Visible = False
End If
End Sub
Obviously, different buttons would require different linked cells, but it does mean that you could have a single calendar control that it displyed by multiple buttons (if that is what you want).
Unfortunately, it would appear that you cannot hide the control while any of its events are firing (e.g AfterUpdate). It just doesn't want to disappear!!
Hide/Close a calendar control still not works (new year 2015 = almost four years later) but I think I found a workaround to hide the control after firing events.
I have a Calendar1_AfterUpdate(), which launches before Calendar1_Click(). Code is placed directly in a worksheet and NOT in a module.
Private Sub Calendar1_AfterUpdate()
Range("a1") = Me.Calendar1.Value
' Next two lines does not work within AfterUpdate
' When running step by step it seems to work but the control is
' visible when End Sub has run
Me.Calendar1.Visible = True
Me.Calendar1.Visible = False
End Sub
To that I simply added
Private Sub Calendar1_Click()
Me.Calendar1.Visible = True
Me.Calendar1.Visible = False
End Sub
Please note that the control for some reason must be made visible before hiding.
Why it does not work directly in Calendar1_AfterUpdate() is a mystery to me.
Next problem is to hide the control when I remove the mouse. Mouse-events seems to be impossible in a calendar control ...

Can't close userform

Let me set up the environment.
This is VBA code running in Excel.
I have a userform that contains a msflexgrid. This flexgrid shows a list of customers and the customer', salesperson, csr, mfg rep, and territories, assignments. When you click in a column, let's say under the Territory column, another userform opens to show a list of Territories. You then click on the territory of your choice, the userform disappears and the new territory takes the place of the old territory.
This all works great until you click on the territory of your choice the 'Territory' userform does not disappear (it flickers) and the new territory does not transfer the underlying userform.
I should mention that when I'm stepping through the code it works great.
I'm assuming it has something do to with the flexgrid as all the other userform (that don't have flexgrids) that open userform work just fine.
Following is the some code sample:
** Click event from flexgrid that shows Territory userform and assignment of new territory when territory userform is closed.
Private Sub FlexGrid_Customers_Click()
With FlexGrid_Customers
Select Case .Col
Case 0
Case 2
Case 4
Case 6
UserForm_Territories.Show
Case Else
End Select
If Len(Trim(Misc1)) > 0 Then
.TextMatrix(.Row, .Col) = Trim(Misc1)
.TextMatrix(.Row, .Col + 1) = Trim(Misc2)
End If
End With
End Sub
** The following Subs are used in the Territory userform
Private Sub UserForm_Activate()
Misc1 = ""
Misc2 = ""
ListBox_Territory.Clear
Module_Get.Territories
End Sub
Private Sub UserForm_Terminate()
Set UserForm_Territories = Nothing
End Sub
Private Sub ListBox_Territory_Click()
With ListBox_Territory
Misc1 = Trim(.List(.ListIndex, 0))
Misc2 = Trim(.List(.ListIndex, 1))
End With
Hide
UserForm_Terminate
End Sub
I know this a long winded explanation but I'm a fairly decent VBA programmer and this has me stumped.
Any help would be greatly appreciated.
I'm not going to say what you're doing is wrong (in that it won't ever work), but it scares the heck out of me. This is not the way I'd deal with forms.
Firstly, you're using UserForm_Territories (the class/form name) to refer to an implicitly-created instance of the form. This is something I've always avoided doing. I would always create an instance of a form explicitly, so instead of:
UserForm_Territories.Show
I would do:
Dim oTerritoriesForm As UserForm_Territories
Set oTerritoriesForm = New UserForm_Territories
oTerritoriesForm.Show vbModal
' get the values from the form here
Unload oTerritoriesForm
Next, and much more worryingly, you're subverting the UserForm_Terminate behaviour by calling it explicitly. Why you're doing this I can't imagine, unless you thought that it would work around your stated problem. My advice: don't do that.
Worse, you're attempting to assign to the implicitly-created instance of the form within that Terminate method. You shouldn't be doing that, either. I'm surprised that even compiles.
It seems like you're trying to force the implicitly-created instance of the form to mimic an explicitly-created one. In which case, create it explicitly, as shown above.

Resources