Category Archives: Bag of Tricks

Playing around with the Ribbon and RichTextBox–2 of 4: Creating a good User Experience with Reveal

The Ribbon provides for an overall good User Experience, however it takes a big chunk of screen space. The Ribbon design doesn’t allow for it to be minimalist, and if it was it wouldn’t be the same Ribbon we are used to from all those Microsoft applications.

This is where Reveal from Bag of Tricks comes in handy. By placing the Ribbon inside a Reveal and controlling it’s appearance we can make good use of screen space in scenarios where the User’s screen is already filled with other things, or even more Ribbon’s!

The Reveal that I use in my solution has added features to it besides the one from BoT, as described in a previous post.

Binding the Reveal’s IsExpanded

What I found that worked best, in terms of User Experience, and after responding to some user feedback, was to hook the Reveal to the Focus handlers on the top Grid inside the UserControl. I hooked it to IsReadOnly on the RichTextBox and control that property directly on the Focus hooks.

First I declared the reveal around the Ribbon declaration

<PlayingWithRibbon:Reveal VerticalReveal="FromTopToBottom"
                    HorizontalReveal="None"
                    Duration="200"
                    IsExpanded="{Binding ElementName=_richTextBox, Path=IsReadOnly, Converter={StaticResource ResourceKey=InverseBooleanConverter}}"
                    Stretch="HorizontalAndVertical"
                    Grid.Row="0"
                    >

    <ribbon:Ribbon Name="_ribbon">

    (...)

    </ribbon:Ribbon>
</PlayingWithRibbon:Reveal>

Because I want the Reveal.IsExpanded to be true when RichTextBox.IsReadOnly is false, I had to put in a simple InverseBooleanConverter that’s declared on the code behind of the RibbonRichTextBox.

/// <summary>
/// Inverse Boolean Converter - Inverts the value of a boolean bind.
/// </summary>
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (targetType != typeof(bool))
            throw new InvalidOperationException("The target must be a boolean");

        return !(bool)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (targetType != typeof(bool))
            throw new InvalidOperationException("The target must be a boolean");

        return !(bool)value;
    }
}
<UserControl.Resources>
    <PlayingWithRibbon:InverseBooleanConverter x:Key="InverseBooleanConverter" />
</UserControl.Resources>

Then I went and setup the event handlers for the GotFocus and LostFocus on the top Grid of the UserControl

<Grid GotFocus="GridGotFocus" LostFocus="GridLostFocus">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition />
    </Grid.RowDefinitions>

    (...)

</Grid>

With a very simple implementation on the code behind file

private void GridGotFocus(object sender, RoutedEventArgs e)
{
    _richTextBox.IsReadOnly = false;
}

private void GridLostFocus(object sender, RoutedEventArgs e)
{
    _richTextBox.IsReadOnly = true;
}

In the end what you get is the Reveal to show the Ribbon on Focus and closing it when losing Focus. This makes a very smooth and cool effect while keeping the screen space to a minimum.

blog7

The Full Series

The full solution can be downloaded from here.

This is the second article in the series Playing around with the Ribbon and RichTextBox.

Glyphs that can be Outlined in WPF

Most modern day applications use glyphs these days. A glyph in developer lingo is a monochromatic icon. In technologies like WPF that use render engines that can easily work with vectors, these glyphs tend to be in the form of vectors, so that UI designers and developers can use them in any form of scale without destroying any of the looks of the glyph.

While using glyphs, one of the things I wanted to do was bind some of them to boolean properties and have the glyph in it’s full colour when it was true, and revert back to an outlined form when it was false.

The example application has 2 glyphs and a CheckBox, both glyphs Outlined property are bound to the CheckBox’s IsChecked property, so that we can go from this state

blog5

To this state

blog6

The glyphs

The glyphs in this example were made in Adobe Illustrator, then imported into blend and the corresponding Path’s generated. They were then inserted into a Viewbox to allow scaling while retaining Blend’s absolute margin values for alignment. In the end what we have is a WPF vector glyph.

bell

