Monday, December 27, 2010

Create custom windows in WPF with ease

One of the features I wanted to add to Synergy toolkit was the ability to quickly create custom theme windows with all the features of a standard windows. In this article I am demonstrating how to create a custom window theme visually using declarative XAML and apply it to windows in your applications.

You can download Synergy SDK with full source code here.
You may also want to see my window docking solution in Synergy here.
I will be posting updates on Synergy SDK on my twitter account: MixModes

The sample application Synergy uses main window as the custom window whose theme is defined within Windows.xaml resource dictionary within MixModes.Synergy.Themes project.

Declarative is the key

As we all know XAML is declarative and simple and so there is no reason why traditional approach of writing code for windows and controls should apply. One must be able to simply create a visual and stick it in a control template to get things working. That was exactly my motivation when I started out developing look-less window functionality in Synergy.

Declare a template

The first thing you may want to do in creating a custom window is to actually create a visual template. The easiest way to create this template is to create a user control in Microsoft Blend and then define extension points in XAML that look-less control will liven up once the template is applied. Once the visual is ready all that needs to be done is a style creation for CustomWindow where the template can be pasted and then the temporary user control can be discarded.

Following extension points are supported for current implementation:

  • PART_TITLEBAR (UIElement) - For displaying window title, dragging and maximize / restore operations
  • PART_MINIMIZE (Button) – Window minimize button
  • PART_MAXIMIZE_RESTORE (Button) – Maximize restore button
  • PART_CLOSE (Button) – Close button
  • PART_LEFT_BORDER (UIElement) – Left resizable border
  • PART_RIGHT_BORDER (UIElement) – Right resizable border
  • PART_TOP_BORDER (UIElement) – Top resizable border
  • PART_BOTTOM_BORDER (UIElement) – Bottom resizable border

One more thing to note is that while defining the window template, you must declare the ContentPresenter (which ultimately contains window content) within AdornerDecorator tag (which is the adorner layer for the window) as this is WPF requirement.

Here is the template I have created within Windows.xaml resource dictionary within MixModes.Synergy.Themes project:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="MainWindow"
           TargetType="{x:Type Window}">
        <Setter Property="Foreground"
                Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Grid>
                        <Border x:Name="MainBorder"
                                BorderBrush="{DynamicResource MainWindowBorderBrush}"
                                BorderThickness="1"
                                CornerRadius="2"
                                Background="{DynamicResource MainWindowBackgroundBrush}">
                            <DockPanel LastChildFill="True">
                                <Rectangle x:Name="PART_LEFT_BORDER"
                                           Width="2"
                                           Cursor="SizeWE">
                                    <Rectangle.Fill>
                                        <SolidColorBrush Color="Transparent" />
                                    </Rectangle.Fill>
                                </Rectangle>
                                <Rectangle x:Name="PART_RIGHT_BORDER"
                                           Cursor="SizeWE"
                                           Width="2"
                                           DockPanel.Dock="Right">
                                    <Rectangle.Fill>
                                        <SolidColorBrush Color="Transparent" />
                                    </Rectangle.Fill>
                                </Rectangle>
                                <Rectangle x:Name="PART_TOP_BORDER"
                                           Cursor="SizeNS"
                                           DockPanel.Dock="Top"
                                           Height="2">
                                    <Rectangle.Fill>
                                        <SolidColorBrush Color="Transparent" />
                                    </Rectangle.Fill>
                                </Rectangle>
                                <Rectangle x:Name="PART_BOTTOM_BORDER"
                                           Cursor="SizeNS"
                                           Height="2"
                                           DockPanel.Dock="Bottom">
                                    <Rectangle.Fill>
                                        <SolidColorBrush Color="Transparent" />
                                    </Rectangle.Fill>
                                </Rectangle>
                                <Border x:Name="PART_TITLEBAR"
                                        Margin="2,0,2,2"
                                        Height="40"
                                        DockPanel.Dock="Top"
                                        CornerRadius="2"
                                        Background="Transparent">
                                    <DockPanel LastChildFill="False">
                                        <TextBlock Margin="8,0,0,4"
                                                   VerticalAlignment="Center"
                                                   FontStretch="UltraExpanded"
                                                   Foreground="Black"
                                                   TextTrimming="CharacterEllipsis"
                                                   TextWrapping="NoWrap"
                                                   Text="{TemplateBinding Title}"
                                                   FontSize="16" />
                                        <Button x:Name="PART_CLOSE"
                                                DockPanel.Dock="Right"
                                                Style="{DynamicResource FlatButton}"
                                                VerticalAlignment="Center"
                                                Margin="0,0,4,0">
                                            <Image Source="/MixModes.Synergy.Resources;component/Resources/Close.png"
                                                   Stretch="None"
                                                   Margin="4" />
                                        </Button>
                                        <Button x:Name="PART_MAXIMIZE_RESTORE"
                                                DockPanel.Dock="Right"
                                                HorizontalAlignment="Center"
                                                VerticalAlignment="Center"
                                                Style="{DynamicResource FlatButton}">
                                            <Image x:Name="MaximizeRestoreImage"
                                                   Source="/MixModes.Synergy.Resources;component/Resources/Restore.png"
                                                   Stretch="None"
                                                   Margin="4" />
                                        </Button>
                                        <Button x:Name="PART_MINIMIZE"
                                                HorizontalAlignment="Center"
                                                Style="{DynamicResource FlatButton}"
                                                VerticalAlignment="Center"
                                                DockPanel.Dock="Right">
                                            <Image Margin="4"
                                                   Source="/MixModes.Synergy.Resources;component/Resources/Minimize.png"
                                                   Stretch="None" />
                                        </Button>
                                    </DockPanel>
                                </Border>

                                <!-- Title bar separator-->
                                <Border Height="1"
                                        DockPanel.Dock="Top"
                                        Background="{DynamicResource MainWindowTitleBarSeparator}" />

                                <!-- Actual Window Content -->
                                <AdornerDecorator DockPanel.Dock="Bottom">
                                    <ContentPresenter />
                                </AdornerDecorator>
                            </DockPanel>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>                       
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Maximized}"
                                     Value="False">
                            <Setter TargetName="MaximizeRestoreImage"
                                    Property="Source"
                                    Value="/MixModes.Synergy.Resources;component/Resources/Maximize.png" />
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

