milliseconds-timer returns Unexpected error 35010 - excel

I have code in Excel VBA which I like to run several times a second.
Before I used this method :
Application.On Time Now + Timevalue("00:00:01")
Although this method can only handle seconds (and no milliseconds).
For that reason I replaced this timer with a milliseconds timer (see code below).
Although since I integrated the milliseconds timer with my other Excel VBA code it sometimes gives this error: "Unexpected error 35010".
There are several reasons mentioned for this error:
VBA compile Error: unexpected error 35010
I did test the code on two computers and on both it returns the error.
I also did test on Excel Pro Plus 2019 and Excel Pro Plus 2016. Both 64 bit versions. Both return the error.
Since I expected it was due to a corrupt file, I did made a new workbook and copied all VBA code into this workbook. I started with empty sheets and copied all formulas from the old workbook to the new workbooksheets. I did this twice although these files still return the error.
The error always occurs directly after I start the code. The error does not refer to a specific line of code (in yellow).
Somtimes the error occurs, while with exactly the same file and contents the error does not occur.
This is the code for the timer:
Option Explicit
Private Declare PtrSafe Function SetTimer Lib "user32" _
(ByVal hWnd As LongPtr, _
ByVal nIDEvent As LongPtr, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare PtrSafe Function KillTimer Lib "user32" _
(ByVal hWnd As LongPtr, _
ByVal nIDEvent As LongPtr) As Long
Private m_TimerID As LongPtr
'Note: The duration is measured in milliseconds.
' 1,000 milliseconds = 1 second
Public Sub StartTimer(ByVal Duration As Long)
'If the timer isn't already running, start it.
If m_TimerID = 0 Then
If Duration > 0 Then
m_TimerID = SetTimer(0, 0, Duration, AddressOf TimerEvent)
If m_TimerID = 0 Then
MsgBox "Timer initialization failed!"
End If
Else
MsgBox "The duration must be greater than zero."
End If
Else
MsgBox "Timer already started."
End If
End Sub
Public Sub StopTimer()
'If the timer is already running, shut it off.
If m_TimerID <> 0 Then
KillTimer 0, m_TimerID
m_TimerID = 0
Else
MsgBox "Timer is not active."
End If
End Sub
Public Property Get TimerIsActive() As Boolean
'A non-zero timer ID indicates that it's turned on.
TimerIsActive = (m_TimerID <> 0)
End Property
This is example code how I use the timer:
Public CountSomething As Variant
Private timerRunning As Boolean
Sub UPDATECLOCK()
''Application.Calculation = xlManual
If Not timerRunning Then
CountSomething = 0
r = 0
StartTimer 100
timerRunning = True
End If
End Sub
Sub STOPCLOCK()
StopTimer
timerRunning = False
End Sub
Sub TimerEvent()
On Error GoTo ErrHandler
'force code to run on the main thread instead of Windows api thread:
If timerRunning Then Application.OnTime Now, "updateWorksheet"
Exit Sub
ErrHandler:
Debug.Print Err.Number
End Sub
Sub updateWorksheet()
'call code to process here
'or this example code in this case:
CountSomething = CountSomething + 1
Worksheets("Sheet1").Cells(2, 9).Value = CountSomething
'write 1 in cel A1 to stop code running:
knop = ActiveSheet.Cells(1, 1)
If knop = 1 And timerRunning Then
StopTimer
timerRunning = False
End If
End Sub
Does anyone know what the cause of the error can be?
Thanks a lot!

There are some conceptual errors in the code: using OnTime Now ... is equivalent to immediately calling updateWorksheet.
The timerRunning variable is superfluous, you can use the TimerIsActive function.
The code for updateWorksheet can (should) be inserted into the TimerEvent and there is no reason to check if the timer is active; the routine is called only if the timer is active.
This code is equivalent
Sub UPDATECLOCK()
If Not TimerIsActive Then
CountSomething = 0
r = 0
StartTimer 100
End If
End Sub
Sub STOPCLOCK()
StopTimer
End Sub
Sub TimerEvent()
CountSomething = CountSomething + 1
Worksheets("Sheet1").Cells(2, 9).Value = CountSomething
'write 1 in cel A1 to stop code running:
If ActiveSheet.Cells(1, 1) = 1 Then 'why ActiveSheet?
StopTimer
End If
End Sub

Related

Workbook opens again when I close it if opened by someone else also

Sorry about the confusing title. But if a file is opened by someone else, when I open it I get the message that it's opened by someone else and I get the option to open it without being able to save it.
If I choose that option when I close the file it auto opens again.
The file has a few macros that is most likely the cause but I can't understand how it causes this issue.
First of all. The file has an inactivity tracker in a module that runs every five seconds:
Private Type LASTINPUTINFO
cbSize As Long
dwTime As Long
End Type
#If VBA7 Then
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Private Declare PtrSafe Sub GetLastInputInfo Lib "User32" (ByRef plii As LASTINPUTINFO)
#Else
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Declare Sub GetLastInputInfo Lib "User32" (ByRef plii As LASTINPUTINFO)
#End If
Public tid As Variant
Public lista As Scripting.Dictionary
Function IdleTime() As Single
Dim a As LASTINPUTINFO
a.cbSize = LenB(a)
GetLastInputInfo a
IdleTime = (GetTickCount - a.dwTime) / 1000
End Function
Sub Form_Timer()
' lookup the inaktivity time for current user if 0
If tid = 0 Then
LR = ThisWorkbook.Sheets("Inaktivitet").Cells(ThisWorkbook.Sheets("Inaktivitet").Rows.Count, "B").End(xlUp).Row
tid = Application.VLookup(UCase(Environ("UserName")), ThisWorkbook.Sheets("Inaktivitet").Range("B17:G" & LR), 6, False)
If Not IsError(tid) Then
tid = tid * 60
Else
' if user does not have a specified inactivity time set the "other" time to user
tid = Application.VLookup("Övriga", ThisWorkbook.Sheets("Inaktivitet").Range("B17:G" & LR), 6, False)
tid = tid * 60
End If
End If
tme = IdleTime
'Debug.Print tme & " " & Now()
' display warning when less than 65 seconds
If tid - tme < 65 Then
UserForm2.Show vbModeless
DoEvents
End If
If tme >= tid Then
If lista.Exists(UCase(Environ("UserName"))) Then ThisWorkbook.Save
Application.DisplayAlerts = False
ThisWorkbook.Close
End If
On Error Resume Next
Application.OnTime RunTime, "Form_Timer", Schedule:=False
Application.OnTime Now + TimeSerial(0, 0, 5), "Form_Timer"
End Sub
This code is initiated with the workbook_open event below.
The dictionary created is to save the inactivity times (and write permissions) in a dictionary so that it "can't" be manipulated without save permission.
Public RunTime
Private Sub Workbook_Open()
Set lista = New Scripting.Dictionary
For I = 18 To ThisWorkbook.Sheets("Inaktivitet").Range("B200").End(xlUp).Row
If ThisWorkbook.Sheets("Inaktivitet").Range("B" & I).Value <> "" And ThisWorkbook.Sheets("Inaktivitet").Range("B" & I).Value <> "Övriga" Then
lista.Add Key:=ThisWorkbook.Sheets("Inaktivitet").Range("B" & I).Value, Item:=ThisWorkbook.Sheets("Inaktivitet").Range("C" & I).Value
End If
Next I
Application.Calculation = xlCalculationManual ' This is done to make the workbook more responsive due to other event macros
Form_Timer ' <---- Here
End Sub
And in BeforeSave I make sure the username is in the dictionary, if not they are not allowed to save, and in BeforeClose I turn off the inactivity tracker and set calculation to automatic:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
On Error Resume Next
Application.OnTime RunTime, "Form_Timer", Schedule:=False
Application.Calculation = xlAutomatic
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
On Error GoTo err
If Not lista.Exists(UCase(Environ("UserName"))) Then
Cancel = True
MsgBox "Du har inte behörighet att spara schemat" ' you don't have permissions to save.
End If
GoTo subend:
err:
MsgBox "Något har gått fel, det går inte spara." & vbNewLine & "filen går in i felsäkert läge nu. Kopiera celler/blad som är ändrade till en ny excelfil och spara den." & vbNewLine & "Stäng därefter alla Excelfiler innan du försöker öppna någon Excelfil igen."
Application.EnableEvents = False
subend:
On Error GoTo 0
End Sub
When the user change active workbook the calculation toggles:
But I doubt this is the cause of it.
Private Sub Workbook_WindowActivate(ByVal Wn As Window)
If Wn.Caption = ThisWorkbook.Name Then
Application.Calculation = xlCalculationManual
Else
Application.Calculation = xlCalculationAutomatic
End If
End Sub
Private Sub Workbook_WindowDeactivate(ByVal Wn As Window)
Application.Calculation = xlCalculationAutomatic
End Sub
The rest of the macro running is SheetChange, SheetActivate, and SheetSelectionChange code to color text on the sheets to help the user and display various messages.
What I believe is the issue is that the inactivity timer, that it by some reason runs again after the workbook has been closed and thus open the workbook to do it.
Or do you see something else that cause the workbook to auto open again? It only happens when someone else has the write permissions and I open it no write permissions.
When I close the workbook I get the question if I want to save the changes and I press No.
This should turn off the inactivity timer and then close it, and stop. At least as I see it.
I know that the inactivity timer can be turned off by a skilled person and that the write permission using the dictionary is not 100% safe, but it's not intended to be.
The issue is that RunTime variable is not updated in the inactivity timer.
I missed that part.
The current inactivity (sub Form_timer()) code ends with:
If tme >= tid Then
If lista.Exists(UCase(Environ("UserName"))) Then ThisWorkbook.Save
Application.DisplayAlerts = False
ThisWorkbook.Close
End If
On Error Resume Next
Application.OnTime RunTime, "Form_Timer", Schedule:=False
Application.OnTime Now + TimeSerial(0, 0, 5), "Form_Timer"
End Sub
It should have been:
If tme >= tid Then
If lista.Exists(UCase(Environ("UserName"))) Then ThisWorkbook.Save
Application.DisplayAlerts = False
ThisWorkbook.Close
End If
On Error Resume Next
Application.OnTime RunTime, "Form_Timer", Schedule:=False
RunTime = Now + TimeSerial(0, 0, 5)
Application.OnTime RunTime, "Form_Timer"
End Sub
This now makes sure I have RunTime set correctly so that it can be canceled with the BeforeSave event.

Each VBA sub works perfectly separately, but calling subs from another function doesn't work

I wrote 2 subs to automate a daily task.
First sub MatriksFlowUpdate calls 2 other subs RightClick and SingleClick to simulate a right click and then a left click on a certain part of the screen. This is done in order to prompt another program to create an Excel file and save it under C:. This sub works correctly on its own (i.e. it simulates a right click and a left click at the desired locations on the screen, prompting another program to produce an Excel sheet)
Second sub CloseInstance finds the Excel sheet created above, and closes it. This sub also works correctly on its own.
However, when I try to call these 2 subs one after the other in another sub MainSequence, I get an error saying the Excel that should be found and closed by the second sub can't be found. So I get an error on the CloseInstance sub at the location below
Set xlApp =GetObject("C:\MATRIKS\USER\REPORTS\EXCEL\Temp.xls").Application
I've tried many things to fix this, but I feel like I am going around in circles for the past few days. Any help would be much much appreciated.
P.S. My first time posting a q on stackoverflow so please bear with me with the formatting.
Public Declare PtrSafe Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Long
Public Declare PtrSafe Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Public Const MOUSEEVENTF_LEFTDOWN = &H2
Public Const MOUSEEVENTF_LEFTUP = &H4
Public Const MOUSEEVENTF_RIGHTDOWN As Long = &H8
Public Const MOUSEEVENTF_RIGHTUP As Long = &H10
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Sub MainSequence()
'This sub pieces together MatriksFlowUpdate and CloseInstance
Call MatriksFlowUpdate
Sleep 2000
Call CloseInstance
End Sub
Sub MatriksFlowUpdate()
'Prompts 3rd party software (Matriks) to produce Excel with latest flow data
Call RightClick
Call SingleClick
End Sub
Private Sub RightClick()
'Simulates a mouse right click at desired screen coordinates
Sleep 1000
SetCursorPos 1750, 750 'x and y position
mouse_event MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0
End Sub
Private Sub SingleClick()
'Simulates a mouse left click at desired screen coordinates
Sleep 1000
SetCursorPos 1750, 650 'x and y position
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
End Sub
Sub CloseInstance()
'Finds the instance of Excel where Matriks exported its excel and closes that instance of Excel
Dim xlApp As Excel.Application
Dim WB As Workbook
Set xlApp =GetObject("C:\MATRIKS\USER\REPORTS\EXCEL\Temp.xls").Application
Set WB = xlApp.Workbooks("Temp.xls")
WB.Close
End Sub
Thanks to all your help, I was able to solve the problem as below:
as per DisplayName's suggestion, this was an Excel freeze issue when Sleep function was called. When Sleep function was called, Excel froze and blocked the 3rd party program from creating its own Excel instance.
I built on this idea and created a new function called WasteTime and added it to my code. I am using this function instead of Sleep in the code, thereby bypassing the Excel freeze problem.
Full code below now.
Please note that WasteTime sub was found on myonlinetraininghub.com
Public Declare PtrSafe Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Long
Public Declare PtrSafe Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Public Const MOUSEEVENTF_LEFTDOWN = &H2
Public Const MOUSEEVENTF_LEFTUP = &H4
Public Const MOUSEEVENTF_RIGHTDOWN As Long = &H8
Public Const MOUSEEVENTF_RIGHTUP As Long = &H10
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Sub MainSequence()
'This sub pieces together MatriksFlowUpdate and CloseInstance
Call MatriksFlowUpdate
WasteTime(2) #This is the code change, it was Sleep 2000 before
Call CloseInstance
End Sub
Sub MatriksFlowUpdate()
'Prompts 3rd party software (Matriks) to produce Excel with latest flow data
Call RightClick
Call SingleClick
End Sub
Private Sub RightClick()
'Simulates a mouse right click at desired screen coordinates
Sleep 1000
SetCursorPos 1750, 750 'x and y position
mouse_event MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0
End Sub
Private Sub SingleClick()
'Simulates a mouse left click at desired screen coordinates
Sleep 1000
SetCursorPos 1750, 650 'x and y position
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
End Sub
Sub CloseInstance()
'Finds the instance of Excel where Matriks exported its excel and closes that instance of Excel
Dim xlApp As Excel.Application
Dim WB As Workbook
Set xlApp =GetObject("C:\MATRIKS\USER\REPORTS\EXCEL\Temp.xls").Application
Set WB = xlApp.Workbooks("Temp.xls")
WB.Close
End Sub
Sub WasteTime(Finish As Long) #This is what I use instead of Sleep
Dim NowTick As Long
Dim EndTick As Long
EndTick = GetTickCount + (Finish * 1000)
Do
NowTick = GetTickCount
DoEvents
Loop Until NowTick >= EndTick
End Sub
Maybe try something like that
Sub CloseInstance()
Dim WB As Workbook
Set WB = Application.Workbooks("Temp.xls")
If Not WB Is Nothing Then
WB.Close
End If
End Sub
Or try this to open
Sub test()
IsWorkBookOpen ("C:\MATRIKS\USER\REPORTS\EXCEL\Temp.xls")
End Sub
Sub IsWorkBookOpen(ByVal fullFileName)
Dim wBook As Workbook
If FileExists(fullFileName) Then
On Error Resume Next
'Test to see if a Workbook is open.
Set wBook = Workbooks(Dir(fullFileName))
If wBook Is Nothing Then 'Not open
Workbooks.Open (fullFileName)
Set wBook = Nothing
On Error GoTo 0
Else 'It is open
MsgBox "Yes it is open", vbInformation, "Founded"
Set wBook = Nothing
On Error GoTo 0
End If
Else
MsgBox "File does not exists"
End If
End Sub
Function FileExists(ByVal fullFileName) As Boolean
FileExists = VBA.Len(VBA.Dir(fullFileName)) > 0
End Function
should it be a timing issue you could keep on trying and getting the Excel application until it's found (not tested):
Sub CloseInstance()
'Finds the instance of Excel where Matriks exported its excel and closes that instance of Excel
Dim xlApp As Excel.Application
On Error Resume Next
Do
Set xlApp = GetObject("C:\MATRIKS\USER\REPORTS\EXCEL\Temp.xls").Application
DoEvents
Loop While xlApp Is Nothing
xlApp.Workbooks("Temp.xls").Close
End Sub

Switching between the sheets periodically using VBA

What I am trying to achieve here is I have three different dashboards running in all the three sheet which I was to switch every 1 Min. I am stuck with the below code. Any help would be appreciated.
I have three sheets to switch between
1. First_sheet, 2. Second_Sheet, 3. Third_Sheet
Sub Swap_Sheets()
Dim Sheets As Workbook
Dim dTime As Date
dTime = Now + TimeValue("00:00:60")
Application.OnTime dTime, "Swap_Sheets"
If ActiveSheet.Name = "First_Sheet" Then
Sheets("Second_Sheet").Activate
Else
    Sheets("Third_Sheet").Activate
Else
Sheets("First_Sheet").Activate
End If
If Sheets("Second_sheet").CheckBox1.Value = False Then
Application.OnTime dTime, "Swap_Sheets", , False
End If
End Sub
This is a good way to do it, avoiding multiple if-s,select case and recursion:
Option Explicit
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Sub SwapSheets()
Dim dTime As Date
Dim i As Long: i = 1
While i <= ThisWorkbook.Worksheets.Count
If ActiveSheet.Name = Worksheets(Worksheets.Count).Name Then
Worksheets(1).Activate
Else
Worksheets(ActiveSheet.index + 1).Activate
End If
i = i + 1
Sleep (10000) '10 seconds
Wend
End Sub
The idea is that every sheet has an index, and you simply have to increment the index of the active one. If the last sheet is the activeone, start from the beginning. Sleep takes milliseconds as a parameter, for 60 seconds it is 60.000.
Plus - in your code you have Dim Sheets As Workbook and here you probably mean Worksheet (I am only guessing).
If you only want to activate 3 worksheets, this is probably the easiest way to do it:
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Sub SwapSheets()
Dim sheetNames() As Variant
Dim i As Long
sheetNames = Array("Sheet1Name", "Sheet2Name", "Sheet3Name")
For i = LBound(sheetNames) To UBound(sheetNames)
Sheets(sheetNames(i)).Activate
Sleep (10000) '10 seconds
Next i
End Sub

How fire EXCEL event BeforeDoubleClick BEFORE SelectionChange?

I want to fire the BeforeDoubleClick-event BEFORE the SelectionChange-event for an EXCEL work-sheet.
The order is normally the other way round: SelectionChange-event first, and later BeforeDoubleClick-event.
My goal is to either run MyDoubleClickCode, if there a double-click, or if NOT, run MyChangeSelectionCode.
The problem relies in the order of event-triggering!
My best solution comes here:
' This Event is **MAYBE** fired secondly and runs the MyDoubleClickCode
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
dblFlag = true
...
MyDoubleClickCode
...
End Sub
' This event is always fired AND runs first
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
dblFlag = false
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "MyChangeSelectionSub"
End Sub
' Userdefined subroutine which will run one second after an event ( doubleclick or not).
public sub MyChangeSelectionSub()
If NOT dblFlag then
...
MyChangeSelectionCode
...
End if
End Sub
I use OnTime in my SelectionChange-event to call the MyChangeSelectionSub one second after a selection-change is triggered. This gives times to handle an BeforeDoubleClick-event and do the MyDoubleClickCode - if the cell was also double-clicked. My wanted logic is reached , BUT...
... it is of course very clumpsy and not satisfying: I have to wait one second before the MyChangeSelectionSub starts, instead of just after the BeforeDoubleClick-event has been dealed with.
Maybee there is a kind of logic to make this happend? Any idea?
EDIT: I've edited the code-exampel to be more clear about my problem! And I know now that I can't change the order of events, but how to not use the onTime solution??
This "works" for me, but it doesn't seem stable. Probably the timing of the OnTime method causes an "uncomfortable pause" in execution that we might need to accept. (or improve upon.)
'worksheet (Name) is "Sheet17" in the VBA Properties window
'worksheet Name is "Sheet1" as shown in the worksheet tab in the application Excel
Private double_click_detected As Boolean
Private SelectionChange_target As Range
' This Event is **MAYBE** fired secondly and runs the MyDoubleClickCode
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
double_click_detected = True
'...
MsgBox "MyDoubleClickCode"
'...
End Sub
' This event is always fired AND runs first
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Set SelectionChange_target = Target
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "Sheet17.MyChangeSelectionSub"
End Sub
' Userdefined subroutine which will run one second after an event ( doubleclick or not).
Public Sub MyChangeSelectionSub()
If Not double_click_detected Then
'...
MsgBox "MyChangeSelectionCode"
'...
End If
End Sub
I found a solution for a similar issue, to avoid Worksheet_SelectionChange before the event Worksheet_BeforeRightClick on https://www.herber.de/forum/archiv/1548to1552/1550413_Worksheet_BeforeRightClick.html (in german) and used it for my test sub.
The whole list of virtual key codes you find on https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)'just for sleep
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Const VK_LBUTTON = &H1 'Left mouse button
Const VK_RBUTTON = &H2 'Right mouse button
Const VK_SHIFT = &H10'Shiftkey just for fun to exit the sub
Sub TestGetAsyncKeyState()
Dim RMouseClick As Long, LMouseClick As Long
Dim RMouseClickpr As Long, LMouseClickpr As Long
Dim lShift As Long, iC As Integer
iC = 0
Do
DoEvents
lShift = GetAsyncKeyState(VK_SHIFT)
RMouseClickpr = RMouseClick
LMouseClickpr = LMouseClick
RMouseClick = GetAsyncKeyState(VK_RBUTTON)
LMouseClick = GetAsyncKeyState(VK_LBUTTON)
If RMouseClick <> RMouseClickpr Or LMouseClick <> LMouseClickpr Then Debug.Print vbLf; CStr(iC); ":"
If RMouseClick <> RMouseClickpr Then Debug.Print "Right: "; RMouseClick; "Previous: "; RMouseClickpr
If LMouseClick <> LMouseClickpr Then Debug.Print "Left : "; LMouseClick; "Previous: "; LMouseClickpr
' If RMouseClick <> RMouseClickpr Or LMouseClick <> LMouseClickpr Then Stop
Sleep (1000)
iC = iC + 1
If iC > 120 Then Stop '2
Loop While GetAsyncKeyState(VK_SHIFT) = 0 'End Loop by pressing any of the Shift-Keys
End Sub
It works for mouseclick (1), shortly held mousebutton (-32767) and longer held mousebutton (-32768). Unfortunately not for doubleclick.
Attention: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate says that it detects the physical mousebuttons. If someone changed the setting it will not detect the correct button. MS says you can correct that with GetSystemMetrics(SM_SWAPBUTTON).
Hope it helps.