<l:BaseGlyph x:Class="GlyphOutline.Bell"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:l="clr-namespace:GlyphOutline"
            mc:Ignorable="d" 
            d:DesignHeight="200" d:DesignWidth="200">

    <Viewbox Stretch="Fill">
        <Canvas x:Name="Icon" Height="64" Width="64">
            <Path Data="F1M32,6C34.205,6,36,7.797,36,10C36,12.203,34.205,14,32,14C29.795,14,28,12.203,28,10C28,7.797,29.795,6,32,6 M53,32C53,21.5,47.217,15.563,41.719,12.266C41.889,11.535,42,10.781,42,10C42,4.477,37.523,0,32,0C26.477,0,22,4.477,22,10C22,10.781,22.111,11.535,22.281,12.266C16.783,15.563,11,21.5,11,32C11,52,4,40,4,56L60,56C60,40,53,52,53,32" Height="56" Canvas.Left="0" Stretch="Fill" Canvas.Top="0" Width="64"/>
            <Path Data="F1M32,64C35.723,64,38.824,61.445,39.717,58L24.283,58C25.176,61.445,28.277,64,32,64" Height="6" Canvas.Left="21.783" Stretch="Fill" Canvas.Top="58" Width="18.434"/>
        </Canvas>
    </Viewbox>

</l:BaseGlyph>

calculator

<l:BaseGlyph x:Class="GlyphOutline.Calculator"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:l="clr-namespace:GlyphOutline"
            mc:Ignorable="d" 
            d:DesignHeight="200" d:DesignWidth="200">

    <Viewbox Stretch="Fill">
        <Canvas x:Name="Icon" Height="64" Width="64">
            <Path Data="F1M48,16C48,16.547,47.555,17,47,17L17,17C16.453,17,16,16.547,16,16L16,9C16,8.445,16.453,8,17,8L47,8C47.555,8,48,8.445,48,9z M44,31.5C42.07,31.5,40.5,29.938,40.5,28C40.5,26.07,42.07,24.5,44,24.5C45.937,24.5,47.5,26.07,47.5,28C47.5,29.938,45.937,31.5,44,31.5 M44,43.5C42.07,43.5,40.5,41.934,40.5,40C40.5,38.066,42.07,36.5,44,36.5C45.937,36.5,47.5,38.066,47.5,40C47.5,41.934,45.937,43.5,44,43.5 M44,55.5C42.07,55.5,40.5,53.934,40.5,52C40.5,50.066,42.07,48.5,44,48.5C45.937,48.5,47.5,50.066,47.5,52C47.5,53.934,45.937,55.5,44,55.5 M32,31.5C30.07,31.5,28.5,29.938,28.5,28C28.5,26.07,30.07,24.5,32,24.5C33.937,24.5,35.5,26.07,35.5,28C35.5,29.938,33.937,31.5,32,31.5 M32,43.5C30.07,43.5,28.5,41.934,28.5,40C28.5,38.066,30.07,36.5,32,36.5C33.937,36.5,35.5,38.066,35.5,40C35.5,41.934,33.937,43.5,32,43.5 M32,55.5C30.07,55.5,28.5,53.934,28.5,52C28.5,50.066,30.07,48.5,32,48.5C33.937,48.5,35.5,50.066,35.5,52C35.5,53.934,33.937,55.5,32,55.5 M20,31.5C18.07,31.5,16.5,29.938,16.5,28C16.5,26.07,18.07,24.5,20,24.5C21.937,24.5,23.5,26.07,23.5,28C23.5,29.938,21.937,31.5,20,31.5 M20,43.5C18.07,43.5,16.5,41.934,16.5,40C16.5,38.066,18.07,36.5,20,36.5C21.937,36.5,23.5,38.066,23.5,40C23.5,41.934,21.937,43.5,20,43.5 M20,55.5C18.07,55.5,16.5,53.934,16.5,52C16.5,50.066,18.07,48.5,20,48.5C21.937,48.5,23.5,50.066,23.5,52C23.5,53.934,21.937,55.5,20,55.5 M49,0L15,0C11.148,0,8,3.148,8,7L8,57C8,60.852,11.148,64,15,64L49,64C52.852,64,56,60.852,56,57L56,7C56,3.148,52.852,0,49,0" Height="64" Canvas.Left="9" Stretch="Fill" Canvas.Top="0" Width="48"/>

        </Canvas>
    </Viewbox>

</l:BaseGlyph>

The MainWindow

The layout of the main window is simple, it’s a stackpanel nested inside a Grid for aligning the glyphs and the CheckBox and a Viewbox to scale the CheckBox to a nice size.

