ファイルを挿入するプログラムを作成しましたが、OLEオブジェクトを編集した場合、データの同期ができません。そこで今回はこれを可能にする方法を考えます。アドバイザリーシンクを使えばできそうです。
まずOLEオブジェクトが開かれるときにIOleObject.Adviseを実行すれば、編集が保存されたり終了したときに通知を受け取ることができます。そのときにデータを同期させる処理を実行すればいいわけです。
さっそくやってみましょう。
OLEオブジェクトをダブルクリックすると編集可能になりますが、このときにIOleObject.Adviseを実行する必要があります。
MouseDoubleClickイベントを使えばできそうなのですが、OLEオブジェクトをダブルクリックしても、このイベントは起きません。そこでWndProcをオーバーライドする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
internal class RichTextBoxEx : RichTextBox { public event EventHandler OleDoubleClick; const int WM_LBUTTONDBLCLK = 0x0203; if (m.Msg == WM_LBUTTONDBLCLK) { if (SelectionType == RichTextBoxSelectionTypes.Object && SelectionLength == 1) { OleDoubleClick?.Invoke(this, new EventArgs()); return; } base.WndProc(ref m); return; } } public partial class SyncRichTextBox : UserControl { public SyncRichTextBox() { // その他、省略 richTextBoxEx1.OleDoubleClick += RichTextBoxEx1_OleDoubleClick; } private void RichTextBoxEx1_OleDoubleClick(object sender, EventArgs e) { } } |
RichTextBoxEx1_OleDoubleClickをどう書くか?
まずオブジェクトを開くためにはIOleObjectを取得する必要があります。その処理をしているのが以下のプログラムです。
そのまえに以前のプラグラムをちょっと修正。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
namespace RichTextBoxOle { public class RichTextBoxOle { const int WM_USER = 0x0400; const int EM_GETOLEINTERFACE = WM_USER + 60; static public IRichEditOle GetRichEditOle(RichTextBox rich) { IRichEditOle richEditOle = null; SendMessage(rich.Handle, EM_GETOLEINTERFACE, 0, out richEditOle); return richEditOle; } static public IOleObject GetOleObject(RichTextBox rich, int cp) { IRichEditOle richEditOle = GetRichEditOle(rich); int count = richEditOle.GetObjectCount(); int i = 0; REOBJECT reo = null; for (i = 0; i < count; i++) { reo = new REOBJECT(); richEditOle.GetObject(i, reo, GETOBJECTOPTIONS.REO_GETOBJ_NO_INTERFACES); if (reo.cp == cp) break; reo = null; } if (reo != null) { richEditOle.GetObject(i, reo, GETOBJECTOPTIONS.REO_GETOBJ_POLEOBJ); return reo.poleobj; } return null; } static public IOleClientSite GetOleClientSite(RichTextBox rich, int cp) { IRichEditOle richEditOle = GetRichEditOle(rich); int count = richEditOle.GetObjectCount(); int i = 0; REOBJECT reo = null; for (i = 0; i < count; i++) { reo = new REOBJECT(); richEditOle.GetObject(i, reo, GETOBJECTOPTIONS.REO_GETOBJ_NO_INTERFACES); if (reo.cp == cp) break; reo = null; } if (reo != null) { richEditOle.GetObject(i, reo, GETOBJECTOPTIONS.REO_GETOBJ_POLESITE); return reo.polesite; } return null; } } } |
これでIOleObjectやIOleClientSiteが取得できるようになります。
IOleObject oleObject = RichTextBoxOle.RichTextBoxOle.GetOleObject(richTextBoxEx1, richTextBoxEx1.SelectionStart);
データを同期させたいので開くOLEオブジェクトは別のRichTextBoxにコピーしてそこで開くことにします。RichTextBoxはXAdviseSink内におき、XAdviseSinkオブジェクトはRichData内におきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class RichData { internal XAdviseSink _xAdviseSink = null; } public partial class SyncRichTextBox : UserControl { private void RichTextBoxEx1_OleDoubleClick(object sender, EventArgs e) { if (Data._xAdviseSink != null) { MessageBox.Show("編集可能なOLEオブジェクトはひとつだけです"); return; } RichTextBox richOleTextBox = new RichTextBox(); richOleTextBox.SelectedRtf = richTextBoxEx1.SelectedRtf; IOleObject oleObject = RichTextBoxOle.RichTextBoxOle.GetOleObject(richOleTextBox, 0); int ret = oleObject.DoVerb(0, null, null, 0, IntPtr.Zero, null); if (ret != 0) { MessageBox.Show("OLEオブジェクトを開くことができません"); return; } SetAdvise(oleObject, new XAdviseSink()); } } |
IOleObjectが取得できたらIOleObject.DoVerbを実行します。
int ret = oleObject.DoVerb(0, null, null, 0, IntPtr.Zero, null);
戻り値が0ならOLEオブジェクトを開くことに成功しています。それ以外の値なら失敗です。
OLEオブジェクトを開くことが成功しているのであればIAdviseSinkをセットします。
IAdviseSinkを継承したクラス、XAdviseSinkを作成します。そのためには Microsoft.VisualStudio.OLE.Interopを追加する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class XAdviseSink : IAdviseSink { internal RichData data = null; internal int selectionStart = 0; void IAdviseSink.OnSave() { } void IAdviseSink.OnClose() { } void IAdviseSink.OnDataChange(FORMATETC[] pFormatetc, STGMEDIUM[] pStgmed) { } void IAdviseSink.OnViewChange(uint dwAspect, int lindex) { } void IAdviseSink.OnRename(IMoniker pmk) { } } public partial class SyncRichTextBox : UserControl { void SetAdvise(IOleObject oleObject, XAdviseSink xAdviseSink) { uint i = 0; oleObject.Advise(xAdviseSink, out i); xAdviseSink.connect = i; xAdviseSink.data = Data; } } |
これでOleオブジェクトの編集が終了すると、IAdviseSink.OnClose()が呼ばれます。
IAdviseSink.OnClose()が呼ばれたときに対応するOleオブジェクトを変更すればいいのですが、そのためには対応しているOleオブジェクトがわかるようにしておく必要があります。さて、どうすればいいのでしょうか?
Oleオブジェクトの位置を記憶しておき、文書が変更されたときは適切に調整する
Oleオブジェクトが編集されているときは文書の編集を禁止する
後者が簡単でよいと思います。
文書の編集を禁止するにはKeyDownイベントを無視、ドラッグドロップイベントを無視することで実現できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
public partial class SyncRichTextBox : UserControl { protected override void OnKeyDown(KeyEventArgs e) { if (Data._xAdviseSink != null) { MessageBox.Show("Oleオブジェクト編集中は操作できません"); e.Handled = true; return; } // 以下、省略 } protected override void OnDragOver(DragEventArgs e) { if (Data._xAdviseSink != null) { e.Effect = DragDropEffects.None; return; } // 以下、省略 } private void RichTextBoxEx1_OleDoubleClick(object sender, EventArgs e) { if (Data._xAdviseSink != null) { MessageBox.Show("編集可能なOLEオブジェクトはひとつだけです"); return; } Data.richOleTextBox = new RichTextBox(); Data.richOleTextBox.SelectedRtf = richTextBoxEx1.SelectedRtf; IOleObject oleObject = RichTextBoxOle.RichTextBoxOle.GetOleObject(Data.richOleTextBox, 0); int ret = oleObject.DoVerb(0, null, null, 0, IntPtr.Zero, null); if (ret != 0) { MessageBox.Show("OLEオブジェクトを開くことができません"); return; } foreach (var rich in Data.SyncRichTextBoxes) { if (rich.Data == Data) { var site = RichTextBoxOle.RichTextBoxOle.GetOleClientSite(rich.richTextBoxEx1, SelectionStart); if (site != null) site.OnShowWindow(1); } } SetAdvise(oleObject, new XAdviseSink(SelectionStart, Data, richOleTextBox)); } void SetAdvise(IOleObject oleObject, XAdviseSink xAdviseSink) { uint i = 0; oleObject.Advise(xAdviseSink, out i); xAdviseSink.connect = i; } } public class XAdviseSink : IAdviseSink { internal RichData data = null; internal uint connect = 0; internal int selectionStart = 0; // コンストラクタを変更 public XAdviseSink(int selectionStart0, RichData data0, RichTextBox tempRich) { selectionStart = selectionStart0; data = data0; richOleTextBox = tempRich; richOleTextBox.TextChanged += RichOleTextBox_TextChanged; richOleTextBox.Select(0, 1); oldRtf = richOleTextBox.SelectedRtf; } private void RichOleTextBox_TextChanged(object sender, EventArgs e) { Timer t = new Timer(); t.Interval = 100; t.Tick += T_Tick; t.Start(); void T_Tick(object sender1, EventArgs e1) { Timer t0 = (Timer)sender1; t0.Stop(); t0.Dispose(); richOleTextBox.Select(0, 1); string rtf = richOleTextBox.SelectedRtf; foreach (var rich in data.SyncRichTextBoxes) { int oldStart = rich.SelectionStart; int oldLen = rich.SelectionLength; if (rich.Data == data) { rich.richTextBoxEx1.Select(selectionStart, 1); rich.richTextBoxEx1.SelectedRtf = richOleTextBox.SelectedRtf; rich.richTextBoxEx1.Select(oldStart, oldLen); var site = RichTextBoxOle.RichTextBoxOle.GetOleClientSite(rich.richTextBoxEx1, selectionStart); if (site != null) site.OnShowWindow(1); } } } } void IAdviseSink.OnClose() { richOleTextBox.Select(0, 1); string rtf = richOleTextBox.SelectedRtf; foreach (var rich in data.SyncRichTextBoxes) { int oldStart = rich.SelectionStart; int oldLen = rich.SelectionLength; if (rich.Data == data) { rich.richTextBoxEx1.Select(selectionStart, 1); rich.richTextBoxEx1.SelectedRtf = richOleTextBox.SelectedRtf; rich.richTextBoxEx1.Select(oldStart, oldLen); } } Undobuf buf = new Undobuf(); buf.removeStart = selectionStart; buf.removeLength = 1; buf.insertStart = selectionStart; buf.insertLength = 1; buf.oldSelectionStart = selectionStart; buf.oldSelectionLength = 1; buf.newSelectionStart = selectionStart; buf.newSelectionLength = 1; buf.insertRtf = rtf; buf.insertText = " "; buf.removeRtf = oldRtf; buf.removeText = " "; data.InsertUndobuf(buf); buf.action = "OleDataEdited"; RichTextBoxOle.RichTextBoxOle.GetOleObject(richOleTextBox, 0).Unadvise(connect); richOleTextBox.Dispose(); data._xAdviseSink = null; foreach (var rich in data.SyncRichTextBoxes) { int oldStart = rich.SelectionStart; int oldLen = rich.SelectionLength; if (rich.Data == data) { var site = RichTextBoxOle.RichTextBoxOle.GetOleClientSite(rich.richTextBoxEx1, selectionStart); if (site != null) site.OnShowWindow(0); } } } } |
このプログラムにはちょっと困った部分があります。それはフォルダのショートカットを貼り付けて開いた場合、IAdviseSink.OnCloseが実行されないらしく、編集することができなくなる点です。さて困った・・・。
そこで思いついた方法が、oleObject.DoVerbを実行する前と後では、プロセスの数が増えているはずだ、そこで
int i = 0;
foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses())
{
if (p.ProcessName != “explorer”)
i++;
}
return i;
として、explorer以外のプロセスの数を数えてみることにしました。OleOLEオブジェクトがフォルダのショートカットの場合、ダブルクリックしてもexplorer以外のプロセス数は増えないはずなので、これで場合分けできないかと・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public SyncRichTextBox() { private void RichTextBoxEx1_OleDoubleClick(object sender, EventArgs e) { if (Data._xAdviseSink != null) { MessageBox.Show("編集可能なOLEオブジェクトはひとつだけです"); return; } RichTextBox richOleTextBox = new RichTextBox(); richOleTextBox.SelectedRtf = richTextBoxEx1.SelectedRtf; IOleObject oleObject = RichTextBoxOle.RichTextBoxOle.GetOleObject(richOleTextBox, 0); IOleClientSite clientSite = RichTextBoxOle.RichTextBoxOle.GetOleClientSite(richOleTextBox, 0); int pre = GetCountProcessNotExplorer(); int ret = oleObject.DoVerb(0, null, null, 0, IntPtr.Zero, null); int after = GetCountProcessNotExplorer(); if (ret != 0) { MessageBox.Show("OLEオブジェクトを開くことができません"); return; } if (pre == after) return; foreach (var rich in Data.SyncRichTextBoxes) { if (rich.Data == Data) { var site = RichTextBoxOle.RichTextBoxOle.GetOleClientSite(rich.richTextBoxEx1, SelectionStart); if (site != null) site.OnShowWindow(1); } } SetAdvise(oleObject, new XAdviseSink(SelectionStart, Data, richOleTextBox)); } int GetCountProcessNotExplorer() { int i = 0; foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses()) { if (p.ProcessName != "explorer") i++; } return i; } } |
なんかインチキくさい方法だな。もっといい方法があるはずなのだが・・・。