Pixel-in-Gene

Exploring Creativity with Design / Graphics / Technology

Building a CoverFlow Visualization Using Quartz Composer

I have been playing around with Quartz Composer (included as part of the Developer tools installation on Mac OSX) for almost a year. It’s a great tool for creating screen savers, music visualizations and also for quick prototyping of some visual concepts. I personally find the patch-based approach to solving problems quite refreshing and offers a different perspective to looking at complex problems. It is very much like programming but with functional blocks (called patches) rather than real code.

Understanding the CoverFlow visualization

image

Before I talk about how we can pull this off in QC, it would be good to take a quick look at the building blocks of CoverFlow. We all have seen it before, but it would be good to analyze it from the point of view of a QC composition:

  1. We have a set of tiles placed left to right with some gap between tiles. The selected tile is in the center of the viewport with some additional gap between the center tile and the adjacent tiles.

  2. A tile can be in one of three orientations: tilted on left, straight center, tilted on right (as shown in the picture above)

  3. Each tile has a reflection at the bottom, fading into the background

  4. As the selected tile changes, every item does a sliding+rotating animation to take up a new position and orientation

  5. A few polishing touches include a dark vertical-gradient as background and a horizontal gradient that fades off the edges. The edge-fading gradient (called a vignette) is needed to give a lighting effect on the center, selected tile.

Decomposing the Quartz composition!

Quartz Composer is a patch-based programming tool, which means we will have to think in terms of functional blocks or patches that do some simple operation. A patch has some inputs and outputs. Your job is to provide proper inputs and use the outputs to feed other patches. You can also encapsulate a set of patches into a Macro Patch, which provides a simpler abstraction over some complex operation. The Macro Patch can be used like regular patches, possibly having its own inputs and outputs.

Connecting these patches together we can create visualizations. I like to think of this as circuit-board design, where we take a bunch of ICs (Integrated Chips) and design a micro-processor. The Quartz Composer provides a way to design a micro-processors that can generate visualizations. The figure below shows a part of the CoverFlow composition.

image

The CoverFlow composition is built up of a few Macro Patches that are layered to create the visualization. Lets understand this from the bottom-up.

[A] A CoverFlow tile

At the bottom of the stack is the notion of a single CoverFlow tile. This tile provides the base properties that will be controlled from higher level patches. From our earlier analysis, we know we need the following capabilities:

  • Reflection
  • Positioning on the X-axis
  • Y-Rotation to show the tilt
  • An Image that acts as the texture for the tile and the reflection

A Sprite patch provides all of these capabilities, except for the reflection. To get the reflection we use an extra Sprite and apply an inverted version of the supplied Image. We finally apply a gradient mask to obtain the fading reflection effect. The mask gradient is from 85% transparency to 100% transparency (fading into background). To make it easy, I have encapsulated all of these patches into a Macro Patch called “Reflected Tile”. The following figure shows the “Reflected Tile” patch. Click on it for a full-size image.

image

One thing you will notice is that I am exposing the “Image”, “X Position” and “Y Rotation” as inputs (shown with light-green dots) into the “Reflected Tile” Patch. These will be set from a higher level patch, the “CoverFlow Tile” patch, which is yet another Macro Patch.

The “CoverFlow Tile” Patch

This patch represents a single CoverFlow Tile.

image

This patch provides an numbered input called “State Index” that can be either 0,1,2, representing the three different orientations: left tilted, straight-center, right tilted respectively. Depending on the state we apply the correct “Tilt Angle”. The left tilt is positive and the right tilt is negative. The negative tilt is applied with a simple Math patch that multiplies the “Tilt Angle” by –1. This patch wraps the Reflected Tile patch and also exposes the “Y Rotation” input on the Reflected Tile patch as “Tilt Angle”. We have also applied a Smooth patch for the “X Position” and the “Y Rotation” to animate the position and orientation changes on the tile. The default animation-time is 0.5 seconds, and can be controlled with the “Animation Duration” input on the CoverFlow Tile patch.