This is a pretty simple theme which creates a rounded rectangle window with 2 pixel wide resizers and custom window minimize, maximize and restore buttons. It also contains a template trigger that changes the image of maximize restore button if window is not maximized.

The window created using this theme looks like the following:

image

Inherit from CustomWindow class

The final step is to inherit from CustomWindow class (which in turn inherits from Window class) instead of directly inheriting from Window class and refer to the style created in the previous step. This is again very simple:

  • Import visual framework namespace:
    xmlns:visualFx="http://mixmodes.com/visualFx"
  • Inherit from CustomWindow class:
    use “visualFx:CustomWindow” as your window tag in XAML
  • Refer to the style created in previous step in your visualFx:CustomWindow tag:
    Style="{DynamicResource MainWindow}"

That’s all you have to do to get your custom window working !

How does it work?

If you crack open the CustomWindow class you will see that bulk of the work happens in the AttachToVisualTree method which is called from OnApplyTemplate (which in turn is called anytime template is applied to our custom window).

AttachToVisualTree in turn calls AttachCloseButton, AttachMaximizeButton, AttachMaximizeRestoreButton, AttachTitleBar and AttachBorders methods, each of which queries for visual parts (the PART_… named parts we defined in the template) and attaches functionality via events.

So that’s it ! Creating custom windows using Synergy is really that simple !

Building a docking window management solution in WPF

Window docking is a familiar functionality in multi-windows applications. As a user interface developer this behavior has always charmed me and so I thought of developing the same functionality in my own WPF toolkit. I know there are many implementations of similar solutions out there, some even open source, but my aim was to take this personal project as a challenge and learning opportunity.

I should also mention that the docking solution I have implemented is part of a bigger WPF toolkit that I am building on an ongoing basis under my company MixModes Inc, however I intend to keep it open source and royalty free project. To preview the full feature set of this library you can visit my blog here.

Many existing implementations of window docking solutions have floating windows as separate windows managed under MDI parent window. However I have kept floating windows contained strictly within parent window as I intend to port this code to Silverlight soon.

