... a journey through WPF, MVVM and .NET4 RSS 2.0
# Thursday, September 04, 2008

In the last post, I wrote about how focus is generally managed in WPF - we have focus scopes to track a single element within that scope for logical focus, and then one of those elements is given physical, or keyboard focus.

Now, let's talk a little about how you can influence that programatically.  First, you can always determine which element has logical focus in your application through the FocusManager.GetFocusedElement method -- pass it the window in question and it will return which element has logical focus in that window.  Remember that logical focus != keyboard focus at all times -- toolbars and menus track their own focus so if you are currently interacting with a menu then the menu has physical focus.  But in general, the following code will tell you which element WPF thinks has focus in the window:

IInputElement focusedElement = FocusManager.GetFocusedElement(thisWindow);

To determine whether this element has keyboard focus, we can check the IsKeyboardFocused property - if it's set to true, then that element currently has the keyboard focus (as well as being the logical focus for that focus scope).

Keyboard focus is most often set through runtime activity - the user clicks on an element, or uses the TAB key to move around the UI.  You can also set it programatically a couple of ways.  First, there is a Keyboard class in WPF which exposes several methods and properties.  There is a Keyboard.FocusedElement read-only property which returns the current keyboard focused element, and there is a Keyboard.Focus method which attempts to change keyboard focus.  It returns the element that now has focus - so you can check to see if your request was fulfilled or ignored.  So, for example, you can change focus during your application initialization:

void OnLoaded(object sender, RoutedEventArgs e)
{
   Keyboard.Focus(firstTextBox);
}

Notice that we uses the Loaded event - this is because no focus requests will be accepted prior to the element being initialized and loaded.  That's the first place in the application where you can make focus changes.

When would setting focus fail?  Well, it can fail for a lot of reasons, but the most common are:

  1. The element has Focusable = false
  2. The element has Enabled = false
  3. The element has IsVisible = false
  4. The element has not been loaded yet
  5. The currently focused element will not release focus.

That final one is important, changing focus involves potentially taking it away from an existing element - they receive a PreviewLostKeyboardFocus and LostKeyboardFocus event.  If they handle the preview event, focus will not change.

You can also manipulate focus through programatic keyboard navigation - simulating the user pressing TAB to cycle through the focusable elements.  This is controlled through the KeyboardNavigation class which is used when the user presses a key that changes focus (TAB, SHIFT+TAB, Up, Down, etc.).

Controls can set a TabIndex property assignment which determines the tabbing order.  The default is to tab through them in order of declaration.  You can also use the KeyboardNavigation.TabIndex attached property which works for any element - not just controls.

To control navigation, the KeyboardNavigation class has an attached property TabNavigation allowing you to change how navigation occurs within a container.  You can set it to:

  1. Continue - each focusable element receives focus and the container is exited when the edge is reached.
  2. Cycle - focus does not leave the container but wraps around the edges
  3. Once - the container itself is treated as a single focusable element where only the first child receives focus
  4. Local - uses TabIndex locally within the container - independant of any outside elements.
  5. Contained - focus statys in the container but does not wrap (stays at edges when top/bottom are reached)
  6. None - no keyboard navigation allowed in the container

The default is Continue, but you can set the attached property on any element to change it for that element and any children. To see this in action, paste the following into your XAML editor of choice and change the ComboBox while tabbing through the TextBlock elements.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="Simple Focus"
>

<Window.Resources>
   <Style TargetType="TextBox">
      <Setter Property="Margin" Value="10" />
      <Setter Property="Width" Value="100" />
   </Style>
</Window.Resources>

<StackPanel>
   <ComboBox x:Name="tabStyles" SelectedIndex="0" Focusable="False">
      <sys:String>None</sys:String>
      <sys:String>Continue</sys:String>
      <sys:String>Cycle</sys:String>
      <sys:String>Once</sys:String>
      <sys:String>Local</sys:String>
      <sys:String>Contained</sys:String>
   </ComboBox>

   <TextBox TabIndex="1" />
   <TextBox TabIndex="2" />
   <StackPanel
       
