Soledad Pano's Blog

Just Technical Stuff
How to find Visual Studio command bars

When developing a Visual Studio Addin it is a common task to add custom commands to existing menus and toolbars. It is a common problem too, not to find the proper command bar (we have to traverse all command bars to see its names, the names are not unique, etc). In these two posts: Using IVsProfferCommands to retrieve a Visual Studio CommandBar and  Using EnableVSIPLogging to identify menus and commands with VS 2005 + SP1 it is very well explained how to solve this problem.

The answer relies on the fact that every toolbar and menu is uniquely identified in Visual Studio by a GUID,Id pair. In order to see which is the GUID, Id pair for a given command, you must:

  • Add or change the registry key HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\General\EnableVSIPLogging to 1.

  • Click on the toolbar or menu you want identify while keeping CTRL+SHIFT pressed. This will show a dialog with the command bar properties. Take note of the Guid and the CmdID:

  • From the Addin, use the following code to add your command in the desired command bar:

 

private void AddMyCustomCommand()

 

{

   

 

 

    object[] contextGuids = new object[0];

 

    myCommand = ((Commands2)this._applicationObject.Commands).AddNamedCommand2(this._addInInstance, 

             partialCommandName,displayName, tooltip, true, null, ref contextGuids,

 

            (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,

 

            (int)vsCommandStyle.vsCommandStyleText,vsCommandControlType.vsCommandControlTypeButton);

 

    CommandBar ownerBar = FindCommandBar(new Guid("{9AEB9524-82C6-40B9-9285-8D85D3DBD4C4}"), 1280);

 

    myControl = workflowCommand.AddControl(ownerBar, 1) as CommandBarButton;   

}

 

private CommandBar FindCommandBar(Guid guidCmdGroup, uint menuID)

 

{

 

   // Retrieve IVsProfferComands via DTE's IOleServiceProvider interface

 

   IOleServiceProvider sp = (IOleServiceProvider)_applicationObject;

 

   Guid guidSvc = typeof(IVsProfferCommands).GUID;

 

   Object objService;

 

   sp.QueryService(ref guidSvc, ref guidSvc, out objService);

 

   IVsProfferCommands vsProfferCmds = (IVsProfferCommands)objService;

 

   return vsProfferCmds.FindCommandBar(IntPtr.Zero, ref guidCmdGroup, menuID) as CommandBar;

 

}

[ComImport,Guid("6D5140C1-7436-11CE-8034-00AA006009FA"),

 

InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]

 

internal interface IOleServiceProvider

 

{

 

    [PreserveSig]

 

    int QueryService([In]ref Guid guidService, [In]ref Guid riid,

 

       [MarshalAs(UnmanagedType.Interface)] out System.Object obj);

 

}

Posted: Oct 07 2008, 12:07 PM by spano | with no comments
Filed under:
CodeCamp Buenos Aires 2008

I attended yesterday to the Microsoft Code Camp event at Buenos Aires. It was fun!

Here's a picture of the Lagash stand:

 

UI Automation Article Published on Level Extreme

On this month's edition of the Level Extreme .Net Magazine, my article about Building an UI Automation Client Application was published. The article walks through building a client application for automating a win 32 target application (the Windows Address Book). The idea is to show through simple code snippets how to use the UI Automation API for manipulating a target application programmatically through its UI. It shows how to find the UI elements, and how to work with the UI Automation patterns and handle events. The sample client application code contains further examples and can be downloaded from the article's page. Hope you read it!

March edition Level Extreme 

Read more on the magazine from Martin Salias.

Posted: Mar 12 2008, 03:38 PM by spano | with 9 comment(s)
Filed under:
CCF: Listening to External Application Events

It is often necessary to listen to external application UI events when automating applications in CCF. For example, we may need to save a value in the Context if the user presses a button, or do something when an alert dialog appears. There are many ways we can handle events. In CCF 2008, the Hosted Application Toolkit (HAT) provides an easy way to listen to them. If not using HAT we can hook to events using the UIAutomation or MSAA (Microsoft Active Accessibility) APIs within our own application adapters.

Events in HAT

When we configure a hosted application for using an Automation Adapter, this adapter listens to the application events. We can hook and unhook to these events using the CCF's WF activities RegisterActionForEvent (see figure bellow)  and UnregisterActionForEvent respectively.

 

It is the Data Driven Adapter (DDA) the one who throws the ControlChanged event (to which the activity subscribes). The arguments of the event consists of eventTypeName, controlName and controlValue.

For example, the WinDataDrivenAdapter that CCF provides supports the following event types:

  1. SetControlValue: When the DDA set's a control's value.
  2. ExecuteControlAction: When the DDA executes a control.
  3. ContextChanged: When the context changes.
  4. MenuShown: When a menu is shown
  5. WindowShown: When a window is shown
  6. WindowDisappeared: When a window is closed
  7. LostFocus
  8. SetFocus
  9. CheckBoxSet
  10. CheckBoxCleared
  11. RadioButtonSet
  12. RadioButtonCleared
  13. ButtonPressed
  14. ButtonReleased

There are some tricks about how to use this events, for example:

  • 4-6: For menu and window events we must not register with the control's friendly name that we set in the databindings section of the AppInitString. We must specify the window or menu's caption instead. 
  • 7-14: For these events the related control must be previously found on the UI. You can use the FindControl activity to ensure that the control is successfully found. When the DDA finds the control it registers it in a KnownControls collection. Only for those registered controls the events are thrown.

Custom DDAs or Legacy Adapters

If we need to handle events from a custom DDA or from a legacy adapter we can use win32 or accessibility APIs. If we use a custom DDA we can throw a ControlChanged event in order to use the WF/Automation activities mentioned before.

WinEvents

A low level approach is to use the SetWinEventHook and UnhookWinEvent functions from user32.dll. When we hook for events we specify the callback function that will handle the events. You can see the list of supported events and its constants in the MSAA SDK documentation on msdn.

In order to show some sample code, here are the user32 functions imports:

[Flags]

internal enum SetWinEventHookFlags

{

    WINEVENT_INCONTEXT = 4,

    WINEVENT_OUTOFCONTEXT = 0,

    WINEVENT_SKIPOWNPROCESS = 2,

    WINEVENT_SKIPOWNTHREAD = 1

}

 

internal delegate void WinEventProc(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime);

[DllImport("user32.dll", SetLastError = true)]

internal static extern IntPtr SetWinEventHook(int eventMin, int eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, int idProcess, int idThread, SetWinEventHookFlags dwflags);

[DllImport("user32.dll", SetLastError = true)]

internal static extern int UnhookWinEvent(IntPtr hWinEventHook);

 

Here's how we can subscribe to win events from our custom adapter:

WinEventProc wep = new WinEventProc(this.EventCallback);

SetWinEventHook(1, 0x7fffffff, IntPtr.Zero, wep, 0, threadId, SetWinEventHookFlags.WINEVENT_OUTOFCONTEXT);

 

And finally how to react to events in the event handler method:

private void EventCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)

