Macro runs SQL query in loop (104 times) after entering data in first cell - vba

I've a macro which runs when user enters data in first cell. However, when I enter the data in first cell the macro executes the sql query and runs in the loop (104 times) and puts the column headers of the sql select statement on the spreadsheet.
Here is my macro:
Sub JobTaskHistory(ByVal Target As Range)
Dim sqlstring As String
Dim connstring As String
Dim Strcode As String
Dim rangeTest As Range
Set rangeTest = Range("A1")
'Strcode = Trim(InputBox("Please enter a Job Number", "Job Task history"))
Strcode = Target.Cells(1, 1).Value
sqlstring = "select distinct m.JobNumber , cast(m.ExpectedDate as DATE) 'Ship Date' , m.QuantityOrdered 'Quantity' & _
" from ArchiveJobHeader m left join AuxiliaryInfoFile af (nolock) on af.jobnumber=m.jobnumber left join JobExtra j on j.JobNumber = m.JobNumber " & _
" where m.JobNumber = '" & Trim(Strcode) & "'" & _
" order by 'Resulttime'"
connstring = "ODBC;DSN=Test;UID=Test;PWD=Test123"
Dim thisQT As QueryTable
Dim lastRow As Long, nextRow As Long
lastRow = Sheets("Sheet1").Range("A" & Rows.Count).End(xlUp).Row
nextRow = lastRow + 1
'Set thisQT = ActiveSheet.QueryTables.Add(Connection:=connstring, Destination:=Range("a1", "a1000"))
Set thisQT = ActiveSheet.QueryTables.Add( _
Connection:=connstring, _
Destination:=Range("A" & nextRow))
thisQT.BackgroundQuery = False
thisQT.Sql = sqlstring
thisQT.Refresh
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Call JobTaskHistory(Target)
End Sub

You can solve this by placing a Public variable in the module that contains the macro. If you don't have your macro in a module, you will need to put it in one, because this won't work from the Sheet code.
What Happens:
A value changes on the sheet.
The worksheet change event calls the macro.
The macro sets the public variable to TRUE.
Things change, triggering the change event again.
The change event tests for inProgress and if it IS TRUE, skips calling the macro.
The macro completes, and sets inProgress to False again.
In your module:
Public inProgress As Boolean
Sub JobTaskHistory(ByVal Target As Range)
inProgress = True
'YOUR CODE AS WRITTEN
inProgress = False
End Sub
In the worksheet code:
Private Sub Worksheet_Change(ByVal Target As Range)
If inProgress = False Then
Call JobTaskHistory(Target)
End If
End Sub
note:
Any time you have a change event, look to see what the scope is (ie, "worksheet_change"), because when you run anything as a result of it, if it causes the event to trigger again, you will have circular looping.
If you get caught in a loop you can't exit, try using 'Ctrl+PauseBreak'

Related

Excel VBA Onaction with .Select or .ScrollColumn

Good Morning everyone,
I am facing a strange Problem in Excel VBA.
So, I have this minimal Example. The only thing it's supposed to do is, add a Button to the Rightklick context menu. This button should then select a cell.
I searched a bit on StackOverflow and found a solution to passing string arguments in .onaction. But then it gets tricky. I can assign a Range and I can Print the Address and the second Argument in a Mesgbox. But I can't set Breakpoints and even stop doesn't work, nor will .select or .ScrollColumn do anything.
To Replicate just copy the Following code into a standard Module and Execute AddContextmenu to add the Button to the Contextmenu.
Option Explicit
Public Sub AddContextmenu()
Dim MySubMenu As CommandBarControl
Dim i As Long
'Clear Previous Menu Items
For Each MySubMenu In Application.CommandBars("Cell").Controls
If Not MySubMenu.BuiltIn Then
MySubMenu.Delete
End If
Next
'add menu
AddScrollButtons Application.CommandBars("Cell"), 1
End Sub
Public Sub AddScrollButtons(ByVal ContextMenu As CommandBar, ByVal baseindex As Long)
Dim cbb As CommandBarButton
Dim sFunction As String
'Add Button
Set cbb = ContextMenu.Controls.Add(Temporary:=True)
With cbb
sFunction = BuildProcArgString("ScrolltoColTest", "$F$10", "TestArg") ' Get Onaction string
.OnAction = sFunction
.Caption = "Scroll Tester"
.Style = msoButtonAutomatic
End With
End Sub
Function BuildProcArgString(ByVal ProcName As String, ParamArray Args() As Variant)
Dim tempArg As Variant
Dim temp As String
For Each tempArg In Args
temp = temp + Chr(34) + tempArg + Chr(34) + ","
Next
BuildProcArgString = "'" & ThisWorkbook.Name & "'!" & ProcName + "(" + Left(temp, Len(temp) - 1) + ")" ' (Workbook has to be included to ensure that the sub will be executed in the correct workbook)
End Function
Public Sub ScrolltoColTest(Addr As String, OtherArg As String)
Dim cell As Range
Set cell = ActiveSheet.Range(Addr) 'Get Cell that sould be selected from Addr
MsgBox cell.Address & vbNewLine & OtherArg 'Test if the Arguments have been passed correctly and the cell has been assigned
Stop 'Why doesn' this stop?
cell.Select 'Why doesn't this do anything
ActiveWindow.ScrollColumn = cell.Column 'Why doesn't this do anything
End Sub
As you will see in ScrolltoColTest the Part after the Msgbox will not work at all.
Does anyone know why that happens?

