Excel VBA: Variables resetting to 0 for some reason - vba

here's my current code:
Private Sub Workbook_Open()
sumn1 = Sheets("Main").Cells(1, 1).Value
sumn2 = Sheets("Main").Cells(2, 1).Value
sumn3 = Sheets("Main").Cells(3, 1).Value
sumn4 = Sheets("Main").Cells(1, 2).Value
sumn5 = Sheets("Main").Cells(2, 2).Value
sumn6 = Sheets("Main").Cells(3, 2).Value
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Sheets("Main").Cells(1, 1) = sumn1
Sheets("Main").Cells(2, 1) = sumn2
Sheets("Main").Cells(3, 1) = sumn3
Sheets("Main").Cells(1, 2) = sumn4
Sheets("Main").Cells(2, 2) = sumn5
Sheets("Main").Cells(3, 2) = sumn6
End Sub
So, while the workbook is open and it's being worked in, after some actions the variables sumn1, sumn2..etc. (which are global variables) are getting added values, like +10 to sumn1, or +5 to sumn2 and so on. Since I want them saved after closing the workbook, I save them in a cell that I have hidden with ";;;".
The problem is, sometimes it works correctly, but sometimes (usually after longer time since workbook has been closed since) the variables reset to 0.
So first of, is my approach an alright one on how I am saving the data or its just not working because this approach is bad? If it's an alright approach then I guess I should search for my mistake somewhere else then.

All your variables are also reset to zero when "reset" is clicked within the VBA editor, or an "End" statement is reached. There could be other occasions too although I can't think of any. You are currently only guarding against the workbook being closed and reopened.
So your approach is brittle.
What you could do is remove the global variables altogether, and always refer to the worksheet for the setting and retrieval of their values.

You can improve your approach with a couple of helper routines
Dim gVars as Variant
Sub LoadVariables()
' Load data from s/s into variable
gVars = Sheets("Main").Range(Cells(1, 1), Cells(3, 2)).Value
End sub
Sub SaveVariables()
' Save data from variable into variables
Sheets("Main").Range(Cells(1, 1), Cells(3, 2)).Value = gVars
End sub
Then in your code use LoadVariables when you need to use the data (usually at the start of whatever sub needs it) and SaveVariables when you have updated the values. Obviously this needs more error handling, etc.
The loading of the entire variables set into a single array also makes it easier to maintain the code (less chance of typos) and handle iterations if needed.

Related

Excel File Open Macro

So I'm trying to create a Macro That will update a cell when the file is opened. I'm getting the 424 Error and so I tried to do a better job defining my code/object but It still wasn't succesfull. I think I'm missing/overlooking something really easy but I can't figure it out and this is my first project so I'm trying to learn and gain a better understanding then just googling a segment of code that will work.
Private Sub Auto_Open()
Dim Try1 As String
Try1 = ActivateSheet.Cells(3, 2).Select
Tryl = "-"
' My first attempt is shown below
'
'Sheets("Current Calc").Activate
'ActivateSheet.Cells(3, 2).Value = "-"
End Sub
You've got a typo in your commented code
What you have...
Sheets("Current Calc").Activate
ActivateSheet.Cells(3, 2).Value = "-"
What it should be...
Sheets("Current Calc").Activate
ActiveSheet.Cells(3, 2).Value = "-"
Also, I should mention you should avoid using .Activate and .Select unless necessary. With that being said, I'd suggest the below code instead...
Sheets("Current Calc").Cells(3, 2).Value = "-"
EDIT:
When using Auto_Open, Excel must be opened MANUALLY in order for the code to execute; thus if it is opened via VBA this event will NOT trigger. If you want an event to trigger via VBA as well as manually, I'd suggest using Workbook_Open
Try with the following sub:
Private Sub Workbook_Open()
Dim Try1 As String
Try1 = ActiveSheet.Cells(3, 2).Select
Tryl = "-"
End Sub
Some advices:
write your code in lowercase. When you change a line, your code will change to upper and lower case automatically, this way you will detect if you have some typo error.
write a function or object ant type . This will open a dropdown list, this way you will also avoid typo error.

.find() triggers run-time error 91 even though all variables are set VBA possibly due to bad references

