Category Archives: XAML

Playing around with the Ribbon and RichTextBox–4 of 4: Adding Hyperlink support to the RichTextBox

For my work, one of the things I had to support was Hyperlinks. I could do this the traditional way, where I would create a button in the Ribbon

blog10

And follow it up with a MessageDialogBox asking the user additional Hyperlink details

blog11

However, because my hyperlinks will always list the URI and never a user typed name, detecting hyperlinks as the user types and creating them is a lot better then specifically forcing the user to press a button and fill in the details.

Adding Hyperlink detection to the RichTextBox

I found a very good article on MSDN about this, written by Prajakta Joshi that I modified to suit my needs. It all starts with detecting the preceding word in a FlowDocument Paragraph object

private static string GetPreceedingWordInParagraph(TextPointer position, out TextPointer wordStartPosition)
{
    wordStartPosition = null;
    var word = String.Empty;
    var paragraph = position.Paragraph;

    if (paragraph != null)
    {
        var navigator = position;
        while (navigator != null && navigator.CompareTo(paragraph.ContentStart) > 0)
        {
            var runText = navigator.GetTextInRun(LogicalDirection.Backward);

            if (runText.Contains(" "))
            {
                var index = runText.LastIndexOf(" ", StringComparison.Ordinal);
                word = runText.Substring(index + 1, runText.Length - index - 1) + word;
                wordStartPosition = navigator.GetPositionAtOffset(-1 * (runText.Length - index - 1));
                break;
            }

            wordStartPosition = navigator;
            word = runText + word;
            navigator = navigator.GetNextContextPosition(LogicalDirection.Backward);
        }
    }

    return word;
}

I then hooked an Event Handler on the KeyDown Event of the RichTextBox on the constructor of my UserControl

public RibbonRichTextBox()
{
    InitializeComponent();
    _richTextBox.KeyDown += RibbonRichTextBoxKeyDown;
}

With the following implementation

private static void RibbonRichTextBoxKeyDown(object sender, KeyEventArgs e)
{
    var rtb = (RichTextBox) sender;
    if (e.Key != Key.Space && e.Key != Key.Return) return;

    var caretPosition = rtb.Selection.Start;
    TextPointer wordStartPosition;

    var word = GetPreceedingWordInParagraph(caretPosition, out wordStartPosition);
    if (!Uri.IsWellFormedUriString(word, UriKind.Absolute)) return;

    if (wordStartPosition == null || caretPosition == null) return;

    var tpStart = wordStartPosition.GetPositionAtOffset(0, LogicalDirection.Backward);
    var tpEnd = caretPosition.GetPositionAtOffset(0, LogicalDirection.Forward);

    if(tpStart != null && tpEnd != null)
    {
        var link = new Hyperlink(tpStart, tpEnd)
                        {
                            NavigateUri = new Uri(word)
                        };

        link.MouseLeftButtonDown += FollowHyperlink;
    }
}

Notice that I’m using the Uri class to check if the word is an URI or not, this could be changed for a Regex or other forms of checking this out

if (!Uri.IsWellFormedUriString(word, UriKind.Absolute)) return;

Adding the ability to Ctrl+Click Hyperlinks and open them in IE

If you notice the RibbonRichTextBoxKeyDown imlpementation, there’s a line there, after creating the Hyperlink, where I add an event handler for the MouseLeftButtonDown Event

link.MouseLeftButtonDown += FollowHyperlink;

In the Handler implementation, I check for Ctrl (left or right) is down, then just start IE with the URI present on the Link and make sure the event’s Handled is set to true to stop the routing

private static void FollowHyperlink(object sender, MouseButtonEventArgs e)
{
    if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl)) return;

    var link = (Hyperlink) sender;
    Process.Start(new ProcessStartInfo(link.NavigateUri.ToString()));
    e.Handled = true;
}

The Full Series

The full solution can be downloaded from here.

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

Playing around with the Ribbon and RichTextBox–3 of 4: Creating an Insert Picture button in the Ribbon

For the work I was doing I had to design and create special buttons that extended the EditingCommands Class like special text formatting for special text blocks, an Insert Image command and an insert Video command. The special text formatting was very specific to my work and the Video command is a lot more tricky to implement and the implementation is bound to a transformation that is performed later where I convert the RichTextBox’s document XAML to XML.