<Window x:Class="GlyphOutline.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:GlyphOutline="clr-namespace:GlyphOutline"
        Title="MainWindow" Height="140" Width="150">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        
        <StackPanel Grid.Row="0"
                    Orientation="Horizontal"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center">

            <GlyphOutline:Bell Height="40"
                               Width="40"
                               Outlined="{Binding ElementName=OutlineCheck, Path=IsChecked}" />
            <GlyphOutline:Calculator Height="40"
                                     Width="40"
                                     Margin="20 0 0 0"
                                     Outlined="{Binding ElementName=OutlineCheck, Path=IsChecked}" />
        </StackPanel>
        
        <Viewbox Grid.Row="1"
                 Height="40"
                 Width="40"
                 HorizontalAlignment="Center"
                 VerticalAlignment="Center">

            <CheckBox Name="OutlineCheck" />
        </Viewbox>
    </Grid>

</Window>

The Outlining

To implement the Outline feature I created a custom ContentControl that I called the “BaseGlyph”. This control hosts the Outlined property and it’s implementation.

First there’s the Outline property implementation with a callback defined to handle the conversion of styles on the paths when the property is either true or false.

public static readonly DependencyProperty OutlinedProperty = DependencyProperty.Register(
    "Outlined",
    typeof(Boolean),
    typeof(BaseGlyph),
    new UIPropertyMetadata(
        false, // default value for this DP
        OnOutlinedChanged)); // callback on change
                

private static void OnOutlinedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var caller = (BaseGlyph)d;

    if ((bool)e.NewValue)
    {
        foreach (var item in d.GetVisualDescendents().OfType<Path>())
            item.Style = (Style)caller.FindResource("OutlinePath");
    }
    else
    {
        foreach (var item in d.GetVisualDescendents().OfType<Path>())
            item.Style = (Style)caller.FindResource(typeof(Path));
    }
}

public Boolean Outlined
{
    get { return (Boolean)GetValue(OutlinedProperty); }
    set { SetValue(OutlinedProperty, value); }
}

There’s a bit of code on the constructor to create the ResourceDictionary that needs to be present to transform the Paths in the glyph in Shapes with thickness.

public BaseGlyph()
{
    // Build the Resource Dictionary
    Resources = new ResourceDictionary();

    var binding = new Binding();
    var style = new Style();

    // Ancestor Foreground Binding,
    binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(BaseGlyph), 1);
    binding.Path = new PropertyPath(ForegroundProperty);

    style.TargetType = typeof(Path);
    style.Setters.Add(new Setter(Shape.FillProperty, binding));

    var transparentBrush = new SolidColorBrush(Colors.White) {Opacity = 0};
    var outline = new Style {TargetType = typeof (Path)};

    outline.Setters.Add(new Setter(Shape.StrokeThicknessProperty, 2d));
    outline.Setters.Add(new Setter(Shape.StrokeProperty, binding));
    outline.Setters.Add(new Setter(Shape.FillProperty, transparentBrush));

    Resources.Add(typeof(Path), style);
    Resources.Add("OutlinePath", outline);

    // Register event handlers
    Loaded += BaseIconLoaded;
}

To support the callback method in the Outlined property I used some Bag-of-Tricks visual helpers and isolated them in the static class VisualHelper

namespace GlyphOutline
{
    #region Using Declarations

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Media;

    #endregion

    public static class VisualHelper
    {
        public static IEnumerable<DependencyObject> GetVisualDescendents(this DependencyObject source)
        {
            return source
                .GetVisualChildren()
                .SelectRecursive(element => element.GetVisualChildren());
        }

        public static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject source)
        {
            var count = VisualTreeHelper.GetChildrenCount(source);

            for (var i = 0; i < count; i++)
                yield return VisualTreeHelper.GetChild(source, i);
        }

        public static IEnumerable<TSource> SelectRecursive<TSource>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TSource>> recursiveSelector)
        {
            var stack = new Stack<IEnumerator<TSource>>();
            stack.Push(source.GetEnumerator());

            try
            {
                while (stack.Any())
                {
                    if (stack.Peek().MoveNext())
                    {
                        var current = stack.Peek().Current;
                        yield return current;
                        stack.Push(recursiveSelector(current).GetEnumerator());
                    }
                    else
                    {
                        stack.Pop().Dispose();
                    }
                }
            }
            finally
            {
                while (stack.Any())
                {
                    stack.Pop().Dispose();
                }
            }
        }

    }
}

The full source of BaseGlyph is listed below

namespace GlyphOutline
{
    #region Using Declarations

    using System;
    using System.Linq;
    using System.Windows.Controls;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using System.Windows.Data;

    #endregion

    public class BaseGlyph : ContentControl
    {
        
        #region Dependency Properties

        public static readonly DependencyProperty OutlinedProperty = DependencyProperty.Register(
            "Outlined",
            typeof(Boolean),
            typeof(BaseGlyph),
            new UIPropertyMetadata(
                false, // default value for this DP
                OnOutlinedChanged)); // callback on change
                