I am writing code to create a template. This code populates a tab named "fullDistribution" from user-input on different tabs in the same wb. I have a working section of code that I wrote in a separate module (for testing) away from my master module. The code runs properly and executes completely when it is separate. When I pasted this section of code into my master module and ran it, I began receiving "Run-time error 91: object variable or with block variable not set" at the start of the newly-pasted code. I am not using any with blocks, and all of my variables are set. I made no changes in my code when I transferred it to my master module, and I carried over the new variables I created.
This is the selection of code that I wrote in a separate module:
Worksheets("bls2016").Activate
tcount = WorksheetFunction.CountA(Worksheets("detailedEntity").Range("D2:D" & Cells(Rows.Count, "D").End(xlUp).Row))
acount = WorksheetFunction.CountA(Worksheets("detailedEntity").Range("K2:K7"))
Application.ScreenUpdating = False
Dim h As Integer
Dim f As Integer
Dim blstate As Range
Dim bl As Range
Dim state As Range
Dim deat As Range
Dim agje As Range
Dim e As Integer
Dim r As Integer
Dim ii As Integer
Set blstate = Worksheets("bls2016").Range("D2:D" & Cells(Rows.Count, "D").End(xlUp).Row)
Set state = Worksheets("detailedEntity").Range("Q1")
Set deat = Worksheets("detailedEntity").Range("D2:D" & Cells(Rows.Count, "D").End(xlUp).Row)
Set agje = Worksheets("detailedEntity").Range("L2:M" & Cells(Rows.Count, "M").End(xlUp).Row)
h = Activecolumn
f = Activerow
r = 2
x = 120
For e = 1 To (acount * acount)
blstate.Find(state).Select
For ii = 1 To x
'ccnt = acst.Offset(0, 1)
ccgv = ActiveCell.Offset(0, 2)
acem = ActiveCell.Offset(0, 5)
Do While True
vl1 = Application.IfNa(Application.VLookup(Worksheets("fullDistribution").Cells(r, 2), deat, 1, False), 0)
If vl1 = 0 Then
Worksheets("fullDistribution").Cells(r, 4) = 0
Else:
vl2 = Application.IfNa(Application.VLookup(Worksheets("fullDistribution").Cells(r, 1), agje, 2, False), 0)
If ActiveCell.Offset(0, 1).Value = "Unknown Or Undefined" Then
Exit Do
Else:
If vl2 = ccgv Then
Worksheets("fullDistribution").Cells(r, 4) = acem
ElseIf vl2 <> ccgv Then
Worksheets("fullDistribution").Cells(r, 4) = ActiveCell.Offset(x + 1, 5)
Else:
End If
End If
End If
Exit Do
Loop
ActiveCell.Offset(f + 1, h).Select
r = r + 1
Next ii
Next e
The error triggers at the line "blstate.find(state).select" which tells excel to look in a dynamic range that contains the names of states and select the first instance of the state to use as the Activecell. Again, this works when it's run outside of the main module.
I believe this has something to do with a reference area. When this runs alone and finishes, I have to have a specific worksheet activated for it to run properly. If my excel workbook is open to a different tab, it will not run. My main module too only executes properly if it is run on a specific worksheet/tab.
If need be, I can edit my post and provide my whole master code.
It may be a problem of not fully referencing sheets, eg amend your blstate line to
with Worksheets("bls2016")
Set blstate = .Range("D2:D" & .Cells(.Rows.Count, "D").End(xlUp).Row)
end with
Then it might find the value and not error. You should look up how to use the Find method as your way is destined to cause you headaches.
blstate.Find(state).Select
Your code assumes that .Find finds what it's looking for. When Find doesn't find what it's looking for, the function returns Nothing, which is essentially a null object reference - and you can't make member calls on Nothing without getting run-time error 91.
Split it up:
Dim result As Range
Set result = blstate.Find(state)
If Not result Is Nothing Then
result.Select 'questionable anyway, but that's another issue
Else
MsgBox "Value '" & state & "' was not found in " & blstate.Address(External:=True) & "."
Exit Sub
End If
As for why it's not finding what you're looking for, Tim Williams already answered that:
Find recalls all settings used in the last call (even if you use the GUI to perform the Find), so make sure you specify the settings you want when you call it via VBA. If you don't do that, it may not work as you expect.... – Tim Williams 42 mins ago
My issue was very much related to incorrect referencing, however, I was able to resolve this issue by keeping the specific piece of code I was testing in a separate sub, and calling it from my main code, 'full distribution'.
Call test
'test' is the name of the sub with the tested code. This is a temporary fix to the solution, and if anyone struggles with referencing, try this.

Randomising a number then displaying a corresponding cell on workbook open