[B] A list of CoverFlow Tiles

With the basic tile patches in place, we are now ready to tackle the display of a set of tiles. Each tile will be rendered using the “CoverFlow Tile” patch but needs to be positioned and oriented depending on a “Selected Index”. The Selected Index is exposed as input into the Iterator patch, a stock Macro Patch provided within Quartz Composer. The Selection Index divides the tiles into 3 sets: the selected tile (center tile), the tiles to the left and the tiles to the right. The X Position and Tilt Angle of each tile is entirely driven by the Selected Index and the “Current Index” of the Iterator patch.

To obtain the “Current Index” within the Iterator Macro Patch, we use the standard Iterator Variables patch provided within QC. Using the Selected Index and the Current Index of the Iterator, we can determine the “State Index” of the tile. Recall that we have 3 states for each tile, that specify the orientation of the tile. The state of the tile is controlled by this State Index.

The X Position of the tile requires more calculation as we use the “Item Gap” and the “Front Item Gap” inputs to correctly position the tile. Note that the Item Gap and Front Item Gap is only applied to the left and right tiles. The center tile is unaffected by these inputs. The figure below shows the complete wiring of the “Tile Iterator” patch. The value of the X Position is governed by the fact that we use a 3D co-ordinate system where the origin is in the center of the screen. The X values decrease to the left of the origin and increase to the right of the origin.

image

The “Image List” input is the set of the images over which the Iterator patch iterates. This is fed as input from higher level patch that represents our entire coverflow visualization. This patch is aptly called the “CoverFlow Control” patch, as shown in the figure below. Note that all the necessary inputs are exposed on this patch.

image

[C] Providing Keyboard input for selection

Of course, no CoverFlow control is void of user interaction! Thus our composition dutifully provides some control using keyboard input. In Quartz Composer the Keyboard patch can be used to detect keyboard input. A set of keys can be configured in the settings for the Keyboard patch, which will generate a signal (a momentary boolean value) whenever that key is hit. In our case, we use the Left/Right arrow keys for changing the Selected Index of the CoverFlow Control.

The Keyboard generated signals are fed into a Counter patch that increments or decrements a count. The Left arrow decrements and the Right arrow increments. The output of the Counter acts as input to the Selected Index of the CoverFlow Control patch. We also use a Conditional patch to ensure that the count generated by the Counter is always within the index range [0, n-1] of the Image List . A Directory Scanner patch provides the location that contains all the images. This patch creates a Structure (an array of objects) that then becomes the Image List input to the CoverFlow Control patch.

With this setup we now have a way to drive the CoverFlow visualization. The figure below shows the wiring for the keyboard input.

image

Final polishing

The final polish to this composition is done by applying a bunch of Gradient patches that generate the background and the fading edge effect (vignette) on the CoverFlow. We also wrap the previously described patch in section [C] inside a 3D Transformation. This is used to scale down the entire visualization and leave some room at the bottom to show keyboard-navigation instructions.

image

Quick video showing it in action

CoverFlow using Quartz Composer

Download

For exploring all the details about this composition you can download and peruse the .QTZ file:

image

Further Reading

Tokenizing Control – Convert Text to Tokens

In this post I want to talk about some interesting ideas regarding a control called TokenizingControl? What is that you may ask, so lets start with the basics. A Tokenizing control takes in some text, delimited by some character and converts that text to a token, a token that is represented by some UI element other than the original text. For example, if you have text like “John Doe;” (note the ; acting as delimiter), then the tokenizing control will convert it to some UI Element like a Button, say.

image

In fact the text can be replaced by a complex backing object (ViewModel) that is rendered using some UI element (in this case a Button). We can add more flexibility for the UI element by making it into a ContentPresenter that takes in a DataTemplate, with the Content being the ViewModel!

This is the purpose of a tokenizing control. Since we are dealing with text most of the time and replacing some pattern of text into a token, we continue to retain the editing capabilities. In other words, if I hit the BackSpace key on the token-UI (shown as a Button), it is deleted completely. You can think of this control as a runtime parser that detects some pattern of text and converts that into a UI token (backed by some ViewModel, potentially).