        private static void OnOutlinedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var caller = (BaseGlyph)d;

            if ((bool)e.NewValue)
            {
                foreach (var item in d.GetVisualDescendents().OfType<Path>())
                    item.Style = (Style)caller.FindResource("OutlinePath");
            }
            else
            {
                foreach (var item in d.GetVisualDescendents().OfType<Path>())
                    item.Style = (Style)caller.FindResource(typeof(Path));
            }
        }

        public Boolean Outlined
        {
            get { return (Boolean)GetValue(OutlinedProperty); }
            set { SetValue(OutlinedProperty, value); }
        }

        #endregion

        #region Constructors

        public BaseGlyph()
        {
            // Build the Resource Dictionary
            Resources = new ResourceDictionary();

            var binding = new Binding();
            var style = new Style();

            // Ancestor Foreground Binding,
            binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(BaseGlyph), 1);
            binding.Path = new PropertyPath(ForegroundProperty);

            style.TargetType = typeof(Path);
            style.Setters.Add(new Setter(Shape.FillProperty, binding));

            var transparentBrush = new SolidColorBrush(Colors.White) {Opacity = 0};
            var outline = new Style {TargetType = typeof (Path)};

            outline.Setters.Add(new Setter(Shape.StrokeThicknessProperty, 2d));
            outline.Setters.Add(new Setter(Shape.StrokeProperty, binding));
            outline.Setters.Add(new Setter(Shape.FillProperty, transparentBrush));

            Resources.Add(typeof(Path), style);
            Resources.Add("OutlinePath", outline);

            // Register event handlers
            Loaded += BaseIconLoaded;
        }

        #endregion

        #region Event Handlers

        private void BaseIconLoaded(object sender, RoutedEventArgs e)
        {
            // if it's Outlined (set in XAML) need to raise the event at the end of the Load
            // to actually load it as Outlined
            if (Outlined)
                OnOutlinedChanged(this, new DependencyPropertyChangedEventArgs(OutlinedProperty, false, true));
        }

        #endregion

    }
}

The full project can be downloaded here.

Adding Stretching support to Bag of Tricks Reveal control for WPF

One of the components of Bag of Tricks that I use the most is the Reveal control. It allows for great transitions that translate into better User Experiences and all very easy to write. For developers that have used this control, you know that the default behaviour is that the reveal will only occupy the desired space that it’s content requires.

This can be a problem, when we actually want the content inside the reveal to stretch to all the space he is allowed to occupy in the layout. To give an example I’ll illustrate with a problem I had recently: I was developing this RichTextBox control with the Ribbon attached to it, and because I don’t want the Ribbon to showup always, I added a reveal to the Ribbon and showed it on double click, hide it on the “Save” button in the Ribbon. What I got with a Reveal from BOT was this

reveal1

And what I really wanted was the Ribbon stretching to the full length of the RichTextBox, like this

reveal2

Adding a DependencyProperty to support a Stretch Mode

The first thing I had to do was to define an Enum that would list all my supported Stretch Modes:

public enum StretchMode
{
    /// <summary>
    /// No vertical or horizontal stretch.
    /// </summary>
    None,

    /// <summary>
    /// Stretch Horizontally but not Vertically.
    /// </summary>
    Horizontal,

    /// <summary>
    /// Stretch Vertically but not Horizontally.
    /// </summary>
    Vertical,

    /// <summary>
    /// Stretch Vertically and Horizontally.
    /// </summary>
    HorizontalAndVertical,
}

Next I just had to define a DependencyProperty to store the Stretch Mode value, it didn’t had to have any change callbacks because this property is just used for setup:

public StretchMode Stretch
{
    get { return (StretchMode)GetValue(StretchProperty); }
    set { SetValue(StretchProperty, value); }
}

// Using a DependencyProperty as the backing store for StretchMode.  This enables control over how Reveal occupies space
public static readonly DependencyProperty StretchProperty =
    DependencyProperty.Register(
        "Stretch",
        typeof(StretchMode),
        typeof(Reveal),
        new UIPropertyMetadata(StretchMode.None));

Extending the Reveal’s ArrangeOverride to support the new Property

WPF handles space in layouts in two different passes, the first one is MeasureOverride, where it will query the control for how much space would he like to have. On the second pass, the ArrangeOverride, WPF will tell the control how much space if really has, allowing the control to make a final set of computation and return it’s final size.