Good afternoon. I have tried multiple methods now to achieve this. In essence the code works when I run it, however, it doesn't seem to do it when the Workbook opens as it should. Is there an easier way to achieve this?
Basically in rows 5-28 there are random strings, and I want cell G4 to show one of the random strings every time the workbook is opened.
I think I might be along the right lines, but am I missing anything obvious?
Many thanks
Private Sub Workbook_Open()
wbOpenEventRun = True
Dim MyValue
MyValue = Int((28 * Rnd) + 5)
Sheets("Hints & Tips").Range("G4") = Cells(MyValue, 7)
End Sub
Try this:
Private Sub Workbook_Open()
Randomize 'As Suggested by John Coleman
wbOpenEventRun = True
Dim ws As Worksheet
Set ws = Sheets("Hints & Tips")
ws.Range("G4").Value = ws.Range("G" & Int((23 * Rnd()) + 5)).Value
End Sub
You could use Application.WorksheetFunction.RandBetween():
Private Sub Workbook_Open()
wbOpenEventRun = True
Dim MyValue
MyValue = Application.WorksheetFunction.RandBetween(5,28)
Sheets("Hints & Tips").Range("G4") = Cells(MyValue, 7)
End Sub
You were trying to assign something to the Range object, not to the cell's value. Read the Value property of the source cell and write that to the Value property of the destination:
Sheets("Hints & Tips").Range("G4").Value = Sheets("SourceSheet").Cells(MyValue, 7).Value
It's best to also specify which sheet is the source of the data, or it will depend on which sheet is active at the time the macro runs - unless that's the behaviour you want.

Determining When a Row/Cell is Inserted or Deleted in Excel VBA [duplicate]

This question already has answers here:
Determine whether user is adding or deleting rows
(7 answers)
Closed 6 years ago.
I was trying to figure out how to determine when a row (or column) is inserted or deleted in Excel using VBA during the Worksheet_Change event and came across the topic here. However, the suggested answers given there, didn't fully capture all instances when a row is inserted or deleted on a worksheet including:
While they capture instances of when ENTIRE rows are inserted or deleted, they don't capture instances where only a group of cells are inserted or deleted shifting cells below or above down or up.
While they capture instances of rows inserted or deleted within a USEDRANGE, they don't capture instances where you are inserting outside of the used range (e.g. inserting a row below where you have data entered).
Given the above statements, I searched for more options and could not find any. Then, I came up with my own solution which I'm providing here to help others. I'm also hoping at the same time to get feedback on any flaws or areas of improvement.
The below Excel VBA codes will determine whether or not a user has either inserted or deleted an entire row or group of cells (shifting cells up or down) all within or out of the used range.
Here's the code (could also be converted for columns as well):
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Cells(Target.Row + Target.Rows.Count, Target.Item(1, 1).Column).ID = Target.Address
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Item(1, 1).ID <> "" Then
MsgBox "deleted row"
Else
MsgBox "inserted row"
End If
Target.Item(1, 1).ID = ""
Cells(Target.Row + Target.Rows.Count, Target.Item(1, 1).Column).ID = Target.Address
End Sub
It's very simple actually and uses cell tagging to keep track of what the user actions which you can then use to determine if an insert or delete occurred.
"Wait?!" You might say. "There's a way to tag cells?" Or... "What is cell tagging".
Well, there really isn't a "tag" property for a cell, but you can retrofit the "Cell.ID" property and use it as a tag for a cell. The "Cell.ID" property's original intent is to be used when saving the Excel file as a webform, but it works pretty as a tag as well if your not going to create webforms. The only downside is you cannot save it's value with the workbook when you close and save the file. But, that's okay for this because you don't need to save tag values anyway.
Anyway, I think it works well. But, please let me know if you see any areas for improvement or flaws.
Only issue I can think of if as you click around, it tags the cells continually and leaves a lot of left over trash lying around if you don't end up making a change to a cell. But, this is not saved when you close the workbook and only occurs while the file is open.
Ok I'm back. Still testing, but here is my status so far. It got more complicated because you have to keep track of an additional tag and clean-up after yourself. It's a matter of keeping track of what Excel does to the tagged cells (did they shift down, shift up, etc...). Once that pattern is figured out and fully trapped, then I think this should work. Fingers crossed.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Call TagCellManager(Target)
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim CurCel As Range, CurRow As Range
Dim TagCel As Range, TagRow As Range
Dim NxtCel As Range, NxtRow As Range
Set CurCel = Target.Item(1, 1)
Set CurRow = Cells(CurCel.Row, 1)
Set TagCel = Cells(CurCel.Row + Target.Rows.Count, CurCel.Column)
Set TagRow = Cells(CurCel.Row + Target.Rows.Count, 1)
Set NxtCel = TagCel.Offset(Target.Rows.Count, 0)
Set NxtRow = TagRow.Offset(Target.Rows.Count, 0)
If TagCel.ID <> "" And TagRow.ID <> "" Then
'ignore me. i'm entering data
ElseIf NxtCel.ID <> "" Or NxtRow.ID <> "" Then
MsgBox "row inserted"
Set Target = Range(NxtCel.ID)
Call TagCellManager(Target)
ElseIf CurCel.ID <> "" Or CurRow.ID <> "" Then
MsgBox "row deleted"
Set Target = Range(CurCel.ID)
Call TagCellManager(Target)
End If
End Sub
Sub TagCellManager(Target As Range)
Dim CurCel As Range, CurRow As Range
Dim TagCel As Range, TagRow As Range
Dim NxtCel As Range, NxtRow As Range
Set CurCel = Target.Item(1, 1)
Set CurRow = Cells(CurCel.Row, 1)
Set TagCel = Cells(CurCel.Row + Target.Rows.Count, CurCel.Column)
Set TagRow = Cells(CurCel.Row + Target.Rows.Count, 1)
Set NxtCel = TagCel.Offset(Target.Rows.Count, 0)
Set NxtRow = TagRow.Offset(Target.Rows.Count, 0)
CurCel.ID = ""
CurRow.ID = ""
TagCel.ID = Target.Address
TagRow.ID = Target.Address
NxtCel.ID = ""
NxtRow.ID = ""
End Sub