Now you may be thinking where could such a control be used. Well, the most common place is in an Email editor for the To/CC/BCC input areas. When you type in a prefix of a name, the control will try to match that against some AddressBook and convert that typed text into a rich token (backed by the Contact from the AddressBook). Outlook does this and so do many other email programs. One other place you will find a use is in data-entry forms where some typed text is converted to a matched object. Having such a control minimizes the errors in typing because you will get real-time validation with some visual feedback. Now that we know there is a “real” use case for this control, lets get into some implementation details, in WPF.

Behavior of the TokenizingControl

The general expectations from this control are outlined below:

  1. Should allow text editing with inline representation of matched text with a UI token
  2. Should provide ability to customize appearance of the UI token
  3. Should provide a way to match text and convert matched-text to a token object

Lets tackle one at a time. The first bullet tells us that the TokenizingControl is like a hybrid TextBox that can hold text as well as other UI elements, in other words we are looking at a RichTextBox as our base. A RichTextBox encapsulates a FlowDocument (as its Document property), which can hold both text and UI elements. By manipulating this Document, we should be able to convert the typed text (which would be in a Run) and convert that to a InlineUIContainer (a subclass of Inline that wraps a UIElement). Thus at the UI level we are looking at a Run to InlineUIContainer conversion, all happening inside the FlowDocument.

Now that we have an InlineUIContainer to work with, we will need to address bullet 2: customizing appearance of the token. This can be easily achieved by using a ContentPresenter with a DataTemplate. We can expose a DataTemplate property (let’s call it TokenTemplate) on the TokenizingControl and provide this ability.

As regards the last bullet, we can have a Func<string,object> that takes in a string (typed-text) and returns an object for the matched token and null for no-match. We can expose this with a TokenMatcher property of type Func<string, object>. This is the lambda that you will use to convert text to a backing object (aka ViewModel). This backing object becomes the Content of the ContentPresenter.

With that we have our class definition for the TokenizingControl, which looks like below:

image

You can see the methods in this class that do the actual work. The processing begins in the TextChanged event handler, where we get the text from the CaretPosition and apply the TokenMatcher to determine the token. If a valid token is found, we create the UI container and replace the Run with the InlineUIContainer. The code below shows this in detail:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public TokenizingControl()
{
    TextChanged += OnTokenTextChanged;
}

private void OnTokenTextChanged(object sender, TextChangedEventArgs e)
{
    var text = CaretPosition.GetTextInRun(LogicalDirection.Backward);
    if (TokenMatcher != null)
    {
        var token = TokenMatcher(text);
        if (token != null)
        {
            ReplaceTextWithToken(text, token);
        }
    }
}

private void ReplaceTextWithToken(string inputText, object token)
{
    // Remove the handler temporarily as we will be modifying tokens below, causing more TextChanged events
    TextChanged -= OnTokenTextChanged;

    var para = CaretPosition.Paragraph;

    var matchedRun = para.Inlines.FirstOrDefault(inline =>
    {
        var run = inline as Run;
        return (run != null && run.Text.EndsWith(inputText));
    }) as Run;
    if (matchedRun != null) // Found a Run that matched the inputText
    {
        var tokenContainer = CreateTokenContainer(inputText, token);
        para.Inlines.InsertBefore(matchedRun, tokenContainer);

        // Remove only if the Text in the Run is the same as inputText, else split up
        if (matchedRun.Text == inputText)
        {
            para.Inlines.Remove(matchedRun);
        }
        else // Split up
        {
            var index = matchedRun.Text.IndexOf(inputText) + inputText.Length;
            var tailEnd = new Run(matchedRun.Text.Substring(index));
            para.Inlines.InsertAfter(matchedRun, tailEnd);
            para.Inlines.Remove(matchedRun);
        }
    }

    TextChanged += OnTokenTextChanged;
}