KeyboardNavigation.TabNavigation="{Binding ElementName=tabStyles,Path=SelectedItem}">
      <TextBox TabIndex="1" />
      <TextBox TabIndex="2" />
   </StackPanel>
</StackPanel>
</Window>

You can have the system ignore specific elements (but still allow them to have focus) by setting the KeyboardNavigation.IsTabStop="false" attached property.  This will cause keyboard navigation to "jump" over the control as if it were not present.

Three methods are exposed by UIElement and FrameworkElement to programatically shift focus: Focus, MoveFocus and PredictFocus.

To force focus to a specific element, you can call Focus on it.  For example, above we set the keyboard focus by calling Keyboard.Focus(), but the same effect can be achieved like this:

void OnLoaded(object sender, RoutedEventArgs e)
{
   firstTextBox.Focus();
}

This method attempts to set focus using Keyboard.Focus().  If that fails, but the element is Focusable and enabled, it finds the focus scope for the element and sets logical focus there (so that keyboard focus will eventually end up on the control).

FrameworkElement.MoveFocus is used to change the keyboard focus in the application using the same algorithm as the TAB traversal.  You pass in the direction (specified through a TraversalRequest object) and the method returns true/false to indicate success.   Under the covers it actually uses the KeyboardNavigation class, but it's an easy way to push focus around the window:

firstTextBox.MoveFocus(
           
new TraversalRequest(FocusNavigationDirection.Next));

PredictFocus works the same way, but instead of actually shifting focus, it returns what would be the focused item if you were to execute MoveFocus.

So, up to this point, we've seen a lot of code to change focus.  However, the most common request is to set initial focus to a specific control - remember that WPF doesn't do that by default.  You can do it in code, just like the above example where we use the Loaded event.  Or, it turns out you can do it in XAML too.  The key to remember is that the FocusedElement of the main focus scope (the Window) is the one that will get initial focus.  That is (by default) null, but you can set it in XAML using the attached property syntax.  Using the above XAML example, we can supply a name for one of the TextBox controls and then a little data binding magic to set that onto the Window:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="Simple Focus"
        FocusManager.FocusedElement="{Binding ElementName=tb2}">
  ...
    <TextBox TabIndex="1" />
    <TextBox x:Name="tb2" TabIndex="2" />
  ...
</Window>

Now when you run the application, focus is placed into the second text box in the window.  This technique works great as long as the element you want to assign focus to is declared here in the same XAML file.  However, a popular way to develop WPF applications is to separate out chunks of UI into separate UserControls.  When you do that, the above trick fails -- even if you put the FocusManager.FocusedElement binding into the UserControl!

How we solve that is what we'll look at in the next post!  Stay tuned...

 

 


Thursday, September 04, 2008 3:44:44 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Tuesday, September 02, 2008

Focus Types

As you may know, in WPF there are two types of focus: logical focus and keyboard focus. 

Keyboard focus is the easiest to grasp: the element with keyboard focus is where any keystrokes will end up.  Only one element can have keyboard focus at any particular time, and it’s possible that no element currently has keyboard focus (this happens most commonly when the application itself is not the activated application).

Logical focus is a little murkier.  Logical focus represents where keyboard focus could go within a group of elements.  There may be multiple elements with logical focus within the application and one of them likely has keyboard focus.  Having keyboard focus automtically indicates logical focus but not vice-versa.

This distinction is modeled after Win32 itself -- each thread has one HWND it has identified to have focus but only one of them really has input focus at any given point in time.

In WPF, logical focus is tracked and managed by a Focus Scope.  A focus scope is created by certain elements to keep track of which element should have focus.  There can be multiple focus scopes within the application – each identifying a single element that has logical focus.   Focus scopes are created and managed by the FocusManager class.  It exposes two attached properties and the requisite static method wrappers to access them:

IsFocusScope - determines whether the current object is a focus scope.
FocusedElement - returns which element child of the given focus scope object has logical focus.

It also exposes a handy method to find the focus scope for a given element - GetFocusScope

