.Net, C#, Material Design, UI, UX, WinApi, WPF, xaml

How to use the Material Design theme with Dragablz Tab Control

In this post I will demonstrate how to – very quickly – combine Dragablz and MaterialDesignColors in WPF to create a great looking control which supports full tear out and can use the Google Material Design colour palette.

Dragablz Tab Contrtol and Material Design
Dragablz Tab Contrtol and Material Design

Start a new WPF project.  We rely on two NuGet packages, so get them installed straight away.  Install from the Package Manager tool in Visual Studio, or, from the NuGet console run these commands:

Install-Package Dragablz
Install-Package MaterialDesignColors

In the MainWindow.xaml, setup a simple usage of Dragablz TabablzControl:

<Window x:Class="MaterialDesignTabExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dragablz="clr-namespace:Dragablz;assembly=Dragablz"
        Title="Material Design Demo" Height="350" Width="525">
    <dragablz:TabablzControl>
        <dragablz:TabablzControl.InterTabController>
            <dragablz:InterTabController />
        </dragablz:TabablzControl.InterTabController>
        <TabItem Header="HELLO">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Hello World</TextBlock>
        </TabItem>
        <TabItem Header="MATERIAL">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Material Design</TextBlock>
        </TabItem>
        <TabItem Header="DESIGN">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Looks Quite Nice</TextBlock>
        </TabItem>
    </dragablz:TabablzControl>
</Window>

Already if you run this project you will have a tab control that supports Chrome-style tearing out of tabs. But it wont look too good. So, the next step is to bring in the Material Design colours, and tell Dragablz to use the Material Design style.

Open up your App.xaml. We have to merge in three dictionaries.  The first two are to set up your Material Design colour palette.  The MaterialDesignColors assembly contains a ResourceDictionary for each color (a collection of hues and accents).  To create a full palette we need to bring in a primary colour, set up some hue brushes, and then bring in a secondary color for our accent color.  The third resource dictionary is to include the Dragablz theme for Material Design.  Finally we instruct our tab control to use the correct style.

Don’t worry, it’s not too complicated.  The full App.xaml is below:

<Application x:Class="MaterialDesignColors.WpfExample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:dragablz="clr-namespace:Dragablz;assembly=Dragablz"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!-- primary color -->
                <ResourceDictionary>
                    <!-- include your primary palette -->
                    <ResourceDictionary.MergedDictionaries>
                        <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/MaterialDesignColor.Indigo.xaml" />
                    </ResourceDictionary.MergedDictionaries>
                    <!--
                            include three hues from the primary palette (and the associated forecolours).
                            Do not rename, keep in sequence; light to dark.
                        -->
                    <SolidColorBrush x:Key="PrimaryHueLightBrush" Color="{StaticResource Primary100}"/>
                    <SolidColorBrush x:Key="PrimaryHueLightForegroundBrush" Color="{StaticResource Primary100Foreground}"/>
                    <SolidColorBrush x:Key="PrimaryHueMidBrush" Color="{StaticResource Primary500}"/>
                    <SolidColorBrush x:Key="PrimaryHueMidForegroundBrush" Color="{StaticResource Primary500Foreground}"/>
                    <SolidColorBrush x:Key="PrimaryHueDarkBrush" Color="{StaticResource Primary700}"/>
                    <SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="{StaticResource Primary700Foreground}"/>
                </ResourceDictionary>

                <!-- secondary colour -->
                <ResourceDictionary>
                    <!-- include your secondary pallette -->
                    <ResourceDictionary.MergedDictionaries>
                        <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/MaterialDesignColor.Yellow.xaml" />
                    </ResourceDictionary.MergedDictionaries>

                    <!-- include a single secondary accent color (and the associated forecolour) -->
                    <SolidColorBrush x:Key="SecondaryAccentBrush" Color="{StaticResource Accent200}"/>
                    <SolidColorBrush x:Key="SecondaryAccentForegroundBrush" Color="{StaticResource Accent200Foreground}"/>
                </ResourceDictionary>

                <!-- Include the Dragablz Material Design style -->
                <ResourceDictionary Source="pack://application:,,,/Dragablz;component/Themes/materialdesign.xaml"/>                

            </ResourceDictionary.MergedDictionaries>

            <!-- tell Dragablz tab control to use the Material Design theme -->
            <Style TargetType="{x:Type dragablz:TabablzControl}" BasedOn="{StaticResource MaterialDesignTabablzControlStyle}" />
        </ResourceDictionary>
    </Application.Resources>