private InlineUIContainer CreateTokenContainer(string inputText, object token)
{
    // Note: we are not using the inputText here, but could be used in future

    var presenter = new ContentPresenter()
    {
        Content = token,
        ContentTemplate = TokenTemplate,
    };

    // BaselineAlignment is needed to align with Run
    return new InlineUIContainer(presenter) { BaselineAlignment = BaselineAlignment.TextBottom };
}
  

In action

The following picture shows the different stages in converting the text to a token: user inputs text, types a semi-colon ;, text converted to a token. We are using ; as our delimiter here.

image

The TokenMatcher for this example looks like so:

1
2
3
4
5
6
7
8
9
10
Tokenizer.TokenMatcher = text =>
                             {
                                 if (text.EndsWith(";"))
                                 {
                                     // Remove the ';'
                                     return text.Substring(0, text.Length - 1).Trim().ToUpper();
                                 }

                                 return null;
                             };

If you run the example from the attached solution, there is a nice animation that fades-in the token-UI once the user types in the ;. This gives a nice effect of some transformation happening to the text.

Summary

This post showed you a neat way to transform text, that matches some criteria, into tokens represented by a different UI. The TokenizingControl does this job by using the TokenMatcher (Func<string, object>) and converting matched text into tokens represented by the TokenTemplate (DataTemplate). The tokens are inserted inline using the InlineUIContainer. The RichTextBox was the base for the TokenizingControl.

As a parting thought, I want to mention that you can add several useful features to this control:

  • An inline autocompletion popup that shows up as you type text. The autocompletion helps in narrowing down the options even more
  • The token-UI can be much more sophisticated (potentionally a custom control of its own that is used internally by the TokenizingControl)
  • There can be an ItemsSource-like property that takes in a collection of ViewModel objects and converts them to UI-tokens and also the other way around
  • The TokenMatcher can be far more intelligent and generate not just a single token out of the text but also help in the autocompletion!

[Note: I do have a control that does all of the above Winking
smile]. Hopefully this post shares enough info to create one of your own!

Source code for TokenizingControl

A DoubleBorderDecorator to Simplify Rounded-border Decorations

The designers in my team use a lot of nested double-Border elements to achieve a nice rounded border-effect around containers. In XAML this looks like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Border Background="#FF414141"
        Padding="3"
        Width="300"
        Height="200"
        CornerRadius="8">
    <Border Padding="3"
            CornerRadius="8">
        <Border.Background>
            <LinearGradientBrush EndPoint="0.5,1"
                                 StartPoint="0.5,0">
                <GradientStop Color="#FF910000"
                              Offset="1" />
                <GradientStop Color="#FFAF5959" />
            </LinearGradientBrush>
        </Border.Background>
    </Border>
</Border>
  

image

You will notice that there is no use of the BorderThickness and BorderBrush properties. Instead this effect achieved by nesting one Border inside another using the Background property and setting a Padding on the outer-Border. The reason why they do this is because of a rendering glitch in WPF’s Border element. If you try to create the above look using a single Border with BorderThickness, BorderBrush and Background, you will see the corners having some glitches. The image below (zoomed to 150%) shows this clearly. Notice the white inner-curve around the corners and compare this to the image above. Clearly there is a glitch here.

image

Here’s the single-Border XAML that was used for the above look:

1
2
3
4
5
6
7
8
9
10
11
12
13
<Border BorderBrush="#FF414141"
        CornerRadius="8"
        Margin="137,111,193,137"
        BorderThickness="3">
    <Border.Background>
        <LinearGradientBrush EndPoint="0.5,1"
                             StartPoint="0.5,0">
            <GradientStop Color="#FF910000"
                          Offset="1" />
            <GradientStop Color="#FFAF5959" />
        </LinearGradientBrush>
    </Border.Background>
</Border>

So, now you know why the nested double-Border technique is so effective and creates a much more pleasing look. But, we have a problem here. If your project is large and using several different kinds of containers with the rounded–border effect, you will be using a LOT of these Borders! It also hampers the readability at some point. Surely there can be a better way…in the form of a custom DoubleBorderDecorator!

