質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

マクロ

定義された処理手続きに応じて、どのような一連の処理を行うのかを特定させるルールをマクロと呼びます。

Q&A

解決済

4回答

6664閲覧

Excel365の「変更内容を保存しますか?」を自動で閉じたい

退会済みユーザー

退会済みユーザー

総合スコア0

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

マクロ

定義された処理手続きに応じて、どのような一連の処理を行うのかを特定させるルールをマクロと呼びます。

0グッド

0クリップ

投稿2020/11/19 12:32

Excel365で作ったマクロをWindowsタスクで起動して自動実行させるものがあり、運用していたのですが、時間がかかりすぎた場合に、外部プログラムから終了させるニーズが生じて、苦戦しています。

対応方法は、FindWindow+SendMessage(WM_SYSCOMMAND, SC_CLOSE)のWin32APIを使う方法で考えています。
この方法で、SC_CLOSEはExcelマクロに正しく届いて、終了しようとするのですが、「変更内容を保存しますか?」の確認ダイアログが出てしまい、困っています。

このダイアログは、普通のダイアログ「#32770」と違って「NUIDialog」のようで、FindWindowExでボタンのハンドルを取得できず、ボタンにBM_CLICKを送信する方法が使えません。
確認ダイアログそのものはFindWindowで取得できますが、ダイアログにEnterキーイベントなどを送っても効果はなく。。

Excelマクロは、時刻などをシートに書き出しているので、SC_CLOSE受信でブックの保存確認ダイアログが出るのは仕方なさそうですが、無人運転なのでなんとか自動で閉じたいです。

ThisWorkbookモジュールに、Workbook_BeforeCloseは追加しましたが、これが呼ばれる前に保存確認ダイアログが出てしまい、そこで制御が止まって、Workbook_BeforeCloseの処理が動きません。

VBA

1Private Sub Workbook_BeforeClose(Cancel As Boolean) 2 Application.DisplayAlerts = False 3 ThisWorkbook.Save 4 ThisWorkbook.Close 5 Application.Quit 6End Sub

自動保存を無効にすればよいかと思って、Workbook_Openに追加しましたが、効果がありません。

VBA

1Private Sub Workbook_Open() 2 ThisWorkbook.EnableAutoRecover = False 3End Sub

何かいい方法はないでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YT0014

2020/11/19 14:34

該当のボタンにショートカットは設定されていないのでしょうか? あれば、Alt+keyを、親にキーイベントで送信すれば動きそうですが。
退会済みユーザー

退会済みユーザー

2020/11/20 01:57

保存確認ダイアログの中のボタンにショートカットを設定するのは無理なようですが、できるのでしょうか?単独の上書き保存(Ctrl+S)のような機能とは違うので。 ちなみに、手でExcelを開いてウィンドウ右上の[×]アイコンを押した時は、Workbook_BeforeCloseが呼ばれてコントロールできるのですが、外部からWM_SYSCOMMANDのSC_CLOSEを飛ばした時は、Workbook_BeforeCloseより先にExcelの保存確認ダイアログが出てしまいます。 これはどうしようもないでしょうか。。
YT0014

2020/11/20 02:38

Excel365が使える環境がないのですが、手元のExcel2013での終了時確認では、[保存(S)][保存しない(N)][キャンセル]のボタンが表示されます。 この時、キーボードでAlt+Nと打ち込むと、[保存しない(N)]のクリックと同じ動作をします。ボタンに(N)などの表記はないでしょうか? 不明なようなら、お手数ですが、問題となっているDialogの画面画像をご提示ください。
YT0014

2020/11/20 02:44

https://pc-chain.com/office-excel-closeapp/5709/ 上記、「Office365のExcelを終了する」の3.1メッセージダイアログ表示に、問題のDialogと思われる画像がありますが、これと同一ならば、Alt+Sで保存、Alt+Nで保存しないのショートカットとなります。
退会済みユーザー

退会済みユーザー

2020/11/20 07:04

