Soledad Pano's Blog

Just Technical Stuff

September 2007 - Posts

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:
Hosting a Windows Form Control in a web page

Although it is not the most common use of it, it is possible to host a Windows Form Control in a Web Page and run it from within Internet Explorer. This allows to build powerful client side functionality with all the advantages of using the .Net framework and executing the control in the client side. Of course there are some restrictions that cannot be left aside. At least the .Net framework must be installed on the client for the control to run. In addition, it is possible that some permission must be granted to the control, too, depending on the actions the control will take on the client machine.

Let’s build an example to see how this works:

1. Create the Windows Form Control

Create a “Windows Control Library” project in Visual Studio. For this simple example we will add a label to the control and a public “SendMessage” method to change the label’s text. In order to be able to call the method from IE, we will set the control’s COMVisible attribute to true.

 

using System;

using System.Windows.Forms;

using System.Runtime.InteropServices;

 

namespace WindowsControlLibrary1

{

    [ComVisible(true)]

    public partial class UserControl1 : UserControl

    {

        public UserControl1()

        {

            InitializeComponent();

        }

 

        public void SendMessage(string msg){

            _lblMessage.Text = msg;

        }

    }

}

 

2. Create the hosting HTML document

 

The control will be hosted using the HTML <object> element. In its classid attribute we will place the control's reference in the following manner: classid="http:[relativePath/]<winControlAssemblyName>.dll#<ControlNamespace>.<ControlClassName>". For this example we will have an HTML page (index.html) with the following code:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >   
 <head>       
  <title>Windows Form Control Test Page</title>       
  <script type="text/javascript">           
    function SendMessageToWinControl()
    {                     
     var winCtrl = document.getElementById("MyWinControl");                   
     winCtrl.SendMessage("Message sent from the HTML page!!");               
    }       
  </script>   
 </head>   
 <body>       
  <h1>Windows Form Control:</h1>       
  <object id="MyWinControl" classid="http:WindowsControlLibrary1.dll#WindowsControlLibrary1.UserControl1"           
          height="100" width="300" VIEWASTEXT/>       
  <br/><br/>       
  <input type="button" onclick="SendMessageToWinControl()" value="Send Message" />   
 </body>
</html> 

  

3. Deploy the sample

 

Copy both the index.html page and the control library WindowControlLibrary1.dll to a directory (we will name it WinControlTest). In the “Web Sharing” dir properties select  Share this folder, leave the default alias and application permissions to Scripts and make sure not to select  the Execute permission).

 

4. Try it in Internet Explorer

 

Open the page http://localhost/WinControlTest/index.html in Internet Explorer. We can see the control hosted in the html page:

 

 

By clicking the Send Message button, we will call the Control's SendMessage method from javscript. Resulting in the label's text changed:

 

 

 

If you modify the control after its first use, you will need to close IE, replace the control's dll in the virtual directory and clean the contents of the download cache. You do this cleaning using the following command: "gacutil /cdl".To list the contents of the download cache use "gacutil /ldl".

 

This solution is not intended to replace a WebControl or any common web artifact. But is good to know it can be use in an uncommon scenario if needed.

 

Sole

 

Posted: Sep 19 2007, 03:56 PM by spano | with 49 comment(s)
Filed under: ,
WPF Accessibility

Windows Presentation Foundation (WPF) provides a very interesting API for Accessibility called Microsoft UI Automation. It allows programmatic access to most user interface elements on the desktop, addressing the needs of assistive technology products and also for User Interface (UI) tests automation.

The framework provides solutions for both accessibility providers and clients, and it is conformed of four main components (see UI Automation Overview):

  1. The Provider API (UIAutomationProvider.dll and UIAutomationTypes.dll) defines a set of interfaces that are implemented by UI Automation providers, objects that provide information about UI elements and respond to programmatic input.
  2. The Client API (UIAutomationClient.dll and UIAutomationTypes.dll) is a set of types for managed code that enables UI Automation client applications to obtain information about the UI and to send input to controls.
  3. The UI Automation Core (UiAutomationCore.dll) has the underlying code that handles communication between providers and clients.
  4. The UIAutomationClientsideProviders.dll that has a set of UI Automation providers for standard legacy controls. (WPF controls have native support for UI Automation.) This support is automatically available to client applications.

We will typically use the Provider API to create support for our WPF custom controls, since they don't provide accessibility support by default. We will use the Client API for creating applications that need to communicate with UI elements and eventually automate the use of other program's UI.

You can find good documentation in the WPF Accessibility section on MSDN, and several code samples in the SDK Sample Applications

Just to mention, Microsoft’s earlier solution for Accessibility was Active Accessibility. The main advantage of UI Automation over Active Accessibility is the technology in which it is based on (managed code vs COM) and, as a consequence, the programming languages that can be used. You can read more of the differences in UI Automation and Microsoft Active Accessibility.