</Application>

And that’s it. Fire up your baby and you are done. You can change the colours by changing the two colour resource dictionaries which are referenced. You can also tweak the hues, but do not change the brush names.  Dragablz will be looking for these.

Links:

Enjoy!

.Net, C#, UI, WinApi, WPF

Getting Windows Snap to Play with WPF Borderless Windows

In making the Dragablz library I quickly realised I needed be able to push the tabs higher up the window as we see in Chrome, and also, to achieve the IE affect, I really needed Window transparency. Therefore I introduced DragablzWindow.

DragablzWindowSnap

The easiest way to do the above is set the WindowStyle to None. But this has immediate draw backs: no dragging and resizing. Getting the dragging up and running is pretty easy and well illustrated on the web:

MouseLeftButtonDown += (s, e) => DragMove();

But this only partially works with Windows Snap. The Window will snap; left, right, top/maximised. But you cant drag the maximised window down to restore it.

Snap Attack

So I had to blend some aspects of WPF and WinApi to achieve the full effect.

In the XAML template for DragablzWindow I placed a Thumb control behind the content. Note how the hit test is off:

<Thumb Style="{StaticResource InvisibleThumbStyle}"
       IsHitTestVisible="False"
       x:Name="PART_WindowRestoreThumb"/>
<ContentPresenter Margin="4"/>

The hit test is off so it doesn’t interfere with our DragMove. Until the Window becomes maximised. Once the Window becomes maximised we enable the thumb and listen to it’s drag delta. Monitoring the drag delta is where we perform our trick, sending a Windows message to restart the drag as usual, handing back off the Thumb, to an Api instigated drag. Basically a slight re-working of what happens inside Windows.DragMove:

private void WindowMoveThumbOnDragDelta(object sender, DragDeltaEventArgs dragDeltaEventArgs)
{
    if (WindowState != WindowState.Maximized ||
        (!(Math.Abs(dragDeltaEventArgs.HorizontalChange) > 2) &&
         !(Math.Abs(dragDeltaEventArgs.VerticalChange) > 2))) return;

    WindowState = WindowState.Normal;
    Native.SendMessage(CriticalHandle, WindowMessage.WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);
    Native.SendMessage(CriticalHandle, WindowMessage.WM_SYSCOMMAND, (IntPtr)SystemCommand.SC_MOUSEMOVE, IntPtr.Zero);
}

Try this on for Resize

To make the Window resizable we get back to the WPF side of things, using another Thumb, this time laid over the content (instead of under). Applying a custom clip, we can make the thumb only hit-able around the border of the Window, leaving the behaviour of the remaining content intact:

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    var resizeThumb = GetTemplateChild(WindowResizeThumbPartName) as Thumb;
    if (resizeThumb != null)
    {
        var outerRectangleGeometry = new RectangleGeometry(new Rect(sizeInfo.NewSize));
        var innerRectangleGeometry =
            new RectangleGeometry(new Rect(ResizeMargin, ResizeMargin, sizeInfo.NewSize.Width - ResizeMargin * 2, sizeInfo.NewSize.Height - ResizeMargin*2));
        resizeThumb.Clip = new CombinedGeometry(GeometryCombineMode.Exclude, outerRectangleGeometry,
            innerRectangleGeometry);
    }

    base.OnRenderSizeChanged(sizeInfo);
}

We must now handle the sizing manually, based on how the user drags the thumb around. It’s worth seeing the source code to see how that’s handled.

The Result

A Window which supports:

  • Transparancy
  • Dragging
  • Resizing
  • Snapping
  • And, all of the cool tab features of Dragablz!

DragablzWindowSnap

Source Code

The DragablzWindow is part of the Dragablz project on GitHub. And the style is here.