VBA Auto Save workbook every 10 seconds without activating workbook?

I am using the following vba code in a workbook open event:
Private Sub Workbook_Open()
On Error GoTo Message
Application.AskToUpdateLinks = False
ThisWorkbook.UpdateLinks = xlUpdateLinksNever
ActiveSheet.DisplayPageBreaks = False
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim currentTime As Date
currentTime = DateAdd("s", 10, Now)
Call CurUserNames
Application.OnTime currentTime, "SaveFile"
Exit Sub
Message:
Application.DisplayAlerts = False
Exit Sub
End Sub
I also have this code in a module:
Public Sub SaveFile()
On Error GoTo Message
ThisWorkbook.Save
Dim currentTime As Date
currentTime = DateAdd("s", 10, Now)
Application.OnTime currentTime, "SaveFile"
Exit Sub
Message:
Application.DisplayAlerts = False
Exit Sub
End Sub
What I am trying to do is automatically save my workbook every 10 seconds.
This works.
However, something quite annoying I've noticed happens. If a user has this workbook open in the background and is working on another excel workbook then this workbook will activate and display on top of the other workbook when saving.
This can be quite annoying for the user.
Is there a way I can get my workbook to save without activating the workbook?
P.S:
For some unknown reason, this also causes the workbook to reopen when its been closed.
EDIT:
List active users in workbook code:
Sub CurUserNames()
Dim str As String
Dim Val1 As String
str = "Users currently online:" & Chr(10)
For i = 1 To UBound(ThisWorkbook.UserStatus)
str = str & ThisWorkbook.UserStatus(i, 1) & ", "
Next
Val1 = DeDupeString(Mid(str, 1, Len(str) - 2))
Worksheets("Delivery Tracking").Range("F4").Value = Val1
End Sub
Function DeDupeString(ByVal sInput As String, Optional ByVal sDelimiter As String = ",") As String
Dim varSection As Variant
Dim sTemp As String
For Each varSection In Split(sInput, sDelimiter)
If InStr(1, sDelimiter & sTemp & sDelimiter, sDelimiter & varSection & sDelimiter, vbTextCompare) = 0 Then
sTemp = sTemp & sDelimiter & varSection
End If
Next varSection
DeDupeString = Mid(sTemp, Len(sDelimiter) + 1)
End Function
Users of a shared workbook can see Who has this workbook open now: just by going to the Review tab in the Ribbon and click the Shared Workbook icon in the Changes group. This will open the Shared Workbook dialog box, in it the tab Editing' shows *Who has this workbook open now:`*. Additionally the tab 'Advance' can be used to update the settings dealing with:
Track changes
Update changes
Conflicting changes between users
Include in personal view
Th9is example comes from How can I get list of users using specific shared workbook?
It is a little overkill. It creates a new workbook to put the users name in. But you can modify it to put the names in whatever sheet and whatever cells you want.
Put it in the sheet module under the selection change module. Then it will update every time the user moves to a different cell. If it is open and he's not at his desk - it doesn't do anything.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
End Sub
At the bottom is the code from the above link that you can modify to suit your own needs. It will be 1000 times better than saving a workbook every ten seconds. Which can actually take 3 or 4 seconds itself.
If you don't want to use selection change in the worksheet module then you could put your code into the workbook module Private Sub Workbook_Open()
and put it on a timer to run every 10 seconds. It will only take a fraction of a second instead of several seconds.
users = ActiveWorkbook.UserStatus
With Workbooks.Add.Sheets(1)
For row = 1 To UBound(users, 1)
.Cells(row, 1) = users(row, 1)
.Cells(row, 2) = users(row, 2)
Select Case users(row, 3)
Case 1
.Cells(row, 3).Value = "Exclusive"
Case 2
.Cells(row, 3).Value = "Shared"
End Select
Next
End With