A few words about the API's Object Model: Every piece of UI, such as a window, a button, etc, is represented by the AutomationElement class in the System.Windows.Automation namespace of the UIAutomationClient assembly. An AutomationElement corresponds to a piece of UI regardless of the underlying implementation (WPF or Win32). All automation elements are part of a tree, in which the root element is the Desktop. Through the AutomationElement.RootElement static property you can obtain a reference to the Desktop and find any child piece of UI from there to access.

AutomationElements expose Control Patterns that provide properties specific to their Control Types (window, button, checkbox, etc). Control Patterns also expose methods that enable clients to get further information about the element and to provide input. We are going to see how this works in the examples below.

 

A very useful tool for finding the element's properties in the tree is UISpy, which comes with the Windows SDK. It lets you see all the UI tree and each element's properties. In the Focus Tracking mode, you can select the real UI element in the Desktop and UISpy will automatically show its properties in the tree view.

UISpy 

Code sample

This is an example of a client application for automating a win32 application (in this case the Windows Address Book). For this example, we only need a Windows Console Application and references to the UIAutomationClient and UIAutomationTypes assemblies. From the code it can be seen how to match a control to a given pattern depending on what we want to do with the control.

The controls' names and/or ids where obtained using UISpy.

These are the UI artifacts that we want to automate:

Address Book Application 

Find People Dialog

And here is the code to:

- Get a reference to the Address Book application main window:

using System.Windows.Automation;

...

AutomationElement mainWindow = FindWindowByName(AutomationElement.RootElement, "Address Book - Main Identity");

...               

private static AutomationElement FindWindowByName(AutomationElement rootElement, string name)

{

       PropertyCondition nameCondition = new PropertyCondition(AutomationElement.NameProperty, name);

       PropertyCondition typeCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window);

       AndCondition andCondition = new AndCondition(nameCondition,typeCondition);

       return rootElement.FindFirst(TreeScope.Element | TreeScope.Descendants, andCondition);

}

 

- Press the Find People button:

AutomationElement findPeopleButton = FindElementById(mainWindow, "Item 8084");

if (findPeopleButton != null)

{

           InvokePattern invPattern = findPeopleButton.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;

            if (invPattern != null)

            {

                    invPattern.Invoke();

            }

}

...

private static AutomationElement FindElementById(AutomationElement parentElement, string automationID)

{

            PropertyCondition propertyCondition = new PropertyCondition( AutomationElement.AutomationIdProperty, automationID);

            return parentElement.FindFirst(TreeScope.Element | TreeScope.Descendants, propertyCondition);

}

 

- Attach to the Find People dialog Closed event:

AutomationElement dialog = FindWindowByName(AutomationElement.RootElement, "Find People");

if (dialog != null)

{

     Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, dialog, TreeScope.Element,

     dialogEventHandler = new AutomationEventHandler(OnWindowClosedEvent));

}

...

 

private static void OnWindowClosedEvent(object src, AutomationEventArgs e)

{

        Console.WriteLine("On window closed event");

}

 

- Attach to the Dialog's Close Button Clicked event:

AutomationElement closeButton = FindElementById(dialog, "68");

if (closeButton != null)

{

          Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent,closeButton, TreeScope.Element,

          clickedEventHandler = new AutomationEventHandler(OnCloseButtonClickedEvent));                      

}

...

private static void OnCloseButtonClickedEvent(object src, AutomationEventArgs e)

{

        Console.WriteLine("On button clicked event");

}

 

- Get and set the value of the Name text box of the Find People dialog:

 

AutomationElement text = FindElementById(mainWindow, "1152");      //Automation Id = 1152 of Name text box find with UISpy

if (text != null)

{

           //get content

             TextPattern txtPattern = text.GetCurrentPattern(TextPattern.Pattern) as TextPattern;

             if (txtPattern != null)

             {

                            string content = txtPattern.DocumentRange.GetText(-1);

                            Console.Out.WriteLine(content);

             }

             //write content

             ValuePattern valuePattern = text.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;

             valuePattern.SetValue("new value");

}

 

- Set a combo box value:

AutomationElement combo = FindElementById(dialog, "99");

if (combo != null)

{

          AutomationElement item = FindListItemByName(combo,"Active Directory");

          SelectionItemPattern itemPattern = item.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;

          itemPattern.Select();

}

 

Well...that's it. I hope this helps for a start. Enjoy!

 

Sole

Posted: Sep 07 2007, 01:55 PM by spano | with 9 comment(s)
Filed under: , ,
New @ weblogs
HI! this is my first post @ weblogs.asp.net. I'm moving from http://solepano.blogspot.com, where I've been blogging since last year. I would like to introduce myself with a few words: My name is Soledad Pano, I live in Buenos Aires - Argentina, have a degree in Electronic Engineer from the University of Buenos Aires, and work as a software consultant at Lagash Systems. I’ve been working with the .Net technology for the last four years, and the purpose of this blog is to share thoughts, news, code samples, etc, about several .Net technologies and products. I'm also interested in more general topics, such as software quality, metrics, testing, agile, technical tools, etc. You are totally welcome to contribute with any comments, questions, answers, suggestions, whatever. I will really appreciate your participation. Have fun!

Sole  

More Posts