助言ありがとうございます。ショートカットキーはその通りでした。 手でExcelを開いてキーボードを叩く分には確かにショートカットキーは効くのですが、外部プログラムからキーイベントを飛ばした場合は無視されて効きません。コードは以下のような感じです。(元はRubyですが、VBAなどでも同じです) WM_SYSCOMMAND = 0x0112 WM_SYSKEYDOWN = 0x0104 WM_SYSKEYUP = 0x0105 SC_CLOSE = 0xf060 VK_MENU = 0x12 VK_S = 0x53 hwndExcel = FindWindow(0, "Test.xlsm - Excel") PostMessage(hwndExcel, WM_SYSCOMMAND, SC_CLOSE, 0) sleep(0.2) // ダイアログが出るまで少し待つ hwndDialog = FindWindow("NUIDialog", "Microsoft Excel") SetForegroundWindow(hwndDialog) SendMessage(hwndDialog, WM_SYSKEYDOWN, VK_MENU, 0) SendMessage(hwndDialog, WM_SYSKEYDOWN, VK_S, 0) SendMessage(hwndDialog, WM_SYSKEYUP, VK_S, 0) SendMessage(hwndDialog, WM_SYSKEYUP, VK_MENU, 0) SetForegroundWindowまでは期待通りに動作したのでハンドルは正しく取れていますが、その後のキーイベント送信が効きません。Excel365のNUIDialogはそれ以前のバージョンとは違うのかもしれません。 別の方のコメントでUIAutomationを使う例があったので、そちらで調査してみます。
guest

回答4

0

コメントをいただいたUIAutomationを使って解決しました。最終的に以下のようになりました。

C#

1 2using System; 3using System.Runtime.InteropServices; // DLL Import 4using Automation = System.Windows.Automation; // UIAutomation 5using AutomationElement = System.Windows.Automation.AutomationElement; 6using PropertyCondition = System.Windows.Automation.PropertyCondition; 7using ControlType = System.Windows.Automation.ControlType; 8 9class DialogCloser 10{ 11 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 12 public static extern IntPtr FindWindow(string sClassName, string sWinTitle); 13 14 public static void Main() 15 { 16 IntPtr hWndDialog; 17 hWndDialog = FindWindow("NUIDialog", "Microsoft Excel"); 18 if (hWndDialog != IntPtr.Zero) { 19 AutomationElement elemDialog; 20 elemDialog = AutomationElement.FromHandle(hWndDialog); 21 FindElement(elemDialog, "保存しない"); 22 } 23 } 24 25 private static bool FindElement(AutomationElement elemNode, string sFindText) 26 { 27 AutomationElement.AutomationElementInformation elemInfo; 28 elemInfo = elemNode.Current; 29 if (elemInfo.Name == sFindText && elemInfo.ClassName == "NetUIButton") { 30 elemNode.SetFocus(); 31 object oPattern; 32 if (elemNode.TryGetCurrentPattern(Automation.InvokePattern.Pattern, out oPattern)) { 33 ((Automation.InvokePattern)oPattern).Invoke(); 34 } 35 return true; 36 } 37 Automation.AutomationElementCollection elements; 38 elements = elemNode.FindAll(Automation.TreeScope.Children, Automation.Condition.TrueCondition); 39 foreach (AutomationElement element in elements) { 40 if (FindElement(element, sFindText)) { 41 return true; 42 } 43 } 44 return false; 45 } 46} 47

投稿2020/11/24 11:18

編集2020/11/24 13:40
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

このダイアログは、普通のダイアログ「#32770」と違って「NUIDialog」のようで、FindWindowExでボタンのハンドルを取得できず、ボタンにBM_CLICKを送信する方法が使えません。

UIAutomationを使うことで解決できるかもしれません。おおむね同じような要望、Office製品でのNUIDialogの操作についての質問回答がありましたので、ご紹介します。

How to press buttons on NUIDialog? - MSDN Visual Studio

Visual StudioでC++ COMを使った実装が回答で紹介されていますが、こちらをもとに当方の環境、Windows 10/Visual Studio 2017/Excel 2013で試したところ、Excel終了時の「'Book1'の変更内容を保存しますか?」NUIDialogでの「保存(&S)」「保存しない(&N)」「キャンセル」ボタンの押下操作をすることができました。

ポイントとしては:

  • ボタン名にはアクセスキーは除いて指定する。"保存", "保存しない", "キャンセル"。
  • Win32ネイティブのCOMプログラミングだが、Win32(32ビット)、x64(64ビット)のどちらのビルドでも動作した。Excel 2013は32ビット版だが、影響は無い模様。
  • 可能であればコードはC#のプロジェクトで書き換えた方が使い易いかもしれない。C#、.NET FrameworkのプロジェクトでAnyCPUビルドで動作すれば32ビット、64ビットを気にする必要もなくなる。

