久々にC#でExclのプロセスと格闘したのでメモ書きを残す。
環境とか
- Windows10 Pro
- VisualStudio2022 Pro
- C# .NET Framework4.6.2
- Windowsフォームアプリ(レガシーなあれ
- Microsoft365 Excel
やりたいこと
- フォーム上でGridに(ファイルサーバー上の)ファイルパスを表示
- Gridの行をクリック
- ファイルサーバーから選択したファイルをクライアント端末にコピー
- コピーしたファイルを開く(Excelファイル)
- Excelを編集して保存
- Excelを閉じる
- クライアント端末のExcelファイルをファイルサーバー上のオリジナルを上書きコピー
- クライアント端末からファイルを消す
ファイルは複数同時に起動できるものとし、閉じたら対象ファイルのみ移動する。
あと、本当は暗号化とかDB更新とかいろいろ間に挟まってるけどひとまず省略。
大まかなつくり
フォーム上でGirdクリックしたら、サブフォームを起動して、サブフォーム内でファイルのコピーとかいろいろやる感じ。サブフォームにしてるのは本来サブフォーム側でいろいろやりたい処理があるからという理由。
ただ、今回はGrid作るのも面倒なので、
- フォーム上のボタンクリックで、サブフォームが起動してTestExcel01.xlsxがコピーされて起動する
- もう一回ボタンクリックしたら、またサブフォームが起動してTestExcel02.xlsxがコピーされて起動する
という感じで簡略化して検証する。
ファイルもローカルにコピー元とコピー先のフォルダを適当に作ってやり取りする。
フォーム1のボタンクリック処理
コピー元のファイルパスは決め打ちでセットしてサブフォームに渡す。
private void btnNoWaitProcess_Click(object sender, EventArgs e)
{
string strFileFullPaath = "";
if (boolFileflag){
strFileFullPaath = @"C:\work\TestDoc\CopyFrom\TestExcel01.xlsx";
boolFileflag = false;
}
else{
strFileFullPaath = @"C:\work\TestDoc\CopyFrom\TestExcel02.xlsx";
boolFileflag = true;
}
//フォーム生成(ファイルパスを引き巣にセット)
SubForm objSubForm = new SubForm(@strFileFullPaath);
objSubForm.Show();
//わかりやすくボタンのテキスト変えとく
btnNoWaitProcess.Text = "2つ目のExcelを起動";
}
サブフォーム側の画面
フォームの中はなくてもいいけどとりあえず適当にラベル配置(気分
サブフォームの処理
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
namespace ExcelTest230714
{
public partial class SubForm : Form
{
private string strTargetFileName = "";
private string strTargetFilePathFrom = "";
private string strTargetFilePathTo = "";
public SubForm(string _TargetFileFullPath)
{
InitializeComponent();
strTargetFileName = Path.GetFileName(@_TargetFileFullPath);
strTargetFilePathFrom = @_TargetFileFullPath;
strTargetFilePathTo = $@"C:\work\TestDoc\CopyTo\{strTargetFileName}";
}
private void SubForm_Load(object sender, EventArgs e)
{
lblTargetFileName.Text = @strTargetFileName;
lblTargetFilePaathFrom.Text = @strTargetFilePathFrom;
lblTargetFilePathTo.Text = @strTargetFilePathTo;
//ファイルコピー
File.Copy(@strTargetFilePathFrom, @strTargetFilePathTo,true);
Process objProcess = new Process();
objProcess.StartInfo.FileName = "excel.exe";
objProcess.StartInfo.Arguments = @strTargetFilePathTo;
//プロセス終了を検知するようイベント登録する
objProcess.EnableRaisingEvents = true;
objProcess.Exited += Process_Exited;
//Excel起動
objProcess.Start();
}
//プロセスが終了したらファイルを戻す処理を実行
private void Process_Exited(object sender, EventArgs e)
{
// Excelプロセスが終了したときに実行される処理
MessageBox.Show($"{strTargetFileName}のExcelファイルが終了");
//ファイルを元の場所に戻す(上書き保存)
File.Copy(@strTargetFilePathTo, @strTargetFilePathFrom, true);
//ローカルのファイルを消す
File.Delete(@strTargetFilePathTo);
this.Invoke(new Action(() => this.Close()));
}
}
}
実行するが思ってた動きと違う
1つ目のExcel起動後、2つ目のExcel起動するとなぜか2つ目のExcelのプロセス終了イベントが発火する。。。
なんかExcel2013以降の仕様ということらしい。
複数のプロセスが起動すると、後に起動したプロセスが落とされて、1つ目のプロセスに統合される感じ。
1個目のプロセスを起動するとPID=16816でExcelのプロセス(インスタンスって言ったがいいのかな?)が生まれて、2個目のExcelを起動するとPID=2900でExcelがもう1つ起動するが、直後に消える。消えて、1つ目のほうに吸い込まれてる。なので、消えるタイミングでプロセス終了が検出されてしまうという。
Excelを別インスタンスで起動するようにする
Excelを別インスタンスで起動する方法
このサイトを見るに引数に/xをつければいいらしい。
なので、ソースを修正する
Process objProcess = new Process();
objProcess.StartInfo.FileName = "excel.exe";
//ArgumentsにExcelの別のインスタンスで起動するよう修正
objProcess.StartInfo.Arguments = @strTargetFilePathTo + " /x";
//プロセス終了を検知するようイベント登録する
objProcess.EnableRaisingEvents = true;
objProcess.Exited += Process_Exited;
//Excel起動
objProcess.Start();
Excelのインスタンスがそれぞれ起動して消えない
当然だが、フォームからExcelを起動した後、フォームとは全然関係なくWindowsのメニューとかからExcelを別途起動した場合は、インスタンスが統合されるので、全然関係ないファイルも含めて閉じないとプロセス終了のイベントが実行されないので注意。(フォームからExcelを起動する前に、全然関係ないExcelを開いてるのは大丈夫)
おわり