Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / XAML

CharmFlyout – The Design

0.00/5 (No votes)
29 Oct 2012CPOL3 min read 7.9K  
This post describes the design of the CharmFlyout custom control, discussing both the C# and XAML that achieves the desired functionality.

This post describes the design of the CharmFlyout custom control, discussing both the C# and XAML that achieves the desired functionality.

Posts in this series:

CharmFlyout is a custom control with the following dependency properties:

  • bool IsOpen – Set this to true to show the flyout, and false to close the flyout. What could be easier?
  • double FlyoutHeight – This read-only property is automatically set by CharmFlyout to match the height of the screen.
  • double FlyoutWidth – Set this to either 346 (default) or 646 to remain in compliance with the Metro style. Of course, you could be a rebel and set it to 347 – no one would know.
  • string Heading – Set this to the text you want displayed on the top of the flyout.
  • ICommand BackCommand – This read-only property allows CharmFlyout to respond to the user clicking the back arrow.
  • Brush HeadingForegroundBrush – White by default. Use whatever color works best for your application.
  • Brush HeadingBackgroundBrush – Black by default.
  • Brush ContentForegroundBrush – Black by default.
  • Brush ContentBackgroundBrush – White by default.
  • CharmFlyout ParentFlyout – If this CharmFlyout is a sub-flyout, then set this to the parent CharmFlyout.

It would be tedious to paste all the code that implements these properties. They really are nothing special. Because CharmFlyout derives from ContentControl, it also inherits many other dependency properties. One important property is:

  • object Content – This should contain the user interface elements that make up the bulk of the flyout content.

So then, all that is left to discuss regarding CharmFlyout’s C# code is this:

CharmFlyout.cs

C#
[ContentProperty(Name = "Content")]
public sealed class CharmFlyout : ContentControl
{
    public CharmFlyout()
    {
        DefaultStyleKey = typeof(CharmFlyout);
        FlyoutWidth = 346; // or 646
        BackCommand = new RelayCommand(OnBack);
        this.SizeChanged += OnSizeChanged;
        HeadingBackgroundBrush = new SolidColorBrush(Colors.Black);
        HeadingForegroundBrush = new SolidColorBrush(Colors.White);
        ContentBackgroundBrush = new SolidColorBrush(Colors.White);
        ContentForegroundBrush = new SolidColorBrush(Colors.Black);
    }

    void OnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        FlyoutHeight = e.NewSize.Height;
    }

    private void OnIsOpenChanged()
    {
        if (ParentFlyout != null && IsOpen)
        {
            ParentFlyout.IsOpen = false;
        }
    }

    private void OnBack(object obj)
    {
        IsOpen = false;

        if (ParentFlyout != null)
        {
            ParentFlyout.IsOpen = true;
        }
        else
        {
            SettingsPane.Show();
        }
    }

The first line (ContentProperty) is simply a convenience for the user of this control. It specifies which dependency property should be set if the user simply adds content to the XAML of this control. For example, without the line, the user would have to add their own content to the flyout like this:

XML
<cfo:CharmFlyout>
    <cfo:CharmFlyout.Content>
        <TextBlock
           Text="My Content" />
    </cfo:CharmFlyout.Content>
</cfo:CharmFlyout>

With the line, the user can add their own content with less clutter:

XML
<cfo:CharmFlyout>
    <TextBlock
       Text="My Content" />
</cfo:CharmFlyout>

The next line of interest is the one:

C#
DefaultStyleKey = typeof(CharmFlyout);

Setting the DefaultStyleKey causes the style “local:CharmFlyout” defined in Generic.xaml to be associated with this custom control. I will show the entire style a bit later in this post. For now, just know that there is a back button defined in the style that looks like this:

XML
<Button
   Command="{TemplateBinding BackCommand}"

When the user clicks this button, we want to set IsOpen to false. We also want to re-show the settings pane if this is a root-level flyout. If this is a sub flyout, we want to re-show the parent flyout. Since the Command handler for the button is already bound to the BackCommand dependency property in CharmFlyout, we just need to associate this code with BackCommand:

C#
private void OnBack(object obj)
{
    IsOpen = false;

    if (ParentFlyout != null)
    {
        ParentFlyout.IsOpen = true;
    }
    else
    {
        SettingsPane.Show();
    }
}

For more information on RelayCommand, see Josh Smith’s WPF Apps With The Model-View-ViewModel Design Pattern.

To ensure that the height of the flyout PopUp tracks the height of the window that contains CharmFlyout, we respond to changes in the window size of this custom control by setting FlyoutHeight. The Popup in Generic.xaml then binds to this height.

