Close UserForm from Module - vba

I am trying to Close a User Form from a module, but it's not working.
Here is what I have tried
Sub UpdateSheetButton()
Dim subStr1 As String
Dim subSrrt2() As String
Dim tmp As Integer
Dim pos As Integer
Dim Form As WaitMessage
Set Form = New WaitMessage
With Form
.Message_wait = Module2.Label_PleaseWait
.Show
End With
For Each Cell In ActiveSheet.UsedRange.Cells
subStr1 = RemoveTextBetween(Cell.formula, "'C:\", "\AddIns\XL-EZ Addin.xla'!")
tmp = Len(subStr1) < 1
If tmp >= 0 Then
Cell.formula = subStr1
status = True
End If
Next
Unload Form
MsgBox Module2.Label_ProcessComplete
End Sub
Form Name is WaitMessage.
I have also tried WaitMessage.Hide but it's also not working.

Considering a modeless form, create a subroutine within the userform:
Sub UnloadThisForm ()
unload me
End Sub
and call the sub from outside the userform;
call Userform1.UnloadThisForm

Another possibility could be to put your code to ClassModule and to use Events to callback to WaitMessage user form. Here short example. HTH
Standard module creates the form and the updater object and displays the form which starts processing:
Public Sub Main()
Dim myUpdater As Updater
Dim myRange As Range
Dim myWaitMessage As WaitMessage
Set myRange = ActiveSheet.UsedRange.Cells
Set myUpdater = New Updater
Set myUpdater.SourceRange = myRange
' create and initialize the form
Set myWaitMessage = New WaitMessage
With myWaitMessage
.Caption = "Wait message"
Set .UpdaterObject = myUpdater
' ... etc.
.Show
End With
MsgBox "Module2.Label_ProcessComplete"
End Sub
Class module containes the monitored method and has events which are raised if progress updated or finished. In the event some information is send to the form, here it is the number of processed cells but it can be anything else:
Public Event Updated(updatedCellsCount As Long)
Public Event Finished()
Public CancelProcess As Boolean
Public SourceRange As Range
Public Sub UpdateSheetButton()
Dim subStr1 As String
Dim subSrrt2() As String
Dim tmp As Integer
Dim pos As Integer
Dim changesCount As Long
Dim myCell As Range
Dim Status
' process task and call back to form via event and update it
For Each myCell In SourceRange.Cells
' check CancelProcess variable which is set by the form cancel-process button
If CancelProcess Then _
Exit For
subStr1 = "" ' RemoveTextBetween(Cell.Formula, "'C:\", "\AddIns\XL-EZ Addin.xla'!")
tmp = Len(subStr1) < 1
If tmp >= 0 Then
myCell.Formula = subStr1
Status = True
End If
changesCount = changesCount + 1
RaiseEvent Updated(changesCount)
DoEvents
Next
RaiseEvent Finished
End Sub
User form has instance of updater class declared with 'WithEvent' keyword and handles events of it. Here form updates a label on 'Updated' event and unloads itself on 'Finished' event:
Public WithEvents UpdaterObject As Updater
Private Sub UpdaterObject_Finished()
Unload Me
End Sub
Private Sub UpdaterObject_Updated(updatedCellsCount As Long)
progressLabel.Caption = updatedCellsCount
End Sub
Private Sub UserForm_Activate()
UpdaterObject.UpdateSheetButton
End Sub
Private Sub cancelButton_Click()
UpdaterObject.CancelProcess = True
End Sub

A userform is an object in it's own right, you do not need to declare or set as a variable. Also, when you use the .Show Method it will set the Modal property to True by default, which will pause code execution until the user interacts in some way (i.e. closes the form).
You can get around this by using a boolean declaration after the .Show method to specify if the userform is to be shown modal.
Try this instead:
Sub UpdateSheetButton()
Dim subStr1 As String
Dim subSrrt2() As String
Dim tmp As Integer
Dim pos As Integer
With WaitMessage
.Message_wait = Module2.Label_PleaseWait
.Show False
End With
For Each Cell In ActiveSheet.UsedRange.Cells
subStr1 = RemoveTextBetween(Cell.Formula, "'C:\", "\AddIns\XL-EZ Addin.xla'!")
tmp = Len(subStr1) < 1
If tmp >= 0 Then
Cell.Formula = subStr1
Status = True
End If
Next
Unload WaitMessage
MsgBox Module2.Label_ProcessComplete
End Sub

i guess you can do yourself the screenupdating and enableevents, so here is :
(next time add more description to what you are trying to do, and no just post code...)
Option Explicit 'might help to avoid future miss declaring of variables...
Sub UpdateSheetButton()
Dim subStr1 As String
'Dim subSrrt2() As String 'not used in shown code !
'Dim tmp As Integer
Dim pos As Long
Dim Cell as Range 'you forgot to declare this
Dim Form As object
Set Form = New WaitMessage
load Form
With Form
.Message_wait = Module2.Label_PleaseWait
.Show false 'if you ommit false, the code won't continue from this point unless the Form is closed !
End With
For Each Cell In ActiveSheet.UsedRange.Cells
subStr1 = RemoveTextBetween(Cell.formula, "'C:\", "\AddIns\XL-EZ Addin.xla'!")
'tmp = Len(subStr1) < 1 'might replace with a boolean (true/false, instead 0/-1)
'If tmp >= 0 Then 'you don't use tmp later, so i guess its just using variables without need
if substr1<>"" then 'why use a cannon to put a nail in a wall?, go to the point
Cell.formula = subStr1
pos = pos+1 'you declared pos but didn't use it !?
Form.SomeTextbox.caption = pos 'or other counter
'can also use the .width property of a button or picture... to make a progression bar.
status = True 'Status is not declared , and not used elsewhere , so what ?!
End If
Next
Unload Form
set Form = Nothing
MsgBox Module2.Label_ProcessComplete
End Sub

Related

Code to account for all checkboxes in a userform?

I have code on a userform that contains several checkboxes and several DTPickers.
The code looks like so:
Private Sub CheckBox11_Click()
If CheckBox11.Value = True Then
DTPicker22.Enabled = True
Else
DTPicker22.Enabled = False
End If
End Sub
Private Sub CheckBox12_Click()
If CheckBox12.Value = True Then
DTPicker24.Enabled = True
Else
DTPicker24.Enabled = False
End If
End Sub
The Userform contains a lot of checkboxes that have clauses next to them. Upon their completion the DTPicker will enable entering the date of completion.
Whilst this does what I want, it only enables one DTPicker when the checkbox is ticked per private sub. There has to be some way to make this so I wouldn't need to create different private subs for every checkbox click event.
Could you also tell me where to put it, as in, what event?
A "control array" is the typical approach for something like this.
See:
http://www.siddharthrout.com/index.php/2018/01/15/vba-control-arrays/
eg:
Class module clsEvents
Option Explicit
'Handle events for a checkbox and a date control, associated with a worksheet cell
Private WithEvents m_CB As MSForms.CheckBox
Private WithEvents m_DP As DTPicker
Private m_dateCell As Range
'set up the controls and the cell
Public Sub Init(cb As MSForms.CheckBox, dp As DTPicker, rng As Range)
Set m_CB = cb
Set m_DP = dp
Set m_dateCell = rng
If rng.Value > 0 Then
cb.Value = True
m_DP.Value = rng.Value
Else
cb.Value = False
End If
m_DP.CustomFormat = "dd/MM/yyyy"
End Sub
Private Sub m_CB_Change()
m_DP.Enabled = (m_CB.Value = True)
End Sub
Private Sub m_DP_Change()
m_dateCell.Value = m_DP.Value 'update the cell
End Sub
Userform:
Option Explicit
Dim colObj As Collection 'needs to be a Global to stay in scope
Private Sub UserForm_Activate()
Dim obj As clsEvents, i As Long, ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set colObj = New Collection
'loop over controls and create a class object for each set
' 3 pairs of controls on my test form...
For i = 1 To 3
Set obj = New clsEvents
obj.Init Me.Controls("CheckBox" & i), _
Me.Controls("DTPicker" & i), _
ws.Cells(i, "B")
colObj.Add obj
Next i
End Sub
The first thing I'd recommend is following a proper naming convention. "CheckBox11" and "DTPciker1" are really vague and once you get further into your code, you'll forget which control is which. I would recommend naming them something that relates the two control together, like "firstDate" and "firstDateDTP". My alternate answer below uses this approach.
You could make a public function that enables the DTPicker based upon the checkbox's value.
Public Function EnableDTPicker(myPicker as String, enableBool as Boolean)
UserFormName.Controls(myPicker).Enabled = enableBool
End Function
Then, you can call the function in your CheckBox123_Click() subs like this:
Private Sub CheckBox123_Click()
EnableDTPicker("thePickerName", CheckBox123.Value)
End Sub
Alternatively, you could make a timer event that runs x number of seconds that just loops through the controls and performs the checks as needed. See this page on how to set up the timer. Using the code in the link shown, You could do something along the lines of:
'Put this in Workbook events
Private Sub Workbook_Open()
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "EventMacro"
UserForm1.Show
End Sub
'Put this in a Module
Public Sub EventMacro()
With UserForm1
For each ctrl in .Controls
If TypeName(ctrl) = "CheckBox" Then
'The code below assumes the naming convention outlined above is followed
.Controls(ctrl.Name & "DTP").Enabled = ctrl.Value
End If
Next ctrl
End With
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "EventMacro"
End Sub

clsCommandButton: Microsoft Excel VBA - Run-Time Error '-2147024809 (80070057)'

I want to add dynamically CommandButtons to my Userform within the For-Loop. How can i get add new CommandButtons in the For-Loop?
Dim CommandButtons(5) As clsCommandButtons
Private Sub UserForm_Initialize()
Dim zaehler As Integer
For zaehler = 0 To 4
Set CommandButtons(zaehler) = New clsCommandButtons
Set CommandButtons(zaehler).cmdCommandButton = Me.Controls(zaehler)
Next
End Sub
And This is my class:
Option Explicit
Public WithEvents cmdCommandButton As CommandButton
Private Sub cmdCommandButton_Click()
Dim sFilepath As String
With Application.FileDialog(msoFileDialogFilePicker)
.AllowMultiSelect = False
.InitialFileName = ActiveWorkbook.Path & "\"
.Filters.Add "TextFiles", "*.txt", 1
.FilterIndex = 1
If .Show = -1 Then
sFilepath = .SelectedItems(1)
End If
End With
Cells(c_intRowFilterPathStart, c_intClmnFilterPath) = sFilepath
End Sub
I don't know how to handle this Error. How can i fix this?
I assume you get the error because you're accessing a control that doesn't exist. Note that the controls are counted from 0 to Me.Controls.count-1, so probably your issue is solved with
Set CommandButtons(zaehler).cmdCommandButton = Me.Controls(zaehler-1)
But I guess a better solution is to name your buttons and assign them by name:
Set CommandButtons(zaehler).cmdCommandButton = Me.Controls("CommandButton" & zaehler)
Define the CommandButtons collection as a Variant:
Dim CommandButtons(15) As Variant, instead of Dim CommandButtons(15) As clsCommandButtons.
In this Variant, you would put your CommandButtons. This is some minimal code, that would help you get the basics of what I mean:
CustomClass:
Private Sub Class_Initialize()
Debug.Print "I am initialized!"
End Sub
In a module:
Private SomeCollection(4) As Variant
Public Sub TestMe()
Dim cnt As Long
For cnt = 1 To 4
Set SomeCollection(cnt) = New CustomClass
Next cnt
End Sub
From this small running code, you can start debugging further :)
I think your problem is in the Me.Controls(zaehler) part. zaehler starts at 1, but Me.Controls(...) starts at 0.
Set CommandButtons(zaehler).cmdCommandButton = Me.Controls(zaehler - 1)
would probably solve it
Dim a() As clsCommandButton
Private Sub UserForm_Initialize()
Dim c As Control
On Error GoTo eHandle
For Each c In Me.Controls
If TypeName(c) = "CommandButton" Then
ReDim Preserve a(UBound(a) + 1)
Set a(UBound(a)) = New clsCommandButton
Set a(UBound(a)).cmd = c
End If
Next c
Exit Sub
eHandle:
If Err.Number = 9 Then
ReDim a(0)
End If
Resume Next
End Sub
With a class as follows
Public WithEvents cmd As commandbutton
Private Sub cmd_Click()
MsgBox "test"
End Sub

Excel VBA Userform - Execute Sub when something changes on dynamic comboBox

I created a userform where is a comboBox
Depending which result user chooses, new comboBoxes will appear with new choices to choose from.
Below is the latest test I tried.
How do I make it so that when user changes the value in the new comboBox it will execute a premade function/sub
Code in Form
Dim WB As Workbook
Dim structSheet As Worksheet
Dim tbCollection As Collection
Private Sub UserForm_Activate()
Dim ignoreList(3) As String
ignoreList(0) = "main"
ignoreList(1) = "configurator"
ignoreList(2) = "create structure"
Set WB = Excel.ActiveWorkbook
For Each sheet In WB.Worksheets
If Not isInTable(ignoreList, sheet.Name) Then
supercode_box.AddItem sheet.Name
End If
Next
End Sub
Private Sub supercode_box_Change()
If Not sheetExists(supercode_box.text) Then Exit Sub
Set structSheet = WB.Worksheets(supercode_box.text)
'Dim obj As clsControlBox
topPos = 10
leftPos = 54
ID = 1
' For ID = 1 To 2
Set ComboBox = createProductForm.Controls.add("Forms.ComboBox.1")
With ComboBox
.Name = "comboBoxName"
.Height = 16
.Width = 100
.Left = leftPos
.Top = topPos + ID * 18
.AddItem "test"
.Object.Style = 2
End With
Set tbCollection = New Collection
tbCollection.add ComboBox
'Next ID
End Sub
code in class1 module
Private WithEvents MyTextBox As MSForms.controlBox
Public Property Set Control(tb As MSForms.controlBox)
Set MyTextBox = tb
MsgBox ("did it get here?")
End Property
Public Sub comboBoxName_Change()
MsgBox ("start working ffs")
End Sub
Public Sub comboBoxName()
MsgBox ("?? maybe this?")
End Sub
Judging on your code, the easiest way is to write the value you need in a separate worksheet.
Then make a check, whether it is changed and if it is changed, write the new value and fire the procedure that you want.
In short, something like this:
Sub TestMe()
If Worksheets("Special").Cells(1, 1) = WB.Worksheets(supercode_box.Text) Then
Call TheSpecificSub
End If
Worksheets("Special").Cells(1, 1) = WB.Worksheets(supercode_box.Text)
End Sub

Excel - How to create button upon Workbook_Open event

I'm trying to make an Excel Add-In to create a simple button when any workbook is opened, but I'm getting
Object variable or With Block variable not set
I think this is happening because technically there is no 'ActiveWorkbook' yet.
First thing I want to do is delete any buttons currently on the sheet. Then I want to place a button.
Anyone know how to make that happen?
Code
Private Sub Workbook_Open()
ActiveWorkbook.ActiveSheet.Buttons.Delete
Dim CommandButton As Button
Set CommandButton = ActiveWorkbook.ActiveSheet.Buttons.Add(1200, 100, 200, 75)
With CommandButton
.OnAction = "Test_Press"
.Caption = "Press for Test"
.Name = "Test"
End With
End Sub
I then have a Private Sub Test_Press() to display a MsgBox. The button is not being created though.
Credit goes to http://www.jkp-ads.com/Articles/FixLinks2UDF.asp
Note: I have another module I didn't post below, which houses the macro Project_Count I tied to the button I place on the workbook only if the workbook name is TT_GO_ExceptionReport
I also have a VBScript that downloads the Add-In, places it in the users addin folder, and installs it. If you want to know how to do that, leave a comment.
Code of Add-In that solved the problem:
ThisWorkbook
Option Explicit
Private Sub Workbook_Open()
' Purpose : Code run at opening of workbook
'-------------------------------------------------------------------------
'Initialise the application
InitApp
modProcessWBOpen.TimesLooped = 0
Application.OnTime Now + TimeValue("00:00:03"), "CheckIfBookOpened"
End Sub
Module 1 named modInit
Option Explicit
'Create a module level object variable that will keep the instance of the
'event listener in memory (and hence alive)
Dim moAppEventHandler As cAppEvents
Sub InitApp()
'Create a new instance of cAppEvents class
Set moAppEventHandler = New cAppEvents
With moAppEventHandler
'Tell it to listen to Excel's events
Set .App = Application
End With
End Sub
Module 2 named modProcessWBOpen
Option Explicit
'Counter to keep track of how many workbooks are open
Dim mlBookCount As Long
'Counter to check how many times we've looped
Private mlTimesLooped As Long
' Purpose : When a new workbook is opened, this sub will be run.
' Called from: clsAppEvents.App_Workbook_Open and ThisWorkbook.Workbook_Open
'-------------------------------------------------------------------------
Sub ProcessNewBookOpened(oBk As Workbook)
If oBk Is Nothing Then Exit Sub
If oBk Is ThisWorkbook Then Exit Sub
If oBk.IsInplace Then Exit Sub
CountBooks
'This checks to make sure the name of the new book matches what I
'want to place the button on
If oBk.Name = "TT_GO_ExceptionReport.xlsm" Then
Dim CommandButton As Button
Set CommandButton = Workbooks("TT_GO_ExceptionReport.xlsm").Sheets(1).Buttons.Add(1200, 100, 200, 75)
With CommandButton
.OnAction = "Project_Count"
.Caption = "Press for Simplified Overview"
.Name = "Simplified Overview"
End With
End If
End Sub
Sub CountBooks()
mlBookCount = Workbooks.Count
End Sub
Function BookAdded() As Boolean
If mlBookCount <> Workbooks.Count Then
BookAdded = True
CountBooks
End If
End Function
' Purpose : Checks if a new workbook has been opened
' (repeatedly until activeworkbook is not nothing)
'-------------------------------------------------------------------------
Sub CheckIfBookOpened()
If BookAdded Then
If ActiveWorkbook Is Nothing Then
mlBookCount = 0
TimesLooped = TimesLooped + 1
'May be needed if Excel is opened from Internet explorer
Application.Visible = True
If TimesLooped < 20 Then
Application.OnTime Now + TimeValue("00:00:01"), "CheckIfBookOpened"
Else
TimesLooped = 0
End If
Else
ProcessNewBookOpened ActiveWorkbook
End If
End If
End Sub
Public Property Get TimesLooped() As Long
TimesLooped = mlTimesLooped
End Property
Public Property Let TimesLooped(ByVal lTimesLooped As Long)
mlTimesLooped = lTimesLooped
End Property
Class Module named cAppEvents
' Purpose : Handles Excel Application events
'-------------------------------------------------------------------------
Option Explicit
'This object variable will hold the object who's events we want to respond to
Public WithEvents App As Application
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
'Make sure newly opened book is valid
ProcessNewBookOpened Wb
End Sub
Private Sub Class_Terminate()
Set App = Nothing
End Sub
something like this?
Option Explicit
Sub Button()
Dim cButton As Button
Dim rng As Range
Dim i As Long
ActiveSheet.Buttons.Delete
For i = 2 To 3 Step 2
Set rng = ActiveSheet.Range(Cells(i, 2), Cells(i, 2))
Set cButton = ActiveSheet.Buttons.Add(rng.Left, rng.Top, rng.Width, rng.Height)
With cButton
.OnAction = "Test_Press"
.Caption = "Press for Test " & i
.Name = "Test" & i
End With
Next i
End Sub
See Example here

Why VBA global variables loses values when closing UserForm?

I have a macro code behind Worksheet. When button is clicked on the sheet, new user form is initialised and showed to user. If user closes the windows with red X, or form is closed with "hide" function/method, all global variables that are behind Worksheet loses their values. Is it possible to preserve this values?
Worksheet code behind:
Private MeasurementCollection As Collection
Dim CurrentMeasurement As measurement
Dim NewMeasurement As measurement
Private Sub Worksheet_Activate()
Initialize
End Sub
Public Sub Initialize()
Set NewMeasurement = New measurement
Dim DropDownDataQueries As Collection
Set DropDownDataQueries = DBQueries.GetAllUpdateQueries
For i = 1 To DropDownDataQueries.Count
Dim Values As Collection
Set Values = DataBase.GetData(DropDownDataQueries(i))
With Me.OLEObjects("Combo" & i).Object
For Each value In Values
.AddItem value
Next value
End With
Next i
End Sub
Private Sub UpdateDB_Click()
UpdateGeneralData
If (CurrentMeasurement Is Nothing) Then
MsgBox ("Message text")
Else
Dim form As UpdateComentForm
Set form = New UpdateComentForm
form.Show
End If
End Sub
Private Sub Combo1_Change()
If Application.EnableEvents = True Then
If (Combo1.value <> "") Then
NewMeasurement.DN = Combo1.value
Else
NewMeasurement.DN = 0
End If
End If
End Sub
UserForm code
Private Sub UpdateDBData_Click()
If (Komentar.value <> "") Then
Me.Hide
Else
MsgBox ("Prosimo napiĊĦite vzrok za spremembe podatkov v belo polje!")
End If
End Sub
Private Sub UserForm_Terminate()
Me.Hide
End Sub
Experiments show that the module-level variables are cleared upon exiting a procedure that involves calling = New Form, provided that the form designer window is opened somewhere in the IDE.
Close all user forms designer windows you might have open in the VBA IDE and try again.
NewMeasurement as been declared but never assigned.
You could do something like Dim NewMeasurement As New measurement to create an instance of the object.