In MS Excel 2010 I am trying to copy some text to the clipboard using SendKeys. However, it does not work.
Is this some kind of security measure that Microsoft took in order to prevent people from creating fraudulent macros? Here's some code that shows what I'm trying to do (assume, that you're in the vba window and have some text selected):
Public Sub CopyToClipboardAndPrint()
Call SendKeys("^(C)", True)
Dim Clip As MSForms.DataObject
Set Clip = New MSForms.DataObject
Clip.GetFromClipboard
Debug.Print Clip.GetText
End Sub
Note that in order to use the MSForms.DataObject you'll have to reference %windir%\system32\FM20.DLL (i.e. Microsoft Forms 2.0 Object Library).
Edit:
The text I'm trying to copy is not in the document window, but in the immediate window of the vba project window! So Selection.Copy won't work here.
The following code uses the SendInput function from the Windows API to simulate the Control-C key combination, in order to copy the current text selection to the Clipboard.
The copy/print subroutine (the very last procedure in the code) calls two utility functions to trigger the necessary key presses and then uses the code you prepared to retrieve the text from the Clipboard.
I've tested the code in the Immediate window, the code editor pane, and the worksheet.
Option Explicit
'adapted from:
' http://www.mrexcel.com/forum/excel-questions/411552-sendinput-visual-basic-applications.html
Const VK_CONTROL = 17 'keycode for Control key
Const VK_C = 67 'keycode for "C"
Const KEYEVENTF_KEYUP = &H2
Const INPUT_KEYBOARD = 1
Private Type KEYBDINPUT
wVK As Integer
wScan As Integer
dwFlags As Long
time As Long
dwExtraInfo As Long
End Type
Private Type GENERALINPUT
dwType As Long
xi(0 To 23) As Byte
End Type
Private Declare Function SendInput Lib "user32.dll" _
(ByVal nInputs As Long, _
pInputs As GENERALINPUT, _
ByVal cbSize As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(pDst As Any, _
pSrc As Any, _
ByVal ByteLen As Long)
Private Sub KeyDown(bKey As Byte)
Dim GInput(0 To 1) As GENERALINPUT
Dim KInput As KEYBDINPUT
KInput.wVK = bKey
KInput.dwFlags = 0
GInput(0).dwType = INPUT_KEYBOARD
CopyMemory GInput(0).xi(0), KInput, Len(KInput)
Call SendInput(1, GInput(0), Len(GInput(0)))
End Sub
Private Sub KeyUp(bKey As Byte)
Dim GInput(0 To 1) As GENERALINPUT
Dim KInput As KEYBDINPUT
KInput.wVK = bKey
KInput.dwFlags = KEYEVENTF_KEYUP
GInput(0).dwType = INPUT_KEYBOARD
CopyMemory GInput(0).xi(0), KInput, Len(KInput)
Call SendInput(1, GInput(0), Len(GInput(0)))
End Sub
Sub CopyToClipboardAndPrint()
Dim str As String
'Simulate control-C to copy selection to clipboard
KeyDown VK_CONTROL
KeyDown VK_C
KeyUp VK_C
KeyUp VK_CONTROL
DoEvents
Dim Clip As MSForms.DataObject
Set Clip = New MSForms.DataObject
Clip.GetFromClipboard
Debug.Print Clip.GetText
End Sub
Related
I've found a lot of resources for this for Excel, but have not been able to get any of them to work 100% with Access.
I tried this, but it ultimately didn't work because I'm not working with a string, I'm working with a bmp.
This post got me 90% of the way there, I am able to save the screenshot and see it in the clipboard, but I can't figure out how to proceed. I've tried other resources that build a new email from HTML, but I couldn't get that to work. I also tried building an email without HTML, and ultimately also could not get that to work. So then I tried to save the file locally and then add it to my email, but the code runs with no errors but doesn't save the file, so I hit a dead end there as well.
I'm mixing methods here, but I will post everything I have so it's complete:
In my Access database, I have a form. I click one button to take the screenshot of the form, and another button to send the email that I want to paste the screenshot into. I was able to create the email by using the query, but this doesn't work for me because the conditional formatting applied to the form is critical, and I lose that if it's just a plain table. Eventually this will all be automatic, the buttons are just for testing.
The form:
Access form
Form code:
Option Compare Database
Option Explicit
Private Sub Command15_Click()
Screenshot.PrintScreen
End Sub
Public Sub Command4_Click()
Dim olapp As Object
Dim olItem As Variant
Dim strQry As String
Dim aHead(1 To 3) As String
Dim aRow(1 To 3) As String
Dim aBody() As String
Dim lCnt As Long
Dim objDoc As Word.Document
Dim objSel As Word.Selection
Dim db As Variant
Dim rec As Variant
Dim DataObj As MSForms.DataObject
Set DataObj = New MSForms.DataObject
Dim preamble As String
'Create the header row
aHead(1) = "AGC"
aHead(2) = "Battery Install Date"
aHead(3) = "Last EQ Charge"
preamble = "This email has been sent automatically because an AGC is due for an EQ charge. Please refer to the below table."
lCnt = 1
ReDim aBody(1 To lCnt)
aBody(lCnt) = "<HTML><body><table border='2'><tr><th>" & Join(aHead, "</th><th>") & "</th></tr>"
'Create each body row
strQry = "SELECT * From Query"
Set db = CurrentDb
Set rec = CurrentDb.OpenRecordset(strQry)
If Not (rec.BOF And rec.EOF) Then
Do While Not rec.EOF
lCnt = lCnt + 1
ReDim Preserve aBody(1 To lCnt)
aRow(1) = rec("AGC")
aRow(2) = rec("Battery Install Date")
aRow(3) = rec("Last EQ Charge")
aBody(lCnt) = "<tr><td>" & Join(aRow, "</td><td>") & "</td></tr>"
rec.MoveNext
Loop
End If
aBody(lCnt) = aBody(lCnt) & "</table></body></html>"
DataObj.GetFromClipboard
'strPaste = DataObj.GetText(1) 'Insert contents from clipboard to this variable so it can be added to email body
'create the email
Set olapp = CreateObject("Outlook.application")
Set olItem = olapp.CreateItem(0)
'olItem.Display
olItem.To = "user#redacted.com"
olItem.Subject = "AGC Battery Notification"
olItem.HTMLBody = Join(aBody, vbNewLine) '"<p><font face=""Times New Roman"" size=""3"" color=""red""><b>" & preamble & "</b></font></p><p></p>"
olItem.Display
End Sub
Here is the other module I'm using:
Option Compare Database
Option Explicit
Public Declare PtrSafe Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal _
bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Const VK_SNAPSHOT = &H2C
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Private Declare PtrSafe Function OleCreatePictureIndirect Lib "olepro32.dll" _
(PicDesc As uPicDesc, RefIID As GUID, ByVal fPictureOwnsHandle As Long, _
IPic As IPicture) As Long
'\\ Declare a UDT to store a GUID for the IPicture OLE Interface
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
'\\ Declare a UDT to store the bitmap information
Private Type uPicDesc
Size As Long
type As Long
hPic As Long
hPal As Long
End Type
Private Const CF_BITMAP = 2
Private Const PICTYPE_BITMAP = 1
Sub PrintScreen()
keybd_event VK_SNAPSHOT, 1, 0, 0
End Sub
Public Sub MyPrintScreen(FilePathName As String)
Call PrintScreen
Dim IID_IDispatch As GUID
Dim uPicinfo As uPicDesc
Dim IPic As IPicture
Dim hPtr As Long
Dim DataObj As MSForms.DataObject
Set DataObj = New MSForms.DataObject
OpenClipboard 0
hPtr = GetClipboardData(CF_BITMAP)
CloseClipboard
'\\ Create the interface GUID for the picture
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With
'\\ Fill uPicInfo with necessary parts.
With uPicinfo
.Size = Len(uPicinfo) '\\ Length of structure.
.type = PICTYPE_BITMAP '\\ Type of Picture
.hPic = hPtr '\\ Handle to image.
.hPal = 0 '\\ Handle to palette (if bitmap).
End With
'\\ Create the Range Picture Object
OleCreatePictureIndirect uPicinfo, IID_IDispatch, True, IPic
'\\ Save Picture Object
stdole.SavePicture IPic, FilePathName
Dim oPic
On Error Resume Next
Set oPic = Clipboard.GetData
On Error GoTo 0
If oPic Is Nothing Then
MsgBox "No image"
Else
SavePicture oPic, "C:\pic.jpg"
End If
End Sub
This runs with no errors. I can take a screenshot from the form and see that it is in the clipboard. It creates the new email, but doesn't paste, and doesn't save to my machine anywhere. But when I manually do CTRL+V/Paste, it pasted into the email just fine but I can't get VBA to do that on initial creation. SavePicture oPic runs without errors but doesn't actually do anything. There is no "no image" message that pops up. I've tried defining a FilePathName, but that also just does nothing.
Picture of screenshot in the clipboard
Currently this code produces an email like this which removes my formatting. If I delete the table it pulls in and hit paste, it brings in my screenshot:
Desired result
Here are all of the references I am using
I'm super stuck here, I feel like it's so close to working but I can't figure it out. Any help with this is appreciated and thanks in advance.
At long last, I have gotten it to work. It wasn't just a single thing that worked, it was several minor things so I will post the new code that does the job. Keep in mind that this code is based on an Access form with one button to take the screenshot, and one button to send the email. It is also not perfect; it sometimes takes a screenshot of a random part of the screen, so I have to click on my form and make sure it's active before trying again. I also sometimes get memory errors and the screenshot shows up in the path, but it's broken. However, when it does work it works fine, and I'm sure all of these issues can be solved so I am going to mark this post solved. Here is my working code and I will note the changes at the end. This is the code behind the form itself:
Option Compare Database
Option Explicit
Private Sub Command15_Click()
Screenshot.MyPrintScreen ("C:\Temp\test.jpg")
End Sub
Public Sub Command4_Click()
Dim oApp As Outlook.Application
Dim oEmail As MailItem
Dim colAttach As Outlook.Attachments
Dim oAttach As Outlook.Attachment
'create new Outlook MailItem
Set oApp = CreateObject("Outlook.Application")
Set oEmail = oApp.CreateItem(olMailItem)
'add graphic as attachment to Outlook message
'change path to graphic as needed
Set colAttach = oEmail.Attachments
Set oAttach = colAttach.Add("C:\temp\test.jpg")
oEmail.Close olSave
'change the src property to 'cid:your picture filename'
'it will be changed to the correct cid when its sent.
oEmail.HTMLBody = "<BODY><IMG src=""cid:test.jpg"" </BODY>"
oEmail.Save
oEmail.To = "someemailtogoinhere#gmail.com"
oEmail.Subject = "test"
oEmail.Display
'oEmail.Send
Set oEmail = Nothing
Set colAttach = Nothing
Set oAttach = Nothing
Set oApp = Nothing
End Sub
And the code for the screenshot module:
Option Compare Database
Option Explicit
Public Declare PtrSafe Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal _
bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
Private Const VK_SNAPSHOT = &H2C
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleaut32.dll" (PicDesc As uPicDesc, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
'\\ Declare a UDT to store a GUID for the IPicture OLE Interface
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
'\\ Declare a UDT to store the bitmap information
Private Type uPicDesc
Size As Long
type As Long
hPic As Long
hPal As Long
End Type
Private Const CF_BITMAP = 2
Private Const PICTYPE_BITMAP = 1
Sub PrintScreen()
keybd_event VK_SNAPSHOT, 1, 0, 0
End Sub
Public Sub MyPrintScreen(FilePathName As String)
Call PrintScreen
Dim IID_IDispatch As GUID
Dim uPicinfo As uPicDesc
Dim IPic As IPicture
Dim hPtr As Long
Dim DataObj As MSForms.DataObject
Set DataObj = New MSForms.DataObject
OpenClipboard 0
hPtr = GetClipboardData(CF_BITMAP)
CloseClipboard
'\\ Create the interface GUID for the picture
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With
'\\ Fill uPicInfo with necessary parts.
With uPicinfo
.Size = Len(uPicinfo) '\\ Length of structure.
.type = PICTYPE_BITMAP '\\ Type of Picture
.hPic = hPtr '\\ Handle to image.
.hPal = 0 '\\ Handle to palette (if bitmap).
End With
'\\ Create the Range Picture Object
OleCreatePictureIndirect uPicinfo, IID_IDispatch, True, IPic
'\\ Save Picture Object
stdole.SavePicture IPic, FilePathName
Set DataObj = Nothing
End Sub
Out of the blue I had an error for a missing olepro32.dll. Several scans later telling me there was nothing wrong, I ended up having to make it oleaut32.dll and that was a step in the right direction. Also note that you have to do some extra stuff in order to get the img HTML embedding to work, I ended up redoing that entire section and replacing it with code found from [this][8] other post.
Next I had to delete this part:
Dim oPic
On Error Resume Next
Set oPic = Clipboard.GetData
On Error GoTo 0
If oPic Is Nothing Then
MsgBox "No image"
Else
SavePicture oPic, "C:\pic.jpg"
End If
That stackoverflow I linked originally says it was needed to save, but it was already being done by the rest of the procedure and it was causing errors. I just deleted it entirely. I began setting objects to none to help with the memory problems.
I'm running a PowerPoint Macro-Enabled Slide Show. When a user opens this file the presentation starts immediately. The presentation contains various shapes that, when pressed (use of links), will open a new Powerpoint Slide Show in front of the main Slide Show.
In the background I'm using VBA (code is located in the main Macro enabled slide show) to measure the time a user spends on all of the slides. I want the user to be able to stop this timer with a userform and a button. However, when a new Powerpoint Slide Show is opened, it appears in front of the main slide show. The userform will then disappears behind the new slide show. Using a second screen i have been able to view the userform. But when clicked on the userform it brings the main slide show in front of the other slide show.
So in short: I would like a userform that is in front of all slide show windows.
I tried using vbmodeless but this does not help. I've also tried out various bits of code:
http://www.vbaexpress.com/forum/showthread.php?58189-Make-userform-stay-on-top-of-all-windows-when-macro-is-fired
https://www.mrexcel.com/board/threads/userform-always-on-top.386643/
https://www.mrexcel.com/board/threads/keeping-userform-on-top-when-changing-active-workbook.1165439/
Unfortunately, none of these seem to be working. Some of these are for excel and I've not been able to rewrite these bits of code.
P.S. If this isn't possible, maybe I could hide the main slide show?
Simple version
Create a class module, MyClass, and put this code there:
Public WithEvents App As Application
Private Sub App_SlideShowBegin(ByVal Wn As SlideShowWindow)
UserForm1.Show False
End Sub
Create a module and put this code in it:
Dim MyThing As New MyClass
Sub InitializeApp()
Set MyThing.App = Application
End Sub
Run the InitializeApp method first. Now, when you start your presentation, your UserForm1 will show up. The False flag makes it non-modal, which is what I think you are looking for.
Slightly more advanced version
As above, but change the module to this:
Option Explicit
Public Const SWP_NOMOVE = &H2
Public Const SWP_NOSIZE = &H1
Public Const HWND_TOP = 0
Public Const HWND_BOTTOM = 1
Public Const HWND_TOPMOST = -1
Public Const HWND_NOTOPMOST = -2
Public Declare Function SetWindowPos Lib "user32" _
(ByVal hWnd As Long, _
ByVal hWndInsertAfter As Long, _
ByVal X As Long, _
ByVal Y As Long, _
ByVal cx As Long, _
ByVal cy As Long, _
ByVal uFlags As Long) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Dim MyThing As New MyClass
Sub InitializeApp()
Set MyThing.App = Application
End Sub
And add this to your form code:
Option Explicit
Option Explicit
Private Sub UserForm_Initialize()
Dim formHWnd As Long
formHWnd = FindWindow("ThunderDFrame", Me.Caption)
SetWindowPos formHWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE Or SWP_NOSIZE
End Sub
This question already has answers here:
Why MS Excel crashes and closes during Worksheet_Change Sub procedure?
(3 answers)
Closed 7 years ago.
I'm looking for a way to automatically start a certain Sub when the cell's value is Zero.
E.g. if I enter "0" into Cell A1 the following Sub is supposed to run
Range("H32").FormulaR1C1 = "=SUM(R[-4]C:R[-2]C)"
And if I enter 1 (or any other Value above 0) into Cell A1 another Sub should run, e.g.
Range("B15").FormulaR1C1 = "=SUM(R[-1]C:R[-1]C)"
The calling of a Sub should happen right after I enter the value in excel, without pressing a button oder anything else.
Is there any way to do this?
Let's start with this code, which I will explain below.
Open the VB Editor Alt+F11. Right click the sheet that you want this behavior to occur on and select View Code.
Copy and paste the following code into the worksheet code.
Private Sub Worksheet_Change(ByVal Target As Range)
'CountLarge is an Excel 2007+ property, if using Excel 2003
'change to just Count
If Target.Cells.CountLarge > 1 Or IsEmpty(Target) Then Exit Sub
If Target.Address = "$A$1" Then
If Target.Value = 0 Then
Me.Range("H32").FormulaR1C1 = "=SUM(R[-4]C:R[-2]C)"
ElseIf Target.Value = 1 Then
Me.Range("B15").FormulaR1C1 = "=SUM(R[-1]C:R[-1]C)"
End If
End If
End Sub
The Worksheet_Change event is fired every time a user makes a change to the worksheet. If you change a cell value, for example, this event is triggered.
The first line within this subroutine checks to ensure that multiple cells weren't changed and that there was in fact an actual cell change, if either is not true then it will not continue.
Then we check to ensure that the value change happened in cell A1, if it did, we enter that IF statement.
From there, we check the value that was entered into cell A1. If the value was 0, the appropriate formula is added to H32. If the value was 1, the appropriate formula is added to B15. If a value other than 0 or 1 is entered into cell A1, nothing happens.
It is important to note that you must leave the cell for this event to trigger, so while this is a good start, I don't currently know of a way to get this event to fire without at least pressing enter or leaving the cell.
Update
After a bit of research and playing around, I've figured out how you can make this change without pressing enter or any other button, this will occur immediately after either '0' or '1' is pressed, even if you are editing the cell value. I used a keyboard handler from this previous SO question.
The code between the BEGIN KEYBOARD HANDLING and END KEYBOARD HANDLING event was from above.
Copy and paste the following code into the worksheet code of whichever sheet you want to capture these key strokes on:
Option Explicit
'BEGIN KEYBOARD HANDLING
Private Type POINTAPI
x As Long
y As Long
End Type
Private Type MSG
hwnd As Long
Message As Long
wParam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type
Private Declare Function WaitMessage Lib "user32" () As Long
Private Declare Function PeekMessage Lib "user32" _
Alias "PeekMessageA" _
(ByRef lpMsg As MSG, ByVal hwnd As Long, _
ByVal wMsgFilterMin As Long, _
ByVal wMsgFilterMax As Long, _
ByVal wRemoveMsg As Long) As Long
Private Declare Function TranslateMessage Lib "user32" _
(ByRef lpMsg As MSG) As Long
Private Declare Function PostMessage Lib "user32" _
Alias "PostMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Const WM_KEYDOWN As Long = &H100
Private Const PM_REMOVE As Long = &H1
Private Const WM_CHAR As Long = &H102
Private bExitLoop As Boolean
Sub StartKeyWatch()
Dim msgMessage As MSG
Dim bCancel As Boolean
Dim iKeyCode As Integer
Dim lXLhwnd As Long
'handle the ESC key.
On Error GoTo errHandler:
Application.EnableCancelKey = xlErrorHandler
'initialize this boolean flag.
bExitLoop = False
'get the app hwnd.
lXLhwnd = FindWindow("XLMAIN", Application.Caption)
Do
WaitMessage
'check for a key press and remove it from the msg queue.
If PeekMessage _
(msgMessage, lXLhwnd, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE) Then
'strore the virtual key code for later use.
iKeyCode = msgMessage.wParam
'translate the virtual key code into a char msg.
TranslateMessage msgMessage
PeekMessage msgMessage, lXLhwnd, WM_CHAR, _
WM_CHAR, PM_REMOVE
'for some obscure reason, the following
'keys are not trapped inside the event handler
'so we handle them here.
If iKeyCode = vbKeyBack Then SendKeys "{BS}"
If iKeyCode = vbKeyReturn Then SendKeys "{ENTER}"
'assume the cancel argument is False.
bCancel = False
'the VBA RaiseEvent statement does not seem to return ByRef arguments
'so we call a KeyPress routine rather than a propper event handler.
Sheet_KeyPress _
ByVal msgMessage.wParam, ByVal iKeyCode, ByVal Selection, bCancel
'if the key pressed is allowed post it to the application.
If bCancel = False Then
PostMessage _
lXLhwnd, msgMessage.Message, msgMessage.wParam, 0
End If
End If
errHandler:
'allow the processing of other msgs.
DoEvents
Loop Until bExitLoop
End Sub
Sub StopKeyWatch()
'set this boolean flag to exit the above loop.
bExitLoop = True
End Sub
Private Sub Worksheet_Activate()
Me.StartKeyWatch
End Sub
Private Sub Worksheet_Deactivate()
Me.StopKeyWatch
End Sub
'End Keyboard Handling
Private Sub Sheet_KeyPress(ByVal KeyAscii As Integer, ByVal KeyCode As Integer, ByVal Target As Range, Cancel As Boolean)
'CountLarge is an Excel 2007+ property, if using Excel 2003
'change to just Count
If Target.Cells.CountLarge > 1 Or IsEmpty(Target) Then Exit Sub
If Target.Address = "$A$1" Then
If KeyAscii = 48 Then
Me.Range("H32").FormulaR1C1 = "=SUM(R[-4]C:R[-2]C)"
ElseIf KeyAscii = 49 Then
Me.Range("B15").FormulaR1C1 = "=SUM(R[-1]C:R[-1]C)"
End If
End If
End Sub
Additionally, right click on the ThisWorkbook object --> View Code, and add this code in:
Private Sub Workbook_Open()
Sheets("Sheet1").StartKeyWatch
End Sub
Be sure to change Sheet1 to whatever the name of your worksheet is.
The VBA will 'listen' for key presses and if the active cell is A1 and either a 0 or 1 is entered, the appropriate action will be performed even before the user does anything else.
I will add that his comes at a slight performance cost, as the code that executes on Workbook_Open takes a couple seconds to run.
Thanks to user Siddharth Rout for pointing out the potential issue with Count from Excel 2007 and on and directing me to use CountLarge instead.
In VB6 I could easily create a balloon message that would be shown next to the textbox.
It would automatically disappear as soon as the text is changed.
I could use this balloon tooltip for messages like "Enter a valid eMail address!".
I used the Windows API to create this balloon. I have attached the code below.
Is there no framework solution for this?
Thank you for the help!
Option Explicit
Private Const ECM_FIRST = &H1500 '// Edit control messages
Private Const EM_SETCUEBANNER = (ECM_FIRST + 1)
Private Const EM_GETCUEBANNER = (ECM_FIRST + 2) '// Set the cue banner with the lParm = LPCWSTR
Private Type EDITBALLOONTIP
cbStruct As Long
pszTitle As Long
pszText As Long
ttiIcon As Long ' ; // From TTI_*
End Type
Private Const EM_SHOWBALLOONTIP = (ECM_FIRST + 3) '// Show a balloon tip associated to the edit control
Private Const EM_HIDEBALLOONTIP = (ECM_FIRST + 4) '// Hide any balloon tip associated with the edit control
Private Declare Function SendMessageW Lib "user32" ( _
ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function LocalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal wBytes As Long) As Long
Private Declare Function LocalLock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function LocalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function LocalFree Lib "kernel32" (ByVal hMem As Long) As Long
Private Const GMEM_FIXED = &H0
Private Const GMEM_ZEROINIT = &H40
Private Const GPTR = (GMEM_FIXED Or GMEM_ZEROINIT)
Private m_hWnd As Long
Private m_sCueBanner As String
Private m_sTitle As String
Private m_sText As String
Private m_eIcon As BalloonTipIconConstants
Public Property Let TextBox(txtThis As TextBox)
m_hWnd = txtThis.hwnd
End Property
Public Property Let CueBanner(ByVal value As String)
m_sCueBanner = value
setCueBanner
End Property
Public Property Get CueBanner() As String
CueBanner = m_sCueBanner
End Property
Public Property Let BalloonTipTitle(ByVal value As String)
m_sTitle = value
End Property
Public Property Get BalloonTipTitle() As String
BalloonTipTitle = m_sTitle
End Property
Public Property Let BalloonTipText(ByVal value As String)
m_sText = value
End Property
Public Property Get BalloonTipText() As String
BalloonTipText = m_sText
End Property
Public Property Let BalloonTipIcon(ByVal value As BalloonTipIconConstants)
m_eIcon = value
End Property
Public Property Get BalloonTipIcon() As BalloonTipIconConstants
BalloonTipIcon = m_eIcon
End Property
Public Sub ShowBalloonTip()
Dim lR As Long
Dim tEBT As EDITBALLOONTIP
tEBT.cbStruct = LenB(tEBT)
tEBT.pszText = StrPtr(m_sText)
tEBT.pszTitle = StrPtr(m_sTitle)
tEBT.ttiIcon = m_eIcon
lR = SendMessageW(m_hWnd, EM_SHOWBALLOONTIP, 0, tEBT)
End Sub
Public Sub HideBalloonTip()
Dim lR As Long
lR = SendMessageLongW(m_hWnd, EM_HIDEBALLOONTIP, 0, 0)
Debug.Print lR
End Sub
Private Sub setCueBanner()
Dim lR As Long
' Reports success, but doesn'/t actually work...
' (is this because the VB text box is ANSI?)
lR = SendMessageLongW(m_hWnd, EM_SETCUEBANNER, 0, StrPtr(m_sCueBanner))
Debug.Print lR
End Sub
You can set a tooltip like this
Dim toolTip1 As New ToolTip()
toolTip1.SetToolTip(Me.textbox1, "Hello World")
and hide the tooltip with
toolTip1.SetToolTip(Me.textbox1, "")
Note that you can also add a ToolTip-Control to the form the toolbox instead of creating it programmatically. It magically adds a ToolTip-property to all controls in the properties window where you can enter a tooltip message. The controls themselves, however, will not aquire a ToolTip property and you can only set or remove the tooltip through the ToolTip.SetToolTip method in code.
I think it can only be done in the way I want it using the API way.
I found a perfect migration of my VB6 sample here:
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=7109&lngWId=10
I am trying to automate data population on some excel sheets that have some macros. Now the excel is protected and I cannot get the secret key. Now I am able to run the macros but when I try to pass arguments I get arguments mismatch.
If I just run the macro with the name, I get an inputbox which takes an extra argument as input and auto generates some of the values for the columns. I have to manually enter this value into the inputbox as of now. Is there any way that I could automate that process, i.e capture the inputbox thrown by the macro in the vb.net script and enter the values from there? i.e., I would like to run the macro and after I get the popup asking me to enter some value, use the vb.net code to enter the value to that popup.
Here is what I have till now
Public Class Form1
Dim excelApp As New Excel.Application
Dim excelWorkbook As Excel.Workbook
Dim excelWorkSheet As Excel.Worksheet
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
excelWorkbook = excelApp.Workbooks.Open("D:/excelSheets/plan_management_data_templates_network.xls")
excelApp.Visible = True
excelWorkSheet = excelWorkbook.Sheets("Networks")
With excelWorkSheet
.Range("B7").Value = "AR"
End With
excelApp.Run("createNetworks")
// now here I would like to enter the value into the createNetworks Popup box
excelApp.Quit()
releaseObject(excelApp)
releaseObject(excelWorkbook)
End Sub
Macro definition
createNetworks()
//does so basic comparisons on existing populated fields
//if true prompts an inputbox and waits for user input.
This stall my vb.net script too from moving to the next line.
Like you and me, we both have names, similarly windows have handles(hWnd), Class etc. Once you know what that hWnd is, it is easier to interact with that window.
This is the screenshot of the InputBox
Logic:
Find the Handle of the InputBox using FindWindow and the caption of the Input Box which is Create Network IDs
Once that is found, find the handle of the Edit Box in that window using FindWindowEx
Once the handle of the Edit Box is found, simply use SendMessage to write to it.
In the below example we would be writing It is possible to Interact with InputBox from VB.Net to the Excel Inputbox.
Code:
Create a Form and add a button to it.
Paste this code
Imports System.Runtime.InteropServices
Imports System.Text
Public Class Form1
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Integer
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _
ByVal lParam As String) As Integer
Const WM_SETTEXT = &HC
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Ret As Integer, ChildRet As Integer
'~~> String we want to write to Input Box
Dim sMsg As String = "It is possible to Interact with InputBox from VB.Net"
'~~> Get the handle of the "Input Box" Window
Ret = FindWindow(vbNullString, "Create Network IDs")
If Ret <> 0 Then
'MessageBox.Show("Input Box Window Found")
'~~> Get the handle of the Text Area "Window"
ChildRet = FindWindowEx(Ret, 0, "EDTBX", vbNullString)
'~~> Check if we found it or not
If ChildRet <> 0 Then
'MessageBox.Show("Text Area Window Found")
SendMess(sMsg, ChildRet)
End If
End If
End Sub
Sub SendMess(ByVal Message As String, ByVal hwnd As Long)
Call SendMessage(hwnd, WM_SETTEXT, False, Message)
End Sub
End Class
ScreenShot
When you run the code this is what you get
EDIT (Based on further request of automating the OK/Cancel in Chat)
AUTOMATING THE OK/CANCEL BUTTONS OF INPUTBOX
Ok here is an interesting fact.
You can call the InputBox function two ways in Excel
Sub Sample1()
Dim Ret
Ret = Application.InputBox("Called Via Application.InputBox", "Sample Title")
End Sub
and
Sub Sample2()
Dim Ret
Ret = InputBox("Called Via InputBox", "Sample Title")
End Sub
In your case the first way is used and unfortunately, The OK and CANCEL buttons do not have a handle so unfortunately, you will have to use SendKeys (Ouch!!!) to interact with it. Had you Inbutbox been generated via the second method then we could have automated the OK and CANCEL buttons easily :)
Additional Info:
Tested on Visual Studio 2010 Ultimate (64 bit) / Excel 2010 (32 bit)
Inspired by your question, I actually wrote a blog Article on how to interact with the OK button on InputBox.
Currently, I employ a method where I run a thread before the macro is called by the script. The thread checks if the inputbox has been called. If it is, it picks up the value from the location and using sendkeys, submits the box.
This is a rudimentary solution but I was hoping for a more elegant solution to this problem.
My solution Code:
Public Class Form1
Dim excelApp As New Excel.Application
Dim excelWorkbook As Excel.Workbook
Dim excelWorkSheet As Excel.Worksheet
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
excelWorkbook = excelApp.Workbooks.Open("D:/excelSheets/some_excel.xls")
excelApp.Visible = True
excelWorkSheet = excelWorkbook.Sheets("SheetName")
With excelWorkSheet
.Range("B7").Value = "Value"
End With
Dim trd = New Thread(Sub() Me.SendInputs("ValueForInputBox"))
trd.IsBackground = True
trd.Start()
excelApp.Run("macroName")
trd.Join()
releaseObject(trd)
excelApp.Quit()
releaseObject(excelApp)
releaseObject(excelWorkbook)
End Sub
Private Sub releaseObject(ByVal obj As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
Private Sub SendInputs(ByVal noOfIds As String)
Thread.Sleep(100)
SendKeys.SendWait(noOfIds)
SendKeys.SendWait("{ENTER}")
SendKeys.SendWait("{ENTER}")
End Sub