You may download the source code here.

Anatomy of Synergy Toolkit

MixModes Synergy toolkit consists of following top level projects:

  • MixModes.Synergy.Resources – Contains image and language resources
  • MixModes.Synergy.Themes – Defines default themes for user controls, custom windows, colors, brushes and text themes
  • MixModes.Synergy.Utilities – Common utilities
  • MixModes.Synergy.VisualFramework – Contains behaviors, adorners, commands, user controls, docking framework and other WPF specific functionality
  • Synergy – Sample project highlighting features of the toolkit

Anatomy of a Dockable window

To understand window docking solution, it is necessary to understand the very primitive control that drives it all – the dockable window. A dockable window is a special window that in addition to content and title can be in following several states:

1. Pinned state – Dockable window can be pinned to the side of parent window to have consistent visibility. Usually frequently used content is pinned for easier and constant access.

image

2. Auto hidden state – Less frequently used windows can be auto hidden so when mouse is not hovering over them they collapse into a condensed form (which I refer to as header). When mouse hovers over the headers, full window slides out from the docked side.

image

3. Document state – When used as a document, dockable windows can merge as tab items within a tab control.

image

4. Floating – Usual floating windows

image

Docking window support is provided by DockPane control that is derivative of HeaderedContentControl. In addition to Header and Content properties that it inherits from HeaderedContentControl, it also contains the following properties:

  • Icon – Icon for the docked window
  • CondencedDockPanelTemplate – Template for condensed DockPane
  • CondencedDockPanel – Condenced form of DockPane
  • DockPaneState – State of the dock pane

DockPane also contains the following events:

  • Close – Close event
  • TogglePin – Toggling of pin / auto-hide button
  • HeaderDrag – Notification that user has started to drag header (title) of the DockPane

Default theme for DockPane is defined in the DockPane.xaml resource dictionary within MixModes.Synergy.Themes project.

Document Containers

DockPane(s) are contained within document containers. Document container is modeled via DocumentContainer class which is derivative of ContentControl. DocumentContainer can be in one of the following (mutually exclusive) states:

  • Empty – DocumentContainer does not contain any DockPane
  • ContainsDocuments – DocumentContainer contains one or more DockPane(s) as documents
  • SplitHorizontally – DocumentContainer is split horizontally
  • SplitVertically - DocumentContainer is split vertically

image

DocumentContainer is complex in terms of its template since it is required to represent either a split view or a tabbed view. I have used a persistent TabControl within the template that is hidden if Content property is ever non-null. Content of-course is used exclusively to contain split views via Grid with two children DocumentContainer.

DocumentContainer contains the following properties:

  • State – State of the DocumentContainer
  • Documents – contains documents represented in TabControl
  • DocumentsTab – TabControl containing documents
  • DockIllustrationPanel – This is a panel which contains docking illustration points indicating to the user where a content should be docked. If user drags a DockPane to any one of these points it gets docked at the respective position. The image below shows content of the DockIllustrationPanel:

    image

DocumentContainer contains the following methods:

  • AddDockPane(DockPane, ContentDockPoint) – Adds a DockPane as a docked window
  • AddDocumentContainers(IEnumerable<DocumentContainer>, bool) – Splits and add child DocumentContainers
  • AddDocument(DockPane) – Adds a DockPane as a tabbed document
  • RemoveDocument(DockPane) – Removes a DockPane as a tabbed document

The template of DocumentContainer contains the following visuals in layers (bottom visuals are in increasing Z-Order):

  • TabControl (PART_DOCUMENTS) – Bound to Documents property of DocumentContainer
  • ContentPresenter – This is where split children are added
  • Grid (PART_DOCK_POINTS) – Panel for hosting dock illustration points
  • Grid (PART_DOCK_ILLUSTRATION) – DockIllustrationPanel for illustrating future docking via cues

Windows Manager

Windows manager is the component that binds DockPanel(s) and DocumentContainer(s) together to provide window management functionality in applications. In addition, window manager contains auto-hide and pinned dock points on all four window sides so DockPane(s) can be pinned or auto hidden outside of the DocumentContainer(s). WindowsManager also contains the root DocumentContainer that can host documents in tab control or nested-split DocumentContainer instances hosting documents.