DoubleBorderDecorator

This is a simple Decorator derived class that I created which bakes in the above discussed technique with simple configurable properties. To achieve the effect from our previous example, here is the XAML using the DoubleBorderDecorator:

1
2
3
4
5
6
7
<shared:DoubleBorderDecorator Height="200"
                              Width="300"
                              OuterBorderThickness="3"
                              OuterBorderBrush="#FF414141"
                              CornerRadius="8"
                              Background="{DynamicResource RedGradient}" />
  

Visually it looks exactly like our first figure, but lot simpler to use and modify. In fact, you can even define a Style and set the defaults at a global level (inside Application Resources).

Here is a Blend screenshot that shows other useful properties of this decorator:

image

With an inner-border and an outer-border, you get a pretty flexible way of creating rounder-border effects that look great. Here are a few examples:

image

image

image

Download Source

Simple TriggerAction for Docking Using GridSplitters

GridSplitters work great if you want to provide a split view between two views. By dragging the GridSplitter you can adjust the space allocated to each view. As one view grows in size, the other reduces by the same size. For most cases, this is exactly what is required. In my case, this wasn’t enough. In addition to adjusting the splitter, I also wanted a way to quickly collapse one view and allocate all the space to the other view (a.k.a. Docking). The views were arranged horizontally like so:

image

To do the collapse action to one side, I added two buttons to the GridSplitter’s ControlTemplate (shown below). One would collapse the left-view and the other the right-view.

image

What you are seeing in the figure above is a Grid with 3 columns. The middle column is assigned to the GridSplitter. This is an important assumptionthat I make: that the GridSplitter occupies the center column (or center row) and there are views to the either side in their own columns (or rows). This assumption will be used in my custom TriggerAction, called CollapseAction.

The CollapseAction for the GridSplitter buttons

The idea of collapse is to maximize one of the views and assign zero space to the other view. This can be achieved by adjusting the Widths of the ColumnDefinitions of the containing Grid (or adjusting the Heights of the RowDefinitions). It is probably easier to see it all in code, so here you go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class CollapseAction : TriggerAction<Button>
{
    public Dock Direction { get; set; }
    protected override void Invoke(object parameter)
    {
        // First find the nearest splitter
        var splitter = FindVisual<GridSplitter>(AssociatedObject);

        if (splitter != null)
        {
            var grid = FindVisual<Grid>(splitter); // Find nearest Grid
            if (grid != null)
            {
                ApplyDock(grid);
            }
        }
    }

    private void ApplyDock(Grid grid)
    {
        var cDef1 = grid.ColumnDefinitions.FirstOrDefault();
        var cDef2 = grid.ColumnDefinitions.LastOrDefault();
        var rDef1 = grid.RowDefinitions.FirstOrDefault();
        var rDef2 = grid.RowDefinitions.LastOrDefault();
        switch (Direction)
        {
            case Dock.Left:
                cDef1.Width = new GridLength(0);
                cDef2.Width = new GridLength(1, GridUnitType.Star);
                break;
            case Dock.Right:
                cDef2.Width = new GridLength(0);
                cDef1.Width = new GridLength(1, GridUnitType.Star);
                break;
            case Dock.Top:
                rDef1.Height = new GridLength(0);
                rDef2.Height = new GridLength(1, GridUnitType.Star);
                break;
            case Dock.Bottom:
                rDef2.Height = new GridLength(0);
                rDef1.Height = new GridLength(1, GridUnitType.Star);
                break;
        }
    }

    private T FindVisual<T>(FrameworkElement relElt) where T : FrameworkElement
    {
        var parent = VisualTreeHelper.GetParent(relElt);

        while (parent != null && !(parent is T))
        {
            parent = VisualTreeHelper.GetParent(parent);
        }

        return parent as T;
    }
}
  