{

    //get accObj from handler

    //get accObj's name, role, etc...

    ...

    switch (iEvent)

    {

        case 0x10: //EVENT_SYSTEM_DIALOGSTART

            if (role.Equals(localize.ACC_ROLE_TEXT_WINDOW))

            {

                  if (name.Length > 0)

                  {

                      //throw ControlChangedEvent

                      this.ControlChanged(accObj, new ControlChangedEventArgs("DialogStart", controlName, string.Empty));

                  }

            }

            break;

        ...

    }

}

 

UIAutomation

Another option is to use the UIAutomation accessibility API, included on .Net framework 3.0 to hook to events from custom adapters. The Automation element provides the following static methods to hook to different event on different controls:

//Registers a method that handles UI Automation events.

public static void AddAutomationEventHandler(AutomationEvent eventId, AutomationElement element, TreeScope scope, AutomationEventHandler eventHandler);

//Registers a method that will handle focus-changed events.

public static void AddAutomationFocusChangedEventHandler(AutomationFocusChangedEventHandler eventHandler);

//Registers a method that will handle property-changed events.

public static void AddAutomationPropertyChangedEventHandler(AutomationElement element, TreeScope scope, AutomationPropertyChangedEventHandler eventHandler, params AutomationProperty[] properties);

//Registers the method that will handle structure-changed events.

public static void AddStructureChangedEventHandler(AutomationElement element, TreeScope scope, StructureChangedEventHandler eventHandler);

 

The UIAutomation API is much more friendly to use, but some times it takes too long for the event to reach the handler. You should be very precise when setting the element and scope to subscribe the event to. You can find more information about this API and sample code on my previous post about WPF Accessibility.