So what creates a focus scope and why do we need more than one?  Well, the elements in WPF that create focus scopes by default are Window,  Menu, ContextMenu and ToolBar.  The reason we need more than one is to ensure your application works the way you expect it to.  When you are typing in a TextBox and click a menu item, you really want focus to return to the original TextBox once the menu is dismissed.  That’s focus scopes in action – the Window maintains logical focus in the TextBox and when you click on the menu, keyboard focus shifts to the menu.  Since it maintains its own focus scope, it has its own logically focused element that WPF sets focus to (the menu and menu items in this case).  Once you shift back to the window, keyboard focus moves back to that focus scope’s logical focus: the text box.  Without focus scopes to track the original focus holder, WPF wouldn't know where focus should go.

Focus scopes are also critical to command routing – often execution of commands depends upon focus.  Some commands become active because a specific control which has a handler for the command has focus.  Without focus scopes, we could not have menu items and toolbar buttons initiate those commands – they would steal focus away from the target, making the command unavailable.  However, when WPF encounters a focus scope it checks the element that has logical focus in that scope to see if it can handle the command.  If not, the command continues routing up to the parent of the focus scope.

To show all of this behavior, I have rigged up a sample application with two windows.  The first is a traditional text editor window with a menu and toolbar and a RichEdit control for the content.  It looks like:

ste001.jpg

Forgive my 5-minute graphics for the buttons – I just threw them together in Visual Studio, a real project would use Blend to generate the graphics. 

Regardless of it's look, the application functions the way you expect – you can type in the text field, click buttons and select menu choices to Open, Cut, Copy and Paste content.  The Cut/Copy/Paste commands are only available when the TextBox is in the appropriate state:

Cut:       TextBox has focus and has some text selected.
Copy:    TextBox has focus and has some text selected.
Paste:    TextBox has focus and text exists on the clipboard.

This is all an artifact of the routed command system in WPF – the TextBox has command handlers registered for ApplicationCommands.Cut, ApplicationCommands.Copy and ApplicationCommands.Paste and when it has focus (and the above criteria is met) those commands could be executed.

When you click on a button or a menu choice, it executes the command it is associated with and WPF decides which handler should be called.  This normally involves walking the visual tree:

ste002.jpg

In this case, if the menu item for “Cut” were selected, it would start looking at the menu, then move up the tree consulting each parent and looking for a CommandBinding to handle the command.  If that were the whole story then the Cut command would never happen because nothing in the visual tree above the menu has a binding to execute the Cut command.  But, of course,  it’s not the whole story – in this case, WPF sees that the Window is a focus scope and so it gets the logically focused element from the Window and looks for a CommandBinding there.  That happens to be our RichTextBox – which is where the command ultimately gets handled. 

To see all of this in action, the second part of the application is a focus scope monitor window. 

ste004.jpg

It shows all the known focus scopes and what the active focused element is, as well as whether that element has keyboard focus.  The window is live so as you click around in the text editor you can see focus shifting and changing.  The element with keyboard focus is the Paste menu item – notice that the rich text box is the logically focused item for “Window1” but does not have keyboard focus.  Once you select the menu choice (or cancel) then keyboard focus shifts back to Window1 which assigns it to the RichTextBox.

ste003.jpg

If you'd like to play with this sample, you can get the full source code here.

In the next blog post, I’ll write about how you can programmatically assign and control focus in code and XAML.

Tuesday, September 02, 2008 2:55:39 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Monday, September 01, 2008

Recently I was involved in a project where we needed to build a multi-step input application where each step showed progress and you could proceed forward and backward through the pages.  I looked at the Navigation support in WPF (which is nice) but ultimately decided to model it around a styled TabControl – each page being a tab and the progress noted through the custom TabItem visuals across the top.  It all works great and looks fabulous.

One of the bugs that was logged against the application and got assigned to me was related to focus – the initial keyboard focus was not being placed into the first control, instead it appeared that nothing had focus which is totally accurate.  Those that are new to WPF might be surprised that it does not assign initial focus to any particular child control – you must deliberately click or tab into a control to give it focus.   You can, of course, also assign focus programmatically but it turns out to not be as easy as you’d think.  In fact, I encountered several nasty gotchas trying to get it to work exactly the way I wanted it to. 