VBA - range.sort script unexpectedly runs other script it shouldn't run

I'm working on a workbook and I'm getting an error which is driving me crazy. I really have no idea what's going wrong here.
On the main worksheet (Bestandsübersicht) I have a combobox named ddBestand. On the change event of that combobox it runs a script that checks if certain buttons should be enabled or disabled. The code for this is:
Private Sub ddBestand_Change()
On Error GoTo ExitSub
Dim i As Integer
i = 3
Dim WS As Worksheet
Set WS = Sheets("Bestandsübersicht")
If ddBestand.Value = "" Then GoTo ExitSub
Do Until WS.Cells(i, 1).Value = ddBestand.Value
i = i + 1
Loop
If WS.Cells(i, 13).Value = 0 Or _
Right(Sheets("Bestandsübersicht").Range("AL1").Value, 3) <> "yes" Then
btnNetwork.Enabled = False
Else
btnNetwork.Enabled = True
End If
btnChange.Enabled = True
btnSpecifics.Enabled = True
btnCopy.Enabled = True
Exit Sub
ExitSub:
btnChange.Enabled = False
btnSpecifics.Enabled = False
btnNetwork.Enabled = False
btnCopy.Enabled = False
End Sub
This works totally fine when I use ddBestand. But sometimes when I run other scripts this script unexpectedly starts to run, even though those scripts do not relate to eachother. For example, when I run the initialize even for a userform (which is launched from another worksheet) it starts to run at this range.sort method:
Sheets("DB_Network").Columns("A:C").Sort key1:=Sheets("DB_Network").Range("A2"), _
order1:=xlAscending, Header:=xlNo
it gives the error 1004 (Unable to set the Enabled propert of the OLEObject class (which is logica, because as we're on another worksheet, the property for those buttons is wrong). As I didn't know how to stop the first script from running, I fixed the script by changing the OLEObjects to this:
Sheets("Bestandsübersicht").OLEObjects("btnChange").Object.Enabled = True
Solving the symptoms might not be the prettiest solution, but as I couldn't find out what the problem really was, I decided this was a suitable solution. But it got crazier. I'm still using another version of this document as I need it for my work. Somehow the same sort method started running the same script in the other document, which made the same error occur. Now I really want to solve this problem, as I don't want it to unexpectedly run scripts in other documents. Is there anybody out here who can help? Would be much appreciated!
I would guess that your combobox is directly linked to a range using the ListFillRange and/or LinkedCell properties. That is not a good idea for precisely this reason. I suggest you use code to populate the control using its .List property instead, which is easier and faster than .AddItem:
Sheets("Bestandsübersicht").OLEObjects("ddBestand").Object.List = Sheets("blah").Range("A2:A100").Value
for example.