トップ回答者
[VSTO] 新規メール作成時に添付されたファイルを削除時に削除したファイルの名称とパスを取得したい。

質問
-
初めまして。
現在VSTO(Visual Studio Tools for Office)のOutlookで新規メールを送信時にファイルが添付されている場合、そのファイルを鍵付きZIPファイルに圧縮して本文メールと別途パスワードのみを記述したメールを送信するプログラムを作成しています。
現在実装済みの機能としては必要な機能である
- メール送信時にファイルを鍵付きZIPファイルに圧縮し、本文に添付しなおす
- 本文メールとは別途でパスワードのみを記述したメールを送信する
上記が実装できています。
ですが、メール作成時に一度添付したファイルを削除した場合、例外が発生し、メールの送信ができません。
その原因についてなのですが、
実装時のロジックとして
ファイルが添付されたときのイベント(BeforeAttachmentAdd)で添付されたファイル名とファイルが一時保管される場所のパスをListに追加し、送信ボタン押下時のイベント(OnItemSend)でその二つのListを用いて圧縮対象ファイルの抽出と圧縮時のZIPファイル名を作成しています。
ここで本題なのですが、添付ファイルの追加時イベント(BeforeAttachmentAdd)で追加したファイルの情報を取得できてもファイル削除時に発生するイベント(BeforeAttachmentRemove)でユーザが削除したファイルの情報を取得しようとした場合
"Outlookはこの添付ファイルの形式でこのアクションを実行することができません。"というメッセージが表示され取得ができません。
どうにかして添付ファイル削除時に削除したファイルの情報を取得することはできないでしょうか?
以上、宜しくお願いしたします。
下記はファイル追加時と削除時のイベント時のソースコードです。
//添付ファイル追加時のイベント
void Ribbon1_BeforeAttachmentAdd(Attachment attachment, ref bool Cancel) {
//**************************************************************
//tempファイル名の順番と元ファイル名の順番を合わせておく
//**************************************************************
try {
//添付ファイルのtempファイルパスを追加(後でまとめて圧縮するため)
path_list.Add(attachment.GetTemporaryFilePath());
//添付ファイルの元のファイル名を追加(tempファイルは同名が存在する場合勝手にリネームされてしまう)
original_file_list.Add(attachment.FileName);
} catch(System.Exception ex) {
MessageBox.Show(ex.Message + "\nこのファイルは圧縮の対象外です。", "info", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}//添付ファイル削除時のイベント
void Ribbon1_BeforeAttachmentRemove(Attachment attachment) {
//ウォッチ式でattachment.FileNameを見ても"Outlookはこの添付ファイルの形式でこのアクションを実行することができません"と表示される
//添付ファイルが削除された場合、リストを削除する
try {
original_file_list.Remove(attachment.FileName);
path_list.Remove(attachment.GetTemporaryFilePath());
} catch(System.Exception ex) {
MessageBox.Show(ex.Message, "info", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
- 編集済み tomy-7 2017年4月6日 7:24 改行がおかしかったため修正
回答
-
AttachmentAddイベントでAttachmentオブジェクトとファイルパスを保持しておいて、AttachmentRemove時に保持しておいたAttachmentと比較すれば対象のファイルが取れるんじゃないかな。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using Outlook = Microsoft.Office.Interop.Outlook; using Office = Microsoft.Office.Core; namespace OutlookAddIn1 { public partial class ThisAddIn { private void ThisAddIn_Startup(object sender, System.EventArgs e) { foreach (Outlook.Inspector inspector in Application.Inspectors) { inspectors.Add(new InspectorData(inspector, inspectors)); } this.Application.Inspectors.NewInspector += Inspectors_NewInspector; Application.ItemLoad += Application_ItemLoad; } void Application_ItemLoad(object Item) { if (Item is Outlook.MailItem) { //下書きに対してはどうやるのかしらない } } void Inspectors_NewInspector(Outlook.Inspector Inspector) { inspectors.Add(new InspectorData(Inspector, this.inspectors)); } private List<InspectorData> inspectors = new List<InspectorData>(); class InspectorData { public InspectorData(Outlook.Inspector Inspector, List<InspectorData> parentList) { this.parentList = parentList; inspecotEvent = Inspector as Outlook.InspectorEvents_10_Event; inspecotEvent.Close += ev_Close; var mail = Inspector.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem; if (mail != null) { MailData md = new MailData(); foreach (Outlook.Attachment att in mail.Attachments) { AttachData ad = new AttachData(att); } dic.Add(mail, md); mail.AttachmentAdd += mail_AttachmentAdd; mail.AttachmentRemove += mail_AttachmentRemove; mail.BeforeDelete += mail_BeforeDelete; mail.Unload += mail_Unload; } } private List<InspectorData> parentList; private Outlook.InspectorEvents_10_Event inspecotEvent; void ev_Close() { this.parentList.Remove(this); } private Dictionary<Outlook.MailItem, MailData> dic = new Dictionary<Outlook.MailItem, MailData>(); private class MailData { public List<AttachData> Attaches = new List<AttachData>(); } private class AttachData { public AttachData(Outlook.Attachment att) { Attachment = att; Name = att.DisplayName; Path = att.FileName; try { TempPath = att.GetTemporaryFilePath(); } catch { //下書きから開いたら一時ファイルがないね… } } public Outlook.Attachment Attachment { get; private set; } public string Name { get; private set; } public string Path { get; private set; } public string TempPath { get; private set; } } void mail_AttachmentAdd(Outlook.Attachment Attachment) { var mail = Attachment.Parent as Microsoft.Office.Interop.Outlook.MailItem; if (mail != null) { MailData md; if (!dic.TryGetValue(mail, out md)) { md = new MailData(); dic.Add(mail, md); } AttachData ad = new AttachData(Attachment); md.Attaches.Add(ad); } } void mail_AttachmentRemove(Outlook.Attachment removedAttach) { var mail = removedAttach.Parent as Microsoft.Office.Interop.Outlook.MailItem; if (mail != null) { MailData md; if (dic.TryGetValue(mail, out md)) { foreach (AttachData ad in md.Attaches) { if (ad.Attachment == removedAttach) { md.Attaches.Remove(ad); break; } } } } } void mail_BeforeDelete(object Item, ref bool Cancel) { } void mail_Unload() { } } private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { } #region VSTO で生成されたコード private void InternalStartup() { this.Startup += new System.EventHandler(ThisAddIn_Startup); this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown); } #endregion } }
下書き状態での操作はわかりません。一度ファイルに書き出して再添付すれば一時ファイルのパスができるけど…
#下書き保存を禁止するか個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年4月7日 4:23
- 回答としてマーク tomy-7 2017年4月7日 5:31
すべての返信
-
AttachmentAddイベントでAttachmentオブジェクトとファイルパスを保持しておいて、AttachmentRemove時に保持しておいたAttachmentと比較すれば対象のファイルが取れるんじゃないかな。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using Outlook = Microsoft.Office.Interop.Outlook; using Office = Microsoft.Office.Core; namespace OutlookAddIn1 { public partial class ThisAddIn { private void ThisAddIn_Startup(object sender, System.EventArgs e) { foreach (Outlook.Inspector inspector in Application.Inspectors) { inspectors.Add(new InspectorData(inspector, inspectors)); } this.Application.Inspectors.NewInspector += Inspectors_NewInspector; Application.ItemLoad += Application_ItemLoad; } void Application_ItemLoad(object Item) { if (Item is Outlook.MailItem) { //下書きに対してはどうやるのかしらない } } void Inspectors_NewInspector(Outlook.Inspector Inspector) { inspectors.Add(new InspectorData(Inspector, this.inspectors)); } private List<InspectorData> inspectors = new List<InspectorData>(); class InspectorData { public InspectorData(Outlook.Inspector Inspector, List<InspectorData> parentList) { this.parentList = parentList; inspecotEvent = Inspector as Outlook.InspectorEvents_10_Event; inspecotEvent.Close += ev_Close; var mail = Inspector.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem; if (mail != null) { MailData md = new MailData(); foreach (Outlook.Attachment att in mail.Attachments) { AttachData ad = new AttachData(att); } dic.Add(mail, md); mail.AttachmentAdd += mail_AttachmentAdd; mail.AttachmentRemove += mail_AttachmentRemove; mail.BeforeDelete += mail_BeforeDelete; mail.Unload += mail_Unload; } } private List<InspectorData> parentList; private Outlook.InspectorEvents_10_Event inspecotEvent; void ev_Close() { this.parentList.Remove(this); } private Dictionary<Outlook.MailItem, MailData> dic = new Dictionary<Outlook.MailItem, MailData>(); private class MailData { public List<AttachData> Attaches = new List<AttachData>(); } private class AttachData { public AttachData(Outlook.Attachment att) { Attachment = att; Name = att.DisplayName; Path = att.FileName; try { TempPath = att.GetTemporaryFilePath(); } catch { //下書きから開いたら一時ファイルがないね… } } public Outlook.Attachment Attachment { get; private set; } public string Name { get; private set; } public string Path { get; private set; } public string TempPath { get; private set; } } void mail_AttachmentAdd(Outlook.Attachment Attachment) { var mail = Attachment.Parent as Microsoft.Office.Interop.Outlook.MailItem; if (mail != null) { MailData md; if (!dic.TryGetValue(mail, out md)) { md = new MailData(); dic.Add(mail, md); } AttachData ad = new AttachData(Attachment); md.Attaches.Add(ad); } } void mail_AttachmentRemove(Outlook.Attachment removedAttach) { var mail = removedAttach.Parent as Microsoft.Office.Interop.Outlook.MailItem; if (mail != null) { MailData md; if (dic.TryGetValue(mail, out md)) { foreach (AttachData ad in md.Attaches) { if (ad.Attachment == removedAttach) { md.Attaches.Remove(ad); break; } } } } } void mail_BeforeDelete(object Item, ref bool Cancel) { } void mail_Unload() { } } private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { } #region VSTO で生成されたコード private void InternalStartup() { this.Startup += new System.EventHandler(ThisAddIn_Startup); this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown); } #endregion } }
下書き状態での操作はわかりません。一度ファイルに書き出して再添付すれば一時ファイルのパスができるけど…
#下書き保存を禁止するか個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年4月7日 4:23
- 回答としてマーク tomy-7 2017年4月7日 5:31
-
gekka様、回答ありがとうございます。
gekka様の
<AttachmentAddイベントでAttachmentオブジェクトとファイルパスを保持しておいて、AttachmentRemove時に保持しておいたAttachmentと比較すれば対象のファイルが取れるんじゃないかな。
という内容を参考に以下のようにソースを組みなおし、最低限の動作する事を確認しました。(またテスト途中ですが)
public partial class Ribbon1 { MailItem mailItem = null; //添付ファイルのtempファイルパス public List<string> path_list = new List<string>(); //添付ファイルの元のファイル名(temp用にリネームされる前の名前) public List<string> original_file_list = new List<string>(); //Attachmentオブジェクトのリスト public List<Attachment> attach_list = new List<Attachment>(); private void Ribbon1_Load(object sender, RibbonUIEventArgs e) { //メールオブジェクト取得 if(mailItem == null) { var inspector = base.Context as Microsoft.Office.Interop.Outlook.Inspector; mailItem = inspector.CurrentItem as MailItem; //ファイルが添付された時のイベントを登録 (mailItem as ItemEvents_10_Event).BeforeAttachmentAdd += Ribbon1_BeforeAttachmentAdd; //添付ファイルが削除された時のイベントを登録 (mailItem as ItemEvents_10_Event).AttachmentRemove += Ribbon1_AttachmentRemove; } } /// <summary> /// 添付ファイル追加時に発生するイベント /// </summary> /// <param name="attachment"></param> /// <param name="Cancel"></param> void Ribbon1_BeforeAttachmentAdd(Attachment attachment, ref bool Cancel) { //****************************************************************************************** //[Attachmentオブジェクト]と[tempファイルパス]と[元ファイル名]の順番を合わせておく //****************************************************************************************** try { //Attachmentオブジェクトの参照を追加 attach_list.Add(attachment); //添付ファイルのtempファイルパスを追加(後でまとめて圧縮するため) //************************************************************************************** //Attachmentに含まれるtempファイルパスはこのイベント内でのみしか取得不可能で //AttachmentオブジェクトをグローバルなListに保持していても参照ができない(仕様の模様) //************************************************************************************** path_list.Add(attachment.GetTemporaryFilePath()); //添付ファイルの元のファイル名を追加(tempファイルは同名が存在する場合勝手にリネームされてしまう為) original_file_list.Add(attachment.FileName); } catch(System.Exception ex) { MessageBox.Show(ex.Message + "\nこのファイルは圧縮の対象外です。", "info", MessageBoxButtons.OK, MessageBoxIcon.Information); } } /// <summary> /// 添付ファイル削除時に発生するイベント /// </summary> /// <param name="attachment"></param> void Ribbon1_AttachmentRemove(Attachment attachment) { try { //添付ファイルが削除された場合、リストを削除する //********************************************************************************************************* //AttachmentRemoveイベントで取得したAttachmentオブジェクトにはファイルパスやファイル名の情報が含まれない //********************************************************************************************************* RemoveMailFile(attachment); } catch (System.Exception ex) { MessageBox.Show(ex.Message, "info", MessageBoxButtons.OK, MessageBoxIcon.Information); } } public void Ribbon1_Refresh_ItemList() { path_list.Clear(); original_file_list.Clear(); } ///<summary>ファイル削除時に該当するデータを削除する</summary> ///<param name="attach">AttachmentRemoveイベント時に取得したAttachmentオブジェクト</param> private void RemoveMailFile(Attachment attach) { //取得したAttachmentオブジェクトから削除対象の添付ファイルのインデックスを検出 int remvIndex = attach_list.IndexOf(attach); //一時ファイルリスト、元ファイル名リスト、Attachmentリストの該当するデータを削除 attach_list.RemoveAt(remvIndex); path_list.RemoveAt(remvIndex); original_file_list.RemoveAt(remvIndex); } }
最初はAttachmentリストをThisAddInクラスにそのまま渡して一時ファイルのパスや添付ファイル名を取得しようとしたのですが、ThisAddInクラス内でAttachmentリストを参照しようとしたらイベント内でのみ使用してください、というようなメッセージの例外が発生したため、諦めてRibbonクラス内のAttachmentRemoveイベント(添付ファイル削除時に発生するイベント)を通るたびに更新したAttachmntリストから一時ファイルパスと添付ファイル名のリストを作り直すような処理を実装しようとしました。
※以下のようなコード
/// <summary> /// 添付ファイル削除時に発生するイベント /// </summary> /// <param name="attachment"></param> void Ribbon1_AttachmentRemove(Attachment attachment) { try { RefreshList(attachment); } catch (System.Exception ex) { MessageBox.Show(ex.Message, "info", MessageBoxButtons.OK, MessageBoxIcon.Information); } } ///<summary>ファイル削除時に該当するデータを削除する</summary> ///<param name="attach">AttachmentRemoveイベント時に取得したAttachmentオブジェクト</param> private void RefreshList(Attachment attach) { attach_list.RemoveAt(attach); path_list.Clear(); original_file_list.Clear(); //一時ファイルパスリストと元添付ファイル名リストを作り直す foreach(Outlook.Attachment attach in attachment_list){ path_list.Add(attach.GetTemporaryFilePath()); original_file_list.Add(attach.FileName); } } }
ですが、上記のような実装を行い実行した際、AttachmentリストのForEach文内
path_list.Add(attach.GetTemporaryFilePath());
の処理部分で一時ファイルパスが取得できないという例外が発生しました。
どうやら、AttachmentからGetTemporaryFilePath()で一時ファイルパスが取得できるのがAttachmntAddイベント内のみの様だったため、仕方なく
- Attachmentオブジェクト
- 添付ファイルの一時保管パス
- 添付ファイルの元の名前
の3つのリストを作成し、以下のようなロジックで管理するようにしました。
※例 新規メールに3つの添付ファイルを追加し、2番目の添付ファイルを削除して送信させる
①メールに3つの添付ファイル(AAA.txt,BBB.txt,CCC.txt)を追加
・Attachmentリスト
- attachment1(※AAA.txtを添付したときに発生したAttachment)
- attachment2(※BBB.txtを添付したときに発生したAttachment)
- attachment3(※CCC.txtを添付したときに発生したAttachment)
・添付ファイルの一時保管パスリスト
- AppData/~~/AAA(001).txt
- AppData/~~/BBB(002).txt
- AppData/~~/CCC(003).txt
・添付ファイルの元のファイル名リスト
- AAA.txt
- BBB.txt
- CCC.txt
②2番目に追加した添付ファイル(BBB.txt)を削除
・Attachmentリスト
- attachment1(※AAA.txtを添付したときに発生したAttachment)
- ※IndexOfメソッドで削除対象のインデックスを取得後にRemoveAtで削除
- attachment3(※CCC.txtを添付したときに発生したAttachment)
・添付ファイルの一時保管パスリスト
- AppData/~~/AAA(001).txt
- Attachmentリスト削除時に取得したインデックスをもとに削除
- AppData/~~/CCC(003).txt
・添付ファイルの元のファイル名リスト
- AAA.txt
- Attachmentリスト削除時に取得したインデックスをもとに削除
- CCC.txt
-送信ボタン押下時にAttachment以外のリスト情報をThisAddInクラスに渡して
受け取ったデータをもとにファイルの圧縮、パスワードの設定、パスワードとzip内ファイルの情報メール送信
というようなロジックになります。
回りくどく、要素番号で全部管理する形になってしまいましたが、
色々と調べた結果わかった以下の内容から、この方法でしか実装できないという結論になりました・・・
- 添付ファイルの一時ファイルパスはBeforeAttachmentAddイベント内のAttachmentからしか取得できない(AttachmentをListに入れても後から参照できない)
- AttachmentRemoveイベントで取得するAttachmentには削除した添付ファイルの情報は一切含まれない
もしかしたらもっとマシな方法があるのかもしれませんがVSTOの情報が少なすぎてこれが限界でした。
下書きについては今は実装予定がないため、保留中です。
- 編集済み tomy-7 2017年4月7日 5:29 回答者様の名前と回答の候補に設定された方の名前を間違えました。大変申し訳ございません。