How to prevent dropdown from executing when source list is changed programmatically

I have an activeX dropdown form on my spreadsheet which executes code on _Change. My code modifies the dropdowns list source (adding or deleting items). Whenever this happens, the _Change is called again.
I have various workarounds, all of which were some version of changing the list source, but with no success. The reason none of this has worked is because clearing or altering the .ListFillRange actually triggers the _Change event again.
How do I prevent the _Changeevent from getting called if I want to add or delete items in the .ListFillRange
UPDATE w EnableEvents set to false:
Public Sub SetRangeForDropdown()
On Error Resume Next
Application.EnableEvents = False
'Get new List of employees from Employee sheet
Dim rng1 As Range
With wsDB_employee
Set rng1 = .Range("A2:B" & .Range("A10000").End(xlUp).Row)
End With
With wsStage
.Cells.Clear
rng1.Copy .Range(.Cells(1, 1), .Cells(rng1.Rows.Count, 2))
End With
'Set range for dropdown on employee sheet
Dim rng2 As Range
Set rng2 = wsStage.Range("A1:B" & wsStage.Range("A10000").End(xlUp).Row)
'Update employee list named formula
ActiveWorkbook.Names.Add Name:="nfEmployeeList", RefersTo:=rng2
Dim str As String
str = rng2.Parent.Name & "!" & rng2.Address 'Source path for list fill range
wsMA.cmbEmployeeSelection.ListFillRange = str
Application.EnableEvents = True
End Sub
Apperantly EnableEvents does not work for ActiveX controls.
Thank you Microsoft for making life just a little bit more complicated!
Just found this: "Application.EnableEvents=False/True ONLY applies to Sheet and Workbook Events, not ActiveX Control Events" from here enter link description here
You can disable the events in the SetRangeForDropdown and then enable them back.
So, write the following at the start:
Application.EnableEvents = False
And the following at the end:
Application.EnableEvents = true
it's always a good habit to make (nearly) sure that events handling is always brought back, like follows:
Public Sub SetRangeForDropdown()
'...your code
On Error GoTo ExitSub
Application.EnableEvents = False
wsMA.cmbEmployeeSelection.ListFillRange = rng2
'Update employee list named formula
ActiveWorkbook.Names.Add name:="nfEmployeeList", RefersTo:=rng2
ExitSub:
Application.EnableEvents = True
End Sub
Furthermore, avoid On Error Resume Next unless you really need it
I have solved the problem by adding a global variable that prevents the _Change event from firing. Here is that code:
Private Sub cmbEmployeeSelection_Change()
If bNOTRUN = False Then 'Check if ActiveX event should fire or not
modEmployeeDB.SaveEmployeeData 'Save currently selected employee data
modEmployeeDB.DBSoll_To_WorkerInfo 'Get called employee data
End If
End Sub
And this is the module as modified... note the simple Boolean variable that I added:
Public Sub SetRangeForDropdown()
On Error GoTo SetRangeForDropdown_Error
bNOTRUN = True 'Global Variable that when True prevents Active X from firing
'Get new List of employees from Employee sheet
Dim rng1 As Range
With wsDB_employee
Set rng1 = .Range("A2:B" & .Range("A10000").End(xlUp).Row)
End With
With wsStage
.Cells.Clear
rng1.Copy .Range(.Cells(1, 1), .Cells(rng1.Rows.Count, 2))
End With
'Set range for dropdown on employee sheet
Dim rng2 As Range
Set rng2 = wsStage.Range("A1:B" & wsStage.Range("A10000").End(xlUp).Row)
'Update employee list named formula
ActiveWorkbook.Names.Add Name:="nfEmployeeList", RefersTo:=rng2
Dim str As String
str = rng2.Parent.Name & "!" & rng2.Address 'Source path for list fill range
wsMA.cmbEmployeeSelection.ListFillRange = str
bNOTRUN = False
On Error GoTo 0
Exit Sub
SetRangeForDropdown_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure SetRangeForDropdown of Sub modEmployeeDB"
bNOTRUN = False
End Sub

Apply macro on cell value change : 1004 error