Posted: Jan 15 2008, 12:05 PM by spano | with no comments
Filed under:
Hosting a Window Form Control in a SharePoint WebPart

Some months ago I posted about Hosting a Windows Form Control in a web page. I explained there how we can run a WinForm control from Internet Explorer by hosting it in a web page using the <object> tag. Now suppose you want to use the same solution in a custom web part that is included in a Sharepoint site. I will describe here where the control library should be placed and how it can be referenced from the web part.

When we develop a custom web part, we place its resources under the _wpresources web site, into a folder named <WebPartAssemblyName>/<WebPartAssemblyVersion>__<PublicKeyToken>. You can create there a subfolder (named "bin" por example) to place the win control assembly. This folder must have execute permissions set to Script Only (not Script and Executables as is the default under a SharePoint site). The picture bellow shows the resulting structure when placing the win form control assembly in a web part called MyCustomWebPart that is part of the MyCustomWebParts assembly:

 

Once the control assembly is placed in the correct place, the web part can render the <object> tag within the RenderWebPart method:

namespace MyCustomWebParts

{

  [XmlRoot(Namespace = "MyCustomWebParts")]

  public class MyCustomWebPart : WebPart

  {

    protected override void RenderWebPart(HtmlTextWriter output)

    {

        EnsureChildControls();

        string resourcePath = SPWebPartManager.GetClassResourcePath(SPContext.Current.Web, GetType());

          

        output.Write( ReplaceTokens("<h1>Windows Form Control:</h1>" +

          "<object id='MyWinControl' classid='" + resourcePath + 

          "/bin/WindowsControlLibrary1.dll#WindowsControlLibrary1.UserControl1'" +

          "height='300' width='800' VIEWASTEXT>" +

          "  <PARAM name='Prop1Str' value='blabla'  valuetype='data'>" +

          "  <PARAM name='Prop2Int' value='2' valuetype='data'>" +

          " </object>"));

 

          base.RenderWebPart(output);

    }

  }

}

Posted: Dec 06 2007, 10:48 PM by spano | with 1 comment(s)
Filed under:
CCF HAT - Making use of DDAs from Legacy Adapters

Although Data Driven Adapters (DDAs) are mainly designed for Automation Adapters in HAT, we can make use of them from legacy adapters, too. Here's an example of an external VB application, which is configured to use a legacy application adapter and a WinDataDrivenAdapter with the corresponding bindings:

<?xml version="1.0" encoding="utf-16"?>

<initstring>

    <interopAssembly>

        <URL>C:\MyApp.exe</URL>

        <WorkingDirectory>C:\</WorkingDirectory>

        <hostInside />

    </interopAssembly>

    <UseTopLevelWindow class="ThunderRT6Form" />

    <adapter>

        <URL>C:\MyApplicationAdapters.dll</URL>

        <type>Microsoft.Ccf.QuickStarts.MyExtVBAppAdapter</type>

    </adapter>

    <DataDrivenAdapterBindings>

        <Type>Microsoft.Ccf.HostedApplicationToolkit.DataDrivenAdapter.WinDataDrivenAdapter,

              Microsoft.Ccf.HostedApplicationToolkit.DataDrivenAdapter</Type>

        <Controls>

            <AccControl name="button1">

                <Path>

                    <Next>OK</Next>

                </Path>

            </AccControl>

        </Controls>

    </DataDrivenAdapterBindings>

    <optimumSize x="800" y="600" />

    <minimumSize x="640" y="480" />

</initstring>

 

One thing to take into account is that, if the application uses an alternative top level window, the DDA must be instantiated with that top level window instead of the process MainWindowHandle. The picture shows how the alternate top level window configuration is seen in the Admin Console:

And here is the code of the legacy adapter making use of the data driven adapter to automate the hosted application's UI:

using System;

using Microsoft.Ccf.Csr;

using Microsoft.Ccf.HostedApplicationToolkit.DataDrivenAdapter;

namespace Microsoft.Ccf.BancoGalicia.ApplicationAdapters

{

    public class MyExtVBAppAdapter : ApplicationAdapter

    {

        protected DataDrivenAdapterBase _dda;

        public override bool Initialize()

