?

Log in

   Journal    Friends    Archive    Profile    Memories
 

David Airapetyan on C# and .NET

Jun. 26th, 2007 01:44 am Copy path to clipboard

Quite often I need to accomplish a very simple task: get the full path of a file. In Visual Studio, there is a context menu command called "copy file path to the clipboard". Not so in Windows Explorer.

The good news is, adding a new command to the Explorer shell menu is a breeze. Here is a nice little tutorial on how to do it: http://www.codeproject.com/csharp/SimpleContextMenu.asp

But first, we need a program that can copy text to the clipboard. If you don't want to download one than it's pretty much a one-liner in C#:

class Program
{
  [STAThread()]
  static void Main(string[] args)
  {
   if (args.Length > 0)
   {
    Clipboard.SetText(String.Join(" ", args));
   }
   else
  
{
    MessageBox.Show("cc - copies arguments to the clipboard");
   }
  }
}

I have called my program cc.exe and simply copied it into the Windows directory. Putting it all together, here is a registry script that hooks it up:


REGEDIT4

[HKEY_CLASSES_ROOT\*\shell\cc]
@="Copy path to clipboard"

[HKEY_CLASSES_ROOT\*\shell\cc\command]
@="cc \"%L\""

[HKEY_CLASSES_ROOT\Directory\shell\cc]
@="Copy path to clipboard"

[HKEY_CLASSES_ROOT\Directory\shell\cc\command]
@="cc \"%L\""



Now the new command is available anywhere in the Explorer context menu!

Update: based on a request, the entire program is available as an MSI setup package for convenience: http://www.davidair.com/misc/SetupCC.msi

6 comments - Leave a comment

Jun. 7th, 2007 11:35 am Automating existing Internet Explorer instances in C#

Microsoft Internet Explorer is a very rich platform with an extensive OM. It comes to no surprise that it has been chosen as a building block for many applications, including the Vista Sidebar and Microsoft Office InfoPath. I use IE whenever I have to process or display HTML (for example, for parsing out data from websites) as it saves me a lot of time by taking care of all the HTTP interaction and HTML parsing. But what if you wanted to access the Internet Explorer's OM for an existing application? The biggest scenario here is test automation but there are other scenarios as well (for example, enhancing the browsing experience by pre-populating fields – without the complexity of writing an IE toolbar).

The way one attached to an existing IE instance is by first locating the HWND for the window you are interested in and then by using Microsoft Active Accessibility to get to the actual IE instance.