So I choose to demonstrate the Insert Picture command.

blog9

The button is a normal RibbonButton that’s defined inside a RibbonControlGroup inside a RibbonGroup

<ribbon:RibbonGroup Header="Media" x:Name="_mediaHeader">
    <ribbon:RibbonControlGroup>
        <ribbon:RibbonButton x:Name="_btnImage" Label="Image" LargeImageSource="/PlayingWithRibbon;component/Images/picture.png" Click="ButtonImageClick">
            <ribbon:RibbonButton.ControlSizeDefinition>
                <ribbon:RibbonControlSizeDefinition ImageSize="Large" />
            </ribbon:RibbonButton.ControlSizeDefinition>
        </ribbon:RibbonButton>
    </ribbon:RibbonControlGroup>
</ribbon:RibbonGroup>

Once the button is clicked an OpenFileDialog is shown for the user to select the image, since in my work I can only support JPG and PNG file formats, these are the ones that are being filtered

private static Image SelectImage()
{
    var dlg = new OpenFileDialog
    {
        Filter = "Image Files|*.png;*.jpg;*.gif"
    };

    var result = dlg.ShowDialog();
    if (result.Value)
    {
        var bitmap = new BitmapImage(new Uri(dlg.FileName));
        return new Image
        {
            Source = bitmap,
            Height = bitmap.Height,
            Width = bitmap.Width
        };
    }

    return null;
}

The Click Event Handler implementation, that inserts the actual picture in the Document

private void ButtonImageClick(object sender, RoutedEventArgs e)
{
    var image = SelectImage();
    if (image == null) return;

    var tp = _richTextBox.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
    new InlineUIContainer(image, tp);
}

Extra work could be done to create text flow around the image or other fancy tricks that the System.Windows.Document namespace offers, but since my final output is XML, none of those were going to be supported anyways.

The Full Series

The full solution can be downloaded from here.

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

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.

Playing around with the Ribbon and RichTextBox–1 of 4: Putting it all together, the Ribbon and the RichTextBox

The Ribbon for WPF is a very cool control, with a lot of customization options. In terms of User Experience it’s something everyone is used to these days from all the Microsoft Tools that have been using it, including Office since 2007 and all the way up to 2013.

To show off the Ribbon, I present a use of it with just a few commands, most of them are out-of-the box from the RichTextBox and one of them (Insert Picture) is custom.

The application itself is very simple, it’s a set of 2 controls (RichTextBox + Ribbon: RibbonRichTextBox) one on top of the other. I chose to place two of them, so that the focus changing hiding and showing of the Ribbon could be easily demonstrated.

blog7

The solution has a very flat structure, the main functionality is inside the RibbonRichTextBox UserControl. The MainWindow is very simple and just hosts the two RibbonRichTextBoxes:

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

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <PlayingWithRibbon:RibbonRichTextBox Grid.Row="0" Margin="10" />
        <PlayingWithRibbon:RibbonRichTextBox Grid.Row="1" Margin="10" />
    </Grid>

</Window>

blog8

Besides the Reveal, which I will talk about later in the series, there’s the Images Folder, with all the images required by the several controls inside the Ribbon.

The Ribbon

Using the Ribbon in .Net 4.0 or 4.5 just requires a reference to the assembly RibbonControlsLibrary.dll. In a very simplified description (it can do a lot more): The Ribbon control can host RibbonTab’s that can host RibbonControlGroups that can host all sorts of RibbonControls. The various controls in the Ribbon are mostly bound to the RichTextBox EditingCommands part of the System.Windows.Documents namespace.

