static readonly Guid IID_IDispatch=new Guid("{00020400-0000-0000-C000-000000000046}");
[Guid("00020962-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWordWindow
{
}
[DllImport("oleacc.dll")]
public static extern int AccessibleObjectFromWindow(IntPtr hwnd, uint dwObjectID, byte[] riid, out IWordWindow ptr);
[DllImport("user32.dll")]
public static extern int BringWindowToTop(IntPtr hwnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);
[DllImport("user32.dll")]
public static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
public delegate bool EnumChildCallback(IntPtr hwnd, ref IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildCallback lpEnumFunc, ref IntPtr lParam);
The interface IWordWindow is the IDispatch COM interface for the Word COM object. If you want to use another Office Program, you'll have to adapt the Guid attribute (see MSDN).
First of all we need the process of Word
var p=Process.GetProcesses().FirstOrDefault(x => x.ProcessName=="WINWORD");
After that step, we are now ready to get the "Window" of the document we want to play with.
IntPtr hwndChild=IntPtr.Zero;
IWordWindow ptr;
// we now need to find the "WordWindow"
EnumChildCallback chCallback=new Win32Api.EnumChildCallback(enumChildProc);
EnumChildWindows(hwnd, chCallback, ref hwndChild);
AccessibleObjectFromWindow(hwndChild, (uint)0xFFFFFFF0 /*OBJID_NATIVEOM*/, IID_IDispatch.ToByteArray(), out ptr);
Something I didn't bring up yet is the EnumChildCallback method which is implemented as shown below.
static bool enumChildProc(IntPtr hwndChild, ref IntPtr lParam)
{
StringBuilder buf=new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
if (buf.ToString()=="_WwG") // find the word window
{
lParam=hwndChild;
return false;
}
return true;
}
The challenge here is to find the right object since its name is different for each Office program. Word uses the "_WwG" class name for its main window.
Well this was the heavy part. From now on it's easy going. The best way to continue is using Reflection (see MSDN).
To give you a quick start, I suggest using this base class for your specialized Office classes.
class DynamicObject
{
protected Type type;
protected object obj;
public DynamicObject(object obj)
{
if (obj==null) throw new ArgumentNullException("obj");
this.type=obj.GetType();
this.obj=obj;
}
protected T GetProperty(string name)
{
return (T)type.InvokeMember(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty, null, obj, null);
}
protected void SetProperty(string name, T value)
{
type.InvokeMember(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty, null, obj, new object[] { value });
}
protected void CallMethod(string name, params object[] param)
{
type.InvokeMember(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, obj, param);
}
protected T CallMethod(string name, params object[] param)
{
return (T)type.InvokeMember(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod, null, obj, param);
}
}
Now you can easily override this class for accessing the Word window easily in your code.
class WordWindow : DynamicObject
{
public WordWindow(Win32Api.IWordWindow obj)
: base(obj)
{
}
public Selection Selection { get { return new Selection(GetProperty<object>("Selection")); } }
}
You now need to create wrappers for all classes you want to use (i.e. Selection, which is used above).
var word=new WordWindow(ptr);
word.Selection.HomeKey(WindowUnit.Story);
word.Selection.Find.ClearFormatting();
word.Selection.Find.Text="This is a test";
word.Selection.Find.Execute();
For more information about the Word window's members, see MSDN
No comments:
Post a Comment