My frustrations with focus and how WPF manages it have resulted in this set of blog posts

Part 1:  Focus Basics
Part 2:  Setting focus in code and XAML

Part 3:  Getting a little smarter – setting focus to the first focusable control
Part 4:  Setting focus to template items

Monday, September 01, 2008 2:07:39 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Monday, August 25, 2008
Business validations in WPF
Monday, August 25, 2008 9:45:20 AM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Friday, August 01, 2008

Thanks to all who came - it was fast-paced but a lot of fun to hang with all of you.  Here are the:

demos.zip and labs.zip

Friday, August 01, 2008 6:20:02 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET
# Friday, July 25, 2008
It's been a long time since I blogged anything specific on WPF -- I've been doing a lot of it lately, along with some Silverlight.  Recently I was experimenting with dragging tabs around on a TabControl at runtime.  My end goal is really to implement it with Silverlight 2, but I've found it's much easier to prototype things in WPF and then port them over because the debugging experience is easier with WPF.

I didn't want to create derived implementations of any classes - I wanted something that was non-intrusive to my code so I decided to use an attached property.  Attached properties are basically property values "attached" to a class at runtime - where the property itself isn't defined on the target but instead on some other type.  The cool thing about attached properties is they can register a change notification handler which gives them a reference to the object they are being placed on -- this is how the Spell Checker works with the TextBox in WPF.  All the code for the spell checking lives in the SpellChecker class and when you add the SpellCheck.IsEnabled property onto the TextBox, it adds handlers to the TextBlock's TextChanged property and adds all the nifty spell checking goodness without changing the code in TextBox.

Back to my drag/drop prototype.  So with this code, I can add the property to any TabControl and get a nice, simple drag/drop experience.  It's far from complete - it would be cooler if the tabs moved around as you dragged (they don't), but I was just prototyping here.

Here's the code:




