Monday, 27 April 2015

Decorate it

This blog post shows how to decorate controls of an existing WPF/Xaml UI with limited code changes. To achieve this WPF concepts like Adorners, Dependency Properties and ControlTemplates come into play.

Recently I worked on an application which was overloaded with input fields. To make it even worse user input (mostly into a TextBox) triggered internal calculations and the update of multiple dependent fields. To bring changed values to the users attention we decided to highlight the changed values by decorating controls (TextBox elements) whenever the displayed value is changed. The picture below shows a simplified version of the user interface. Input of the TextBox Distance Top updates the values Distance Ax, Distance Ay and Distance Az.



In short the following steps are necessary:

  1. Create an Attached Property to let controls register for/enable the highlight feature
  2. Create a ControlTemplate which is attached to existing controls
  3. Upon registration of an Attached Property register for TextChanged events
  4. Create an Adorner Class which holds the defined ControlTemplate as its only child
  5. Add an instance of Adorner to the TextBox's AdornerLayer.

Create Attached Property

The Attached Property mechanism is a mean to create Dependency Properties and reuse these properties from other elements. Layout constraints are a good example for these kind of properties (e.g. DockPanel.Dock="Top"...).

We need an Attached Property to let a TextBox declare that value changes should be highlighted.

//The property to enable highlight feature for a specific user control public static readonly DependencyProperty HighlightProperty = DependencyProperty.RegisterAttached("Highlight", typeof (Boolean), typeof (UIElement), new FrameworkPropertyMetadata(false, HighlightPropertySetCallback));
public static void SetHighlight(UIElement element, Boolean value) { element.SetValue(HighlightProperty, value); } public static Boolean GetHighlight(UIElement element) { return (Boolean)element.GetValue(HighlightProperty); }

Create a visual indicator using ControlTemplates

The appearance of our value changed notification is defined by a ControlTemplate. The template defines a white flash and positions it within a blue colored ellipse shape. It should look something like this .
<ControlTemplate x:Key="HighlightTemplate">
<DockPanel Name="in">            
<Grid DockPanel.Dock="Right" Width="16" Height="16" VerticalAlignment="Center" Margin="3 4 0 0">
<Ellipse Width="16" Height="16"> <Ellipse.Fill>
</Ellipse>
<SolidColorBrush  x:Name="brush" Color="SteelBlue"></SolidColorBrush></Ellipse.Fill>
<Polyline.Fill>
<Polyline  VerticalAlignment="Top" HorizontalAlignment="Center" StrokeThickness="0"  Margin="0 2 0 0" Points="3,0,6,0,5,5,8,5,3,14,4,8,1.5,8,3,0">
<SolidColorBrush  x:Name="arrowBrush" Color="White"></SolidColorBrush>
<controltemplate>
</Polyline.Fill>
</Polyline>
</Grid>
</DockPanel>

Register on value changes

In the first snippet we registered a callback method upon setting the attached property. What the callback method does is to register for TextChanged events of the given TextBox.
private static void HighlightPropertySetCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
     //get target textbox
     TextBox textBox = dependencyObject as TextBox;
     if (textBox == null) 
     {        
         return;
     }
     //Register on text changed
     textBox.TextChanged += OnTextBoxChanged;
}
Upon text changed we will add an Adorner to our TextBox's Adornerlayer. But first, we need to create an Adorner which holds the ControlTemplate presented above.

Create an Adorner to display the ControlTemplate

The following snippet shows the implementation of the Adorner used to decorate the TextBox with the shown ControlTemplate.
public class PresentationAdorner : Adorner
{
    private Control child;

    public Control Child
    {
        get { return child; }

        set
        {
            if (child != null)
            {
                RemoveVisualChild(child);
            }
            child = value;
            if (child != null)
            {
                AddVisualChild(child);
            }
        }
    }
    
    public PresentationAdorner([NotNull] UIElement adornedElement) : base(adornedElement) { }

    protected override int VisualChildrenCount
    {
       get { return 1; }
    }

