VBA AppActivate Microsoft Excel エラー

初投稿のどんぐりすです。

当方Windows10 + EXCEL2010のVBAで、エクセルをアクティブにするためのAppActivateが正常に動作せず、とても困っていました。
紆余曲折を経て、たどり着いた方法をご紹介します。

※2021年4月19日追記
EXCEL2016以降の場合、下記 ThisWorkbook.Parent とExcel.Applicationの部分をApplication.Caption に変更して下さい。

まず、AppActivateが正常に動作しないことについて確認します。
■再現手順
Sub Test1()
    Application.Wait Now + TimeValue("0:00:03")
    AppActivate ThisWorkbook.Parent, False
End Sub
を実行します。

3秒以内にエクセル以外のアプリケーションをアクティブにします。

本来であれば3秒後にエクセルが最前面に来てアクティブとなるはずですが、タスクバーのエクセルのアイコンが点滅するだけでアクティブとはなりません。

うーん、困った・・・

点滅しているということは、アクティブにしようとしたけども何らかの理由でアクティブにできなかったものと思われます。

このエクセルがタスクバーで点滅している状態で、アプリケーション選択のショートカットキー「Alt+Tabキー」を1回押すとエクセルを最前面に持ってくることができるようです。

そこで、先のTest1()モジュールにの最後に1行追加して、Test2()を作成しました。
Sub Test2()
    Application.Wait Now + TimeValue("0:00:03")
    AppActivate ThisWorkbook.Parent, False
    Application.SendKeys "%{tab}", True
End Sub

これを実行するとうまく行きました!
しかし、もう一度やってみると、うまく行きません。
どうやら一度Test2を実行し、エクセルをアクティブにすることが成功すると、次からはTest1のままでエクセルをアクティブにすることができるようです。
ということは、2回目からはSendkeyは必要ありません。

つまり、

AppActivateを実行してみて、エクセルがアクティブになっていない場合に、Sendkeyの処理をすればいい

ということになります。
最前面のアプリケーション情報を取得するにはどうしたら良いかわからなかったですが、ネットをいろいろと検索するうちに、次のモジュールにたどり着きました。
参考にしたサイト:mougモーグ 様
サイトの情報を活用して完成したのが、次のモジュールです。

Private Declare Function GetForegroundWindow Lib "user32" () As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Sub Test3()
'変数の宣言
    Dim lngHw       As Long
    Dim strTitle    As String
'3秒間停止(この間に、他のアプリケーションをアクティブにする)
    Application.Wait Now + TimeValue("0:00:03")
'エクセルをアクティブに
    AppActivate Excel.Application, False
'現在フォアグラウンドになっているウィンドウハンドルを取得
    lngHw = GetForegroundWindow
'strTitleへフォアグラウンドのタイトルバーテキストを格納
    strTitle = String(100, Chr(0))
    GetWindowText lngHw, strTitle, Len(strTitle)
    strTitle = Left$(strTitle, InStr(strTitle, Chr(0)) - 1)
'フォアグランドのウィンドウがエクセルでなければ「ALT+TAB」キーストロークを送る
    If strTitle <> Application.Caption Then
        Application.SendKeys "%{tab}", True
    End If
End Sub

このモジュールで、ようやくエクセルを確実にアクティブにできるようになりました。
あまりスマートな方法ではありませんが、他の良い方法をご存知の方は、是非教えてください!


前提・実現したいこと

マクロエクセルを開くと、自動でファイルの読み込みと、
その結果を出力するユーザーフォームを表示させるVBAを作っています。
マクロエクセルは最小化して、ユーザーフォームのみ前面に出てほしいです。

下記のようなコードを組むことで、
他のエクセルを開いていないときは問題なく動きますが、
他のエクセルを開いている状態で、マクロエクセルを立ち上げると不具合が出ています。

Private Sub Workbook_Open()

’(他ファイルの読み込み部(略))

Application.WindowState = xlMinimized VBA.AppActivate Excel.Application.Caption '※1 UserForm1.Show vbModeless

End Sub

発生している問題・エラーメッセージ

エラーメッセージ 実行時エラー5 プロシージャの呼び出し、または引数が不正です。

該当のソースコード

VBA.AppActivate Excel.Application.Caption '※1

試したこと

デバックをすると、※1部のExcel.applicition.captionが別のエクセルファイルになっていました(元々開いていたやつ)
マクロファイルを最小化した時点で、アクティブ権が元々開いていたやつに移ってしまった?と考えています。
※1をなくした場合、エラーは出ませんが、
フォームが前面に出てこず裏で埋もれてしまいます。
Thisworkbook.activateの位置を動かしたり、無効にしたりしてみましたが解決には至っていません。

補足情報(FW/ツールのバージョンなど)

Excel2019

  • #2

The AppActivate statement requires the title of the title window. In any case, here's another approach. First, assign your workbook to an object variable, and then refer to that variable when you want to activate that workbook. For example, you can assign your workbook as follows...

Code:

    Dim Wkb As Workbook
    
    Set Wkb = ThisWorkbook

or

Code:

    Dim Wkb As Workbook
    
    Set Wkb = ActiveWorkbook

Then, to active the workbook...

Hope this helps!

  • #3

Domenic,
Thanks for the feedback. I have tried several variations on AppActivate including appActivate thisworkbook.name which does not throw an error, but does not give me focus on thisworkbook. I have also tried something almost identical to what you suggested with creating an object and setting it to = thisworkbook then activating it. Unfortunately that does not bring focus back on thisworkbook either. Any other ideas?