using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApplication1
{
    public class DragDropTabManager
    {
        private static readonly DependencyProperty ManagerProperty =
            DependencyProperty.Register(typeof (DragDropTabManager).ToString(), typeof (DragDropTabManager),
                                        typeof (DragDropTabManager));

        public static readonly DependencyProperty EnabledProperty = 
            DependencyProperty.RegisterAttached("Enabled", typeof(bool), 
                                                typeof(DragDropTabManager),
                                                new PropertyMetadata(false, DDTM_EnabledChanged));

        private static void DDTM_EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tc = d as TabControl;
            if (tc != null)
            {
                var oldValue = (bool) e.OldValue;
                var newValue = (bool) e.NewValue;

                if (oldValue == true && newValue == false)
                {
                    var ddtm = tc.GetValue(ManagerProperty) as DragDropTabManager;
                    if (ddtm != null)
                    {
                        tc.PreviewMouseDown -= ddtm.TabItem_PreviewMouseDown;
                        tc.SetValue(ManagerProperty, null);
                    }
                }
                else if (oldValue == false && newValue == true)
                {
                    var ddtm = new DragDropTabManager();
                    tc.SetValue(ManagerProperty, ddtm);
                    tc.PreviewMouseDown += ddtm.TabItem_PreviewMouseDown;
                }
            }
        }

        public static bool GetEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnabledProperty);
        }

        public static void SetEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(EnabledProperty, value);
        }

        private bool isMoving;
        private TabItem movingTabItem;
        private TabItem lastTab;
        private Point ptStart;

        void TabItem_PreviewMouseDown(object sender, MouseEventArgs e)
        {
            var ti = e.Source as TabItem;
            if (ti != null && e.LeftButton == MouseButtonState.Pressed)
            {
                var tc = ti.Parent as TabControl;
                if (tc != null)
                {
                    tc.MouseMove += tc_MouseMove;
                    tc.MouseLeftButtonUp += tc_MouseLeftButtonUp;

                    ptStart = e.GetPosition(tc);
                    movingTabItem = ti;
                }
            }
        }

        void tc_MouseMove(object sender, MouseEventArgs e)
        {
            var tc = sender as TabControl;
            if (tc == null)
                return;

            Point pt = e.GetPosition(tc);

            if (isMoving == false)
            {
                if (Math.Abs(pt.X - ptStart.X) > 10)
                {
                    movingTabItem.IsHitTestVisible = false;
                    movingTabItem.RenderTransformOrigin = new Point(.5, .5);
                    movingTabItem.RenderTransform = new TranslateTransform(0, 0);
                    tc.Cursor = Cursors.Hand;
                    Panel.SetZIndex(movingTabItem, 1);
                    isMoving = true;
                    tc.CaptureMouse();
                }
                return;
            }

            TabItem newPos = FindTabItem(tc, pt);
            if (newPos == null)
                tc.Cursor = Cursors.No;
            else
            {
                lastTab = newPos;
                var xform = movingTabItem.RenderTransform as TranslateTransform;
                if (xform != null)
                    xform.X = pt.X - ptStart.X;
                tc.Cursor = Cursors.Hand;
            }
        }

        void tc_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var tc = sender as TabControl;
            Debug.Assert(tc != null);

            tc.ReleaseMouseCapture();
            tc.MouseMove -= tc_MouseMove;
            tc.MouseLeftButtonUp -= tc_MouseLeftButtonUp;

            if (isMoving == true)
            {
                isMoving = false;
                tc.Cursor = Cursors.Arrow;
                movingTabItem.RenderTransform = null;
                movingTabItem.IsHitTestVisible = true;

                Panel.SetZIndex(movingTabItem, 0);

                if (lastTab != null)
                {
                    if (lastTab != null && movingTabItem != lastTab)
                    {
                        int targetIndex = tc.Items.IndexOf(lastTab);
                        tc.Items.Remove(movingTabItem);
                        tc.Items.Insert(targetIndex, movingTabItem);

                        movingTabItem.Focus();
                    }
                }
            }
            movingTabItem = lastTab = null;
        }

        private static TabItem FindTabItem(UIElement parent, Point pt)
        {
            var fe = parent.InputHitTest(pt) as FrameworkElement;
            while (fe != null && fe.GetType() != typeof(TabItem))
                fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
            return fe as TabItem;
        }
    }
}
Friday, July 25, 2008 3:09:45 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | Code | WPF
# Wednesday, July 23, 2008

It's been a while since I updated the ATAPI assembly, I've fixed a couple of minor bugs that were reported and updated the samples to compile with VS2008.  I've also added two new events onto the TapiPhone class so you can see state changes and button presses - somehow I missed that when I added the phone support.

Finally, there's now online documentation available at

http://www.julmar.com/atapi_help/index.aspx

You can download the updated assembly, help file and samples from

http://www.julmar.com/tapi/atapinet.zip

Have fun!

Wednesday, July 23, 2008 12:54:45 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | Tapi
# Tuesday, May 13, 2008

I often show students in my classes how to add ILDasm to their tools menu - it's handy because you can then click on ILDasm and it will open the current module allowing you to look at the manifest, IL, etc.

1. Select Tools | External Tools. You’ll get the tools dialog:

Tools1.png

2. Enter ILDASM for the title and set the Command to the path for ILDASM. The path varies a bit from version to version of Visual Studio. For VS2008 you will find it at C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe

3. Click the button to the right of Arguments and select "Target Path" and select the "Use Output Window" checkbox.



5. You should now be able to access Tools | ILDASM.

One student in another class recently asked how to get a keyboard shortcut to this. Michael Kennedy - our resident VS.NET shortcut king showed exactly how to do this. It turns out to be really simple -- go to Tools | Options and select Keyboard. Then 1,2,3:


Tuesday, May 13, 2008 2:13:09 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET
# Monday, May 12, 2008
Thanks to all who attended the Guerrilla .NET event in Boston last week. Speaking as one of the instructors, it was a blast to get to know each of you! As promised, here are the demos -- ping me using email if you have any questions!
Monday, May 12, 2008 1:10:09 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -

# Tuesday, May 06, 2008


Last month, my colleague, Pinku Surana, wrote an article about .NET AppDomains and how they can be used to provide component isolation and make your applications more reliable.  If you missed it, you can read the Developments archives on Developmentor’s website.   This month, I’d like to continue exploring reliability and extensibility by introducing you to a new framework included with .NET 3.5: The Managed Add-in Framework (MAF), sometimes referred to as System.AddIn.

Developers (and managers) have long desired a way to easily create extensible applications that allow new features to be added without jeopardizing the stability of the existing code base.  The .NET framework has provided the underlying support to accomplish this from the very beginning through the reflection API and AppDomain support Pinku examined last month.  MAF builds on that fundamental support to provide a higher-level service that allow you to dynamically discover, load, secure and interact with external assemblies used to provide features for your application.  Several common architectural requirements are made trivial with MAF:

1)   Isolate aspects of your code for security reasons or partial-trust scenarios.

2)   Allow business partners to extend your application safely without access to the source code; Adobe Illustrator is a great example of this style of application.

3)   Separate volatile sections of your application out – where depending on the customer the application needs to execute different sets of code.

4)   Add or change code without unloading the application – for example pay-to-play scenarios, or where you need to update an assembly but the application must continue to run.

5)   Develop and evolve different sections of the application in parallel without any fear of destabilizing one based on the other.

If any of these scenarios sound like something you could utilize in your application then you should know about MAF!

At the center of the MAF-based application is the pipeline.

The Pipeline

Communication between add-ins and the host application is strictly regulated by the pipeline.  The pipeline is dynamically created by MAF through a set of loosely-coupled components which are used to version, marshal and transform the data as it passes back and forth between the host and each loaded add-in.

image001.png

Each section of the pipeline is contained in a separate assembly, loaded as necessary to manage the specific add-in.  MAF discovers each component and then loads them on request using reflection.   

For reliability, MAF allows optional isolation boundaries to be created between the add-in and host – everything to the left of the contract is loaded into the main (host) AppDomain and everything to the right loaded into a newly created AppDomain which has its own security permission set.  The isolation can also be done through cross-process calls if true process-level isolation is necessary.  Under the covers, the system uses traditional remoting calls to do the work of marshaling calls back and forth.

Breaking down the pipeline, there are three main parts starting in the center with the contract.

Contract

As you might expect, MAF is based on interface contracts.  Interfaces allow classes in an application to be loosely coupled, reducing the risk when changes are made between dependent sections of the code.   The interface contract is shared between the host and add-in and once it has been established, it should never be changed. 

Consider the simple example of a translator program.  The host will expect to load one or more translator add-ins that will do some work on an inbound string and return the results.  To accomplish this, I might create an ITranslator interface that looks like:

[AddInContract]

public interface ITranslator : IContract

{

   string Translate(string input);   
}


Notice that the interface derives from IContract.   This is a requirement for add-in contracts and is what will be used to provide marshalling support when the pipeline is established.  We also need to decorate the interface with the [AddInContract] attribute – this is the marker used by MAF to identify the contract when it is dynamically constructing the pipeline.  Both of these types come from the System.AddIn.Pipeline namespace in System.AddIn.Contract.dll.

Views

Moving to the edges, we find the views.  This is the code that the add-in and host directly interact with.  It represents a host or add-in specific “view” of the contract and, like the contract, is contained in a separate assembly.

Both of the view classes will echo the structure of the contract, but not actually be dependent upon the contract.  For example, our host side view might look like this:

public abstract class TranslatorHostView

{

   public abstract string Translate(string input);
}


The view is commonly exposed as an abstract class to make it look more natural when used by the host, but an interface will work just as well.  The host uses the view directly when communicating with any add-in based on the contract.

On the add-in side, we have an almost identical class – except we decorate this type with the [AddInBase] attribute so MAF knows which side of the pipeline this view is for (the add-in).   This is located in the System.AddIn.Pipeline namespace in the System.AddIn assembly.

