I have a question and I don't know if it has an answer.
What I would like to do is track the usage of a macro inside a global variable for an entire day. I want this macro to only be capable of running once a day. I choose if I run it in the morning, noon or evening.
So for example:
Today we are on the 13/01/2022. In the morning I ran this macro and closed the workbook. I want to not be able to run this macro before the 14th (a different day), even if I reopen the Workbook on the 13th in the afternoon, I press the macro button and it won't run.
So I want this macro to check if it was run today and then if the answer is yes to not run, but if the answer is no to run. Can this be done ? I thought about creating a global variable was_run, but how can I store its value even when the workbook is closed and reopen again ? Is this possible ?
Thank you for your help !
Please, try the next way:
Sub myMacroRunningOnlyOncePerDay()
Const myApp As String = "My Daily Variable", Sett As String = "Settings"
Const strDate As String = "myDailyValue"
Dim RegValue As String, NoRun As Boolean
RegValue = GetSetting(myApp, Sett, strDate, "No value")
If RegValue <> "No value" Then
If IsNumeric(RegValue) Then
If CLng(RegValue) = CLng(Date) Then NoRun = True
End If
End If
If NoRun Then Exit Sub
SaveSetting myApp, Sett, strDate, CStr(CLng(Date))
MsgBox "It runs..."
End Sub
It should be good to place the constants (Registry keys) on top of the module (in the declarations area). In this way you can use them from different Subs.
Related
I meet a problem which is probably very simple when I use a VBA function into a formula of a cell, I get the cell content "#NAME" (not found function ? while a macro using the function (for test) is executed normally (displays the wished content for the cell, the returned value by the function, which extracts the filename from a fullpath).
context :
I had by the past (more than 12 years ago) developed, may be 50,000, VBA instructions, using office2003.
Today I have to develop again some macros with Office365. So I have forgotten a lot since this time and some features can have changed which can become tricking (I need to read again my old soft to recall all my knowledge, but I have no access to for now)
The problem
I get the error "#name" when I use a function created into VBA
associated to the current workbook. No explanation, no help, I tried to
find something during several hours and I found nothing.
I have developed too for testing (see code) a "sub" which calls the function, and his execution is successful, but...
I cannot run the function from the formula of any cell.
I have tested the security parameters of macro and fully unlocked the execution temporarily, and too declare the local directory as confident area.
Note : this code is detailed as an example
The code
Public Function FNameOf(CellPointed As Range)
Dim CurCell As Range
Dim Text1 As String
Dim Text2 As String
Set CurCell = CellPointed
Text1 = CurCell.Value
Text2 = Mid$(Text1, InStrRev(Text1, "\") + 1, Len(Text1))
FNameOf = Text2
End Function
Sub DispFileName2()
Dim style, disp, titre
Dim Cursel As Range
'Cursel = ActiveCell
disp = FNameOf(ActiveCell)
style = vbOKOnly
titre = "Nom du fichier extrait du texte (fullpath) de la cellule courante"
MsgBox disp, style, titre
End Sub
If I submit the macro DispFileName2 if get the message with the file name extracted from the path which is the content of the current cell
If I set the formule of a cell :
=FNameOf(AnotherCell) 'which contains a fullpath to a file
I get always the error "#NOM" (in French version) or, I think so, "#NAME" (in english Version) as if the function name (ref) was unknown from the workbook (the code is not reached, a stop is set on the first instruction)
What can be the reason ?
Please place your user defined function somewhere in a module (neither in "ThisWorkbook" nor in the individual worksheet's code, e. g. "Feuil1").
You should add the result declaration As String also.
I understood the example is for reference only, but you may shorten it:
Public Function FNameOf(CellPointed As Range) As String
FNameOf = Mid(CellPointed.Value, InStrRev(CellPointed.Value, "\") + 1)
End Function
I just got the solution while reading in more details previous edited threads in several tabs.
It is explained into a remark of : thread 12351339
The text is :
Microsoft Excel Objects such as 'Sheet1' or 'ThisWorkbook' are
classes. I don't believe you can access Functions which you put in
these classes through a cell. You could access them in VBA e.g.
ThisWorkbook.Square2() but it's recommended to put all UDF's into as
standard module and not a worksheet module. – Eddie Sep 28 '17 at
13:49
By default the creation panel defines code associated to current worksheet, then the function is not visible for the worksheet while the sub is a macro of the worksheet.
I have created a module and the function has run immediately.
Best regards
Trebly
Note : I never met this problem of visibility before because the developments where since the beginning concerning VBA user classes and modules combining multiple Excel workbooks and Word and a Mail manager activeX and so on...
I keep the subject because of the explanations, code and keywords may be to find more easily the solution for anybody else.
I set out to write a simple function to determine the length of a string in points. Having googled around I decided to avoid the font metrics problem by having excel do the work for me.
Here is the code.
Option Explicit
Function txtWidthPts(MyText As String) As Integer
'A cell on a working worksheet has been named "WidthTest" for easy reference & to ensure data is not overwritten.
'set WidthTest word wrapping off so that strings placed in there aren't wrapped
Application.ScreenUpdating = False
With [WidthTest]
.WrapText = False
.Value = MyText
'autofit WidthTest
.Columns.AutoFit
'get the width of the column
txtWidthPts = .Width
.ClearContents
End With
End Function
I tested the function by placing it in a cell on a working worksheet thus:
=txtWidthPts("Test123")
When I have this working I will be using it in code not as a worksheet function.
My problem is that the function does not throw an error and stops execution on the line:
.Value = MyText
I have placed the code and name into an empty workbook to ensure no interaction with other workbook contents / code.
I have searched extensively and tried various suggestions (DoEvents, Application.Update = False, etc, etc.) to no result.
I have cleared all breakpoints, closed and opened the workbook & restarted. I have tested with options set to Break on All Errors.
No result.
I suspect I am missing something obvious but it has me beat at the moment.
Any and all suggestions will be most welcome.
So, #YowE3K was right on the money. After fixing the error in the original code (Corrected code above) this runs fine from vba. I knew I was missing something obvious.
Curiosity sub-question: the function works as desired and indeed, as #YowE3K observed, it does not modify the Excel environment. However the result returned is dependent on it appearing to have modified the Excel environment. Seriously WTF. Just wanting to understand.
Thanks again YoWE3K.
I'm running Excel 2013 and a recent macro that I've developed is inconsistently (hooray!) causing Excel to freeze, giving me the "Excel has stopped working" error. In short, the macro prompts the user for some basic info about what they want to do and then creates a number of new sheets (based on a Template sheet) and populates each with rows of info from a Summary tab (which the user fills out before starting the macro). Every now and then when I run the macro, it freezes as soon as I complete the prompts. I can immediately restart the workbook, run the macro again with the same inputs and it will work. So it’s hard to replicate the problem, making it hard to diagnosis and fix it.
I've tried a number of things to fix this (changing Shift:=xl--- calls to xlShift---; changing the tabs from Page Break Preview to Normal view; moving the macro button to another sheet), but it still freezes. The latest attempt (which seemed to be working for a while) was to change the Form Control button used to start the macro to an ActiveX Control button (which is housed on the Summary tab). I tried setting up a basic error handler, but that didn’t find anything when I got it to freeze again. I’ve tried stepping through the code and when it froze, it was after executing a selection.insert call the first time.
I’ve included the full macro below, but the (potential) problem line is:
Sheets(yr_temp).Rows(3 + task_counter).Insert (xlShiftDown)
Any ideas of what might be going on and what else I can try to fix this problem? The only thing I haven't fully tested is to insert Application.Wait Now + (TimeValue("00:00:01") after the .Insert line; another user said this worked for them (can't find the link at the moment UPDATE #4: Found it! Here's the link.), though it was a slightly different situation. Also, that line can run hundreds of times on a single execution of the macro, so waiting one second each time is pretty burdensome (I haven't tried using a fraction of a second, nor am I sure how short of a wait would still have the desired effect).
UPDATE #1: I forgot about this tidbit. When it freezes, it seems to always be on the first time I run the macro after opening the workbook. I don't believe it has ever frozen on the second or third or... time running the macro.
UPDATE #2: After adding debug.print calls for yr_temp and task_counter (thanks for the suggestion, Alex Bell), and running the macro many many times, I finally got it to freeze on me while I watched the Immediate Window. Again, it appeared to have crashed after the first call of the oddly troublesome sheets.insert line. More importantly, the values for yr_temp and task_counter were the same valid numbers from every previous attempt that ran smoothly. So, any other ideas what might be causing this problem? This weekend I will try to run this on another computer to see if it might be something with this system and not the macro itself.
UPDATE #3: I tried using the workbook on my other computer (running Office 2010). So far I haven't had it freeze, but it's too early for me to claim that it doesn't freeze on that system.
Sub Generate_MM_Sheets()
'' Turn off screen updating for duration of macro (to improve macro speed...and prevent seizures)
Application.ScreenUpdating = False
'' Declare all variables
Dim valid_resp As Boolean, user_resp As String '' For user prompts
Dim yr_start As Integer, yr_num As Integer, yr_counter As Integer, yr_temp As String '' For year values
Dim task_num As Integer, task_counter As Integer, task_temp As Integer '' For task numbers
Dim sheets_num As Integer, sheet_counter As Integer '' For sheet numbers
Dim proj_name As String '' For project name
Dim wb_current As String, wb_new As String '' For workbook names
'' Prompt user to define starting year for the Maintenance Manual sheets
valid_resp = False
Do
user_resp = InputBox("Enter the desired starting year for the Maintenance Manual sheets", , Year(Now()))
If user_resp = "" Then '' If the user hits Cancel or returns an empty response, restore screen updating and end the macro
Application.ScreenUpdating = True
Exit Sub
ElseIf Not IsNumeric(user_resp) Then '' If the response is not a number, warn the user and retry
MsgBox ("Doh! Invalid entry. The value you entered was not a number.")
ElseIf Not user_resp = Int(user_resp) Or user_resp <= 0 Then '' If the response is not a positive integer, warn the user and retry
MsgBox ("Aw snap! Invalid entry. The value you entered was not a positive integer.")
Else '' Otherwise the response is deemed valid. Set the response validity to true and define the macro variable as the user response
valid_resp = True
yr_start = user_resp
End If
Loop Until valid_resp = True '' Loop until a valid response is entered
'' Same logic as above, but this time to define the number of years for the Maintenance Manual sheets
valid_resp = False
Do
user_resp = InputBox("Enter the desired total number of years for the Maintenance Manual sheets", , 30)
If user_resp = "" Then
Application.ScreenUpdating = True
Exit Sub
ElseIf Not IsNumeric(user_resp) Then
MsgBox ("Come on! Invalid entry. The value you entered was not a number.")
ElseIf Not user_resp = Int(user_resp) Or user_resp <= 0 Then
MsgBox ("Bummer, dude! Invalid entry. The value you entered was not a positive integer.")
Else
valid_resp = True
yr_num = user_resp
End If
Loop Until valid_resp = True
'' Prompt user to define project name for use in the Maintenance Manual sheet headers
proj_name = InputBox("Enter the name of the project for the Maintenance Manual sheets")
'' Use the above responses, the data in the Summary tab and the template in the Template tab to create and populate the Maintenance Manual sheets
task_num = Range(Sheets("Summary").Range("A4"), Sheets("Summary").Range("A4").End(xlDown)).Rows.Count '' Count the number of Tasks in the Summary tab
sheets_num = ActiveWorkbook.Sheets.Count '' Count the current number of sheets in the macro workbook (this value is used when moving the generated sheets to new workbook)
Sheets("Template").PageSetup.CenterHeader = proj_name & Chr(10) & "Maintenance Items" '' Update the header of the Template tab to be used on the generated sheets
For yr_counter = 1 To yr_num '' Loop through each year
yr_temp = yr_start + yr_counter - 1 '' Determine the year value for this loop
Sheets("Template").Copy After:=Sheets(Sheets.Count) '' Copy the Template tab to the end of the workbook
Sheets("Template (2)").Name = yr_temp '' Rename the new tab based on the year value for this loop
Sheets(yr_temp).Range("A1").Value = Sheets(yr_temp).Range("A1").Value & yr_temp '' Revise the new tab's description based on the year value for this loop
task_counter = 0
For task_temp = 1 To task_num '' Loop through each task
task_rem = (yr_counter + Sheets("Summary").Range("E4").Offset(task_temp - 1, 0).Value) Mod _
Sheets("Summary").Range("D4").Offset(task_temp - 1, 0).Value '' Check if the task is due this year (i.e., the year count (plus task age) is a factor of the task frequency)
If task_rem = 0 Then '' Then, for each task that is due this year...
task_counter = task_counter + 1 '' Increment the counter for the number of tasks that have been identified as due this year
Sheets("Summary").Rows(3 + task_temp).Copy '' Copy the task from the Summary sheet and insert it at the bottom of the new tab
Sheets(yr_temp).Rows(3 + task_counter).Insert (xlShiftDown)
End If
Next task_temp
Sheets(yr_temp).Columns("D:E").Delete (xlShiftToLeft) '' Delete the frequency and current age columns from the new tab
Sheets(yr_temp).Rows(4 + task_counter).Delete (xlShiftUp) '' Delete the blank placeholder row (used to preserve formatting and print area) from the new tab
Next yr_counter
'' Move all of the newly generated Maintenance Manual sheets to a new workbook
wb_current = ActiveWorkbook.Name '' Note: This is used in the following code block, but needs to be performed here for simplicity
Sheets(sheets_num + 1).Select '' Select the first annual sheet
For sheet_counter = 2 To yr_num '' Add each of the remaining annual sheets to the selection
Sheets(sheets_num + sheet_counter).Select False
Next sheet_counter
ActiveWindow.SelectedSheets.Move '' Move the selected sheets (hopefully all of the newly generated annual sheets) to a new workbook
'' Restore the macro workbook tabs to their original state (for aesthetic/convenience reasons) and then focus back to the newly created workbook
wb_new = ActiveWorkbook.Name
Workbooks(wb_current).Sheets("Instructions").Activate
Workbooks(wb_current).Sheets("Summary").Activate
Workbooks(wb_current).Sheets("Template").PageSetup.CenterHeader = Chr(10) & "Maintenance Items" '' Remove project name from Template tab header
Workbooks(wb_new).Activate
'' Restore screen updating
Application.ScreenUpdating = True
End Sub
I pulled up the error detail reports for the freezes through the Action Center and it looks like the "fault module" is mso.dll (at least it was in the few error reports I checked). The most promising suggested fix that I found was to run a repair of Office (through Programs and Features in the Control Panel). So far this has been working, but I won't call this one solved until I get in a couple more days of use without crashes.
UPDATE: I've been testing it out for a couple days now and still no crashes. I'm marking this as the accepted solution, but will update if the crash comes back. For anyone interest, here is one of the error reports:
**Source**
Microsoft Excel
**Summary**
Stopped working
**Date**
12/11/2014 4:55 PM
**Status**
Report sent
**Description**
Faulting Application Path: C:\Program Files (x86)\Microsoft Office\Office15\EXCEL.EXE
**Problem signature**
Problem Event Name: APPCRASH
Application Name: EXCEL.EXE
Application Version: 15.0.4569.1504
Application Timestamp: 52c5e9e1
Fault Module Name: mso.dll
Fault Module Version: 15.0.4569.1506
Fault Module Timestamp: 52e0c1d0
Exception Code: c0000602
Exception Offset: 0116b30f
OS Version: 6.1.7601.2.1.0.256.4
Locale ID: 1033
Additional Information 1: 6e8a
Additional Information 2: 6e8ae308d57954ab0513d50f2363e5fc
Additional Information 3: 8248
Additional Information 4: 8248af4ab5d8a2564e54f9d6dd7f5d2b
**Extra information about the problem**
Bucket ID: 94371593
Pertinent to your case, it's hard to perform remote debugging without having the actual data. As a general recommendation, apply debugging technique to that problematic statement in you code, like inserting:
' debugging: output to be sent to Immediate Window
Debug.Print task_temp
Debug.Print yr_temp
Debug.Print task_counter
' rest of your code, starting with problematic part
Sheets("Summary").Rows(3 + task_temp).Copy
Sheets(yr_temp).Rows(3 + task_counter).Insert (xlShiftDown)
Analyze the validity of values prior to the execution of that problematic line, in particular: if the row numbers are withing the valid range, and if the Sheets(yr_temp) exists.
Hope this will help to trace down the source of the Error.
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?
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.