不是我變態,我也是被逼的。客戶喜歡Word 2007里的Custom Task Pane,希望在側邊欄上放一些界面。但是他們剛剛從Word 97升級到2003,完全沒有可能升級到更高的版本。我之前給他們做過一個DEMO,是用ActionsPane技術實現的。他們覺得挺好,就要這個。但是我不喜歡ActionsPane,所以只能自己想辦法嘍。為什么不喜歡ActionsPane?理由有很多:
1、綁定到Template,部署困難
2、排版有問題
3、穩定性不好,.NET控件是以ActiveX形式放置在上面的
很快,我就注意到Word 2003不是沒有TaskPane的:
那么我可不可以把我的東西放上去呢?那我們來查查MSDN吧。確實還有這么一個Interface,就叫TaskPane:
http://msdn.microsoft.com/de-de/library/microsoft.office.interop.word.taskpane_members(VS.80).aspx
但是很遺憾,我們在上面找不到任何線索,可以把我們控件加上去的。但是我們可以用Application.TaskPanes[xxx].Visible來控制特定TaskPane的開關。也算有點收獲啦。
然后我們又注意到,這個TaskPane怎么看起來這么像Command Bar?它還真的就是一個CommandBar。用Application.CommandBars["Task Pane"].Visible,我們也可以開關TaskPane(不過必須要在TaskPane已經打開過一次的前提下)。而且MsoControlType里有也有一條msoControlWorkPane。嗯,看起來有戲?。。。門都沒有。MSDN上明說了,Work pane. Cannot be created through the object model。
顯然,用微軟官方API的辦法是死路一條。但是也不是完全沒有收獲。
1、通過Application.TaskPanes[xxx]我們可以可靠的開關TaskPane上的某一頁
2、我們知道Application.CommandBars["Task Pane"]對應的就是TaskPane所在的Command Bar。
曾經有一個讓我恨之入骨的Office開發庫,名字叫Addin-Express(大家不要用啊,誰用誰后悔)。當年我們用它就是因為它可以在Outlook里做類似的事情,在Email Composer上憑空多一塊區域出來(類似于Outlook 2007的Form Region)。它的實現原理其實就是用一個獨立的WinForm窗口,然后用為win32 api SetParent把窗口“融合”到Outlook上去。同時通過SetWindowLong的辦法攔截目標窗口的WndProc(窗口事件處理函數,所有的GUI事件的起點),達到你縮放我也縮放的目的。這次我決定不用Addin-Express來做(它也沒實現這個功能),但是可以利用其原理。
在.NET中攔截WndProc的最簡單的辦法是用NativeWindow這個類。只要override WndProc那個方法就可以了 。
protected override void WndProc(ref Message message) {
// Your code
base.WndProc(ref message);
}
}
雖然知道大概的原理,不過還有一個難點一個疑問需要解決。那就是,到底以哪個Window來Parent?而且,這個Parent在隱藏之后再重現出來會不會被重新創建。如果父窗口每次都會被銷毀,那我們也沒法用SetParent大法了。這個時候就是Spy++出場的時候了。
高亮的那個窗口就是我們的目標了。而且經過試驗證明,窗口SetParent到它身上之后,一直都在。 注意,如果設在NUIPane上是不行的,必須是在NetUIHWND上。這個窗口的ClassName是固定的,但是XML Structure那個TaskPane的窗口ClassName不一樣,不過也是一樣可以SetParent的。
不過還有一個問題。我如何得到這個目標窗口的句柄呢?我甚至都沒法得到當前Word窗口的句柄。這個時候就需要依賴CommandBar的一個特性了,Accessible。這是Windows用來服務殘障人士的API。對于我們來說,就是CommandBar對象都實現了IAccessible這個Interface,然后我們就可以:
WindowFromAccessibleObject是OleAcc這個DLL上的一個API。用來獲得Accssible對象的窗口句柄。利用Accessibility API可以做很多有意思的事情,比如http://blogs.officezealot.com/whitechapel/archive/2005/04/10/4514.aspx。這里就用了相反的一個操作,AccessibleObjectFromWindow來獲得Excel的對象,當年在寫Excel的UDF的時候幫了我的大忙。
還有一個難點就是,如果TaskPane沒有打開我怎么SetParent。這個時候當然是利用前面提到的Application.TaskPanes[xxx]的方法來得到接口,并設置Visible了。不過還是有一個時間差。你設置下Visible之后,是一個異步地打開TaskPane的過程。所以當你接下來做SetParent的時候,那邊可能還沒有完成窗口的初始化。我現在只能用一個新線程Sleep(300)來完成這個任務。并不是完全不可靠,也不是完全可靠。最完美的解決方案還是升級到Word 2007。
知道了上述的機要。實現Word 2003下的Custom Task Pane就是體力活了。用GetWindowRect來縮放,用GetPixel來融合背景色。最終的效果是這個樣子的。
而且能縮能放,能關閉能打開,能Dock能Undock。遺憾的是必須要犧牲掉Word內建的TaskPane中的某一個。不過好在Word 2003中有那么多人們用不著的功能,犧牲掉一兩個,沒誰會注意到的,對吧?:)
浙公網安備 33010602011771號