Maxime FRAPPAT

Hum …no thanks ! – Lordinaire

Tag: UWP (Page 1 of 2)

[UWP] FlyoutView control

In a project, I needed a control like the old SettingsFlyout which is now obselete. What I wanted is just a panel that come-and-go with a great animation.

Style


<Style TargetType="flyoutView:FlyoutView">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="flyoutView:FlyoutView">
                <Grid>
                    <Grid x:Name="PART_OVERLAY" Background="{TemplateBinding BackgroundOverlay}" Visibility="Collapsed" />

                    <Grid x:Name="PART_CONTENT">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition />
                        </Grid.RowDefinitions>

                        <Grid.RenderTransform>
                            <CompositeTransform />
                        </Grid.RenderTransform>

                        <ContentControl ContentTemplate="{TemplateBinding HeaderTemplate}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" />

                        <ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}" />
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The logic

We need to create the storyboards by hand because the content width/height aren’t static.

[TemplatePart(Name = PartOverlay, Type = typeof(Grid))]
[TemplatePart(Name = PartContent, Type = typeof(Grid))]
public partial class FlyoutView : ContentControl
{
    #region Dependency properties

    public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
            "HeaderTemplate", typeof(DataTemplate), typeof(FlyoutView),
            new PropertyMetadata(default(DataTemplate)));

    public DataTemplate HeaderTemplate
    {
        get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
        set { SetValue(HeaderTemplateProperty, value); }
    }

    public static readonly DependencyProperty PlacementProperty = DependencyProperty.Register(
        "Placement", typeof(PlacementType), typeof(FlyoutView),
        new PropertyMetadata(default(PlacementType), OnPlacementChanged));

    public PlacementType Placement
    {
        get { return (PlacementType)GetValue(PlacementProperty); }
        set { SetValue(PlacementProperty, value); }
    }

    public static readonly DependencyProperty UseDismissOvelayProperty = DependencyProperty.Register(
        "UseDismissOvelay", typeof(bool), typeof(FlyoutView),
        new PropertyMetadata(default(bool)));

    public bool UseDismissOvelay
    {
        get { return (bool)GetValue(UseDismissOvelayProperty); }
        set { SetValue(UseDismissOvelayProperty, value); }
    }

    public static readonly DependencyProperty BackgroundOverlayProperty = DependencyProperty.Register(
        "BackgroundOverlay", typeof(SolidColorBrush), typeof(FlyoutView),
        new PropertyMetadata(default(SolidColorBrush)));

    public SolidColorBrush BackgroundOverlay
    {
        get { return (SolidColorBrush)GetValue(BackgroundOverlayProperty); }
        set { SetValue(BackgroundOverlayProperty, value); }
    }

    public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
            "IsOpen", typeof(bool), typeof(FlyoutView),
            new PropertyMetadata(default(bool), OnIsOpenChanged));

    public bool IsOpen
    {
        get { return (bool)GetValue(IsOpenProperty); }
        set { SetValue(IsOpenProperty, value); }
    }

    public static readonly DependencyProperty ContentWidthProperty = DependencyProperty.Register(
        "ContentWidth", typeof(double), typeof(FlyoutView),
        new PropertyMetadata(default(double)));

    public double ContentWidth
    {
        get { return (double)GetValue(ContentWidthProperty); }
        set { SetValue(ContentWidthProperty, value); }
    }

    public static readonly DependencyProperty ContentHeightProperty = DependencyProperty.Register(
        "ContentHeight", typeof(double), typeof(FlyoutView),
        new PropertyMetadata(default(double)));

    public double ContentHeight
    {
        get { return (double)GetValue(ContentHeightProperty); }
        set { SetValue(ContentHeightProperty, value); }
    }

    #endregion

    #region Properties

    public enum PlacementType
    {

        Left,
        Top,
        Right,
        Bottom
    }

    private const string PartOverlay = "PART_OVERLAY";
    private const string PartContent = "PART_CONTENT";

    private Storyboard _openStoryboard;
    private Storyboard _closeStoryboard;
    private Grid _content;
    private Grid _overlay;

    #endregion

    #region Constructor

    public FlyoutView()
    {
        DefaultStyleKey = typeof(FlyoutView);
    }

    #endregion

    #region Override

    protected override void OnApplyTemplate()
    {
        _overlay = GetTemplateChild(PartOverlay) as Grid;
        _content = GetTemplateChild(PartContent) as Grid;

        if (_content != null)
        {
            _content.Loaded += OnContentLoaded;
            HandleOpening();
        }

        if (_overlay != null)
            _overlay.Tapped += OnOverlayTapped;

        UpdateFromLayout();

        base.OnApplyTemplate();
    }

    #endregion

    #region Methods

    private void UpdateFromLayout()
    {
        if (_content == null || _overlay == null)
            return;

        double overflow;
        if (Placement == PlacementType.Left
            || Placement == PlacementType.Right)
            overflow = Placement == PlacementType.Left
                ? -ContentWidth
                : ContentWidth;
        else
            overflow = Placement == PlacementType.Top
                ? -ContentHeight
                : ContentHeight;

        UpdatePlacement(overflow);
        UpdateAnimations(overflow);
    }

    private void UpdatePlacement(double overflow)
    {
        if (Placement == PlacementType.Left
            || Placement == PlacementType.Right)
        {
            _content.Width = ContentWidth;
            _content.Height = double.NaN;
            _content.RenderTransform = new CompositeTransform { TranslateX = overflow, TranslateY = 0 };
            _content.HorizontalAlignment = Placement == PlacementType.Left
                ? HorizontalAlignment.Left
                : HorizontalAlignment.Right;
            _content.VerticalAlignment = VerticalAlignment.Stretch;
        }
        else
        {
            _content.Width = double.NaN;
            _content.Height = ContentHeight;
            _content.RenderTransform = new CompositeTransform { TranslateX = 0, TranslateY = overflow };
            _content.VerticalAlignment = Placement == PlacementType.Top
                ? VerticalAlignment.Top
                : VerticalAlignment.Bottom;
            _content.HorizontalAlignment = HorizontalAlignment.Stretch;
        }
    }

    private void UpdateAnimations(double overflow)
    {
        var targetProperty = Placement == PlacementType.Left || Placement == PlacementType.Right
            ? "(UIElement.RenderTransform).(CompositeTransform.TranslateX)"
            : "(UIElement.RenderTransform).(CompositeTransform.TranslateY)";

        // Create animations
        _openStoryboard = new Storyboard();
        var openAnimation = new DoubleAnimation
        {
            Duration = TimeSpan.FromMilliseconds(500),
            From = overflow,
            To = 0,
            EasingFunction = new PowerEase { EasingMode = EasingMode.EaseIn, Power = 2 }
        };
        Storyboard.SetTargetProperty(openAnimation, targetProperty);
        Storyboard.SetTarget(openAnimation, _content);
        _openStoryboard.Children.Add(openAnimation);

        _closeStoryboard = new Storyboard();
        var closeAnimation = new DoubleAnimation
        {
            Duration = TimeSpan.FromMilliseconds(500),
            From = 0,
            To = overflow,
            EasingFunction = new PowerEase { EasingMode = EasingMode.EaseOut, Power = 2 }
        };
        Storyboard.SetTargetProperty(closeAnimation, targetProperty);
        Storyboard.SetTarget(closeAnimation, _content);
        _closeStoryboard.Children.Add(closeAnimation);
    }

    #endregion

    #region Events

    private void OnContentLoaded(object sender, object o)
    {
        _content.Loaded -= OnContentLoaded;

        UpdateFromLayout();
    }

    private void OnOverlayTapped(object sender, TappedRoutedEventArgs e)
    {
        if (IsOpen)
            IsOpen = false;
    }

    private static void OnIsOpenChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        var control = sender as FlyoutView;

        control?.HandleOpening();
    }

    private void HandleOpening()
    {
        if (IsOpen)
        {
            if (UseDismissOvelay)
                _overlay.Visibility = Visibility.Visible;

            _openStoryboard?.Begin();
        }
        else
        {
            if (UseDismissOvelay)
                _overlay.Visibility = Visibility.Collapsed;

            _closeStoryboard?.Begin();
        }
    }

    private static void OnPlacementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        var control = sender as FlyoutView;

        control?.UpdateFromLayout();
    }

    #endregion
}