です。質問者さんのお使いのExcelはOffice365の新しいバージョンのものなのでまったく同じ環境とは言えませんが、参考になれば。

投稿2020/11/20 03:19

編集2020/11/20 03:24
dodox86

総合スコア9256

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/11/24 11:14

返信が遅くなり、申し訳ありません。教えていただいた方法で無事にできました。 以下のようなC#のコードになりました。 ---------------------------------------------- using System; using System.Runtime.InteropServices; // DLL Import using Automation = System.Windows.Automation; // UIAutomation using AutomationElement = System.Windows.Automation.AutomationElement; using PropertyCondition = System.Windows.Automation.PropertyCondition; using ControlType = System.Windows.Automation.ControlType; class DialogCloser { [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr FindWindow(string sClassName, string sWinTitle); public static void Main() { IntPtr hWndDialog; hWndDialog = FindWindow("NUIDialog", "Microsoft Excel"); if ((int)hWndDialog != 0) { AutomationElement elemDialog; elemDialog = AutomationElement.FromHandle(hWndDialog); FindElement(elemDialog, "保存しない"); } } private static bool FindElement(AutomationElement elemNode, string sFindText) { AutomationElement.AutomationElementInformation elemInfo; elemInfo = elemNode.Current; if (elemInfo.Name == sFindText && elemInfo.ClassName == "NetUIButton") { Console.WriteLine($"★{sFindText}★発見!"); elemNode.SetFocus(); object oPattern; if (elemNode.TryGetCurrentPattern(Automation.InvokePattern.Pattern, out oPattern)) { ((Automation.InvokePattern)oPattern).Invoke(); } return true; } Automation.AutomationElementCollection elements; elements = elemNode.FindAll(Automation.TreeScope.Children, Automation.Condition.TrueCondition); foreach (AutomationElement element in elements) { if (FindElement(element, sFindText)) { return true; } } return false; } } ---------------------------------------------- アドバイス、ありがとうございました。
guest

0

質問の趣旨とは異なりますが・・・

vba

1ThisWorkbook.Saved = True

とすると、実際は保存しなくても、保存した扱いになります。(保存確認ダイアログがでない)
強制終了の流れで、どうにかSaved = Trueとできないものでしょうか?


追記
RunningObjectTableで起動中Excelインスタンスが列挙できますので、
そこからExcelをCOMで操作すると実現できそうですが、質問に対してズレすぎですかね・・・

投稿2020/11/24 08:22

編集2020/11/24 08:45
FKD

総合スコア268

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

VBA

1ThisWorkbook.Save

VBA

1ThisWorkbook.Save = True

とか

VBA

1Me.Save = True

に変更してみてはどうでしょう。

投稿2020/11/20 02:06

sazi

総合スコア25327

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/11/20 02:18

コメントありがとうございます。それは試しましたが、ダメでした。 そもそも、Workbook_BeforeCloseが呼ばれる前に、保存確認ダイアログが出てしまって、マクロ側で何とかしたくてもコントロールできない状況です。。
sazi

2020/11/20 02:33 編集

順序の問題でしたね。 変更せずに閉じる時もメッセージは出ますか? 出ない様なら、閉じる前に保存しておいて、閉じる際は変更が無い状態にするというのはどうでしょう。
退会済みユーザー

退会済みユーザー

2020/11/20 08:36

マクロがシートに何も出力せず、ブックとして変更がない状態を保てれば、保存確認ダイアログは出ませんでした。 ただ、それは無理でして、シートに書き出す処理をなくすことはできないため、「閉じる際に変更が無い状態にする」を実現するには、マクロ実行中に四六時中Saveを実行しないといけませんが、極端に動作が遅くなり、今よりもっと外部から終了させる危険が増して、現実的に不可能です。 別の方のコメントにあったUIAutomationを使うのが、やるとしたらの手段のようです。
sazi

2020/11/20 08:48

遅いから終了させるという事は、謂わば強制終了という事ですから、確かに保存させるというのは変な話でしたね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問