    protected override Visual GetVisualChild(int index)
    {
       if (index != 0) throw new ArgumentOutOfRangeException();
       return child;
    }

    protected override Size MeasureOverride(Size constraint)
    {
       child.Measure(constraint);
       return child.DesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        child.Arrange(new Rect(new Point(0, 0), finalSize));
        return new Size(child.ActualWidth, child.ActualHeight);
    }
}
The implementation needs to be derived from Adorner. Typically Adorner implementation override the OnRender method and directly draw on the passed DrawingContext. In our case the appearance of the Adorner is defined by the ControlTemplate shown above. The template is set as the Adorners only visual child. Reseting the adorners Child property will remove any previously added visual child. Methods MeasureOverride and ArrangeOverride derive Size an Position of the Adorner from its Child Property.

Add Adorner to AdornerLayer on TextChanged

private static void OnTextBoxChanged(object sender, TextChangedEventArgs args)
{
    var textbox = sender as TextBox;
    if (textbox == null)
    {
        return;
    }

    //Get the adorner layer. Be sure your Application has one.
    AdornerLayer layer = AdornerLayer.GetAdornerLayer(textbox);
    if (layer == null)
    {
        return;
    }

    //Get the control template from resources.
    var controlTemplate = Application.Current.Resources["HighlightTemplate"] as ControlTemplate;
    if (controlTemplate == null)
    {
        return;
    }

    //Get allready existing adorner from adorner layer
    var adorners = layer.GetAdorners(textbox);
    if (adorners != null)
    {
        var presentationAdorner = adorners.OfType().First();
        if (presentationAdorner != null)
        {
            presentationAdorner.Child = new Control { Template = controlTemplate, Focusable = false, };
        }
    }
    else
    {
        // Or create a new adorner and add it to the layer
        var adorner = new PresentationAdorner(textbox);
        adorner.Child = new Control { Template = controlTemplate, Focusable = false, };
        layer.Add(adorner);
    }
}

Code above is executed whenever the text of a registered TextBox changes. If the AdornerLayer contains no Adorner of type PresentationAdorner a new instance is created and the Adorner's Child property is set to a Control holding the ControlTemplated shown above.

Be sure that your application provides an AdornerLayer by using an AdornerDecorator element within the applications ui element hierarchy. The AdornerLayer provides the static method GetAdorners to retrieve all Adorners for a specific ui element.

The screenshot above shows our example application after the value in Distance Top changed. For the sake of brevity the implementation shown in this blog post is rather simple and has many limitations (Decorations are not removed once a value change occured, decorations also appear on the control the user entered a value, the ControlTemplate for decorations is hardcoded and cannot changed, ... and many more).

However, for a start I hope this blog helps if you need to add decorations to controls.

Saturday, 21 March 2015

Debugging a Visual Studio Addin

Some time ago (1-2 years) I extended Microsoft Visual Studio 2010 for some custom functionality. I chose to create an Add-In which hooks a command into the solution explorer's context menu, execute some command action and provide user feedback via the output view and error list view.

Recently, I had to update this behaviour, which was quickly done. However, the thing that took me the longest was to find out again how to debug the Add-In from Visual Studio 2010. So primarily for myself :) I want to document the required steps in the following.

How to debug Visual Studio Add-In 

  1. Open your Add-In solution in Visual Studio 2010 
  2. Right mouse click the Add-In project file (.prj) and mark it as "startup project"
  3. Open project properties for your Add-In project. On tab Debug in section Start external program select the devenv.exe from your Visual Studio install
    Visual Studio 2010 project properties
  4. In section command line arguments use /resetaddin yourAddin.addin to force complete (re)initialisation of your Add-In 
  5. Copy your Add-In file (.AddIn) to Visual Studio 10's Add-In folder (i.e.  c:\users\<localuser>\Documents\Visual Studio 10\adddins)
  6. In the copied addin file use the <Assembly> tag to point to the Debug directory of your Add-In project.
  7. Run debug command for your Add-In project from Visual Studio 2010

Links