Excel VBA - make sure Numlock is always ON - excel

I know this has been put to the attention before, but I can't solve it.
I have a button that calls a sub and in that sub I want to make sure that numlock is always on.
The first time, i.e. if the numlock is off it turns it on. If it's already on, clicking the button once or twice keeps the numlock on, but clicking a third time turns the numlock off. Clicking again keeps it off. Clicking again turns it on again. So every 3 clicks it turns it off. I don't understand how to fix it. I ahve Excel 2019 bit and Windows 10 64 bit. Here's the code:
Private Declare PtrSafe Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Const kCapital = 20
Private Const kNumlock = 144
Public Function CapsLock() As Boolean
CapsLock = KeyState(kCapital)
End Function
Public Function NumLock() As Boolean
NumLock = KeyState(kNumlock)
End Function
Private Function KeyState(lKey As Long) As Boolean
KeyState = CBool(GetKeyState(lKey))
End Function
Public Sub ToggleNumlock(choice As Boolean)
Application.Volatile
If choice = True Then
If NumLock = False Then SendKeys "{NUMLOCK}", True
Else
If NumLock = True Then SendKeys "{NUMLOCK}", True
End If
End Sub
In the sub triggered by the button I have:
Application.SendKeys "{F2}"
and just after I have
If NumLock = False Then
ToggleNumlock (True)
End If
Could it be the Sendkeys that causes trouble?
Because I need it, is there a workaround?
Thank you.
UPDATE TO MY CODE:
ActiveSheet.Range(CurrentCell).Value = "="
ActiveSheet.Range(CurrentCell).Select
Application.SendKeys "{F2}", True
Application.SendKeys "=", True
Application.SendKeys "{F2}"
I removed all the code regarding the numlock on off, etc. and trying this it works for now at least on my machine: I just push the keys twice. I'll check this on my office machine tomorrow.
UPDATED 2021-07-19
In my office (Windows 64 localized italian, Excel 2010) I have the same problem with numlock that toggles BUT also the comma on the numpad becomes a point (in Italy it's 3,14 not 3.14). I GIVE UP. Thanks to all who tried to help me. MS must really fix sendkeys.

Based on this article you can turn on Num Lock with the following code
Option Explicit
'https://www.vbarchiv.net/tipps/details.php?id=563
Private Declare PtrSafe Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Declare Sub keybd_event Lib "user32" ( _
ByVal bVk As Byte, _
ByVal bScan As Byte, _
ByVal dwFlags As Long, _
ByVal dwExtraInfo As Long)
Private Const VK_NUMLOCK = &H90
Private Const KEYEVENTF_KEYUP = &H2
Sub pressNumLock()
' press NUM-Lock drücken
' first key down and then key-up
keybd_event VK_NUMLOCK, 1, 0, 0
keybd_event VK_NUMLOCK, 1, KEYEVENTF_KEYUP, 0
End Sub
Sub NumLockOn()
' activate NUM-Lock (in case it is not activated)
If Not (GetKeyState(vbKeyNumlock) = 1) Then
pressNumLock
End If
End Sub

Related

Vba Excel make a button visible in userform as long as multiple key are pressed

I'm programming a small userform with multiple buttons, listboxes.
There is a button called "Delete" that i want to hide, because data should be deleted only in very rare cases.
The button should be only visible when i press certain keys on the keyboard. e.g. ctr + "e" oder ctr +"k".
When this keys are not pressed the button should be hidden again
I tried to use the event keydown of the userform but in vain.
can you help me please with this one ?
Thanks in advance
The easiest way to catch this would probably be to use the KeyPress and KeyUp events in the userform.
Something as simple as:
Private Sub UserForm_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
If KeyAscii = 11 Or KeyAscii = 5 Then Me.Delete.Visible = True
End Sub
Private Sub UserForm_KeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Me.Delete.Visible = False
End Sub
In the userform code.
And the KeyAscii set to whatever value you need, 11 being ctrl + k, 5 being ctrl + e.
Using KeyDown should work in a similar manner, but KeyCode and KeyAscii are different:
Private Sub UserForm_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 75 Then Me.Delete.Visible = True
End Sub
And then finally, for a toggle function:
Private Sub UserForm_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 75 And Not Me.Delete.Visible Then
Me.Delete.Visible = True
Else
Me.Delete.Visible = False
End If
End Sub
Using UserForm_ subs only triggers the code if the useform in question is in focus. If other userforms are in focus, then they would have to call the code, and if no useform is in focus, you can call a sub from a module with a shortcut from the macro menu. The same code, using the specific name rather than Me works for wherever you cal lthe code from.
When this keys are not pressed the button should be hidden again
This doesn't make sense to me. Because to use the button, one will have to let go of the CTRL key. I would recommend a second set of keys to hide it. Or better still... Use the same key!!! :)
Here is an example
Option Explicit
Private Declare PtrSafe Function RegisterHotKey Lib "user32" _
(ByVal hwnd As LongPtr, ByVal id As Long, _
ByVal fsModifiers As Long, ByVal vk As Long) As Long
Private Declare PtrSafe Function UnregisterHotKey Lib "user32" _
(ByVal hwnd As LongPtr, ByVal id As Long) As Long
Private Declare PtrSafe Function WaitMessage Lib "user32" () As Long
Private Declare PtrSafe Function PeekMessage Lib "user32" _
Alias "PeekMessageA" (lpMsg As MSG, ByVal hwnd As LongPtr, _
ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, _
ByVal wRemoveMsg As Long) As Long
Private Type POINTAPI
x As Long
y As Long
End Type
Private Type MSG
hwnd As LongPtr
message As Long
wParam As LongPtr
lParam As LongPtr
time As Long
pt As POINTAPI
End Type
Private Const MOD_CONTROL = &H2
Private Const PM_REMOVE = &H1
Private Const WM_HOTKEY = &H312
Private CancelTrap As Boolean
Private ShowHide As Boolean
Private Const ShowHideKey As Long = vbKeyE
Private Sub UserForm_Activate()
CaptureShowHideKey True
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
CaptureShowHideKey False
End Sub
Private Sub CaptureShowHideKey(Trap As Boolean)
CancelTrap = Not Trap
If Trap = True Then
Call RegisterHotKey(0, &HBFFF&, MOD_CONTROL, ShowHideKey)
ShowHideButton
Else
Call UnregisterHotKey(0, &HBFFF&)
End If
End Sub
Private Sub ShowHideButton()
Dim message As MSG
Do While Not CancelTrap
WaitMessage
If PeekMessage(message, 0, WM_HOTKEY, WM_HOTKEY, PM_REMOVE) Then
If CommandButton1.Visible = False Then
CommandButton1.Visible = True
Else
CommandButton1.Visible = False
End If
End If
DoEvents
Loop
End Sub
IN ACTION

which event fires if excel-vba modeless userform window gets focus back?

I've got a modeless userform in an Excel VBA project.
The userform is loaded by button on spreadsheet clicked (not an active-x button if that's relevant).
Because of modeless the user can work with excel or even other applications and than switch back to the form window. I need an event that fires if the form window becomes the active window again. I thought UserForm_Activate should do the job but it doesn't (neither does UserForm_GotFocus but there is no GotFocus event for userforms?). Is there any event that fires if the user switches back to a modeless userform (or in case not: is there any known work-around)? Or do I've got some strange bug here and Activate should fire?
Here's all the code I used for testing purpose:
' standard module:
Sub BUTTON_FormLoad()
' associated as macro triggered by button click on a sheet
UserForm1.Show vbModeless
End Sub
' UserForm1:
Private Sub UserForm_Activate()
' does not fire if focus comes back
Debug.Print "Activated"
End Sub
Private Sub UserForm_GotFocus()
' does not fire if focus comes back
' wrong code - no GotFocus event for userforms?
Debug.Print "Focussed"
End Sub
Private Sub UserForm_Click()
' only fires if clicked *inside* form
' does not fire eg if user clicks top of form window
Debug.Print "Clicked"
End Sub
Where do I find the documentation of userform events? It's not on the 'UserForm object' page.
The Activate event doesn't fire when you switch between the application and a modeless userform. This is by design.
Like I mentioned in the comments
You can achieve what you want by subclassing the userform and trapping the worksheet events but it very messy.
Here is a very basic example. Sample file can be downloaded from Here
READ ME FIRST:
This is just a basic sample. Please close all Excel Files before testing this.
If the user directly clicks a control on the userform and you want to run the activate code there as well then you will have to handle that as well.
Once you are happy, amend it to suit your need.
Place code in a Module
Option Explicit
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function SetWindowLong& Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long)
Private Const GWL_WNDPROC = (-4)
Private WinProcOld As Long
Private Const WM_NCLBUTTONDOWN = &HA1
Public formWasDeactivated As Boolean
'~~> Launch the form
Sub LaunchMyForm()
Dim frm As New UserForm1
frm.Show vbModeless
End Sub
'~~> Hooking the Title bar in case user clicks on the title bar
'~~> to activate the form
Public Function WinProc(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
If wMsg = WM_NCLBUTTONDOWN Then
'~~> Ignoring unnecessary clicks to the title bar
'~~> by checking if the form was deactivated
If formWasDeactivated = True Then
formWasDeactivated = False
MsgBox "Form Activated"
End If
End If
WinProc = CallWindowProc(WinProcOld&, hwnd&, wMsg&, wParam&, lParam&)
End Function
'~~> Subclass the form
Sub SubClassUserform(hwnd As Long)
WinProcOld& = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WinProc)
End Sub
Sub UnSubClassUserform(hwnd As Long)
SetWindowLong hwnd, GWL_WNDPROC, WinProcOld&
WinProcOld& = 0
End Sub
Create a Userform. Let's call it Userform1. Add a command button in the form. Let's call it CommandButton1
Place code in Userform
Option Explicit
Private Declare Function FindWindow Lib "user32.dll" _
Alias "FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Dim hwnd As Long
Private Sub UserForm_Initialize()
hwnd = FindWindow(vbNullString, Me.Caption)
SubClassUserform hwnd
End Sub
'~~> Userform Click event
Private Sub UserForm_Click()
'~~> Ignoring unnecessary clicks
'~~> by checking if the form was deactivated
If formWasDeactivated = True Then
formWasDeactivated = False
MsgBox "Form Activated"
End If
End Sub
'~~> Unload the form
Private Sub CommandButton1_Click()
'~~> In case hwnd gets reset for whatever reason.
hwnd = FindWindow(vbNullString, Me.Caption)
UnSubClassUserform hwnd
Unload Me
End Sub
Place this code in the Workbook code area
Option Explicit
'~~> Checking if the form was deactivated
'~~> Add more events if you want
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
formWasDeactivated = True
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
formWasDeactivated = True
End Sub
Please feel free to add more workbook events. I have only used Workbook_SheetActivate and Workbook_SheetSelectionChange
And finally add a Form Button in the worksheet and assign the macro LaunchMyForm to it. And we are done
In Action
As far as I know, there isn't such an event within VBA. From the documentation:
The Activate and Deactivate events occur only when you move the focus
within an application. Moving the focus to or from an object in
another application doesn't trigger either event.
However, the Windows APIs can handle the event with a hook. The problem with Win APIs within VBA is that errors aren't handled by VBA so Excel will crash if/when the code encounters an error; so they can be frustrating for the developer. From a purely personal perspective, I like to keep code within the hook procedures to a minimum and pass any values to a class that can then fire events - this at least minimises the crashes. It's also important to remember to unhook before finishing your session.
Basic implementation of a Win API hook would look something like this:
In a class object (here it's called cHookHandler)
Option Explicit
Public Event HookWindowActivated()
Public Event HookIdChanged()
Private mHookId As LongPtr
Private mTargetWindows As Collection
Public Property Get HookID() As LongPtr
HookID = mHookId
End Property
Public Property Let HookID(RHS As LongPtr)
mHookId = RHS
RaiseEvent HookIdChanged
End Property
Public Sub AttachHook()
modHook.AttachHook Me
End Sub
Public Sub DetachHook()
modHook.DetachHook
End Sub
Public Sub AddTargetWindow(className As String, Optional windowTitle As String)
Dim v(1) As String
'Creates an array of [0 => className, 1=> windowTitle]
'which is stored in a collection and tested for in
'your hook callback.
v(0) = className
v(1) = windowTitle
mTargetWindows.Add v
End Sub
Public Sub TestForTargetWindowActivated(className As String, windowTitle As String)
Dim v As Variant
'Tests if the callback window is one that we're after.
For Each v In mTargetWindows
If v(0) = className Then
If v(1) = "" Or v(1) = windowTitle Then
'Fires the event that our target window has been activated.
RaiseEvent HookWindowActivated
Exit Sub
End If
End If
Next
End Sub
Private Sub Class_Initialize()
Set mTargetWindows = New Collection
End Sub
Private Sub Class_Terminate()
modHook.DetachHook
End Sub
Module code (here the module is called modHook)
Option Explicit
Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As LongPtr) As Long
Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" _
Alias "SetWindowsHookExA" _
(ByVal idHook As Long, _
ByVal lpfn As LongPtr, _
ByVal hmod As LongPtr, _
ByVal dwThreadId As Long) As LongPtr
Private Declare PtrSafe Function CallNextHookEx Lib "user32" _
(ByVal hHook As LongPtr, _
ByVal ncode As Long, _
ByVal wParam As LongPtr, _
lParam As Any) As LongPtr
Private Declare PtrSafe Function GetClassName Lib "user32" _
Alias "GetClassNameA" _
(ByVal hwnd As LongPtr, _
ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" _
(ByVal hwnd As LongPtr, _
ByVal lpString As String, _
ByVal cch As Long) As Long
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long
Private Const WH_CBT As Long = 5
Private Const HCBT_ACTIVATE As Long = 5
Private mHookHandler As cHookHandler
Public Sub AttachHook(hookHandler As cHookHandler)
Set mHookHandler = hookHandler
mHookHandler.HookID = SetWindowsHookEx(WH_CBT, AddressOf CBTCallback, 0, GetCurrentThreadId)
End Sub
Private Function CBTCallback(ByVal lMsg As Long, _
ByVal wParam As LongPtr, _
ByVal lParam As LongPtr) As LongPtr
Dim className As String, windowTitle As String
If mHookHandler Is Nothing Then Exit Function
If lMsg = HCBT_ACTIVATE Then
className = GetClassText(wParam)
windowTitle = GetWindowTitle(wParam)
If Not mHookHandler Is Nothing Then
mHookHandler.TestForTargetWindowActivated className, windowTitle
End If
End If
CBTCallback = CallNextHookEx(mHookHandler.HookID, lMsg, ByVal wParam, ByVal lParam)
End Function
Public Sub DetachHook()
Dim ret As Long
If mHookHandler Is Nothing Then Exit Sub
ret = UnhookWindowsHookEx(mHookHandler.HookID)
If ret = 1 Then
mHookHandler.HookID = 0
End If
End Sub
Private Function GetWindowTitle(wParam As LongPtr) As String
Dim tWnd As String
Dim lWnd As Long
tWnd = String(100, Chr(0))
lWnd = GetWindowText(wParam, tWnd, 100)
tWnd = Left(tWnd, lWnd)
GetWindowTitle = tWnd
End Function
Private Function GetClassText(wParam As LongPtr) As String
Dim tWnd As String
Dim lWnd As Long
tWnd = String(100, Chr(0))
lWnd = GetClassName(wParam, tWnd, 100)
tWnd = Left(tWnd, lWnd)
GetClassText = tWnd
End Function
And in this example, all events are captured within the Userform
In this simple example, two buttons on the Userform attach and detach the hook, but you'd probably call the routines from somewhere else (perhaps the userform Initialize and Terminate events). The Userform also has a label lblHook displaying the HookId which I use during development - for production code, you probably wouldn't want this, so you could leave that bit out.
Option Explicit
Private WithEvents mHookHandler As cHookHandler
Private Sub btnHook_Click()
mHookHandler.AttachHook
End Sub
Private Sub btnUnhook_Click()
mHookHandler.DetachHook
End Sub
Private Sub mHookHandler_HookIdChanged()
lblHook.Caption = mHookHandler.HookID
End Sub
Private Sub mHookHandler_HookWindowActivated()
' Caveat: this routine will crash if halted in debugger.
Debug.Print "I've been activated!"
End Sub
Private Sub UserForm_Initialize()
Set mHookHandler = New cHookHandler
mHookHandler.AddTargetWindow "ThunderDFrame", Me.Caption
End Sub
Private Sub UserForm_Terminate()
Set mHookHandler = Nothing
End Sub
Try this. the event occurs after the form appears, so hide the wb inside an initialize event.
Private Sub UserForm_Initialize()
Set WB = ThisWorkbook Windows(WB.Name).Visible = False
The event does not exist and you can use Windows hooks to achieve your desired result. In my opinion, that's the direct answer and everything else is a workaround [unless it was posted by Siddharth Rout, in which case, THAT is the direct answer]

Trigger Enter field behaviour through class for a control

I raised a query which now works as per David Zemens' instructions and BrakNicku guidance.
Problem is one of the events I want to use is Enter. Within the class, I don't get the option for this event. Is there is a way to add this to the class or trigger an Enter event for the control somehow?
I tried most of the events available within the class but none of them behave the way I need them to.
A quick background: I use Enter event to set help text for the field in focus. So every time a user enters a field, I have a help textbox that gets populated with help text.
I am unable to share the workbook.
Let's say your userform (Userform1) looks like this
I am going to demonstrate the Enter Event for 2 controls. TextBox and ComboBox.
Ensure that you place the CommandButton1 first on the userform. Or alternatively, set it's TabIndex to 0. This is so that the command button takes focus first when the userform loads and you can test the Entering of TextBox and ComboBox.
Paste this in a class module. My Class module name is Class1
Option Explicit
Public WithEvents Usrfrm As UserForm1
Const MyMsg As String = "Hiya there. Did you just try to sneak into the "
Private Sub Usrfrm_OnEnter(ctrl As msforms.Control)
Select Case True
Case TypeName(ctrl) Like "ComboBox"
'Call Usrfrm.Combobox_List(ctrl)
MsgBox MyMsg & "combobox?", vbCritical, "Aha!"
Case TypeName(ctrl) Like "TextBox"
MsgBox MyMsg & "textbox?", vbCritical, "Aha!"
End Select
End Sub
Paste this in the userform code area
Option Explicit
Public Event OnEnter(ctrl As msforms.Control)
Private prevCtl As msforms.Control
Private mycls As Class1
Private IsfrmUnloaded As Boolean
Private Sub CommandButton1_Click()
Unload Me
End Sub
Private Sub UserForm_Layout()
Call spyWhatsGoingOn
End Sub
Private Sub spyWhatsGoingOn()
Set mycls = New Class1
Set mycls.Usrfrm = Me
IsfrmUnloaded = False
Set prevCtl = Me.ActiveControl
RaiseEvent OnEnter(Me.ActiveControl)
Do While IsfrmUnloaded = False
If Not prevCtl Is Nothing Then
If Not prevCtl Is Me.ActiveControl Then
RaiseEvent OnEnter(Me.ActiveControl)
Me.ActiveControl.SetFocus
End If
End If
Set prevCtl = Me.ActiveControl
DoEvents
Loop
End Sub
Demo
Here another solution, (doesnot work on a MAC)
Open Notepad and copy code below and paste it in a new txt-file
save it als CatchEvents.cls
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "CatchEvents"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
#If VBA7 And Win64 Then
Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, _
ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, _
Optional ByVal ppcpOut As LongPtr) As Long
#Else
Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, _
ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
#End If
Private EventGuide As GUID
Private Ck As Long
Private ctl As Object
'All Other Control-Events also possible
Public Sub MyEnter()
Attribute MyEnter.VB_UserMemId = -2147384830
Select Case TypeName(ctl)
Case "TextBox": MsgBox "Your code for " & ctl.Name & " here!"
Case Else: MsgBox "You entered no TextBox but another control (" & ctl.Name & ")!"
End Select
End Sub
Public Sub ConnectAllEvents(ByVal Connect As Boolean)
With EventGuide
.Data1 = &H20400
.Data4(0) = &HC0
.Data4(7) = &H46
End With
ConnectToConnectionPoint Me, EventGuide, Connect, ctl, Ck, 0&
End Sub
Public Property Let Item(Ctrl As Object)
Set ctl = Ctrl
Call ConnectAllEvents(True)
End Property
Public Sub Clear()
If (Ck <> 0) Then Call ConnectAllEvents(False)
Set ctl = Nothing
End Sub
In your VBA editor you import this File
In your Userform code you add:(when you have already an Initialize-event you combine those)
Private AllControls() As New CatchEvents 'on top
Private Sub UserForm_Initialize()
ReDim AllControls(Controls.Count - 1)
For j = 0 To Controls.Count - 1
AllControls(j).Item = Controls(j)
Next
End Sub
Now every Enter-event of any control will be catched, so you have to act accordingly.
Every event on a Userform can be catched this way.
So the approach I went with was: I already had Class Module that was trapping Change event (can be seen here). As i didnt have access to Enter event in my class, I used the KeyUp and MouseDown events in this class to set help for each control. This way user can get to a field by clicking on it or tabbing to it: help is displayed for the selected control

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.

Disable VBA UserForm 'x', but still allow Unload Me

I have a user form that, when closed, needs to run cleanup steps. I would like for the X button to be disabled and/or not visible, but I still need to be able to unload the form. I've used code like the below, but it also blocks Unload Me.
'Disables closing via x button
Sub UserForm_QueryClose(Cancel As Integer, ClsoeMode As Integer)
If CloseMode = vbFormControlMenu Then
MsgBox ("BLOCKED")
Cancel = True
End If
End Sub
Do not use the UserForm_QueryClose in such a case. Use the API RemoveMenu, GetSystemMenu and FindWindow
This is my fav site for APIs
RemoveMenu : http://allapi.mentalis.org/apilist/RemoveMenu.shtml
GetSystemMenu : http://allapi.mentalis.org/apilist/GetSystemMenu.shtml
FindWindow : http://allapi.mentalis.org/apilist/FindWindow.shtml
See this example
Option Explicit
Private Declare Function RemoveMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, _
ByVal wFlags As Long) As Long
Private Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Long, ByVal bRevert As Long) As Long
Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Const MF_BYPOSITION = &H400&
Private Sub UserForm_Initialize()
Dim Ret As Long
'~~> Change UserForm1 to match your userform's caption
Ret = FindWindow("ThunderDFrame", "UserForm1")
Do While Ret = 0
'~~> Change UserForm1 to match your userform's caption
Ret = FindWindow("ThunderDFrame", "UserForm1")
DoEvents
Loop
RemoveMenu GetSystemMenu(Ret, 0), 6, MF_BYPOSITION
End Sub
Private Sub CommandButton1_Click()
Unload Me
End Sub
Screenshot:
Instead of giving the user a message saying he can't click the red x, trap it the way you did, and do the cleanup before unloading the form:
Sub UserForm_QueryClose(Cancel As Integer, ClsoeMode As Integer)
If CloseMode = vbFormControlMenu Then
' run cleanup code here
End If
End Sub
If the form has a close button that does the cleanup, then use something like this:
Sub UserForm_QueryClose(Cancel As Integer, ClsoeMode As Integer)
If CloseMode = vbFormControlMenu Then
' click event code for Close button:
btnClose_Click
Cancel = True
End If
End Sub
No need to go overboard with Windows APIs, since this is all built-in.
I know this is an old feed but you spelt ClsoeMode wrong. Simply change that to CloseMode and this should resolve your issue.

Resources