WindowsManager has the following properties:

  • DockPaneIllustrationStyle – Illustration for docking a window within WindowsManager or DocumentsContainer
  • DockIllustrationContentStyle – Illustration for merging documents while dragging a DockPane in TabControl
  • ActiveWindowsManager – Static property indicating a WindowsManager undergoing the drag operation
  • DraggedPane – DockPane that is being dragged
  • <Orientation>WindowHeaders – StackPanel containing condensed auto-hidden DockPane(s)
  • <Orientation>PinnedWindows – DockPanel containing pinned DockPane(s)
  • DocumentContainer – Root document container
  • DockingIllustrationPanel – Docking illustration panel for future pinned DockPanel(s)
  • PopupArea – DockPanel where auto-hidden DockPane(s) slide out when mouse hovers upon the condensed headers
  • FloatingPanel – Canvas that contains floating DockPane(s)
  • DockingPanel – DockPanel that contains dock points for pinned windows as shown in image below:

    image

WindowsManager has the following methods:

  • AddPinnedWindow(DockPane, Dock) – Adds a pinned DockPane
  • AddAutoHideWindow(DockPane, Dock) – Adds an auto-hidden DockPane
  • AddFloatingWindow(DockPane) – Adds a floating DockPane
  • RemoveDockPane(DockPane) – Removes a DockPane from (pinned, auto-hidden or floating portion of ) WindowsManager
  • Clear – Clears the WindowManager of all DockPane(s)
  • StartDockPaneStateChangeDetection – Starts state monitoring for DraggedPane
  • StopDockPaneStateChangeDetection – Stops state monitoring for DraggedPane

How it all works together

The image below illustrates the relationship between various components of the docking solution:

1

Structurally WindowsManager is the encompassing component that contains pinned and auto-hidden DockPane(s). It also contains the root DocumentContainer. DocumentContainer on the other hand can either contain documents in the tab control by wrapping DockPane within DocumentContent instance or it can contain split windows where a grid holds child DocumentContainer(s) each of which recursively can either contain documents or further child DocumentContainer(s).

WindowsManager constantly monitors the state change of DockPane. When a DockPane drag is detected, it is placed on the FloatingPanel canvas of WindowsManager as a floating window that can be dragged around. During a drag of a DockPane hit testing is turned off on the DockPane so mouse events can flow down to controls below it such as WindowsManager and DocumentContainer.

For orchestrating drag and drop and docking functionality I have used a behavior driven approach. The idea is to expose functional end-points (such as methods and properties) on visual entities such as DockPane, DocumentContainer and WindowsManager and use behaviors to orchestrate and call these functional end-points. This approach also resulted in manageable-encapsulated components that were easier to trace and test.

DockPointBehavior monitors the dragged DockPane over WindowsManager and pops up dock points for pinning it. ContentPointBehavior on the other hand injects similar functionality within DocumentContainer for splitting and tab merging purpose.

Both WindowsManager and DocumentContainer have dock illustration grid containing the docking behavior. DockPointBehavior behavior illustrates pinned docking illustration on  WindowsManager whereas ContentDockBehavior illustrates splitting and tab merging illustration on DocumentContainer.

Using the code

Using WindowsManager is extremely simple:

  • Import the namespace in the xaml
    xmlns:visualFx="http://mixmodes.com/visualFx"
  • Drop the WindowsManager in the xaml
    <visualFx:WindowsManager x:Name="WindowsManager"/>
  • Start creating DockPane and insert them within WindowsManager / DocumentContainer
    DockPane pane = new DockPane();
    pane.Header = …
    pane.Content = ….
    WindowsManager.AddPinnedWindow(pane, Dock.Top);
    // OR
    WindowsManager.AddAutoHideWindow(pane, Dock.Left);
    // OR
    // Assuming DocumentContainer is either in Empty or ContainsDocuments state
    WindowsManager.DocumentContainer.AddDocument(pane);

Serializing Window State

Out of the box, Synergy provides XML serialization of window states through XmlWindowsManagerSerializer and XmlWindowsManagerDeserializer classes. Custom serialization is supported via specialization of base classes WindowsManagerSerializer and WindowsManagerDeserializer respectively.