By the way, it would seem that we could dispense with FlyoutHeight and just bind to ActualHeight. However, in practice that does not seem to work very well in Metro. I have not looked into why this does not work.

Probably now is as good a time as any to show some portions of Generic.xaml. First, the back button is a work of art. It is a 50x50 sized target with a circle and an arrow. It took a while to get the font size and margins just right. Compared to this, the style for the CharmFlyout is downright boring. It consists of a PopUp with various embedded borders and grids that eventually make up the entire flyout.

Generic.xaml

XML
<ResourceDictionary
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:local="using:CharmFlyoutLibrary">
    <Style
       x:Key="BackButtonStyle"
       TargetType="Button">
        <Setter
           Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid
                       Width="50"
                       Height="50">
                        <TextBlock
                           Text="?"
                           FontFamily="Segoe UI Symbol"
                           FontSize="41"
                           Margin="8,-5,-8,5" />
                        <TextBlock
                           Text="?"
                           FontFamily="Segoe UI Symbol"
                           FontSize="16"
                           Margin="16,14,-16,-14" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style
       TargetType="local:CharmFlyout">
        <Setter
           Property="Template">
            <Setter.Value>
                <ControlTemplate
                   TargetType="local:CharmFlyout">
                    <Popup
                       Width="{TemplateBinding FlyoutWidth}"
                       Height="{TemplateBinding FlyoutHeight}"
                       IsOpen="{TemplateBinding IsOpen}"
                       IsLightDismissEnabled="True"
                       HorizontalAlignment="Right">
                        <Border
                           Width="{TemplateBinding FlyoutWidth}"
                           Height="{TemplateBinding FlyoutHeight}">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition
                                       Height="80" />
                                    <RowDefinition
                                       Height="*" />
                                </Grid.RowDefinitions>
                                <Border
                                   Background="{TemplateBinding HeadingBackgroundBrush}">
                                    <StackPanel
                                       Margin="29,19,5,0"
                                       Orientation="Horizontal">
                                        <StackPanel.Transitions>
                                            <TransitionCollection>
                                                <EntranceThemeTransition />
                                            </TransitionCollection>
                                        </StackPanel.Transitions>
                                        <Button
                                           Command="{TemplateBinding BackCommand}"
                                           Style="{StaticResource BackButtonStyle}"
                                           Foreground="{TemplateBinding HeadingForegroundBrush}" />
                                        <TextBlock
                                           Margin="0,10,0,5"
                                           Text="{TemplateBinding Heading}"
                                           VerticalAlignment="Top"
                                           FontFamily="Segoe UI"
                                           FontSize="28"
                                           FontWeight="Thin"
                                           LineHeight="30"
                                           Foreground="{TemplateBinding HeadingForegroundBrush}" />
                                    </StackPanel>
                                </Border>
                                <Grid
                                   Grid.Row="1"
                                   Background="{TemplateBinding ContentBackgroundBrush}">
                                    <Rectangle
                                       Fill="{TemplateBinding ContentForegroundBrush}"
                                       Width="1"
                                       HorizontalAlignment="Left" />
                                    <Border
                                       Margin="40,26,30,30">
                                        <ContentPresenter
                                           Foreground="{TemplateBinding ContentForegroundBrush}">
                                            <ContentPresenter.Transitions>
                                                <TransitionCollection>
                                                    <EntranceThemeTransition />
                                                </TransitionCollection>
                                            </ContentPresenter.Transitions>
                                        </ContentPresenter>
                                    </Border>
                                </Grid>
                            </Grid>
                        </Border>
                    </Popup>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style
       TargetType="local:CharmFrame">
        <Setter
           Property="HorizontalContentAlignment"
           Value="Stretch" />
        <Setter
           Property="IsTabStop"
           Value="False" />
        <Setter
           Property="VerticalContentAlignment"
           Value="Stretch" />
        <Setter
           Property="Template">
            <Setter.Value>
                <ControlTemplate
                   TargetType="local:CharmFrame">
                    <Grid>
                        <Border
                           BorderBrush="{TemplateBinding BorderBrush}"
                           BorderThickness="{TemplateBinding BorderThickness}"
                           Background="{TemplateBinding Background}">
                            <ContentPresenter
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               ContentTransitions="{TemplateBinding ContentTransitions}"
                               Content="{TemplateBinding Content}"
                               HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                               Margin="{TemplateBinding Padding}"
                               VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                        </Border>
                        <ContentPresenter
                           Content="{TemplateBinding CharmContent}" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Here is a comparison of the CharmFlyout vs. the Permissions flyout:

ap

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)