What we want to do is change the way Reveal is handling the ArrangeOverride, and if we have it to stretch make the childWidth and/or Height to the finalSize value instead of the DesiredSize value.

To accomplish this just add the following switch statement:

var childWidth = child.DesiredSize.Width;
var childHeight = child.DesiredSize.Height;

// Strech Mode support
switch(Stretch)
{
    case StretchMode.None:
        break;
    case StretchMode.Horizontal:
        childWidth = finalSize.Width;
        break;
    case StretchMode.Vertical:
        childHeight = finalSize.Height;
        break;
    case StretchMode.HorizontalAndVertical:
        childWidth = finalSize.Width;
        childHeight = finalSize.Height;
        break;
}

var x = CalculateLeft(childWidth, percent, horizontalReveal);
var y = CalculateTop(childHeight, percent, verticalReveal);

Using the TransitionPresenter with Prism

Bag of Tricks if a very cool project, it contains a lot of eye candy WPF controls that are easy to use and that serve as a great base for learning more WPF advanced features.

Inside Bag of Tricks there’s a control called the TransitionPresenter, and there’s a bunch of Transitions in the package, some of them are very cool.

This control works like a normal ContentControl, you replace it’s Content and it will transition from the previous Content to the new Content with the Transition that’s associated with it.

When a project uses Prism, there are Regions, usually inside ContentControls and we normally do a RequestNavigate(…) on a specific region to tell Prism to Navigate (getting things out of the Dependency Injection Container, swap the Content, etc.).

Extending Prism – Region Mappings

Our ultimate goal is to have Regions inside a TransitionPresenter control, and be able to write something like this:

<t:TransitionPresenter x:Name="MainContent" cal:RegionManager.RegionName="MainRegion" />

To accomplish this we need to add a Region Mapping on our Bootstrapper, on the ConfigureRegionAdapterMappings override, add a new line with the type of TransitionPresenter and the specific RegionAdapter, in our case we will call it TransitionPresenterRegionAdapater:

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    var regionAdapterMappings = ServiceLocator.Current.GetInstance<RegionAdapterMappings>();
    if (regionAdapterMappings != null)
    {
        regionAdapterMappings.RegisterMapping(typeof(Selector), ServiceLocator.Current.GetInstance<SelectorRegionAdapter>());
        regionAdapterMappings.RegisterMapping(typeof(ItemsControl), ServiceLocator.Current.GetInstance<ItemsControlRegionAdapter>());
        regionAdapterMappings.RegisterMapping(typeof(ContentControl), ServiceLocator.Current.GetInstance<ContentControlRegionAdapter>());
        regionAdapterMappings.RegisterMapping(typeof(TransitionPresenter), ServiceLocator.Current.GetInstance<TransitionPresenterRegionAdapater>());
    }

    return regionAdapterMappings;
}

Creating the Region Adapter

The code below has some flavors of Prism already present:

  • The implementation uses MEF as the Dependency Injection Container.
  • The TransitionPresenter is used on the main shell, so a SingleActiveRegion is preferred over AllActiveRegion.

The implementation is as follows:

using System;
using System.Linq;
using Microsoft.Practices.Prism.Regions;
using WPF.Controls.Transitions;
using System.Collections.Specialized;
using System.ComponentModel.Composition;

namespace Sigep.WPF.Infrastructure
{
    [Export(typeof(TransitionPresenterRegionAdapater))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class TransitionPresenterRegionAdapater : RegionAdapterBase<TransitionPresenter>
    {
        [ImportingConstructor]
        public TransitionPresenterRegionAdapater(IRegionBehaviorFactory regionBehaviorFactory)
            : base(regionBehaviorFactory)
        {
        }

        protected override void Adapt(IRegion region, TransitionPresenter regionTarget)
        {
            if (regionTarget == null) throw new ArgumentNullException("regionTarget");
            var contentIsSet = regionTarget.Content != null;

            if (contentIsSet)
            {
                throw new InvalidOperationException("ContentControlHasContentException");
            }
 
            region.ActiveViews.CollectionChanged += delegate
            {
                regionTarget.Content = region.ActiveViews.FirstOrDefault();
            };
 
            region.Views.CollectionChanged +=
                (sender, e) =>
                {
                    if (e.Action == NotifyCollectionChangedAction.Add && !region.ActiveViews.Any())
                    {
                        region.Activate(e.NewItems[0]);
                    }
                };
        }

        protected override IRegion CreateRegion()
        {
            return new SingleActiveRegion();
        }
    }
}