XML serialization of WindowsManager using XmlWindowsManagerSerializer requires two pieces of information during construction:

  • DockPane writer – An Action<XmlElement, DockPane> instance that can write additional metadata about a DockPane to the XmlElement.

  • Document writer – A Func<DocumentContent, string> instance that takes in a DocumentContent and returns a string representation of the content. Note: DocumentContent.DockPane property returns the associated DockPane, however the Header and Content properties of DockPane are set to null. To access Header and Content property, use the Header and Content properties of DocumentContent instance directly.

Once XmlWindowsManagerSerializer instance is created, a call to Serialize(Stream, WindowsManager) method serializes WindowsManager to the stream.

Similar to serialization process, deserialization process requires an instance of Action<DockPane, string> within the constructor of XmlWindowsManagerDeserializer to de-serialize a DockPane from previously saved string representation. Deserialization does not require additional Action to realize DocumentContent since DocumentContent is inherently a serialization wrapper for DockPane.

Once XmlWindowsManagerDeserializer  instance is created, a call to Deserialize(Stream, WindowsManager) deserializes the WindowsManager to previously saved state.

All the docking functionality can be exercised by using the sample app (Synergy project) with the source code. Any comments or suggestions or bug-reports are as usual always welcome. Happy coding !

Thursday, December 23, 2010

Dynamically bind value converters to elements in XAML

Recently I faced a scenario where I wanted to create a generic control template in WPF for a user control that refers to value converters dynamically in order to render parts of the control.

Depending on the enumeration passed as the parameter, converter is supposed to render different aspect of the object. The actual formatter is unknown at the base control level except for the enumeration.

Concrete implementation/usage of the control should specify the converter as its property. I tried doing this in XAML:

SomeProperty = {Binding Converter={Binding MyConverterProperty} }

The above will result in runtime exception as Converter property is not DependencyProperty within BindingMarkupExtension.

There are three ways of achieving above:

 

1. Create a virtual converter and have object implement IConvertable:

One can declare IConvertable interface that provides a converter:

public interface IConvertable
{
    IValueConverter Converter {get;}
}

and a "virtual" converter

public class VirtualConverter : IValueConverter
{

    public void Convert(object value, ....)
    {
        IValueConverter converter;
        if ((value is IConvertible) && ((converter = (value as IConvertible) != null)))
        {
            converter.Convert(....);
        }
        throw new InvalidOperationException();
    }
    :
    :
}

And now you can create a static resource of type VirtualConverter and use Binding normally specifying VirtualConverter as the converter.

 

2. Implement IConvertible but instead of creating Virtual converter, declare a markup extension that takes in IConvertible and returns IValueConverter. Then your markup will look like:

SomeProperty = "{Binding Converter={ConverterExtension Source={Binding}}}"

 

3. Use attached property to define Converter on an object rather than implementing IConvertible and specify it as converter in binding.

Tuesday, December 14, 2010

Introducing MixModes Synergy 2010

For the past few months I have been engaged in the development of Windows Presentation Foundation (WPF) based SDK called MixModes Synergy 2010, under my company MixModes Inc. Synergy is a WPF toolkit that unleashes the power of WPF via simpler programming constructs and provides out of the box custom controls that allows Rapid Application Development of line of business applications. MixModes Synergy 2010 can be downloaded here.

This SDK will be ongoing development that MixModes Inc. will pursue to provide best possible support for WPF and Silverlight development. Some of the features in version 1.0 include:

  • Base framework for WPF development
  • Easy and type-safe adoption of Model-View-ViewModel pattern
  • Custom visual adorners
  • Logical behaviors
  • Out of the box custom controls

MixModes Synergy 2010 is free for all uses and is MixModes Inc.’s initiative to contribute to the developer community. In this blog I will uncover the solutions Synergy provides in each of the above categories.

 

Base framework for WPF development

Commanding

CommandBase class provides a flexible implementation for custom commands. This class works on an Action<object> instance for command invocation and an optional Predicate<object> for command execution evaluation. If predicate is not specified, it is assumed that command can execute at any time. Also CommandBase exposes CanExecuteChanged event that is registered with CommandManager which then re-queries validity of command execution based on user interaction with the application.

