執筆:EugeneAmnis
Excel 2016 for Mac の VBA について
Excel 2016 for Mac の VBA では Excel 2011 for Mac の VBA と比べ大きく変更がありました。 今までも Mac版の VBA は使い勝手がいいとは言えませんでしたがセキュリティが大幅に強化され、既存の資産がほぼ使用できません。 今回FormeCollector、FormeSurveyの開発の中でわかった違いをここに記録しておきたいと思います。 数少ない MacVBAプログラマの為になればと願っています。
Excel 2011 との変化点
開発の中で大きく問題になった点は以下の通りです。また確認はしていませんが、ユーザーフォームの廃止などがあるようです。
- VBA内のAppleScript禁止
- 言語判定の変更
- Name ステートメントの対応
- パスセパレーターの変更
- ファイルアクセス権の確認
- PrintOutの違い
VBA内のApplescript禁止
正確には MacScript から AppleScriptTask への変更です。AppleScript は外部の ~/LibraryApplication Scripts/com.microsoft.Excel/ 内に 置いておかなくてはなりません。Excel 2011 for Mac では使用できません。 上記のディレクトリにはVBAから直接アクセスできないので、一度テキストデータとしてAppleScriptを書き出してから、 ユーザーの手で移動してもらうしかありません。 FormeCollector ではそのようにしています。 AppleScript の存在の有無はVBA内で AppleScriptTask を実行。 エラーが発生しなければ存在してると判断。エラー発生時には、 AppleScript を書き出す VBA を実行という形を取っています。 ついでに FormeCollector で使用している AppleScript は以下です。
注意
データを読み込む AppleScript は拡張子の判定をしていません。使用の際は VBA 側で処理する必要があります。
CSVデータを読み込む処理では容量が大きい物を読み込むと Excel がフリーズします。 (目安は20KBまで)必ず、読み込むデータの容量を確認してから実行してください。 フリーズした場合は Excel を強制終了するしかありません。
FormeFolderChooser
選択したフォルダパスの文字列を返します。キャンセルの場合は False が帰ってきます。
= AppleScriptTask("FormeFolderChooser.applescript", "fc", "")
FormeFileMove
指定したファイルを指定したフォルダへ移動します。エラー発生時には False が帰ってきます。
= AppleScriptTask("FormeFileMove.applescript", "fm", "ファイルパス,フォルダパス")
本来は 3. Name ステートメントの対応とあるように Name ステートメントが使用できるのですが、 5. ファイルアクセス権の問題で、バックグラウンドで動くことができない為使用しています。
FormeGetFiles
指定したフォルダ内の可視ファイルの名前を” , “ カンマ区切りの文字列で返します。キャンセル時には False が帰ってきます。
= AppleScriptTask("FormeGetFiles.applescript", "ls", "フォルダーパス")
FormeLoadTxtD
指定したファイルパスをOS依存のテキストエンコードで読み込み、文字列として返します。エラー発生時には False が帰ってきます。
= AppleScriptTask("FormeLoadTxtD.applescript", "ltd", "ファイルパス")
QueryTableに対応しているのですが、バックグラウンド処理の為使用しています。
FormeLoadTxt16
指定したファイルパスを UTF16 のテキストエンコードで読み込み、文字列として返します。エラー発生時には False が帰ってきます。
= AppleScriptTask("FormeLoadTxt16.applescript", "lt16", "ファイルパス")
QueryTableに対応しているのですが、バックグラウンド処理の為使用しています。
FormeLoadTxt8
指定したファイルパスを UTF8 のテキストエンコードで読み込み、文字列として返します。エラー発生時には False が帰ってきます。
= AppleScriptTask("FormeLoadTxt8.applescript", "lt8", "ファイルパス")
QueryTableに対応しているのですが、バックグラウンド処理の為使用しています。
FormeGetFileSize
指定したファイルパスのサイズを返します。エラー発生時には False が帰ってきます。
= AppleScriptTask("FormeGetFileSize.applescript", "fs", "ファイルパス")
FileLenに対応しているのですが、バックグラウンド処理の為使用しています。
言語判定の変更
言語判定をするために使用していた Application.LocalizedLanguage から Windowsと同様のApplication.LanguageSettings.LanguageID(msoLanguageIDInstall)に 変更になりました。対応をしていないとエラーが発生します。
Name ステートメントの対応
Name ステートメントに対応しました。
パスセパレーターの変更
パスセパレーターが “: “ から ” / “に変更になりました。Application.PathSeparatorを使用していれば問題ありません。
ファイルアクセス権の確認
バックグラウンドまたは、新規の複数ファイルアクセス時には権限をその都度、譲渡しなくてはなりません。 ユーザーが指定していないパスへのアクセスをVBAで行う場合(参照ファイルの移動などの作業で権限譲渡していないパスにアクセスする場合)発生します。 AppleScript を使用することで回避できます。GrantAccessToMultipleFilesを使用する方法も用意されていますが、処理の自動化には向いていません。
FormeCollectorは同一フォーマットのCSVデータが指定フォルダに存在する場合、テーブルに展開しCSVデータをバックアップフォルダに 移動する機能を自動的に行う機能があるので、権限の確認で止まっていては機能不全になってしまいます。それを回避する為に AppleScript を使用しています。
PrintOutの違い
Excel 2011 for Mac のマクロの記録で記録した PrintOut のコードではエラーが発生します。(Windowsでは問題なし)
問題のコード
ActiveWindow.SelectedSheets.PrintOut From:=1, To:=1, Copies:=1
回避コード
ThisWorkbook.ActiveSheet.PrintOut
条件分岐とその他
FormeCollectorとFormeSurveyでは基本的に以下のように条件分岐しています。
If Not Application.OperatingSystem Like "*Mac*" Then
Windows用コード
Else
If Val(Application.Version) < 15 Then
Excel 2011 for Mac用コード
Else
Excel 2016 for Mac用コード
End If
End If
必要であれば、以下も追加します。
#If MAC_OFFICE_VERSION >= 15 Then
Excel 2016 for Mac用コード
#End If
その他
基本的に外部ファイルにアクセスしない場合は問題ないと言えますが、中々難しいところです。また再現性はありませんが VBAプロジェクトのロックを Excel 2013 で 行なった後、Excel 2016 for Mac 及び Excel 2011 for Mac ではロックが解除できないことがありました。 それとは逆に Excel 2016 for Mac でロックしたものは、Excel 2011 for Mac では解除できませんでした。
AppleScriptTaskを使用するとVBAは実行速度がかなり低下します。
リボンのラベル変更は Windows と同一で問題ないようです。
Mac版 VBA は互換性はないという伝統はしっかりと受け継がれたようです。コーディングしている最中、挫折しそうになりましたがなんとか形になりました。 いつも android(java)、javascript、VBAなどでは誰かのお世話になっています。使えなくなる可能性大でもこれくらいしかできないのでお返しという意味でも 行う必要があると思い、公開することにしました。 数少ないとは思いますが他の MacVBAプログラマの少しでも足しになれば幸いです。尚、紹介した AppleScript はライセンスフリーです。 ( AppleScript内にライセンスの明記を忘れたので、ライセンス名は明記しません。自由に使ってください。また使用に対しての責任は取りません。)