        {

            //IntPtr topLevelWnd = Process.MainWindowHandle;//this won't work for a VB app

            IntPtr topLevelWnd = GetTopLevelWndHandle();

            //Initialize Data Driven Adapter with proper top level window handle

            _dda = DataDrivenAdapterBase.CreateInstance(ApplicationInitString, topLevelWnd);

            return _dda != null;

        }

        public override bool DoAction(Action action, RequestActionEventArgs args)

        {

            switch (args.Action)

            {

                case "DefaultAction":

                    break;

                case "PressButton":

                    PressButton();

                    break;

            }

            return base.DoAction(action, args);

        }

        private void PressButton()

        {

            if (_dda.FindControl("button1"))

            {

                _dda.ExecuteControlAction("button1");

            }

        }

        //Find the top level window of the VB application (for example of class "ThunderRT6Form")

        private IntPtr GetTopLevelWndHandle()

        {

            IntPtr agentDesktopPtr = Win32API.FindWindow(null, "Agent Desktio");

            if (agentDesktopPtr != IntPtr.Zero)

            {

                IntPtr tabWindowHandle = Win32API.FindWindowByText(agentDesktopPtr, "MyApp");

                if (tabWindowHandle != IntPtr.Zero)

                {

                    return Win32API.FindWindowByText(tabWindowHandle, "MyAppTopLevelWndCaption");

                }

            }

            return IntPtr.Zero;

        }

    }

Posted: Oct 30 2007, 11:34 PM by spano | with 1 comment(s)
Filed under:
CCF 2008 Hosted Application Toolkit

The Hosted Application Toolkit (HAT) is a new component of CCF 2.6 (2008) that facilitates the task of automating the UI of the hosted applications. It consists of two main parts: the Data Driven Adapters (DDA) and the Automations (WF workflows) that automates the hosted application using the DDAs.

Data Driven Adapters

DDAs must inherit from DataDrivenAdapterBase in Microsoft.Ccf.HostedApplicationToolkit. CCF supports two implementations out of the box: WinDataDrivenAdapter and WebDataDrivenAdapter. We can inherit from any of the three to extend the DDA as necessary. The methods the DDAs expose to manipulate the hosted application UI are ExecuteControlAction, FindControl, GetControlValue and SetControlValue. These methods take a friendly name as parameter and lets the application adapters interact with the hosted application UI abstracting from the details of how to obtain the control from that friendly name. The mapping between the controls' friendly names and the directions for the DDA to finding them is called Data Driven Adapter Binding and is part of the hosted app initialization string:

<DataDrivenAdapterBindings>

    <Type>Microsoft.Ccf.HostedApplicationToolkit.DataDrivenAdapter.WinDataDrivenAdapter</Type>

    <Controls>

        <AccControl name="okButton">

            <Path>

                <FindWindow>

                    <ControlID>15</ControlID>

                </FindWindow>

            </Path>

        </AccControl>

        <AccControl name="cancelButton">

            <Path>

                <FindWindow>

                    <ControlID>15</ControlID>

                </FindWindow>

            </Path>

        </AccControl>

    </Controls>

</DataDrivenAdapterBindings>

 

Note: It is a known issue that the initialization string of a hosted application is limited to 3902 characters. This may result a problem if several controls must be listed.

Although DDAs can be used from any custom adapter (and even outside CCF), they were mainly thought to support Automations in HAT.

 

Automations

In previous versions of CCF we could choose to use an Application Adapter to implement the actions' logic. In version 2.6 we can choose between Automation Adapter, Legacy Adapter, or no adapter at all. The Legacy Adapter is the Application Adapter from the previous versions. We can implement our own Legacy Adapter inheriting from the [Web]ApplicationAdapter class. The Automation Adapter is the adapter that makes use of the new Workflow Foundation (WF) feature. The AutomationAdapter implements the IAutomationAdapter interface. However, if we choose AutomationAdapter in the hosted app configuration in the AdminConsole, we can't choose our own implementation of IAutomationAdapter.

If a hosted application is configured to use the AutomationAdapter, we can specify a WF workflow to implement an action's logic. Workflows in HAT are called Automations.

In order to orchestrate an action automation, a set of WF Activities are provided by CCF that interact with the DDAs to access the UI controls. There are activities for calling an action in a hosted application, setting or getting a control's value, accessing the context, register for an application event, etc.

All together

The connection between the Automations and the DDAs is achieved by the AutomationBridgeService, which is in turn connected to the AutomationAdapter. As the bridge service is added to the WF runtime, it is accessible from the CCF WF activities. We can extend the set of activities and make use of this server, too.

The following diagram depicts the relationship between the components mentioned above:

 

 

 

More detailed documentation can be found in chapter 9 of the CCF 2008 Development Guide.

Posted: Oct 26 2007, 01:40 AM by spano | with 3 comment(s)
Filed under:
Silverlight article published this month

My colleagues Ariel Neisen, Federico García, Rodolfo Finochietti and I wrote the cover article of the #42 edition of the .Code Magazine (in Spanish) about Silverlight. The article talks about this new technology's fundamentals, architecture, programming tools, advanced features, code samples and a lot more.

 

Choosing the right WCF binding

A WCF binding is the endpoint component that defines how the client needs to communicate with the service. It groups settings such as underlying transport protocol, security requirements, and message encoding.

WCF provides nine built-in bindings:

