Is it possible to detect and list all open PDF files from excel Vba? I know I can check for a specific known PDF file and path, however in this case the file name and path will not be known.
Thanks
I was reminded in the comment by Ryan Wildry that I can use AHK for things like this. Here is the code I ended up using:
First I set up a regex pattern in VBA so for how the PDF windows titles appear. Used a couple functions I pulled from the web for previous applications.
Main VBA:
Private Sub Get_PDFs()
Dim pattern As String
Dim ahkParamColl As Collection
Dim windowArr() As String
'regex pattern to match with open Adobe PDF Files
pattern = "^(.+)\.pdf - Adobe Reader$"
'add pattern to AHK parameter collection
Set ahkParamColl = Nothing
Set ahkParamColl = New Collection
ahkParamColl.Add (pattern)
'run window detection AHK Script
Call Functions.Run_AHK("Detect All Open Windows.ahk", ahkParamColl)
'send list to array
windowArr = Split(GetClipBoardText, Chr(10))
End Sub
Function to call AHK:
'these are for AHK scripts to run from Excel
Public Const ahk_ScriptsLoc = """C:\Location of Scripts\" 'starts w/a quote
Public Const ahk_PgmLoc = "C:\Location of AHK Pogram\AHK.exe"
Function Run_AHK(AHK_Script_Name As String, Optional Parameters As Collection)
'Call AHK script from VBA
Dim i As Integer
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Dim AHKscript As String
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
'set the ahk script string to call
AHKscript = ahk_PgmLoc & " " & ahk_ScriptsLoc & AHK_Script_Name & """ """
'add parameters to script string
If Not Parameters Is Nothing Then
For Each s In Parameters
AHKscript = AHKscript & s & """ """
Next s
End If
'run ahk script
wsh.Run AHKscript, windowStyle, waitOnReturn
End Function
Function to get clipboard text:
Public Function GetClipBoardText()
Dim DataObj As MsForms.DataObject
Set DataObj = New MsForms.DataObject
On Error GoTo Whoa
'~~> Get data from the clipboard.
DataObj.GetFromClipboard
'~~> Get clipboard contents
myString = DataObj.GetText(1)
GetClipBoardText = myString
Exit Function
Whoa:
If Err <> 0 Then MsgBox "Data on clipboard is not text or is empty"
End Function
Main AHK (snagged from Here):
;regex pattern sent from calling application
pattern = %1%
;get all window names and loop through
WinGet windows, List
Loop %windows%
{
id := windows%A_Index%
WinGetTitle wt, ahk_id %id%
;if window matches pattern, add to list
IF (RegexMatch(wt,pattern)>0) then
{
s .= wt . "`n"
}
}
;send list to clipboard
Clipboard := s
So the VBA macro will set up the regex pattern to be sent to the AHK script. I can use this for other document types or naming patterns later if need be. AHK will then be called which loops through each open window, checks if it matches the defined pattern, then appends it to a string. This string is sent to the clipboard, which VBA then reads and splits into an Array for me to use.
I'm sure there is probably a more efficient way out there, but this was a fun way and the only way I could put together.
Related
Edit : Sorry i forgot to mention that is VBA for Excel
First time i post on this sub reddit. I would like to something very simple, yet I have no heckin idea how to do it.
Let me give you a bit of context : In my company we have a standard model tool, which uses a standard excel file as input.
When you want to update your inputs from an old template, you download a standard file from a platform, and use a sub that doesn't take any arguments (called "upgrade engine"). Wen you call Upgrade engine, there is a file dialog tab that opens, and helps you select the source file you want to upgrade.
I am in a testing team for the standard model and i have to create a lot of templatse for each new release of the model for non regression testing purpose. So i would like to automatize the process. I cannot , and this is the important detail here, change the code of the standard template.
So i created a kind of masterfile with all my non regression test use cases, their address etc to update them one by one.
Here is my current code:
Public gParamTab As Variant
Public gHypTab As Variant
Public gSourcefolder As String
Public gBlankFolder As String
Public gTgtfolder As String
Public Const gParamTabColUseCase As Byte = 1
Public Const gParamTabColTTtgt As Byte = 2
Public Const gParamTabColTTSource As Byte = 3
Public Const gParamTabColFlagRetrieve As Byte = 4
Public Const gParamTabColTTCase As Byte = 5
Public Const gParamTabColFlagUpgrade As Byte = 6
Public Const gBlankTTName As String = "Table_Template_MVP_case"
Public Const gExtension As String = ".xlsb"
Sub init()
gParamTab = Sheets("Parameters").Range("gParamTab")
gHypTab = Sheets("NDD HYP").Range("gHypTab")
gSourcefolder = Sheets("Parameters").Range("gSourcefolder")
gTgtfolder = Sheets("Parameters").Range("gTgtfolder")
gBlankFolder = Sheets("Parameters").Range("gBlankFolder")
End Sub
Sub updateTT()
Call init
Dim lFullname_blank As String, lFullname_source As String, lFullname_tgt As String
Dim lGlobalrange As Variant
Dim lGlobaltable() As Variant
Dim lBlankTT As Workbook
Dim lLastRow As Long
Dim lSearchedVariable As Variant
Dim lBlankTTupgradeengine As String
lcol = 2
For lUsecase = 2 To UBound(gParamTab, 1)
If gParamTab(lUsecase, gParamTabColFlagUpgrade) = 1 Then
lFullname_blank = gBlankFolder & "\" & gBlankTTName & gParamTab(lUsecase, gParamTabColTTCase) & gExtension
lFullname_source = gSourcefolder & "\" & gParamTab(lUsecase, gParamTabColTTSource) & gExtension
lFullname_tgt = gTgtfolder & "\" & gParamTab(lUsecase, gParamTabColTTtgt) & gExtension
Set lBlankTT = Workbooks.Open(lFullname_blank)
lBlankTTupgradeengine = gBlankTTName & gParamTab(lUsecase, gParamTabColTTCase) & gExtension & "!UpgradeEngine.UpgradeEngine"
Application.Run lBlankTTupgradeengine
End If
Next
End Sub
So i come the main issue, how can I, from another macro, after the statement "Application.Run lBlankTTupgradeengine" , the upgrade engine macro starts, and calls the following function embedded in the "BlankTT" :
Sub UpgradeEngine()
Set wkb_target = ThisWorkbook
Set wkb_source = macros_Fn.Open_wkb()
[...]
Function Open_wkb() As Workbook
Dim fileName As Variant
With Application.FileDialog(msoFileDialogFilePicker)
' Makes sure the user can select only one file
.AllowMultiSelect = False
' Filter to just keep the relevants types of files
.filters.Add "Excel Files", "*.xlsm; *.xlsb", 1
.Show
' Extact path
If .SelectedItems.Count > 0 Then
fileName = .SelectedItems.Item(1)
Else
End
End If
End With
If (fileName <> False) Then
Set Open_wkb = Workbooks.Open(fileName:=fileName, IgnoreReadOnlyRecommended:=False, Editable:=False, ReadOnly:=True, UpdateLinks:=False)
Else
MsgBox "This file is already open. Please close it before launching the function."
End
End If
End Function
This function opens as I said before, a dialog box with a brows button to select the excel spreadsheet to use as ssource.
My question is, how can i fill automatically this Filedialog from my code, without changing the code of the standard excel file?
Thanks a lot for your help!
I tried to search everywhere but i did not find anything about this situation.
I'm trying to move a copy of the upgrade engine, but with an argument in the sub instead of the filedialog but the macro is too complex ..
Your best bet would be to add an optional parameter to UpgradeEngine - something like:
Sub UpgradeEngine(Optional wbPath as String = "")
'...
Set wkb_target = ThisWorkbook
If Len(wbPath) > 0 Then
Set wkb_source = Workbooks.Open(wbPath) 'open using provided file path
Else
Set wkb_source = macros_Fn.Open_wkb() 'open user-selected file
End If
'...
'...
Then you can call it and pass in the path you want.
FYI the code in Open_wkb seems off (at least, the "already open" message seems wrong). fileName <> False only checks if the user made a selection: it doesn't indicate anything about whether a selected file is already open or not.
Using Excel VBA: Is it possibile to get the text contained in the tooltip which shows the argument list of a sub or function?
The Application.MacroOptions method knows the argument "ArgumentDescriptions" but it is possibly only set. Is there any way to read this info?
"Get the tooltip text contained showing the argument list of a sub or function ... The Application.MacroOptions Method has (the) argument ArgumentDescriptions but it Is possibile(!) only set. Is there any way to read this info?"
► Afaik there is no built-in way.
Possible workaround
As you "need this info in VBA code for a function/sub created in other module or class.",
you might want to analyze your code modules by referencing the
"Microsoft Visual Basic for Applications Extensibility 5.3" library in the VB Editor's menu.
Caveats:
Security: Requires to trust access to the VBA project object model.
Rights: If not only for your personal use, consider that other corporate users may
not have enough rights to turn that feature on.
Self reflection: Mirrors the currently compiled/saved code only, so it might not reflect the latest code when the searched procedure body line has been changed.
Line breaks: The following approach assumes that the entire procedure info is coded in one line -
not regarding closing line breaks via "_";
it should be easy to extend the .Lines result in these cases by your own (e.g. benefitting from the count argument or by additional loops through the next lines).
The following code doesn't intend neither to cover or to optimize all possibilities,
but to direct you to a solution keeping it short & simple.
Function GetSyntax()
Function GetSyntax(wb As Workbook, Optional ByVal srchProcName As String = "GetCookie") As String
'Purp: Show name & arguments of a given procedure
'1) escape a locked project
If wb.VBProject.Protection = vbext_pp_locked Then Exit Function ' escape locked projects
'2) loop through all modules
Dim component As VBIDE.VBComponent
For Each component In wb.VBProject.VBComponents
' Debug.Print "***"; component.Name, component.Type
Dim found As Boolean
'3) loop through procedures (as well as Let/Set/Get properties)
Dim pk As Long ' proc kind enumeration
For pk = vbext_pk_Proc To vbext_pk_Get
'a) get the essential body line of the search procedure
Dim lin As String
lin = getLine(component.CodeModule, srchProcName, pk)
'b) found non-empty code line?
found = Len(lin) <> 0
If found And pk = 0 Then GetArgs = lin: Exit For
'c) get proc info(s) - in case of Let/Set/Get properties
Dim Delim As String
GetSyntax = GetSyntax & IIf(found, Delim & lin, "")
Delim = vbNewLine ' don't change line order
Next pk
'If found Then Exit For ' if unique proc names only
Next component
End Function
Help function getLine()
Function getLine(module As VBIDE.CodeModule, ByVal srchProcName As String, ByVal pk As Long) As String
'a) define procedure kind
Dim ProcKind As VBIDE.vbext_ProcKind
ProcKind = pk
'b) get effective row number of proc/prop body line
On Error Resume Next
Dim effectiveRow As Long
effectiveRow = module.ProcBodyLine(srchProcName, ProcKind) ' find effective row of search procedure
'c) provide for non-findings or return function result (Case 0)
Select Case Err.Number
Case 0 ' Found
Dim lin As String
'Syntax: obj.Lines (startline, count) As String
lin = Trim(module.Lines(effectiveRow, 1))
getLine = lin
Case 35 ' Not found
Err.Clear: On Error GoTo 0
Case Else
Debug.Print "** " & " Error " & Err.Number & " " & Err.Description: Err.Clear: On Error GoTo 0
End Select
End Function
Possible Test call
Dim procList, proc
procList = Split("getCookie,foo,myNewFunction", ",")
For Each proc In procList
MsgBox GetSyntax(ThisWorkbook, proc), vbInformation, proc
Next
I'm trying to create a rudimentary glossary macro for a LibreOffice/OpenOffice .odt file.
It will go to the end of the document and paste a list of selected words(found by regex) as a unique set (no doubles)
Where I'm falling down is that once the text has been copied to the clipboard, I need to assign the contents to a variable so that I can create a set.
In OpenOffice's implementation of BASIC, how does one assign the contents of the clipboard to a new variable?
To be clear: I don't need the Paste function, I need to manipulate the contents of the clipboard as an Object before calling Paste
A rough draft of what I'm trying to do is:
dispatcher.executeDispatch(document, ".uno:Copy", "", 0, Array())
rem -------------- PROBLEM IS BELOW -------
Dim oModuleUICommandDescription As Object, myText$(),aCommand
myText = thisComponent.currentSelection(0)
rem -------------- PROBLEM IS ABOVE -------
rem -------------- Followed by an array comparison to get a unique set
i = FreeFile()
Open "/path/to/my/BASIC.txt" For Output As i
Print #i, myText.string
Close #i
So, as far as I can see the answer is that there isn't a simple built-in way to do this.
However, it is possible by using a custom created function posted here(not mine)
https://wiki.documentfoundation.org/Macros/Writer/005
and using that function to assign contents to the variable.
The upper sub here relies on the function defined below it.
Sub WriteClipboardtoTxtFile()
Dim sText As String
Dim myTextFile As String
Dim i%
findAllTags_Switches()
rem ########### ASSIGNMENT OCCURS JUST BELOW
sText= (getClipboardText)
rem ################ ASSIGNMENT OCCURS JUST ABOVE
sText = Replace (sText," ",Chr(10))
rem Replace white spaces with returns
MsgBox(sText)
i = FreeFile()
Open "/path/to/my/file" For Output As i
Print #i, sText
Close #i
End Sub ' InsertClipboardTexttoVariable
Function getClipboardText() As String
'''Returns a string of the current clipboard text'''
Dim oClip As Object ' com.sun.star.datatransfer.clipboard.SystemClipboard
Dim oConverter As Object ' com.sun.star.script.Converter
Dim oClipContents As Object
Dim oTypes As Object
Dim i%
oClip = createUnoService("com.sun.star.datatransfer.clipboard.SystemClipboard")
oConverter = createUnoService("com.sun.star.script.Converter")
On Error Resume Next
oClipContents = oClip.getContents
oTypes = oClipContents.getTransferDataFlavors
For i = LBound(oTypes) To UBound(oTypes)
If oTypes(i).MimeType = "text/plain;charset=utf-16" Then
Exit For
End If
Next
If (i >= 0) Then
On Error Resume Next
getClipboardText = oConverter.convertToSimpleType _
(oClipContents.getTransferData(oTypes(i)), com.sun.star.uno.TypeClass.STRING)
End If
End Function ' getClipboardText
To use in the OpenOffice macro editors, copy and paste the code in so that the new function can be called.
From ActiveWorkbook.name, I would like to extract the strings that are before (left side of ) the numbers. Since I want to use the same code in multiple workbooks, the file names would be variable, but every file name has date info in the middle (yyyymmdd).
In case of excel file, I can use the below formula, but can I apply the same kind of method in VBA?
=LEFT(A1,MIN(FIND({0,1,2,3,4,5,6,7,8,9},ASC(A1)&1234567890))-1)
Example: MyExcelWorkbook_Management_20200602_MyName.xlsm
In above case, I want to extract "MyExcelWorkbook_Management_".
The most basic thing you could do is to replicate something that worked for you in Excel through Evaluate:
Sub Test()
Dim str As String: str = "MyExcelWorkbook_Management_20200602_MyName.xlsm"
Debug.Print Evaluate(Replace("=LEFT(""X"",MIN(FIND({0,1,2,3,4,5,6,7,8,9},ASC(""X"")&1234567890))-1)", "X", str))
End Sub
Pretty? Not really, but it does the job and got it's limitations.
You could use Regular Expressions to extract any letters / underscores before the number as well
Dim str As String
str = "MyExcelWorkbook_Management_20200602_MyName.xlsm"
With CreateObject("vbscript.regexp")
.Pattern = "^\D*"
.Global = True
MsgBox .Execute(str)(0)
End With
Gives:
MyExcelWorkbook_Management_
So basically you want to use the Midfunction to look for the first numerical character in your input string, and then cut your input string to that position.
That means we need to loop through the string from left to right, look at one character at a time and see if it is a digit or not.
This code does exactly that:
Option Explicit
Sub extratLeftText()
Dim someString As String
Dim result As String
someString = "Hello World1234"
Dim i As Long
Dim c As String 'one character of your string
For i = 1 To Len(someString)
c = Mid(someString, i, 1)
If IsNumeric(c) = True Then 'should write "If IsNumeric(c) = True AND i>1 Then" to avoid an "out of bounds" error
result = Left(someString, i - 1)
Exit For
End If
Next i
MsgBox result
End Sub
Last thing you need to do is to load in some workbook name into your VBA function. Generally this is done with the .Name method of the workbookobject:
Sub workbookName()
Dim wb As Workbook
Set wb = ActiveWorkbook
MsgBox wb.Name
End Sub
Of course you would need to find some way to replace the Set wb = ActiveWorkbook line with code that suits your purpose.
I have a class DLL library that I built from a sample I found on a website. The class converts RTF to HTML. I call it from my SQL Server Reporting Services report. The problem is that the code is using the clipboard which needs its own thread.
The error in SSRS states:
Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.
I tried implementing a thread by samples I found from the internet to no avail. Could some help show me how to take the clipboard piece of the function and put it in its own thread, and then when that is done, resume the function that builds the HTML string that sends it back to my SSRS report?
Here is the function:
Public Function sRTF_To_HTML(ByVal sRTF As String) As String
'Declare a Word Application Object and a Word WdSaveOptions object
Dim MyWord As Microsoft.Office.Interop.Word.Application
Dim oDoNotSaveChanges As Object = _
Microsoft.Office.Interop.Word.WdSaveOptions.wdDoNotSaveChanges
'Declare two strings to handle the data
Dim sReturnString As String = ""
Dim sConvertedString As String = ""
Try
'Instantiate the Word application,
'set visible to false and create a document
MyWord = CreateObject("Word.application")
MyWord.Visible = False
MyWord.Documents.Add()
'Create a DataObject to hold the Rich Text
'and copy it to the clipboard
Dim doRTF As New System.Windows.Forms.DataObject
doRTF.SetData("Rich Text Format", sRTF)
'HERE IS WHERE THE CLIPBOARD STATEMENTS BEGIN
Clipboard.SetDataObject(doRTF)
'Paste the contents of the clipboard to the empty,
'hidden Word Document
MyWord.Windows(1).Selection.Paste()
'…then, select the entire contents of the document
'and copy back to the clipboard
MyWord.Windows(1).Selection.WholeStory()
MyWord.Windows(1).Selection.Copy()
'Now retrieve the HTML property of the DataObject
'stored on the clipboard
sConvertedString = _
Clipboard.GetData(System.Windows.Forms.DataFormats.Html)
'HERE IS WHERE THE CLIPBOARD STATEMENTS END
'Remove some leading text that shows up in some instances
'(like when you insert it into an email in Outlook
sConvertedString = _
sConvertedString.Substring(sConvertedString.IndexOf("<html"))
'Also remove multiple  characters that somehow end up in there
sConvertedString = sConvertedString.Replace("Â", "")
'…and you're done.
sReturnString = sConvertedString
If Not MyWord Is Nothing Then
MyWord.Quit(oDoNotSaveChanges)
MyWord = Nothing
End If
Catch ex As Exception
If Not MyWord Is Nothing Then
MyWord.Quit(oDoNotSaveChanges)
MyWord = Nothing
End If
MsgBox("Error converting Rich Text to HTML")
End Try
Return sReturnString
End Function
My attempt to put the function in a thread did not work. Though I get no threading errors when I test the DLL in my solution using a form project to call it, I still get the threading error when calling it from a SQL Server Reporting Services report.
I tried the following thread and did not work.
Imports System.IO
Imports System.Threading
Imports System.Windows.Forms
Imports System.Collections.Generic
Imports System.Runtime.InteropServices
<AttributeUsage(AttributeTargets.Method)> _
Public NotInheritable Class clsGetHTMLfromRTF
Inherits Attribute
Private Sub New()
End Sub
<STAThread()> _
Public Shared Function TranlslateRTFtoHTML(ByVal rtfText As String) As String
Dim thread = New Thread(AddressOf ConvertRtfInSTAThread)
Dim threadData = New ConvertRtfThreadData() With {.RtfText = rtfText}
thread.SetApartmentState(ApartmentState.STA)
thread.isBackground = True
thread.Start(threadData)
thread.Join()
Return threadData.HtmlText
End Function
Public Shared Sub ConvertRtfInSTAThread(ByVal rtf As Object)
Dim threadData = TryCast(rtf, ConvertRtfThreadData)
threadData.HtmlText = sRTF_To_HTML(threadData.RtfText)
End Sub
Public Class ConvertRtfThreadData
Public Property RtfText() As String
Get
Return m_RtfText
End Get
Set(ByVal value As String)
m_RtfText = value
End Set
End Property
Private m_RtfText As String
Public Property HtmlText() As String
Get
Return m_HtmlText
End Get
Set(ByVal value As String)
m_HtmlText = value
End Set
End Property
Private m_HtmlText As String
End Class
Public Shared Function sRTF_To_HTML(ByVal sRTF As String) As String
'Declare a Word Application Object and a Word WdSaveOptions object
Dim MyWord As Microsoft.Office.Interop.Word.Application
Dim oDoNotSaveChanges As Object = _
Microsoft.Office.Interop.Word.WdSaveOptions.wdDoNotSaveChanges
'Declare two strings to handle the data
Dim sReturnString As String = ""
Dim sConvertedString As String = ""
Try
'Instantiate the Word application,
'set visible to false and create a document
MyWord = CreateObject("Word.application")
MyWord.Visible = False
MyWord.Documents.Add()
'Create a DataObject to hold the Rich Text
'and copy it to the clipboard
Dim doRTF As New System.Windows.Forms.DataObject
doRTF.SetData("Rich Text Format", sRTF)
Clipboard.SetDataObject(doRTF)
'Paste the contents of the clipboard to the empty,
'hidden Word Document
MyWord.Windows(1).Selection.Paste()
'…then, select the entire contents of the document
'and copy back to the clipboard
MyWord.Windows(1).Selection.WholeStory()
MyWord.Windows(1).Selection.Copy()
'Now retrieve the HTML property of the DataObject
'stored on the clipboard
sConvertedString = _
Clipboard.GetData(System.Windows.Forms.DataFormats.Html)
'Remove some leading text that shows up in some instances
'(like when you insert it into an email in Outlook
sConvertedString = _
sConvertedString.Substring(sConvertedString.IndexOf("<html"))
'Also remove multiple  characters that somehow end up in there
sConvertedString = sConvertedString.Replace("Â", "")
'…and you're done.
sReturnString = sConvertedString
If Not MyWord Is Nothing Then
MyWord.Quit(oDoNotSaveChanges)
MyWord = Nothing
End If
Catch ex As Exception
Return ex.Message
If Not MyWord Is Nothing Then
MyWord.Quit(oDoNotSaveChanges)
MyWord = Nothing
End If
'MsgBox("Error converting Rich Text to HTML" & vbCrLf & ex.Message)
End Try
Return sReturnString
End Function
End Class
You can create a new Thread object and before you start the thread, call
SetApartmentState(ApartmentState.STA)
on it. In the thread procedure do your clipboard access.