The core of the work happens in the ApplyDock() method. Depending on the Direction, I zero-size one of the columns/rows and give all the layout-space to the other column/row. The middle column/row is of fixed-size and is used by the GridSplitter (as noted earlier). In the ControlTemplate I assign this action to each of the buttons and set the Direction appropriately. This can be seen in the following snippet of the GridSplitter’s ControlTemplate. Note the actions assigned to the Buttons.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<ControlTemplate TargetType="{x:Type GridSplitter}">
    <Border BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            Background="{TemplateBinding Background}">
        <Grid Height="Auto">
            <StackPanel x:Name="ColumnsCollapsers"
                        VerticalAlignment="Stretch"
                        d:LayoutOverrides="Height">
                <Button Height="10"
                        Margin="0,3,0,0"
                        Padding="0"
                        Style="{DynamicResource ButtonStyle1}"
                        RenderTransformOrigin="0.5,0.5"
                        Cursor="Arrow">
                    <Button.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleY="1"
                                            ScaleX="-1" />
                            <SkewTransform AngleY="0"
                                            AngleX="0" />
                            <RotateTransform Angle="0" />
                            <TranslateTransform />
                        </TransformGroup>
                    </Button.RenderTransform>
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <local:CollapseAction />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <Path Data="M20.500334,30.5 L20.500334,49.166667 35.500332,38.833333 z"
                            Fill="Black"
                            HorizontalAlignment="Center"
                            Height="6"
                            Margin="0"
                            Stretch="Fill"
                            Stroke="{x:Null}"
                            Width="6"
                            StrokeThickness="0"
                            VerticalAlignment="Center" />
                </Button>
                <Button Height="10"
                        Margin="0,3,0,0"
                        Padding="0"
                        Style="{DynamicResource ButtonStyle1}"
                        Cursor="Arrow">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <local:CollapseAction Direction="Right" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <Path Data="M6.4035191E-08,0 L-1.5987212E-14,5.9999996 5.9999999,2.6785712 z"
                            Fill="Black"
                            HorizontalAlignment="Center"
                            Height="6"
                            Margin="0"
                            Stroke="{x:Null}"
                            Width="6"
                            StrokeThickness="0"
                            VerticalAlignment="Center"
                            StrokeLineJoin="Round"
                            Stretch="Fill" />
                </Button>
            </StackPanel>
            <StackPanel x:Name="RowCollapsers"
                        VerticalAlignment="Stretch"
                        d:LayoutOverrides="Height"
                        Orientation="Horizontal"
                        Visibility="Collapsed">
                <Button Height="10"
                        Padding="0"
                        Style="{DynamicResource ButtonStyle1}"
                        RenderTransformOrigin="0.5,0.5"
                        Cursor="Arrow"
                        Width="10"
                        HorizontalAlignment="Left"
                        Margin="3,0,0,0">
                    <Button.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleY="1"
                                            ScaleX="-1" />
                            <SkewTransform AngleY="0"
                                            AngleX="0" />
                            <RotateTransform Angle="90" />
                            <TranslateTransform />
                        </TransformGroup>
                    </Button.RenderTransform>
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <local:CollapseAction Direction="Top" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <Path Data="M20.500334,30.5 L20.500334,49.166667 35.500332,38.833333 z"
                            Fill="Black"
                            HorizontalAlignment="Center"
                            Height="6"
                            Margin="0"
                            Stretch="Fill"
                            Stroke="{x:Null}"
                            Width="6"
                            StrokeThickness="0"
                            VerticalAlignment="Center" />
                </Button>
                <Button Height="10"
                        Padding="0"
                        Style="{DynamicResource ButtonStyle1}"
                        Cursor="Arrow"
                        Width="10"
                        HorizontalAlignment="Left"
                        Margin="3,0,0,0"
                        RenderTransformOrigin="0.5,0.5">
                    <Button.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform />
                            <SkewTransform />
                            <RotateTransform Angle="90" />
                            <TranslateTransform />
                        </TransformGroup>
                    </Button.RenderTransform>
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <local:CollapseAction Direction="Bottom" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <Path Data="M6.4035191E-08,0 L-1.5987212E-14,5.9999996 5.9999999,2.6785712 z"
                            Fill="Black"
                            HorizontalAlignment="Center"
                            Height="6"
                            Margin="0"
                            Stroke="{x:Null}"
                            Width="6"
                            StrokeThickness="0"
                            VerticalAlignment="Center"
                            StrokeLineJoin="Round"
                            Stretch="Fill" />
                </Button>
            </StackPanel>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="ResizeDirection"
                    Value="Rows">
            <Setter Property="Visibility"
                    TargetName="ColumnsCollapsers"
                    Value="Collapsed" />
            <Setter Property="Visibility"
                    TargetName="RowCollapsers"
                    Value="Visible" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>
  