Simply speaking one can instantiate a command base class as follows:

CommandBase myCommand = new CommandBase(arg=>DoFoo(arg), arg=> CanFoo(arg));

 
FrameworkElement extensions

FrameworkElementExtensions class contains a list of extension methods for FrameworkElement instances that help in finding logical and visual parents of specified type, clearing adorner layer for element, enforcing element size and displaying tooltips very easily.

 

Window enhancements

CustomWindow class provides a fast way to create a custom window by just declaring a template for custom window and specifying the named parts for window controls.

 

Dialog window enhancements

DialogWindow provides a true dialog box look and feel for modal dialogs by setting appropriate window style on the dialogs. This fixes WPF bugs around inability of dialog windows to hide control box.

 

DependencyProperty change detection

ObservableDependencyProperty class can monitor change in a dependency property of a dependency object without need for separate events. To monitor changes in a dependency property for a dependency object one needs to supply a callback delegate of type DependencyPropertyChangedEventHandler. Following code illustrates usage of ObservableDependencyProperty:

ObservableDependencyProperty descriptor = new ObservableDependencyProperty(
typeof(string), 
TextBox.TextProperty,
OnTextChanged);

descriptor.AddValueChanged(myTextBox);

void OnTextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
       TextBox changedBox = sender as TextBox;
       string oldValue = e.OldValue as string;
       string newValue = e.NewValue as string;
}

If monitoring of a specific dependency property on a list of elements is required, ObservableDependencyPropertyCollection provides the necessary plumbing for ease of tracking changes.

_dockPaneStateMonitorList = new ObservableDependencyPropertyCollection<DockPane>(DockPane.DockPaneStateProperty);
_dockPaneStateMonitorList.DependencyPropertyChanged += OnDockPaneStateChanged;

_dockPaneStateMonitorList.Add(myDockPane);

private void OnDockPaneStateChanged(object sender, DependencyPropertyChangedEventArgs e)
{
            DockPane pane = sender as DockPane;
            DockPaneState state = (DockPaneState)e.NewValue;
}

 

Easy and type-safe adoption of Model-View-ViewModel pattern

ViewModelBase class provides a type safe RaisePropertyChanged method for derived classes and hence provides compile time property name checks. Rather than passing in a string as the property name, derived classes raise PropertyChanged event as follows:

RaisePropertyChanged(x=>this.MyProperty);

 

Custom visual adorners

MixModes Synergy allows for visual creation of adorners. In fact any visual can be transformed into a adorner with practically few lines of code. All this magic is done through the use of ContentAdornerBase class that makes creating visual adorners no different than creating a user control. It just takes a few steps to create a visual adorner:

  • Create a visual resource in your resource dictionary – This can be done using Blend designer. Ensure that x:Shared is set to False on the resource. This means that anytime you host the resource on multiple elements, a copy of visual tree shall be created preventing duplicate parenting issues.
  • If the resource dictionary is different from default application resource dictionary, place a link in the merged dictionary section of default application dictionary to load it.
  • Derive a class from ContentAdornerBase; load and pass in the visual resource from resource dictionary and pass it onto the base constructor.
  • Add the derivative class to adorned element’s adorner layer.

As an example, let’s assume that you want to create a reusable adorner that helps resize adorned element. Following the steps above you could define a resource in the resource dictionary ResizableAdorner.xaml:

<Grid x:Key="ResizableAdorner" x:Shared="False">
:
</Grid>

Link ResizableAdorner.xml to your application resource dictionary as one of the merged dictionary:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ResizableAdorner.xaml" />
        :
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Create a class ResizingAdorner deriving from ContentAdornerBase as follows:

/// <summary>
/// Resizing adorner
/// </summary>
internal class ResizingAdorner : ContentAdornerBase
{
        /// <summary>
        /// Initializes a new instance of the <see cref="ResizingAdorner"/> class.
        /// </summary>
        /// <param name="adornedElement">The element to bind the adorner to</param>
        /// <exception cref="ArgumentNullException">adornedElement is null</exception>
        internal ResizingAdorner(UIElement adornedElement)
            : base(adornedElement, Application.Current.Resources[“ResizableAdorner”] as FrameworkElement)
        {
            AttachNamedParts();
        }
       