I'm trying to apply a macro when value from a cell change. I've this code in the Dashboard Sheet :
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("FilterChoice")) Is Nothing Then Call ApplyDashboardFilter
End Sub
The trigger works fine and the macro is executed right after, but don't know why I got an error on the Advanced filter function : "application-defined or object-defined error"
Option Explicit
Sub ApplyDashboardFilter()
Dim rng As Range
Dim filterName As String
Dim tableName As String
filterName = "Filter" & Replace(Sheets("Dashboard").Range("FilterChoice").Value, " ", "")
tableName = filterName + "[#All]"
Sheets("Dashboard").Activate
Sheets("Dashboard").Columns("A:AN").Cells.Clear
Sheets("Critical Flows").Range("ClosingFlows[#All]").AdvancedFilter Action:=xlFilterCopy _
, CriteriaRange:=Sheets(filterName).Range(tableName) _
, CopyToRange:=Sheets("Dashboard").Range("A1"), Unique:=False
Set rng = Range(Range("A1"), Range("A1").CurrentRegion)
ActiveSheet.ListObjects.Add(xlSrcRange, rng, , xlYes).Name = _
"Flows" & filterName
ActiveSheet.ListObjects("Flows" & filterName).TableStyle = "TableStyleMedium3"
If Sheets("Dashboard").Range("FilterChoice").Value = "Orchestrated" Then
Call ApplyFlormulaRunbookName
End If
End Sub
The macro works when triggered by a button on Dashboard sheet.
Am I missing something ?
Thanks in advance,
EDIT :
Well, something weird happened. I just re-opened the file after a break and it worked.
I suspect something happened with the ActiveSheet and / or a conflict with another workbook since I'm playing with 2 other workbooks and 10 sheets overall.
Is it possible ?
I've added as an answer as the comments won't allow me to format correctly. This code just references the sheets, rather than selecting them:
Sub ApplyDashboardFilter()
Dim rng As Range
Dim filterName As String
Dim tableName As String
Dim wrkShtDash As Worksheet
Dim wrkShtFlows As Worksheet
Set wrkShtDash = ThisWorkbook.Worksheets("Dashboard")
Set wrkShtFlows = ThisWorkbook.Worksheets("Critical Flows")
filterName = "Filter" & Replace(wrkShtDash.Range("FilterChoice").Value, " ", "")
tableName = filterName + "[#All]"
wrkShtDash.Columns("A:AN").Cells.Clear
wrkShtFlows.Range("ClosingFlows[#All]").AdvancedFilter Action:=xlFilterCopy _
, CriteriaRange:=ThisWorkbook.Worksheets(filterName).Range(tableName) _
, CopyToRange:=wrkShtDash.Range("A1"), Unique:=False
Set rng = wrkShtDash.Range(wrkShtDash.Range("A1"), wrkShtDash.Range("A1").CurrentRegion)
wrkShtDash.ListObjects.Add(xlSrcRange, rng, , xlYes).Name = _
"Flows" & filterName
wrkShtDash.ListObjects("Flows" & filterName).TableStyle = "TableStyleMedium3"
If wrkShtDash.Range("FilterChoice").Value = "Orchestrated" Then
Call ApplyFlormulaRunbookName 'Spelt correctly?
End If
End Sub
Note: I haven't tested the code, it's just showing that you don't have to activate the sheet before working on it and is explicit about which file or sheet it's working with - ThisWorkbook means the file that the VBA code is in.

saving global variable from private sheet

I am trying to send out an email with updates when the the sheet are saved. To do this I am tracking changes and then trying to save these changes as a global string:
Public outString As String
Public Sub Worksheet_Change(ByVal Target As Range)
Dim colN, rowN As Integer
Dim changeHeading As String
Dim drawingNumber, partNumber As Integer
'Do nothing if more than one cell is changed or content deleted
If Target.Cells.Count > 1 Or IsEmpty(Target) Then Exit Sub
'Stop any possible runtime errors and halting code
On Error Resume Next
Application.EnableEvents = False
colN = Target.Column
rowN = Target.Row
changeHeading = ThisWorkbook.Sheets("List").Cells(1, colN).Value 'Header of the changed cell
partNumber = ThisWorkbook.Sheets("List").Cells(rowN, 2).Value 'Partnumber changed
drawingNumber = ThisWorkbook.Sheets("List").Cells(rowN, 4).Value 'Drawingnumber changed
outString = outString & vbNewLine _
& "PartNumber: " & partNumber & " DrawingNumber: " & drawingNumber _
& " " & changeHeading & ": " & Target & vbNewLine
'Turn events back on
Application.EnableEvents = True
'Allow run time errors again
On Error GoTo 0
End Sub
So this piece of code works nice except if I alter several column on the same row then each change will be presented on a new line instead of the same line, Maybe i have to use a dictionary with partnumber as key to avoid this.
Then in thisworkbook sheet i have the following code
Public outString
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Call track
Call missingDrawings
Call updateText
End Sub
However now the outString variable is , so what did I do wrong when declaring the global variable outString?
You seem to have two variables called outString one in the worksheet and one in the workbook. You should only have one. If you leave the one in thisWorkbook (adding As String would be a good idea), then you can access it from the sheet by using ThisWorkbook.outString.