When we create each add-in, the implementation will use this type as the base class.

[AddInBase]

public abstract class TranslatorAddInView

{

   public abstract string Translate(stringinp);
}

Adapters

The last piece of the pipeline is the adapters.  The adapters play a very special role in the pipeline – they are the glue that binds the contract to the view: implementing lifetime management and any necessary data conversion between the two ends. 

It may seem redundant to have this class, but by separating the view from the contract we introduce version tolerance into our architecture – the host can version independently of the add-in and vice-versa.  Depending on the situation, the adapters can be as simple as a pass-through class, or can provide higher-level services to allow non-serializable types to be marshaled across the isolation boundaries.

On the host side, the adapter will implement the view (remember it is either an interface or abstract class).  It will be passed a reference to the contract in the constructor and it is responsible for connecting the two together.

[HostAdapter]

public class TranslatorHostViewToContract :  

                     TranslatorHostView

{

   ITranslator _contract;

   ContractHandle _lifetime;

 

   public TranslatorHostViewToContract(

              ITranslator contract)  

   {

      _contract = contract;

      _lifetime = new ContractHandle(contract);

   }

 

   public override string Translate(string inp) 

   {

      return _contract.Translate(inp);
   }
}


In this simple example, the code caches off the contract interface.  This is a remoting proxy to the actual loaded add-in.  The adapter then implements the Translate method and passes it forward to the contract for implementation.  If we had non-serializable types, then the adapter would be responsible for converting them into something that was serializable. 

It also provides some lifetime management for the contract.  Because the contract is a remoting proxy and is likely running in a separate AppDomain (or even process) we have to be concerned with how long it lives.  MAF provides all the support for this through the ContractHandle class.  Most of the time, all you will need to do is store a ContractHandle in your host adapter and then pass it the inbound contract to wrap in your constructor.

Finally, in order for MAF to identify this class, it must be decorated with the [HostAdapter] attribute from the System.AddIn.Pipeline namespace out of the System.AddIn.dll.

The add-in side looks very similar, but does the opposite: it makes the add-in view look like the contract.

[AddInAdapter]

public class TranslatorAddInViewToContract :

                  ContractBase, ITranslator

{

   TranslatorAddInView _view;

 

   public TranslatorAddInViewToContract(

             TranslatorView view)  

   {

      _view = view;

   }

 

   public string Translate(string inp)  { 

      return _view.Translate(inp); 

   }

}

 
MAF passes the view to the constructor and the class caches the reference off in a field.  It implements the contract (ITranslator) and ContractBase which provides the implementation of the IContract interface for us (remember this was a required interface on our contract).  As the host makes calls to the contract interface, this class will translate those calls to the add-in view, which as you will see is the implementation provided by the add-in itself.  Note how the [AddInAdapter] attribute is used to mark this class so MAF can discover it.

If it seems like all the above is a lot of repetitious, boilerplate code well.. you’re right!  To make it easier on the developer to create the pipeline components, the MAF team has created a pipeline builder available at http://www.codeplex.com/clraddins.  It takes the contract assembly and then generates the views and adapters from it:

image003.jpg

Creating an add-in

Once the pipeline pieces are built you can create add-ins.  Each add-in provides an implementation of the abstract add-in view.  For example, we might provide a BabelFish add-in for universalized translation:

[AddIn(“BabelFishTranslator”,

       Description=“Universal translator”,

       Version=“1.0.0.0”, Publisher=“Zaphod Beeblebrox”]

public class BabelFishAddIn : TranslatorAddInView

{

   public string Translate(string input)

   {

     ...

   }
}


The add-in implements the AddInView, providing a concrete implementation of the Translate method.  It is decorated with the [AddIn] attribute that allows it to provide a name, version, description and other data the host can use to decide whether it is a useful translator.

Putting the pieces together – the directory structure

To properly identify each of the components necessary, MAF enforces a particular directory structure you need to follow for deployment.  Each piece is stored in a sub-directory off the root of the pipeline directory (this is typically the APPBASE where the host executable is stored). 

image006.png

The directory names are required, but not case sensitive – each directory holding a single piece of the pipeline that MAF will load dynamically when it is loading the add-in.  The host-side view is always located in the same directory as the host executable itself so it does not need a dedicated directory.  When creating your Visual Studio project, it is important to set the output directories appropriately so that you create the above directory structure.  In addition, all references between the components should be marked as CopyLocal = “false” in Visual Studio to ensure a local copy of the assembly isn’t placed into the sub-directory: 

image008.png

Discovering and loading add-ins from the host

The last piece of the puzzle is the actual loading of add-ins from the host.  This is done in three basic steps:

1.     Identify and catalog the add-ins

2.     Retrieve the list of specific add-ins based on view or name

3.     Activate and use the add-in

The first step is to identify the available add-ins for the host.  This is done through the System.AddIn.AddInStore class:

string[] errorList = AddInStore.Rebuild(

         PipelineStoreLocation.ApplicationBase);

 

Calling Rebuild forces MAF to walk the directory structure and create the pipeline database.  It stores this information in the pipeline root directory and returns a list of errors, if any occur.  The most common errors are missing pipeline components – where MAF is unable to locate some required portion of the pipeline such as a View or Adapter.

Next, the host will retrieve a list of add-ins based on the host view through the FindAddIns method – these will be the add-ins conforming to a specific contract (whatever the view/adapter is bound to):

Collection<AddInToken> addinTokens = AddInStore.FindAddIns(

                typeof(TranslatorHostView),

                PipelineStoreLocation.ApplicationBase);

 

The first parameter is the host view type – so MAF knows what add-ins we are looking for, the second is the pipeline root directory, which is the same directory passed to the Rebuild method and indicates where the pipeline database is stored.

 

The returning collection represents a series of tokens that are used to identify and control the add-ins.  This is how the host can examine, activate and unload the add-in.  To activate a specific add-in, you can take the token and call Activate:

 

Collection<AddInToken> addinTokens;

    ...

foreach (AddInToken token in addinTokens)
{

   TranslatorHostView view =

       token.Activate<TranslatorHostView>(

             AddInSecurityLevel.Internet);

  

   string hello = view.Translate(“Bonjour”);

     ...
}

 

The Activate call loads all the required components, instantiates the add-in type and returns the host view used to interact with the add-in.  Calls made to this object will be marshaled back and forth to the add-in using the pipeline.

 

Notice that the parameter passed to Activate indicates the security level required.  There are several overrides that allow you to dictate exactly how the add-in is loaded.  You can load the add-in into a specific AppDomain – the current one for best performance:

 

token.Activate<TranslatorHostView>(AppDomain.CurrentDomain)

 

You can specify a specific permission set to restrict the things the add-in can do on your behalf:

PermissionSet pset = ...;

token.Activate<TranslatorHostView>(pset);


Or you can specify a known permission set based on the CAS zones:

image010.png

Once the add-in is activated, the host can call it just like any other object – but never forget that you are likely crossing an isolation barrier!  Make sure to design your contracts so that you minimize the number of calls between the host and add-ins to ensure your performance doesn’t suffer.

When you are finished with the add-in, you can tell MAF to unload it through the AddInController associated with the view:

AddInController ctrl =    

      AddInController.GetAddInController(view);

ctrl.Shutdown();


This will unload the add-in side pipeline and then destroy the containing AppDomain so you release the resources associated with it.

There are many other capabilities MAF provides such as versioning, passing collections and WPF visuals, passing non-serializable types, etc.  You can get a pretty decent overview of the features from MSDN and the MAF team has a blog at http://blogs.msdn.com/clraddins/ that has some great, practical information on using this new framework.

Tuesday, May 06, 2008 4:46:26 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | Code | Real World
I'm a WPF Disciple
Search
Categories
Archive
<September 2008>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Mark Smith
Sign In
All Content © 2010, Mark Smith
DasBlog theme 'Business' created by Christoph De Baene (delarou)