Usage

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Image Width="300" Height="300" Source="ms-appx:///Assets/ToolkitLogo.png" />

    <flyoutView:FlyoutView IsOpen="{Binding Path=IsOpen.Value, Mode=TwoWay}" Placement="{Binding Path=Placement.Value, Mode=TwoWay}" UseDismissOvelay="True" BackgroundOverlay="#BBF0F0F0" ContentWidth="300" ContentHeight="150">
        <flyoutView:FlyoutView.HeaderTemplate>
            <DataTemplate>
                <Grid Background="{StaticResource Brush-Grey-02}">
                    <TextBlock Text="{Binding Path=Header.Value}" Style="{StaticResource HeaderTextBlockStyle}" Margin="20" />
                </Grid>
            </DataTemplate>
        </flyoutView:FlyoutView.HeaderTemplate>

        <Grid Background="{StaticResource Brush-Grey-03}">
            <TextBlock HorizontalAlignment="Center" TextWrapping="Wrap" Text="This is the content" VerticalAlignment="Center" Style="{StaticResource SubtitleTextBlockStyle}" />
        </Grid>
    </flyoutView:FlyoutView>
</Grid>

Demo

[UWP] Data binding to method and from event

With the Windows 10 Anniversary update, XAML becomes smarter with some great features for the data binding. It is now possible to bind to a property or an event to a method very easily !