In action

Below you can see the two configurations of docking to the left and to the right. Note that you can still grab the GridSplitter and drag to adjust the columns. image

image

Subtle things to note about the GridSplitter

  1. In the ControlTemplate, I have added two Buttons and set their Cursor to Arrow. This is required because by default the GridSplitter assigns a Cursor of SizeWE or SizeNS depending on the alignment. When you move the mouse on the Buttons, you continue to get this sizing cursor. This hinders with the usability of the buttons and to aid in the usage, we set the Cursor of the buttons to Arrow.
  2. The ResizeBehavior has been explicitly set to PreviousAndNext. This should make sense now given our earlier assumption. Additionally, the ResizeDirection has also been set to Columns.

Download Source

The SpiderWebControl for Silverlight

image

Over the last couple of days I was working on an internal demo and one of the controls I built for that was a SpiderWebControl. It is a Silverlight 4 custom control that allows you to draw a network graph. A quick run down of the features is given below:

  • Adding, removing, renaming of nodes
  • Dragging of nodes
  • CTRL + Drag to drag a subtree/branch
  • CTRL + Drag on the canvas to pan around
  • Selections and hovers using VSM
  • Simple automatic layout with animations

The following video shows the control in action

Design choices

There were a few design decisions made while building this control that are worth highlighting:

  1. The top level control is called the SpiderWebControl and each node within that control is an instance of the SpiderWebItem. Ideally I would have derived SpiderWebControl from ItemsControl if not for the extra level of control that I wanted for adding items to the ItemsPanel.
  2. The node elements (boxes) and the connecting elements (lines) are inside two separate containers. The container that contains the boxes is overlaid on top of the container for lines. This achieves a nice effect of lines that seem to start from the edge of the box. Here you can see the connecting line and the box in different configurations

    image_3

  3. There is a private DependencyProperty on the SpiderWebItem called LinkedConnectorProperty. This is used to store the connector (Line) instance.

  4. The SpiderWebControl makes use of the HierarchicalDataTemplate from the Silverlight Toolkit to expand nested levels (sub-branches)

Sidebar

Some of the ideas in the above list are discussed in more detail in my book “WPF Control Development Unleashed”.

image_4

Although the concepts are WPF specific, the ideas apply equally well to Silverlight.

Demo and code

I am sure you will agree that this post would be incomplete without a real demo and source code :-)

Get Microsoft Silverlight

Download Source code

Quick Tip About Changing SketchFlow’s Startup Page Name (Silverlight)

When working in SketchFlow for SilverLight 3, the default file name of the test page is called TestPage.html. If this is not what you want, you can easily change it in the main SketchFlow project’s .csproj file. If you open up the <project-name\>.csproj file, at around line 27, you should see a tag called <TestPageFileName>. Change this to the filename you want. In my case I wanted index.html since I was hosting the prototypes on the intranet and wanted a simple url like

http://sketch/

image

Hope this helps some folks.

PyBinding Now Hosted on CodePlex

Almost a year back, I posted about an interesting project I was involved in, called PyBinding. PyBinding gives you an easy way to write IronPython snippets inside Xaml Bindings and completely do away with value converters.

image

I am glad to announce that this project is finally online on CodePlex. Since the time I posted, it has undergone many changes and my colleague Andrew Kutruff has been maintaining it.