<ribbon:Ribbon Name="_ribbon">
    <ribbon:RibbonTab Header="Home">
        <ribbon:RibbonGroup Header="Edit">
            <ribbon:RibbonButton x:Name="_btnPaste" Label="Paste" LargeImageSource="/PlayingWithRibbon;component/Images/Paste32.png"
                            Command="{x:Static ApplicationCommands.Paste}" CommandTarget="{Binding ElementName=_richTextBox}">
                <ribbon:RibbonButton.ControlSizeDefinition>
                    <ribbon:RibbonControlSizeDefinition ImageSize="Large" />
                </ribbon:RibbonButton.ControlSizeDefinition>
            </ribbon:RibbonButton>
            <ribbon:RibbonButton x:Name="_btnCut" Label="Cut" SmallImageSource="/PlayingWithRibbon;component/Images/Cut16.png"
                            Command="{x:Static ApplicationCommands.Cut}" CommandTarget="{Binding ElementName=_richTextBox}">
                <ribbon:RibbonButton.ControlSizeDefinition>
                    <ribbon:RibbonControlSizeDefinition ImageSize="Small" />
                </ribbon:RibbonButton.ControlSizeDefinition>
            </ribbon:RibbonButton>
            <ribbon:RibbonButton x:Name="_btnCopy" Label="Copy" SmallImageSource="/PlayingWithRibbon;component/Images/Copy16.png"
                            Command="{x:Static ApplicationCommands.Copy}" CommandTarget="{Binding ElementName=_richTextBox}">
                <ribbon:RibbonButton.ControlSizeDefinition>
                    <ribbon:RibbonControlSizeDefinition ImageSize="Small" />
                </ribbon:RibbonButton.ControlSizeDefinition>
            </ribbon:RibbonButton>
            <ribbon:RibbonButton x:Name="_btnClear" Label="Clear" SmallImageSource="/PlayingWithRibbon;component/Images/Delete16.png"
                            Command="{x:Static EditingCommands.Delete}" CommandTarget="{Binding ElementName=_richTextBox}">
                <ribbon:RibbonButton.ControlSizeDefinition>
                    <ribbon:RibbonControlSizeDefinition ImageSize="Small" />
                </ribbon:RibbonButton.ControlSizeDefinition>
            </ribbon:RibbonButton>
            <ribbon:RibbonButton x:Name="_btnUndo" Label="Undo" SmallImageSource="/PlayingWithRibbon;component/Images/Undo16.png"
                            Command="{x:Static ApplicationCommands.Undo}" CommandTarget="{Binding ElementName=_richTextBox}">
                <ribbon:RibbonButton.ControlSizeDefinition>
                    <ribbon:RibbonControlSizeDefinition ImageSize="Small" />
                </ribbon:RibbonButton.ControlSizeDefinition>
            </ribbon:RibbonButton>
            <ribbon:RibbonButton x:Name="_bntRedo" Label="Redo" SmallImageSource="/PlayingWithRibbon;component/Images/Redo16.png"
                            Command="{x:Static ApplicationCommands.Redo}" CommandTarget="{Binding ElementName=_richTextBox}">
                <ribbon:RibbonButton.ControlSizeDefinition>
                    <ribbon:RibbonControlSizeDefinition ImageSize="Small" />
                </ribbon:RibbonButton.ControlSizeDefinition>
            </ribbon:RibbonButton>
            <ribbon:RibbonButton x:Name="_btnSelectAll" Label="Select All"
                            Command="{x:Static ApplicationCommands.SelectAll}" CommandTarget="{Binding ElementName=_richTextBox}"/>
        </ribbon:RibbonGroup>
        <ribbon:RibbonGroup Header="Font">
            <ribbon:RibbonControlGroup>
                <ribbon:RibbonToggleButton x:Name="_btnBold" 
                                    SmallImageSource="/PlayingWithRibbon;component/Images/Bold16.png" 
                                    Command="{x:Static EditingCommands.ToggleBold}" CommandTarget="{Binding ElementName=_richTextBox}">
                    <ribbon:RibbonToggleButton.ControlSizeDefinition>
                        <ribbon:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="False" />
                    </ribbon:RibbonToggleButton.ControlSizeDefinition>
                </ribbon:RibbonToggleButton>
                <ribbon:RibbonToggleButton x:Name="_btnItalic" SmallImageSource="/PlayingWithRibbon;component/Images/Italic16.png"                                               
                                    Command="{x:Static EditingCommands.ToggleItalic}" CommandTarget="{Binding ElementName=_richTextBox}">
                    <ribbon:RibbonToggleButton.ControlSizeDefinition>
                        <ribbon:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="False" />
                    </ribbon:RibbonToggleButton.ControlSizeDefinition>
                </ribbon:RibbonToggleButton>
                <ribbon:RibbonToggleButton x:Name="_btnUnderline" SmallImageSource="/PlayingWithRibbon;component/Images/Underline16.png"
                                    Command="{x:Static EditingCommands.ToggleUnderline}" CommandTarget="{Binding ElementName=_richTextBox}">
                    <ribbon:RibbonToggleButton.ControlSizeDefinition>
                        <ribbon:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="False" />
                    </ribbon:RibbonToggleButton.ControlSizeDefinition>
                </ribbon:RibbonToggleButton>
            </ribbon:RibbonControlGroup>
        </ribbon:RibbonGroup>
        <ribbon:RibbonGroup Header="Paragraph">
            <ribbon:RibbonControlGroup>
                <ribbon:RibbonRadioButton x:Name="_btnBullets" Label="" SmallImageSource="/PlayingWithRibbon;component/Images/Bullets16.png"
                                    Command="{x:Static EditingCommands.ToggleBullets}" CommandTarget="{Binding ElementName=_richTextBox}">
d                                <ribbon:RibbonRadioButton.ControlSizeDefinition>
                        <ribbon:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="False" />
                    </ribbon:RibbonRadioButton.ControlSizeDefinition>
                </ribbon:RibbonRadioButton>
            </ribbon:RibbonControlGroup>
        </ribbon:RibbonGroup>
        <ribbon:RibbonGroup Header="Media" x:Name="_mediaHeader">
            <ribbon:RibbonControlGroup>
                <ribbon:RibbonButton x:Name="_btnImage" Label="Image" LargeImageSource="/PlayingWithRibbon;component/Images/picture.png" Click="ButtonImageClick">
                    <ribbon:RibbonButton.ControlSizeDefinition>
                        <ribbon:RibbonControlSizeDefinition ImageSize="Large" />
                    </ribbon:RibbonButton.ControlSizeDefinition>
                </ribbon:RibbonButton>
            </ribbon:RibbonControlGroup>
        </ribbon:RibbonGroup>
    </ribbon:RibbonTab>
</ribbon:Ribbon>

The RichTextBox

Connected with the Ribbon is a RichTextBox control. The control is simple, just has a Resource to force the Paragraph Margin to be 0 and is hooking the event SelectionChanged so that we can properly update the Ribbon’s ToggleButtons whenever we change the selection on the RichTextBox.

<RichTextBox x:Name="_richTextBox"
                Grid.Row="1"
                Height="200"
                BorderBrush="#3D5261"
                BorderThickness="2"
                IsReadOnly="True"
                SelectionChanged="RichTextBoxSelectionChanged">
    <RichTextBox.Resources>
        <Style TargetType="{x:Type Paragraph}">
            <Setter Property="Margin" Value="0"/>
        </Style>
    </RichTextBox.Resources>
</RichTextBox>

Updating ToggleButton state with SelectionChanged

Making sure that the ToggleButton’s in the Ribbon have their state updated with the current selection starts with the SelectionChanged Event Handler.

private void RichTextBoxSelectionChanged(object sender, RoutedEventArgs e)
{
    UpdateVisualState();
}

private void UpdateVisualState()
{
    UpdateToggleButtonState();
    UpdateSelectionListType();
}

This is then divided into two groups, the ToggleButton CheckedState and the ListType selection. For the ItemCheckedState we handle it like this:

private void UpdateToggleButtonState()
{
    UpdateItemCheckedState(_btnBold, TextElement.FontWeightProperty, FontWeights.Bold);
    UpdateItemCheckedState(_btnItalic, TextElement.FontStyleProperty, FontStyles.Italic);
    UpdateItemCheckedState(_btnUnderline, Inline.TextDecorationsProperty, TextDecorations.Underline);
}

private void UpdateItemCheckedState(ToggleButton button, DependencyProperty formattingProperty, object expectedValue)
{
    var currentValue = _richTextBox.Selection.GetPropertyValue(formattingProperty);
    button.IsChecked = (currentValue != DependencyProperty.UnsetValue) && (currentValue != null && currentValue.Equals(expectedValue));
}

While the ListType is handled like this:

private void UpdateSelectionListType()
{
    var startParagraph = _richTextBox.Selection.Start.Paragraph;
    var endParagraph = _richTextBox.Selection.End.Paragraph;

    if (startParagraph != null &&
        endParagraph != null &&
        (startParagraph.Parent is ListItem)
        && (endParagraph.Parent is ListItem) &&
        ReferenceEquals(((ListItem)startParagraph.Parent).List, ((ListItem)endParagraph.Parent).List))
    {
        var targetList = (startParagraph.Parent as ListItem).List;
        if (targetList == null) return;

        var markerStyle = targetList.MarkerStyle;

        switch (markerStyle)
        {
            case TextMarkerStyle.Disc:
                _btnBullets.IsChecked = true;
                break;
        }
    }
    else
    {
        _btnBullets.IsChecked = false;
    }
}

The Full Series

The full solution can be downloaded from here.

This is the first 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);

Creating a Notification button in WPF

Recently, I had to create a button that besides doing it’s normal job, would also show the amount of tasks that the User had to do inside that button.

To do this I decided to mimic the Notification button of popular Mobile Operating Systems do these days

ios_notifywp_notify

The end result was this:

notify

The full download of the solution can be found here

1.Button Framework

The button inherits from the WPF base button class, and is declared in a custom user control library, so the button style, that is not looked at in this post, is declared in the Generic.xaml file.

It implements a set of dependency properties to control the pooling for new Notifications, and the actual red circle showing the number is a WPF Adorner, so that it can overlap content on the visual tree and abstract the developer from the implications on the layout while using it.

To keep a clean WPF structure I implemented a fake delegate on a ViewModel that mimics our functionality of pooling a Web Service, this ViewModel is attached to the MainWindow, but in the real world we would have something like a menu bar and a menu bar ViewModel and these things would be implemented in those places.

The button usage is as follow:

<StackPanel VerticalAlignment="Center" Orientation="Vertical">
    <lib:NotificationButton Width="40"
                            Height="40"
                            NotifyFunc="{Binding NotifyDelegate}"
                            Poll="{Binding ElementName=PollCheck,
                                            Path=IsChecked}"
                            PollInterval="5">

        <TextBlock HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    FontSize="20"
                    FontWeight="Bold">
            R
        </TextBlock>

    </lib:NotificationButton>

    <CheckBox Name="PollCheck" IsChecked="True" />
</StackPanel>

2. Dependency Properties

The button exposes the following functionality:

  • NotifyFunc – The delegate of type Func<int> that gives you the Notification Number
  • Poll – True or False, tells the button whether to poll or not. Useful for scenarios where for example the button is hidden or collapsed and you want to stop polling.
  • PollInterval – In seconds, allows to configure the interval of the polling.

The implementation looks something like this:

#region Dependency Properties
#region PollInterval
public static readonly DependencyProperty PollIntervalProperty = DependencyProperty.Register(
    "PollInterval",
    typeof(Int32),
    typeof(NotificationButton),
    new UIPropertyMetadata(60) // default value for this DP
    );

public int PollInterval
{
    get { return (int)GetValue(PollIntervalProperty); }
    set { SetValue(PollIntervalProperty, value); }
}
#endregion

#region Poll
public static readonly DependencyProperty PollingProperty = DependencyProperty.Register(
    "Poll",
    typeof(Boolean),
    typeof(NotificationButton),
    new UIPropertyMetadata(true, OnPollChanged) // default value for this DP
    );

public bool Poll
{
    get { return (bool)GetValue(PollingProperty); }
    set { SetValue(PollingProperty, value); }
}

public void StartPolling()
{
    _timer.Change(PollInterval * 1000, PollInterval * 1000);
}

public void StopPolling()
{
    _timer.Change(Timeout.Infinite, Timeout.Infinite);
}

private static void OnPollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var nb = (NotificationButton)d;

    if ((bool)e.NewValue) nb.StartPolling();
    else nb.StopPolling();
}
#endregion

#region NotificationFunc
public static readonly DependencyProperty NotifyFuncProperty = DependencyProperty.Register(
    "NotifyFunc",
    typeof(Func<int>),
    typeof(NotificationButton)
    );

public Func<int> NotifyFunc
{
    get { return (Func<int>)GetValue(NotifyFuncProperty); }
    set { SetValue(NotifyFuncProperty, value); }
}
#endregion
#endregion

3.The Adorner

The Adorner is implemented on the same file as the button, because it is not shared by other controls, however you could isolate it on a different file to make it easier to reuse it.

The only specific thing is that I use the drawing context to draw direct primitives instead of instantiating WPF controls:

/// <summary>
/// The Notification adorner.
/// On Render implements direct drawing context calls.
/// </summary>
public class NotificationAdorder : Adorner
{
    public NotificationAdorder(UIElement adornedElement)
        : base(adornedElement)
    {
        DataContextChanged += NotificationAdorderDataContextChanged;
    }

    private void NotificationAdorderDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        InvalidateVisual();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (DataContext == null) return; // Break if we don't have the Adorner databound

        var adornedElementRect = new Rect(AdornedElement.DesiredSize);
        var typeFace = new Typeface(new FontFamily("Courier New"), FontStyles.Normal, FontWeights.ExtraBold,
                                    FontStretches.Condensed);

        var text = new FormattedText(
            DataContext.ToString(),
            CultureInfo.GetCultureInfo("en-us"),
            FlowDirection.RightToLeft,
            typeFace,
            16, Brushes.White
            );

        var pointOfOrigin = new Point
                                {
                                    X = adornedElementRect.BottomRight.X,
                                    Y = adornedElementRect.BottomRight.Y - text.Height * 0.7
                                };

        var elipseCenter = new Point
                                {
                                    X = pointOfOrigin.X - text.Width / 2,
                                    Y = pointOfOrigin.Y + text.Height / 2
                                };

        var elipseBrush = new SolidColorBrush
                                {
                                    Color = Colors.DarkRed,
                                    Opacity = 0.8
                                };

        drawingContext.DrawEllipse(
            elipseBrush,
            new Pen(Brushes.White, 2),
            elipseCenter,
            text.Width * 0.9,
            text.Height * 0.5
            );

        drawingContext.DrawText(text, pointOfOrigin);
    }
}

The adorner is attached to the button on the Loaded delegate

var adornerLayler = AdornerLayer.GetAdornerLayer(this);
_adorner = new NotificationAdorder(this) {DataContext = null};
adornerLayler.Add(_adorner);

Of note is also the fact that we need to capture the datacontext changed even, so that we can call the InvalidateVisual to force WPF to redraw the Adorner.

4.Other Parts of interest

It is important to never block the WPF UI thread of calls to Web Services, to accomplish this I used a bit of TPL code to first get some dependency properties in the UI thread, then call the Web Service in a separate thread, and finally rebind the data in the UI thread:

/// <summary>
/// Delegates the fake Web Service call onto a different thread, while retaining the UI Thread to update the UI
/// </summary>
/// <param name="state"></param>
private void CheckContent(object state = null)
{
    int? delegateReturn = 0;
    Func<int> func = null;

    Task.Factory.StartNew(() =>
                                {
                                    func = NotifyFunc;
                                }
        , new CancellationToken(), TaskCreationOptions.None, _uiContext)
        .ContinueWith(t =>
                            {
                                delegateReturn = func();
                                if (delegateReturn < 1) delegateReturn = null; // Normalize to not show if not at least 1
                            }
        )
        .ContinueWith(t =>
                        {
                            _adorner.DataContext = delegateReturn;
                        }
        , new CancellationToken(), TaskContinuationOptions.None, _uiContext);
}

To control the pooling I used a System.Threading.Timer that is created on the Loaded delegate:

_timer = new Timer(CheckContent, null, Timeout.Infinite, Timeout.Infinite);
if (Poll) _timer.Change(PollInterval * 1000, PollInterval * 1000);

And controlled by these 2 utility methods:

public void StartPolling()
{
    _timer.Change(PollInterval * 1000, PollInterval * 1000);
}

public void StopPolling()
{
    _timer.Change(Timeout.Infinite, Timeout.Infinite);
}

5.The ViewModel

It is a very simple ViewModel, that basically declared and implements the Function delegate:

/// <summary>
/// The view model, with a fake implementation that calls a 1 sec sleep to make sure the UI is never blocked.
/// </summary>
public class NotificationViewModel
{
    public NotificationViewModel()
    {
        NotifyDelegate = NotificationImplementation;
    }

    public Func<int> NotifyDelegate { get; set; }

    private static int NotificationImplementation()
    {
        var r = new Random();
        Thread.Sleep(1000);
        return r.Next(1, 99);
    }
}