Thanks again,
CN.

  • #4

Both methods should work. However, I've just tested the line of code that shows the Print Setup dialog box in Excel 2016. The dialog box pops up, and a printer can be selected, but there's no OK button that can be selected. There's only a Cancel button, a greyed out Options button, and a Set as Default Printer button. Do you get the same thing? Maybe this is prevented the code from continuing?

  • #5

I agree both methods should work. Unfortunately they are not. I just tested the same code with wdDialogFilePrintSetup line commented out, and it sets focus on the word doc then refuses to set focus on the Excel App, so I believe that is not the cause of the problem...

  • #6

Try posting your complete code so that we can have a look at it...

  • #7

Code:

Sub PrintMe()

Dim irow As Integer
Dim wdDoc As Word.Document
Dim sPath As String
Dim ccAmount As ContentControl
Dim ccDate As ContentControl
Dim ccPayee As ContentControl
Dim TreatyName As String
Dim rngTreatyNames As Range
Dim rngFound As Range
Dim iRowCheckPaperNeeded As Integer
Dim bPaperNeeded As Boolean
Dim wdApp As Word.Application

sPath = "O:\Accounting\STEVE\Misc\Blank Check.docm"
'sPath = "H:\Development\Check Printing\Blank Check.docm"
If TestIfFileISOpened.IsFileOpen(sPath) <> False Then
    If TestIfFileISOpened.IsFileOpen(sPath) = True Then
        MsgBox ("You must close the file " & sPath & " before you can run this program.  Please close it and start over.")
    Else
        MsgBox ("I cannot access the file " & sPath & " for some reason.  Please contact tech support for help.  Error Number: " & TestIfFileISOpened.IsFileOpen(sPath))
    End If
    Exit Sub
End If
Set wdDoc = GetWord(sPath, wdApp)

Set ccAmount = wdDoc.ContentControls(3)
Set ccPayee = wdDoc.ContentControls(5)
Set ccDate = wdDoc.ContentControls(2)
Set rngTreatyNames = Sheets("Treaty Names").Range("A1:A" & Sheets("Treaty Names").UsedRange.Rows.Count)

MsgBox ("Please choose the printer you wish to use.")
wdApp.Activate
wdDoc.Application.Dialogs(wdDialogFilePrintSetup).Show
'AppActivate ThisWorkbook.Name, 5 'This isnt working... not sure why... Can't seem to get focus back on Excel... I guess they will have to click on Excel... :(

'loop through each row on editable results... if there is a print (Y or y) and a sum of amount payable, then print the check... set CCAmount = sum of amount, set ccPayee = Row labels, and ccDate = today()
'First set First Treaty Name...
TreatyName = Sheets("Editable Results").Cells(5, "A")
bPaperNeeded = False


If checkPaperNeeded(6, Sheets("Editable Results").UsedRange.Rows.Count, rngTreatyNames) Then
    MsgBox ("Please put " & TreatyName & " stock in the printer.")
End If
For irow = 6 To Sheets("Editable Results").UsedRange.Rows.Count
    Set rngFound = rngTreatyNames.Find(Sheets("Editable Results").Cells(irow, "A"), , , xlWhole)
    If rngFound Is Nothing Then
        If Sheets("Editable Results").Cells(irow, "B") > 0 And UCase(Trim(Sheets("Editable Results").Cells(irow, "C"))) = "Y" Then
            ccAmount.Range.Text = Sheets("Editable Results").Cells(irow, "B")
            ccPayee.Range.Text = Sheets("Editable Results").Cells(irow, "A")
            ccDate.Range.Text = Date
            Call ModifyWordDoc.adjustCurrency(ccAmount, wdDoc)
            wdDoc.printout
        End If
    Else
        TreatyName = Sheets("Editable Results").Cells(irow, "A")
        If checkPaperNeeded(irow + 1, Sheets("Editable Results").UsedRange.Rows.Count, rngTreatyNames) Then
            MsgBox ("Please put " & TreatyName & " stock in the printer.")
        End If
    End If
    

Next irow

wdDoc.Saved = True
wdDoc.Close
wdApp.Quit
        

End Sub

Private Function checkPaperNeeded(iRowStart As Integer, iRowEnd As Integer, rngTreatyNames As Range) As Boolean
Dim i As Integer
Dim rngFound As Range
checkPaperNeeded = False
For i = iRowStart To iRowEnd
    If UCase(Trim(Sheets("Editable Results").Cells(i, "C"))) = "Y" Then
        checkPaperNeeded = True
        Exit For
    End If
    Set rngFound = rngTreatyNames.Find(Sheets("Editable Results").Cells(i, "A"), , , xlWhole)
    If Not rngFound Is Nothing Then
        Exit For
    End If
Next i
End Function


Code:

Function IsFileOpen(filename As String)
    Dim filenum As Integer, errnum As Integer

    On Error Resume Next   ' Turn error checking off.
    filenum = FreeFile()   ' Get a free file number.
    ' Attempt to open the file and lock it.
    Open filename For Input Lock Read As #filenum
    Close filenum          ' Close the file.
    errnum = Err           ' Save the error number that occurred.
    On Error GoTo 0        ' Turn error checking back on.

    ' Check to see which error occurred.
    Select Case errnum

        ' No error occurred.
        ' File is NOT already open by another user.
        Case 0
         IsFileOpen = False

        ' Error number for "Permission Denied."
        ' File is already opened by another user.
        Case 70
            IsFileOpen = True

        ' Another error occurred.
        Case Else
            IsFileOpen = errnum
    End Select

End Function