To get to the IE HWND, we can use the EnumWindows()/EnumChildWindows() win32 methods. Here is an example that locates a specified top-level window and gets the IE HWND from it (assuming it's a first-level child):

static bool FindTopLevelWindow(IntPtr hWnd, ref IntPtr lParam)
{

  int textLength = GetWindowTextLength(hWnd) + 1;
  StringBuilder windowTextBuilder = new StringBuilder(textLength);
  GetWindowText(hWnd, windowTextBuilder, textLength);

  string windowName = windowTextBuilder.ToString();

   if (windowName.IndexOf(TopLevelWindowSubstring,
        StringComparison.CurrentCultureIgnoreCase) >= 0)
  {
    lParam = hWnd;

    return false;
  }

  return true;
}

static bool FindEmbeddedIEWindow(IntPtr hWnd, ref IntPtr lParam)
{

  StringBuilder classNameBuilder = new StringBuilder(MaxClassNameLength);
  GetClassName(hWnd, classNameBuilder, MaxClassNameLength);

  string className = classNameBuilder.ToString();

  if (className.Equals(IEWindowName, StringComparison.CurrentCulture))
  {
    lParam = hWnd;

    return false;
  }


  return true;
}

 
IntPtr topLevelWindow = IntPtr.Zero;
EnumWindows(FindTopLevelWindow, ref topLevelWindow);

if (topLevelWindow == IntPtr.Zero)
{

  return;
}

IntPtr ieWindow = IntPtr.Zero;

EnumChildWindows(topLevelWindow, FindEmbeddedIEWindow, ref ieWindow);

if (ieWindow == IntPtr.Zero)
{

  return;
}

Now that we have the IE HWND we can get to the underlying HTML document:

uint htmlGetObjectMessage = RegisterWindowMessage(WMHtmlGetObject);

UIntPtr sendMessageResult = UIntPtr.Zero;

SendMessageTimeout(ieWindow, htmlGetObjectMessage, UIntPtr.Zero, IntPtr.Zero, SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, 1000, out sendMessageResult);

if (sendMessageResult == UIntPtr.Zero)
{

  return;
}

//Get the object from sendMessageResult

System.Guid IID_IHTMLDocument = typeof(IHTMLDocument).GUID;

object documentObject = ObjectFromLresult(sendMessageResult, IID_IHTMLDocument,
                   IntPtr.Zero);

Finally, documentObject can be cast to any of the IHTMLDocumentx interfaces to access and manipulate the HTML DOM.

Note that it is also possible to get to the IE window itself (see reference #2). The IEDriver project (reference #3) comes with a handy set of methods to automate IE. It only automates top-level IE windows but it could be changed using the method described above to automate any embedded IE instance.

Here is a complete example where a console application finds the IE instance embedded in InfoPath and changes the background color of the BODY tag:

http://www.davidair.com/misc/ChangeHostedIEBackground.zip

References:

1. "How to get IHTMLDocument2 from a HWND", a Microsoft KB article written in C++: http://support.microsoft.com/kb/q249232/

2. "ObjectFromLresult Interop Question", an MSDN forum post on which the code in my post is based: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=301976&SiteID=1

3. "Automating Internet Explorer", a CodeProject article on how to automate IE where you attach to the top-level window: http://www.codeproject.com/csharp/AutomatingInternetExplore.asp

4. PInvoke.net, a one-place stop for all your P/Invoke needs: http://pinvoke.net/

Leave a comment

Jun. 7th, 2007 10:04 am Lightweight solution to the mshtml interop DLL distribution

When accessing the Internet Explorer's object model, we often use the mshtml interop assembly called Microsoft.mshtml.dll which comes with an installation of Visual Studio. However there is a common problem – when redistributing the application written using Microsoft.mshtml.dll, the entire 8MB DLL has to be included with your app because otherwise it will fail to execute on machines that do not have VS installed.

If you do not wish to redistribute the entire interop DLL there is another approach.  After all, the entire point of the interop assembly is to instruct .NET how to invoke into unmanaged COM objects so there is no need to include the entire assembly since there will be many interfaces/methods we will not be using!

Here are the steps to move from using mshtml into using a leaner version of it:

1.       Start by writing your code using the mshtml interop assembly

2.       Under debugger, inspect the runtime classes of mshtml object

3.       Using reflection, find out the dispatch definitions of those classes

4.       Create a dispatch interface for each of those, removing all properties/methods you do not need (I use the miniMshtml namespace for those)

5.       Replace the mshtml casts by your miniMshtml ones

Here is a walkthrough of a typical example. In this example, we search for all SPAN elements with non-empty title attributes and set their inner text:

IHTMLDocument3 htmlDocument = (IHTMLDocument3)documentObject;
foreach
(IHTMLElement element in htmlDocument.getElementsByTagName("span"))
{
  if
(!String.IsNullOrEmpty((string)element.getAttribute("title", 0)))
  {
    element.innerText = "Test";
  }
}

 
In the debugger, step through the code and find out the runtime types for htmlDocument and element. It turns out those types are:

HTMLDocumentClass
HTMLSpanElementClass

For each of those classes, find out their corresponding dispatch interfaces. The easiest way to do it is by using built-in reflection in Visual Studio 2005 – simply type HtmlDocumentClass and hit F12 with the cursor positioned on it. When you do this, VS will generate the class definition looking something like that:

public class HTMLDocumentClass : DispHTMLDocument, HTMLDocument, HTMLDocumentEvents_Event, IHTMLDocument2, IHTMLDocument3...

What you are interested in is DispHTMLDocument. Hit F12 on it again and you will get the dispatch interface definition:

[InterfaceType(2)]
[Guid("3050F55F-98B5-11CF-BB82-00AA00BDCE0B")]
[TypeLibType(4112)]

public interface DispHTMLDocument
{
  [DispId(1005)]
  IHTMLElement
activeElement { get; }
  [DispId(1022)]
  object
alinkColor { get; set; }
  [DispId(1003)]
  IHTMLElementCollection
all { get; }

 
...

}

 
Now remove all methods that you are not interested in. In our case, we only want the getElementsByTagName method:

[InterfaceType(2)]
[Guid("3050F55F-98B5-11CF-BB82-00AA00BDCE0B")]
[TypeLibType(4112)]
public
interface DispHTMLDocument
{
  [DispId(1087)]
  IEnumerable
getElementsByTagName(string v);
}

 
Notice a trick: in the original interface, getElementsByTagName returns IHTMLElementCollection  but defining it does not buy you anything because it simply implements IEnumerable. This enumeration is all we care about so we change the return type.

 
By doing the same thing for HTMLSpanElementClass we end up with a trimmed version of  DispHTMLSpanElement as well:

[InterfaceType(2)]
[Guid("3050F548-98B5-11CF-BB82-00AA00BDCE0B")]
[TypeLibType(4112)]
public
interface DispHTMLSpanElement
{
  [DispId(-2147417085)]
  string
innerText { get; set; }
  [DispId(-2147417610)]
  object
getAttribute(string strAttributeName, int lFlags);
}

 
Finally, we can replace the original mshtml implementation with our miniMshtml:

DispHTMLDocument htmlDocument = (DispHTMLDocument)documentObject;

foreach (DispHTMLSpanElement element in       
   htmlDocument.getElementsByTagName("span"))
{
  if
(!String.IsNullOrEmpty((string)element.getAttribute("title", 0)))
  {
     element.innerText = "Test";
  }
}

Now we can remove the reference to Microsoft.mshtml assembly and we're good to go!

3 comments - Leave a comment