copy & paste a picture from one sheet to another

I created a small program using the following code to transfer a picture from one sheet to another in the same workbook.
Sub transferPicturesPAPER_EXAM(pictureNo As Long, p As Integer, srcSht As String, dstSht As String, insertWhere As String)
' Transfers the selected Picture to the exam sheet.
''zxx
If pictureNo = 0 Then Exit Sub
Sheets(srcSht).Select
ActiveSheet.Unprotect
ActiveSheet.pictures("Picture " & pictureNo).Select
'ActiveSheet.Shapes.Range(Array("Picture " & pictureNo)).Select
Selection.Copy
Sheets(dstSht).Select
Range(insertWhere).Select
ActiveSheet.Paste
'== rename to correspond to the problem number
Selection.Name = "Picture " & p
End Sub
This works fine. However, when I place the routine in a larger workbook, I get the following error at the line: Activesheet.paste:
Paste method of Worksheet class failed
The code worked fine for several program executions.
Any help would be greatly appreciated.
Try this :
Sub transferPicturesPAPER_EXAM(pictureNo As Long, _
p As Integer, srcSht As String, _
dstSht As String, insertWhere As String)
' Transfers the selected Picture to the exam sheet.
''zxx
Dim pic As Picture
If pictureNo = 0 Then Exit Sub
Application.EnableEvents = False
Sheets(srcSht).Unprotect
Set pic = Sheets(srcSht).Pictures("Picture " & pictureNo)
pic.Copy
Sheets(dstSht).Activate
Sheets(dstSht).Range(insertWhere).Select
Sheets(dstSht).Paste
'== rename to correspond to the problem number
Selection.Name = "Picture " & p
Application.EnableEvents = True
End Sub
Try this one :
Sub transferPicturesPAPER_EXAM(pictureNo As Long, p As Integer, srcSht As String, dstSht As String, insertWhere As String)
' Transfers the selected Picture to the exam sheet.
''zxx
Dim shpPictureToCopyAs Shape
If pictureNo = 0 Then Exit Sub
With Sheets(srcSht)
.Unprotect
Set shpPictureToCopy= .Shapes(pictureNo).Duplicate
shpPictureToCopy.Cut
End With
Sheets(dstSht).Range(insertWhere).PasteSpecial (xlPasteAll)
End Sub
I recommend disabling and enabling events and screen updating in the main procedure, from which this one has been called. Otherwise you can enable them when you dont want to. Something like this :
Sub MainProcedure() 'your sub name
Application.EnableEvents = False
Application.ScreenUpdating = False
Call transferPicturesPAPER_EXAM(1, 1, "Sheet1", "Sheet2", "A20") 'with your variables as arguments of course
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
I often had this problem too. But you cannot wait 3 seconds per picture , it's too long. I work on 1000 pictures, it's gonna take for ever.
The core of the problem is that Excel copies to windows clipboard first, which is slow.
If you try to paste before the clipboard has the Pic , its will error.
So, some small steps needed for mass copying:
Clear clipbard (not always needed but it makes sure you are not working on older data)
Copy Pic
Test if Pic is in the Clipboard and wait until it is there (loop)
Paste
Here is the code (for Excel 64 bits) :
Option Explicit
'Does the clipboard contain a bitmap/metafile?
Public Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long
'Open the clipboard to read
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal Hwnd As LongPtr) As Long
'clear clipboard
Public Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long
'Get a pointer to the bitmap/metafile
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr 'wformat as long ?
'Close the clipboard
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
'for waiting
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub Clear_Clipboard()
OpenClipboard (0&)
EmptyClipboard
CloseClipboard
Application.CutCopyMode = False
End Sub
Sub PastePic(Pic As Shape)
Dim Rg As Range
Dim T#
Dim Ligne&: Ligne = 5
Dim Sh_Vendeur As Worksheet
Set Sh_Vendeur = ThisWorkbook.Sheets(1)
Clear_Clipboard
Pic.Copy
Set Rg = Sh_Vendeur.Cells(Ligne, 2)
'wait until the clipboard gets a pic, but not over 3 seconds (avoid infinite loop)
T = Timer
Do
Waiting (2)
Loop Until Is_Pic_in_Clipboard Or Timer - T > 0.3
'Rg.Select
'Rg.PasteSpecial
Sh_Vendeur.Paste Destination:=Rg 'paste to a range without select
End Sub
Sub Waiting(ByVal Mili_Seconds&)
Sleep Mili_Seconds
End Sub
Function Is_Pic_in_Clipboard() As Boolean
If IsClipboardFormatAvailable(2) <> 0 Or IsClipboardFormatAvailable(14) <> 0 Then Is_Pic_in_Clipboard = True '2-14 =bitmap et Picture JPEG
End Function
The time delay produced weird results. In some instants some of the pictures were pasted and in others they weren't. Very inconsistent results.
Relocated the Application.wait ... code at the very beginning of the subroutine - ran the program several times - worked perfectly
Would never have guessed that solution.
Thanks to everyone who suggested a solution.
I had success by using the command "DoEvents" just after copying the picture. This way I do not get error when using Paste, otherwise I do.

Resources