  1. BasicHttpBinding: Basic web service communication. Exposes WCF services as legacy ASMX web services. Used for interoperability. No security by default.
  2. WSHttpBinding: Web services with WS-* support. Supports transactions and reliable messaging.
  3. WSDualHttpBinding: Web services with duplex contract and transaction support.
  4. WSFederationHttpBinding: Web services with federated security. Supports transactions.
  5. MsmqIntegrationBinding: Communication directly with MSMQ applications. Supports transactions.
  6. NetMsmqBinding: Communication between WCF applications by using queuing. Supports transactions.
  7. NetNamedPipeBinding: Communication between WCF applications on same computer. Supports duplex contracts and transactions.
  8. NetPeerTcpBinding: Communication between computers across peer-to-peer services. Supports duplex contracts.
  9. NetTcpBinding: Communication between WCF applications across computers. Supports duplex contracts and transactions.

Although most bindings will work on scenarios they are not designed for, it's a good practice to choose the right binding for a given endpoint. In chapter one of the "Programming WCF Services" book, Juval Lowy provides a very useful decision-activity diagram for choosing the right binding:

"The first question you should ask yourself is whether your service needs to interact with non-WCF clients. If the answer is yes, and if the client is a legacy MSMQ client, choose the MsmqIntegrationBinding that enables your service to interoperate over MSMQ with such a client. If you need to interoperate with a non-WCF client and that client expects basic web service protocol (ASMX web services), choose the BasicHttpBinding, which exposes your WCF service to the outside world as if it were an ASMX web service (that is, a WSI-basic profile). The downside is that you cannot take advantage of most of the modern WS-* protocols. However, if the non-WCF client can understand these standards, choose one of the WS bindings, such as WSHttpBinding, WSFederationHttpBinding, or WSDualHttpBinding. If you can assume that the client is a WCF client, yet it requires offline or disconnected interaction, choose the NetMsmqBinding that uses MSMQ for transporting the messages. If the client requires connected communication, but could be calling across machine boundaries, choose the NetTcpBinding that communicates over TCP. If the client is on the same machine as the service, choose the NetNamedPipeBinding that uses named pipes to maximize performance. You may fine-tune binding selections based on additional criteria such as the need for callbacks (WSDualHttpBinding) or federated security (WSFederationHttpBinding)."

You can download the book's sample chapter to read more on this subject. You would also find the diagram in Juval's article WCF Essentials - A Developer's Primer on the CoDe Magazine.  

Posted: Oct 02 2007, 12:43 AM by spano | with no comments
Filed under:
CCF 2008 New Features

The 3.0 version of the Customer Care Framework (CCF) is out! Among its new features we'll find:

  • CCF web services migrated to Windows Communication Foundation (WCF).
  • Agent Desktop use of Composite Application Block (CAB).
  • Hosted Application Toolkit (HAT): new sub-system for providing UI automation, that makes use of Workflow Foundation (WF) and Active Accessibility.
  • ClickOnce support.
  • Dynamic Applications: 3rd category of hosted applications that allow an agent to dynamically launch or close a hosted application on-demand, via the UI or programmatically in code.

I found the HAT to be the most interesting feature, allowing to decouple accessibility implementation details from automation logic and making use of WF to represent this automation logic in a friendly, declarative way. I guess I will be posting more on this subject later.

Posted: Sep 28 2007, 03:00 PM by spano | with no comments
Filed under:
More Posts Next page »