        /// <summary>
        /// Attaches the named parts
        /// </summary>
        private void AttachNamedParts()
        {
            // Use FindElement<T> to find named parts and attach behavior here
        }       
}

Finally just place the adorner to adorned element’s adorner layer:

AdornerLayer layer = AdornerLayer.GetAdornerLayer(someFrameworkElement);
if (layer != null)
{
        layer.Add(new ResizingAdorner(someFrameworkElement));
}

That’s all the steps you need to create a visual adorner !

 

Logical behaviors

Logical behavior is a new concept introduced by MixModes Synergy that decouples behavior from its visual parent. The idea is to define visual behavior in control templates that can then bind to an ancestor visual element or a template parent in attaching the behavior. To create a logical behavior, follow the following steps:

  • Derive a behavior from LogicalParentBehavior<T> class and supply the logical parent as the generic parameter during derived class definition.
  • In the overridden OnAttached method, call base.OnAttached and then access LogicalParent property of the base class to access the logical parent to attach behavior.
  • In the XAML just declare behavior in the usual fashion to any visual element.

 

Out of the box custom controls

MixModes Synergy provides out of the box custom controls that are easily consumable from your WPF based applications. All the controls released as part of MixModes Synergy 2010 SDK are completely look-less and can be custom themed on top of the standard themes that ships as part of the SDK. MixModes will release additional custom controls on a regular basis to enrich Synergy and provide out of the box support in the development for line of business applications.

One major control release in the current release is the window docking solution similar to docking windows in Microsoft Visual Studio. Using docking window control is very simple. In your XAML just add the WindowManager control:

<visualFx:WindowsManager x:Name="WindowsManager"/>

WindowsManager as the name suggests is the primary manager for windows in docking solution. Each dockable window is an instance of DockPane class which is a derivative of HeaderedContentControl. Hence DockPane instance can have a Header (which serves as the title of the DockPane) and a Content (which serves as the content of the DockPane).

DockPane can be added in many different ways to the WindowsManager:

  • As pinned window – via WindowsManager.AddPinnedWindow(DockPane, Dock) method
  • As auto hidden window – via WindowsManager.AddAutoHideWindow(DockPane, Dock) method
  • As floating window – via WindowsManager.AddFloatingWindow(DockPane) method
  • As a document in the main document area – via WindowsManager.DocumentContainer.AddDocument(DockPane) method

DocumentContainer is the component that organizes documents within WindowsManager (this excludes pinned, auto-hidden and floating windows since they are not considered documents).

MixModes Synergy 2010 also provides window state serialization and deserialization for your application environment to preserve and load the window environment of any complexity.

Out of the box, Synergy provides XML serialization of window states through XmlWindowsManagerSerializer and XmlWindowsManagerDeserializer classes. Custom serialization is supported via specialization of base classes WindowsManagerSerializer and WindowsManagerDeserializer respectively.

XML serialization of WindowsManager using XmlWindowsManagerSerializer requires two pieces of information during construction:

  • DockPane writer – An Action<XmlElement, DockPane> instance that can write additional metadata about a DockPane to the XmlElement.
  • Document writer – A Func<DocumentContent, string> instance that takes in a DocumentContent and returns a string representation of the content. Note: DocumentContent.DockPane property returns the associated DockPane, however the Header and Content properties of DockPane are set to null. To access Header and Content property, use the Header and Content properties of DocumentContent instance directly.

Once XmlWindowsManagerSerializer instance is created, a call to Serialize(Stream, WindowsManager) method serializes WindowsManager to the stream.

Similar to serialization process, deserialization process requires an instance of Action<DockPane, string> within the constructor of XmlWindowsManagerDeserializer to de-serialize a DockPane from previously saved string representation. Deserialization does not require additional Action to realize DocumentContent since DocumentContent is inherently a serialization wrapper for DockPane.

Once XmlWindowsManagerDeserializer  instance is created, a call to Deserialize(Stream, WindowsManager) deserializes the WindowsManager to previously saved state.

 

Summary

MixModes Synergy 2010 is an ongoing project and several additions will be made to the SDK based on customer requests. Everyone is encouraged to download and play with the SDK and please send me your questions and suggestions here. Thank you all very much.