Visit PyBinding on CodePlex to learn more !

Sketching, Painting and Figure Drawing

image

On my recent vacation trip to India, I had some time to get back to my old hobby of sketching and painting. A hobby that I discontinued back in 2009. In fact I missed it so much that the feel of getting back to pencil and paper was great. Photoshop and Illustrator can definitely help you make some great illustrations but nothing can substitute the pure joy of using simple materials for drawing.

In 2010, I have decided to allocate more time to sketching and re-discover a new form of relaxation. To get my juices flowing, I am referring to the book: Drawing The Head and Figure by Jack Hamm. In my opinion this is an awesome book on Figure drawing and I just love the illustrative guidance on drawing different parts of the body. Note that this is not a beginner book on figure drawing. If you are curious to expand your knowledge on drawing the human figure, I suggest getting the book: Figure Drawing for Dummies. Although it is a dummies book, it provides an excellent introduction to the subject and inspires you to learn and draw more.

My Sketchbook

The following scans show a few sketches from my sketchbook.

  • Applying the rule of fifth’s for the head proportions. Each feature of the head is some ratio of the eye-width. 5 eye-widths represents the of the head.

image

image

image

  • The logo of the Windows maker …

image

  • Taking a shot at Manga style heads …

image

  • Some perspective drawing …

image

  • Sketching myself at the act of the sketching myself …

image

  • At my in-laws, this was the table where we used to have our meals. (Click image for a full-size view )

image

  • Finally, a depiction of a Hindu-deity that represents an incarnation of four gods in one form: Govinda + Siva + Rama + Krishna. The mark on the forehead, tall golden crown, and the lotus and conch to the side represent Lord Govinda. The crescent moon on the crown, tiger skin around the waist, Trident on the right hand and the snake around the neck represent Lord Siva. The Bow on the left hand represents Lord Rama. The violet skin and the flute represents Lord Krishna. This drawing is an adaptation of a painting done by the famous Indian illustrator: Bapu. (Click image for a full-size view )

Govinda-Siva-Rama-Krishna

Bug Fix in the Sample on Virtualization in WPF Control Development Unleashed

I recently received a mail from Marshall Price, one of the readers of my book WPF Control Development Unleashed, pointing at a bug in the sample from Chapter 8 (Virtualization). He had made some changes to the sample code in order to delete items from the StaggeredPanel. This caused a crash in the virtualized panel.

image

(StaggeredPanel from Chapter 8 – Virtualization)

StaggeredPanel derives from VirtualizingPanel and there is a particular virtual method, OnItemsChanged, that was not implemented. OnItemsChanged is called every time an item is added/removed/moved/replaced in the source collection. When an item is removed (the case for which the panel was crashing), the panel is supposed to delete the UI container associated with the item. This ensures that the internal data structures are in sync with the source collection.

Providing the override for OnItemsChanged avoids the crash. Notice the use of RemoveInternalChildRange to remove the associated UI container(s).

1
2
3
4
5
6
7
8
9
10
11
12
13
protected override void OnItemsChanged(objectsender, ItemsChangedEventArgs args)
{
  base.OnItemsChanged(sender, args);
  
  switch(args.Action)
  {
      case NotifyCollectionChangedAction.Remove:
      case NotifyCollectionChangedAction.Move:
          RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
          break;
  }
}
  

If you have encountered this crash, please add this snippet to StaggeredPanel.cs and you should be fine. Incidentally I also noticed that loading StaggeredPanelTester.xaml in Expression Blend causes an exception. To circumvent that, you need to add this line at the beginning of the method: MeasureOverride, in StaggeredPanel.cs:

1
2
3
if (availableSize.Width == double.PositiveInfinity ||
  availableSize.Height == double.PositiveInfinity)
      return new Size();

This line ensures that we don’t return double.PositiveInfinity as the desired size of the panel, which is the cause of the crash in Blend.

If you find any other bugs in the sample code, please feel free to email me and I’ll fix it immediately.