Here is a sample of different type of bindings (old and new) to compare the XAML synthax.

ViewModel

public class MainViewModel : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged();
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged();
        }
    }

    public MainViewModel()
    {
        FirstName = "Maxime";
        LastName = "Frappat";
    }

    public string ToFullName(string firstName, string lastName)
    {
        return string.Concat(firstName, " ", lastName);
    }

    public void DeleteLastName()
    {
        LastName = string.Empty;
    }

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
    }

    #endregion
}

Helper class

public static class TextHelper
{
    public static string ToFullName(string firstName, string lastName)
    {
        return string.Concat(firstName, " ", lastName);
    }
}

View (Code-Behind)

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel { get; set; }

    public MainPage()
    {
        InitializeComponent();

        ViewModel = new MainViewModel();
        DataContext = ViewModel;
    }
}

View (XAML)

<!-- BINDING TO PROPERTIES -->
<StackPanel Orientation="Vertical">
    <TextBlock Text="BINDING TO PROPERTIES" Style="{StaticResource TitleStyle}"/>

    <TextBlock>
        <Run Text="{Binding FirstName}" />
        <Run Text="{Binding LastName}" />
    </TextBlock>
</StackPanel>

<!-- xBIND TO PROPERTIES -->
<StackPanel Orientation="Vertical">
    <TextBlock Text="xBIND TO PROPERTIES" Style="{StaticResource TitleStyle}" />

    <TextBlock>
        <Run Text="{x:Bind ViewModel.FirstName, Mode=OneWay}" />
        <Run Text="{x:Bind ViewModel.LastName, Mode=OneWay}" />
    </TextBlock>
</StackPanel>

<!-- xBIND TO INTERNAL METHODS -->
<StackPanel Orientation="Vertical">
    <TextBlock Text="xBIND TO INTERNAL METHODS" Style="{StaticResource TitleStyle}" />

    <TextBlock Text="{x:Bind ViewModel.ToFullName(ViewModel.FirstName, ViewModel.LastName), Mode=OneWay}" />
</StackPanel>

<!-- xBIND TO EXTERNAL METHODS -->
<StackPanel Orientation="Vertical">
    <TextBlock Text="xBIND TO EXTERNAL METHODS" Style="{StaticResource TitleStyle}" />

    <TextBlock Text="{x:Bind helper:TextHelper.ToFullName(ViewModel.FirstName, ViewModel.LastName), Mode=OneWay}" />
</StackPanel>

<!-- xBIND FROM EVENT -->
<StackPanel Orientation="Vertical">
    <TextBlock Text="xBIND FROM EVENT" Style="{StaticResource TitleStyle}" />

    <TextBlock Tapped="{x:Bind ViewModel.DeleteLastName}">
        <Run Text="{x:Bind ViewModel.FirstName, Mode=OneWay}" />
        <Run Text="{x:Bind ViewModel.LastName, Mode=OneWay}" />
    </TextBlock>
</StackPanel>

Result

Each bindings will display the same text.

anniverary_binding

Don’t forget to target the last SDK for your project.

So easy !

Page 1 of 2

Powered by WordPress & Theme by Anders Norén

%d bloggers like this: