From 947155fa5f039d4bdf516fc9d0caef31bfb89e96 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Tue, 17 Mar 2020 18:07:14 +0100 Subject: [PATCH 01/11] Initial commit the refactored MasterDetailsView --- .../MasterDetailsView/BackButtonBehavior.cs | 2 +- .../MasterDetailsView.BackButton.cs | 166 +++++ .../MasterDetailsView.Events.cs | 4 +- .../MasterDetailsView.Properties.cs | 277 ++++++--- .../MasterDetailsView/MasterDetailsView.cs | 583 ++++++------------ .../MasterDetailsView/MasterDetailsView.xaml | 249 ++++---- 6 files changed, 704 insertions(+), 577 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.BackButton.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/BackButtonBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/BackButtonBehavior.cs index 694979e659d..af10e07c1b2 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/BackButtonBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/BackButtonBehavior.cs @@ -14,7 +14,7 @@ public enum BackButtonBehavior /// /// /// If the back button controlled by is already visible, the will hook into that button. - /// If the new NavigationView provided by the Windows UI nuget package is used, the will enable and show that button. + /// If the new NavigationView provided by the Windows UI NuGet package is used, the will enable and show that button. /// Otherwise the inline button is used. /// Automatic, diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.BackButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.BackButton.cs new file mode 100644 index 00000000000..36abb0adefe --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.BackButton.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using Windows.ApplicationModel; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Panel that allows for a Master/Details pattern. + /// + /// + public partial class MasterDetailsView + { + private AppViewBackButtonVisibility? previousSystemBackButtonVisibility; + private bool previousNavigationViewBackEnabled; + + // Int used because the underlying type is an enum, but we don't have access to the enum + private int previousNavigationViewBackVisibilty; + private Button inlineBackButton; + private object navigationView; + private Frame frame; + + /// + /// Sets the back button visibility based on the current visual state and selected item + /// + private void SetBackButtonVisibility(MasterDetailsViewState? previousState = null) + { + const int backButtonVisible = 1; + + if (DesignMode.DesignModeEnabled) + { + return; + } + + if (ViewState == MasterDetailsViewState.Details) + { + if ((BackButtonBehavior == BackButtonBehavior.Inline) && (inlineBackButton != null)) + { + inlineBackButton.Visibility = Visibility.Visible; + } + else if (BackButtonBehavior == BackButtonBehavior.Automatic) + { + // Continue to support the system back button if it is being used + SystemNavigationManager navigationManager = SystemNavigationManager.GetForCurrentView(); + if (navigationManager.AppViewBackButtonVisibility == AppViewBackButtonVisibility.Visible) + { + // Setting this indicates that the system back button is being used + previousSystemBackButtonVisibility = navigationManager.AppViewBackButtonVisibility; + } + else if ((inlineBackButton != null) && ((navigationView == null) || (frame == null))) + { + // We can only use the new NavigationView if we also have a Frame + // If there is no frame we have to use the inline button + inlineBackButton.Visibility = Visibility.Visible; + } + else + { + SetNavigationViewBackButtonState(backButtonVisible, true); + } + } + else if (BackButtonBehavior != BackButtonBehavior.Manual) + { + SystemNavigationManager navigationManager = SystemNavigationManager.GetForCurrentView(); + previousSystemBackButtonVisibility = navigationManager.AppViewBackButtonVisibility; + + navigationManager.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible; + } + } + else if (previousState == MasterDetailsViewState.Details) + { + if ((BackButtonBehavior == BackButtonBehavior.Inline) && (inlineBackButton != null)) + { + inlineBackButton.Visibility = Visibility.Collapsed; + } + else if (BackButtonBehavior == BackButtonBehavior.Automatic) + { + if (!previousSystemBackButtonVisibility.HasValue) + { + if ((inlineBackButton != null) && ((navigationView == null) || (frame == null))) + { + inlineBackButton.Visibility = Visibility.Collapsed; + } + else + { + SetNavigationViewBackButtonState(previousNavigationViewBackVisibilty, previousNavigationViewBackEnabled); + } + } + } + + if (previousSystemBackButtonVisibility.HasValue) + { + // Make sure we show the back button if the stack can navigate back + SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = previousSystemBackButtonVisibility.Value; + previousSystemBackButtonVisibility = null; + } + } + } + + private void SetNavigationViewBackButtonState(int visible, bool enabled) + { + if (navigationView == null) + { + return; + } + + System.Type navType = navigationView.GetType(); + PropertyInfo visibleProperty = navType.GetProperty("IsBackButtonVisible"); + if (visibleProperty != null) + { + previousNavigationViewBackVisibilty = (int)visibleProperty.GetValue(navigationView); + visibleProperty.SetValue(navigationView, visible); + } + + PropertyInfo enabledProperty = navType.GetProperty("IsBackEnabled"); + if (enabledProperty != null) + { + previousNavigationViewBackEnabled = (bool)enabledProperty.GetValue(navigationView); + enabledProperty.SetValue(navigationView, enabled); + } + } + + /// + /// Closes the details pane if we are in narrow state + /// + /// The sender + /// The event args + private void OnFrameNavigating(object sender, NavigatingCancelEventArgs args) + { + if ((args.NavigationMode == NavigationMode.Back) && (ViewState == MasterDetailsViewState.Details)) + { + SelectedItem = null; + args.Cancel = true; + } + } + + /// + /// Closes the details pane if we are in narrow state + /// + /// The sender + /// The event args + private void OnBackRequested(object sender, BackRequestedEventArgs args) + { + if (ViewState == MasterDetailsViewState.Details) + { + // let the OnFrameNavigating method handle it if + if (frame == null || !frame.CanGoBack) + { + SelectedItem = null; + } + + args.Handled = true; + } + } + + private void OnInlineBackButtonClicked(object sender, RoutedEventArgs e) + { + SelectedItem = null; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Events.cs index ab7c55a2b29..5caa612b583 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Events.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Events.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -19,7 +19,7 @@ public partial class MasterDetailsView public event SelectionChangedEventHandler SelectionChanged; /// - /// Occurs when the view state changes + /// Occurs when the view state changes. /// public event EventHandler ViewStateChanged; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs index 22f54003331..2b62c68fd1f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -15,6 +15,26 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// public partial class MasterDetailsView { + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty MasterPaneBackgroundProperty = DependencyProperty.Register( + nameof(MasterPaneBackground), + typeof(Brush), + typeof(MasterDetailsView), + new PropertyMetadata(null)); + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty DetailsPaneBackgroundProperty = DependencyProperty.Register( + nameof(DetailsPaneBackground), + typeof(Brush), + typeof(MasterDetailsView), + new PropertyMetadata(null)); + /// /// Identifies the dependency property. /// @@ -26,22 +46,22 @@ public partial class MasterDetailsView new PropertyMetadata(null, OnSelectedItemChanged)); /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - /// The identifier for the dependency property. - public static readonly DependencyProperty DetailsTemplateProperty = DependencyProperty.Register( - nameof(DetailsTemplate), - typeof(DataTemplate), + /// The identifier for the dependency property. + public static readonly DependencyProperty NoItemsContentProperty = DependencyProperty.Register( + nameof(NoItemsContent), + typeof(object), typeof(MasterDetailsView), new PropertyMetadata(null)); /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - /// The identifier for the dependency property. - public static readonly DependencyProperty MasterPaneBackgroundProperty = DependencyProperty.Register( - nameof(MasterPaneBackground), - typeof(Brush), + /// The identifier for the dependency property. + public static readonly DependencyProperty NoItemsContentTemplateProperty = DependencyProperty.Register( + nameof(NoItemsContentTemplate), + typeof(DataTemplate), typeof(MasterDetailsView), new PropertyMetadata(null)); @@ -53,7 +73,7 @@ public partial class MasterDetailsView nameof(MasterHeader), typeof(object), typeof(MasterDetailsView), - new PropertyMetadata(null, OnMasterHeaderChanged)); + new PropertyMetadata(null)); /// /// Identifies the dependency property. @@ -85,16 +105,6 @@ public partial class MasterDetailsView typeof(MasterDetailsView), new PropertyMetadata(null)); - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty MasterPaneWidthProperty = DependencyProperty.Register( - nameof(MasterPaneWidth), - typeof(double), - typeof(MasterDetailsView), - new PropertyMetadata(320d)); - /// /// Identifies the dependency property. /// @@ -116,17 +126,57 @@ public partial class MasterDetailsView new PropertyMetadata(null)); /// - /// Identifies the dependency property + /// Identifies the dependency property. /// - /// The identifier for the dependency property. - public static readonly DependencyProperty ViewStateProperty = DependencyProperty.Register( - nameof(ViewState), - typeof(MasterDetailsViewState), + /// The identifier for the dependency property. + public static readonly DependencyProperty DetailsContentTemplateSelectorProperty = DependencyProperty.Register( + nameof(DetailsContentTemplateSelector), + typeof(DataTemplateSelector), typeof(MasterDetailsView), - new PropertyMetadata(default(MasterDetailsViewState))); + new PropertyMetadata(null)); + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty DetailsTemplateProperty = DependencyProperty.Register( + nameof(DetailsTemplate), + typeof(DataTemplate), + typeof(MasterDetailsView), + new PropertyMetadata(null)); + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty MasterItemTemplateSelectorProperty = DependencyProperty.Register( + nameof(MasterItemTemplateSelector), + typeof(DataTemplateSelector), + typeof(MasterDetailsView), + new PropertyMetadata(null)); /// - /// Identifies the dependency property + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty MasterPaneWidthProperty = DependencyProperty.Register( + nameof(MasterPaneWidth), + typeof(GridLength), + typeof(MasterDetailsView), + new PropertyMetadata(new GridLength(320))); + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty CompactModeThresholdWidthProperty = DependencyProperty.Register( + nameof(CompactModeThresholdWidth), + typeof(double), + typeof(MasterDetailsView), + new PropertyMetadata(640d, OnCompactModeThresholdWidthChanged)); + + /// + /// Identifies the dependency property. /// /// The identifier for the dependency property. public static readonly DependencyProperty MasterCommandBarProperty = DependencyProperty.Register( @@ -136,7 +186,7 @@ public partial class MasterDetailsView new PropertyMetadata(null, OnMasterCommandBarChanged)); /// - /// Identifies the dependency property + /// Identifies the dependency property. /// /// The identifier for the dependency property. public static readonly DependencyProperty DetailsCommandBarProperty = DependencyProperty.Register( @@ -146,22 +196,44 @@ public partial class MasterDetailsView new PropertyMetadata(null, OnDetailsCommandBarChanged)); /// - /// Identifies the dependency property + /// Identifies the dependency property. /// - public static readonly DependencyProperty CompactModeThresholdWidthProperty = DependencyProperty.Register( - nameof(CompactModeThresholdWidth), - typeof(double), + /// The identifier for the dependency property. + public static readonly DependencyProperty BackButtonBehaviorProperty = DependencyProperty.Register( + nameof(BackButtonBehavior), + typeof(BackButtonBehavior), typeof(MasterDetailsView), - new PropertyMetadata(720d, OnCompactModeThresholdWidthChanged)); + new PropertyMetadata(null, OnBackButtonBehaviorChanged)); /// - /// Identifies the dependency property + /// Identifies the dependency property. /// - public static readonly DependencyProperty BackButtonBehaviorProperty = DependencyProperty.Register( - nameof(BackButtonBehavior), - typeof(BackButtonBehavior), + /// The identifier for the dependency property. + public static readonly DependencyProperty ViewStateProperty = DependencyProperty.Register( + nameof(ViewState), + typeof(MasterDetailsViewState), typeof(MasterDetailsView), - new PropertyMetadata(BackButtonBehavior.System, OnBackButtonBehaviorChanged)); + new PropertyMetadata(default(MasterDetailsViewState))); + + /// + /// Gets or sets the Brush to apply to the background of the list area of the control. + /// + /// The Brush to apply to the background of the list area of the control. + public Brush MasterPaneBackground + { + get { return (Brush)GetValue(MasterPaneBackgroundProperty); } + set { SetValue(MasterPaneBackgroundProperty, value); } + } + + /// + /// Gets or sets the Brush to apply to the background of the details area of the control. + /// + /// The Brush to apply to the background of the details area of the control. + public Brush DetailsPaneBackground + { + get { return (Brush)GetValue(DetailsPaneBackgroundProperty); } + set { SetValue(DetailsPaneBackgroundProperty, value); } + } /// /// Gets or sets the selected item. @@ -174,26 +246,31 @@ public object SelectedItem } /// - /// Gets or sets the DataTemplate used to display the details. + /// Gets or sets the content for the master pane's no items presenter. /// - public DataTemplate DetailsTemplate + /// + /// The content of the master pane's header. The default is null. + /// + public object NoItemsContent { - get { return (DataTemplate)GetValue(DetailsTemplateProperty); } - set { SetValue(DetailsTemplateProperty, value); } + get { return GetValue(NoItemsContentProperty); } + set { SetValue(NoItemsContentProperty, value); } } /// - /// Gets or sets the Brush to apply to the background of the list area of the control. + /// Gets or sets the DataTemplate used to display the master pane's no items presenter. /// - /// The Brush to apply to the background of the list area of the control. - public Brush MasterPaneBackground + /// + /// The template that specifies the visualization of the master pane no items object. The default is null. + /// + public DataTemplate NoItemsContentTemplate { - get { return (Brush)GetValue(MasterPaneBackgroundProperty); } - set { SetValue(MasterPaneBackgroundProperty, value); } + get { return (DataTemplate)GetValue(NoItemsContentTemplateProperty); } + set { SetValue(NoItemsContentTemplateProperty, value); } } /// - /// Gets or sets the content for the master pane's header + /// Gets or sets the content for the master pane's header. /// /// /// The content of the master pane's header. The default is null. @@ -241,20 +318,7 @@ public DataTemplate DetailsHeaderTemplate } /// - /// Gets or sets the width of the master pane when the view is expanded. - /// - /// - /// The width of the SplitView pane when it's fully expanded. The default is 320 - /// device-independent pixel (DIP). - /// - public double MasterPaneWidth - { - get { return (double)GetValue(MasterPaneWidthProperty); } - set { SetValue(MasterPaneWidthProperty, value); } - } - - /// - /// Gets or sets the content to dsiplay when there is no item selected in the master list. + /// Gets or sets the content to display when there is no item selected in the master list. /// public object NoSelectionContent { @@ -276,12 +340,52 @@ public DataTemplate NoSelectionContentTemplate } /// - /// Gets the current visual state of the control + /// Gets or sets the for the details presenter. /// - public MasterDetailsViewState ViewState + public DataTemplateSelector DetailsContentTemplateSelector { - get { return (MasterDetailsViewState)GetValue(ViewStateProperty); } - private set { SetValue(ViewStateProperty, value); } + get { return (DataTemplateSelector)GetValue(DetailsContentTemplateSelectorProperty); } + set { SetValue(DetailsContentTemplateSelectorProperty, value); } + } + + /// + /// Gets or sets the DataTemplate used to display the details. + /// + public DataTemplate DetailsTemplate + { + get { return (DataTemplate)GetValue(DetailsTemplateProperty); } + set { SetValue(DetailsTemplateProperty, value); } + } + + /// + /// Gets or sets the for the master list items. + /// + public DataTemplateSelector MasterItemTemplateSelector + { + get { return (DataTemplateSelector)GetValue(MasterItemTemplateSelectorProperty); } + set { SetValue(MasterItemTemplateSelectorProperty, value); } + } + + /// + /// Gets or sets the width of the master pane when the view is expanded. + /// + /// + /// The width of the SplitView pane when it's fully expanded. The default is 320 + /// device-independent pixel (DIP). + /// + public GridLength MasterPaneWidth + { + get { return (GridLength)GetValue(MasterPaneWidthProperty); } + set { SetValue(MasterPaneWidthProperty, value); } + } + + /// + /// Gets or sets the Threshold width that will trigger the control to go into compact mode. + /// + public double CompactModeThresholdWidth + { + get { return (double)GetValue(CompactModeThresholdWidthProperty); } + set { SetValue(CompactModeThresholdWidthProperty, value); } } /// @@ -303,22 +407,22 @@ public CommandBar DetailsCommandBar } /// - /// Gets or sets the Threshold width that witll trigger the control to go into compact mode + /// Gets or sets the behavior to use for the back button. /// - public double CompactModeThresholdWidth + /// The current BackButtonBehavior. The default is System. + public BackButtonBehavior BackButtonBehavior { - get { return (double)GetValue(CompactModeThresholdWidthProperty); } - set { SetValue(CompactModeThresholdWidthProperty, value); } + get { return (BackButtonBehavior)GetValue(BackButtonBehaviorProperty); } + set { SetValue(BackButtonBehaviorProperty, value); } } /// - /// Gets or sets the behavior to use for the back button + /// Gets or sets gets the current visual state of the control. /// - /// The current BackButtonBehavior. The default is System. - public BackButtonBehavior BackButtonBehavior + public MasterDetailsViewState ViewState { - get { return (BackButtonBehavior)GetValue(BackButtonBehaviorProperty); } - set { SetValue(BackButtonBehaviorProperty, value); } + get { return (MasterDetailsViewState)GetValue(ViewStateProperty); } + set { SetValue(ViewStateProperty, value); } } /// @@ -326,5 +430,24 @@ public BackButtonBehavior BackButtonBehavior /// This new model will be the DataContext of the Details area. /// public Func MapDetails { get; set; } + + private static void OnDetailsCommandBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((MasterDetailsView)d).OnDetailsCommandBarChanged(); + } + + private static void OnMasterCommandBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((MasterDetailsView)d).OnMasterCommandBarChanged(); + } + + private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((MasterDetailsView)d).OnSelectedItemChanged(e); + } + + private static void OnCompactModeThresholdWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } + + private static void OnBackButtonBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.cs b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.cs index 62e654d5300..bca808c8445 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.cs @@ -1,59 +1,59 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Toolkit.Uwp.UI.Extensions; using System.Collections.Generic; using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.ApplicationModel; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Navigation; namespace Microsoft.Toolkit.Uwp.UI.Controls { /// /// Panel that allows for a Master/Details pattern. /// - [TemplatePart(Name = PartDetailsPresenter, Type = typeof(ContentPresenter))] - [TemplatePart(Name = PartDetailsPanel, Type = typeof(FrameworkElement))] - [TemplateVisualState(Name = NoSelectionNarrowState, GroupName = SelectionStates)] - [TemplateVisualState(Name = NoSelectionWideState, GroupName = SelectionStates)] - [TemplateVisualState(Name = HasSelectionWideState, GroupName = SelectionStates)] - [TemplateVisualState(Name = HasSelectionNarrowState, GroupName = SelectionStates)] - [TemplateVisualState(Name = NarrowState, GroupName = WidthStates)] - [TemplateVisualState(Name = WideState, GroupName = WidthStates)] + [TemplatePart(Name = PART_DETAILS_PRESENTER, Type = typeof(ContentPresenter))] + [TemplatePart(Name = PART_DETAILS_PANE, Type = typeof(FrameworkElement))] + [TemplateVisualState(Name = NO_SELECTION_NARROW, GroupName = SELECTION_STATES)] + [TemplateVisualState(Name = NO_SELECTION_WIDE, GroupName = SELECTION_STATES)] + [TemplateVisualState(Name = HAS_SELECTION_WIDE, GroupName = SELECTION_STATES)] + [TemplateVisualState(Name = HAS_SELECTION_NARROW, GroupName = SELECTION_STATES)] public partial class MasterDetailsView : ItemsControl { - private const string PartDetailsPresenter = "DetailsPresenter"; - private const string PartDetailsPanel = "DetailsPanel"; - private const string PartBackButton = "MasterDetailsBackButton"; - private const string PartHeaderContentPresenter = "HeaderContentPresenter"; - private const string NarrowState = "NarrowState"; - private const string WideState = "WideState"; - private const string WidthStates = "WidthStates"; - private const string SelectionStates = "SelectionStates"; - private const string HasSelectionNarrowState = "HasSelectionNarrow"; - private const string HasSelectionWideState = "HasSelectionWide"; - private const string NoSelectionNarrowState = "NoSelectionNarrow"; - private const string NoSelectionWideState = "NoSelectionWide"; - - private AppViewBackButtonVisibility? _previousSystemBackButtonVisibility; - private bool _previousNavigationViewBackEnabled; - - // Int used because the underlying type is an enum, but we don't have access to the enum - private int _previousNavigationViewBackVisibilty; - private ContentPresenter _detailsPresenter; - private VisualStateGroup _selectionStateGroup; - private Button _inlineBackButton; - private object _navigationView; - private Frame _frame; + // All view states: + private const string SELECTION_STATES = "SelectionStates"; + private const string NO_SELECTION_WIDE = "NoSelectionWide"; + private const string HAS_SELECTION_WIDE = "HasSelectionWide"; + private const string NO_SELECTION_NARROW = "NoSelectionNarrow"; + private const string HAS_SELECTION_NARROW = "HasSelectionNarrow"; + + private const string HAS_ITEMS_STATES = "HasItemsStates"; + private const string HAS_ITEMS_STATE = "HasItemsState"; + private const string HAS_NO_ITEMS_STATE = "HasNoItemsState"; + + // Control names: + private const string PART_ROOT_PANE = "RootPane"; + private const string PART_DETAILS_PRESENTER = "DetailsPresenter"; + private const string PART_DETAILS_PANE = "DetailsPane"; + private const string PART_MASTER_LIST = "MasterList"; + private const string PART_BACK_BUTTON = "MasterDetailsBackButton"; + private const string PART_HEADER_CONTENT_PRESENTER = "HeaderContentPresenter"; + private const string PART_MASTER_COMMAND_BAR = "MasterCommandBarPanel"; + private const string PART_DETAILS_COMMAND_BAR = "DetailsCommandBarPanel"; /// - /// Initializes a new instance of the class. + /// Used to prevent screen flickering if only the order of the selected item changed. /// + private bool ignoreClearSelectedItem; + + private ContentPresenter detailsPresenter; + private Microsoft.UI.Xaml.Controls.TwoPaneView twoPaneView; + private VisualStateGroup selectionStateGroup; + public MasterDetailsView() { DefaultStyleKey = typeof(MasterDetailsView); @@ -63,217 +63,109 @@ public MasterDetailsView() } /// - /// Invoked whenever application code or internal processes (such as a rebuilding layout pass) call - /// ApplyTemplate. In simplest terms, this means the method is called just before a UI element displays - /// in your app. Override this method to influence the default post-template logic of a class. + /// Updates the visual state of the control. /// - protected override void OnApplyTemplate() + /// False to skip animations. + private void SetVisualState(bool animate) { - base.OnApplyTemplate(); - - if (_inlineBackButton != null) + string noSelectionState; + string hasSelectionState; + if (ViewState == MasterDetailsViewState.Both) { - _inlineBackButton.Click -= OnInlineBackButtonClicked; + noSelectionState = NO_SELECTION_WIDE; + hasSelectionState = HAS_SELECTION_WIDE; } - - _inlineBackButton = (Button)GetTemplateChild(PartBackButton); - if (_inlineBackButton != null) + else { - _inlineBackButton.Click += OnInlineBackButtonClicked; + noSelectionState = NO_SELECTION_NARROW; + hasSelectionState = HAS_SELECTION_NARROW; } - _detailsPresenter = (ContentPresenter)GetTemplateChild(PartDetailsPresenter); - SetDetailsContent(); - - SetMasterHeaderVisibility(); - OnDetailsCommandBarChanged(); - OnMasterCommandBarChanged(); - - SizeChanged -= MasterDetailsView_SizeChanged; - SizeChanged += MasterDetailsView_SizeChanged; - - UpdateView(true); + VisualStateManager.GoToState(this, SelectedItem is null ? noSelectionState : hasSelectionState, animate); + VisualStateManager.GoToState(this, Items.Count > 0 ? HAS_ITEMS_STATE : HAS_NO_ITEMS_STATE, animate); } /// - /// Fired when the SelectedItem changes. + /// Sets the content of the based on current function. /// - /// The sender - /// The event args - /// - /// Sets up animations for the DetailsPresenter for animating in/out. - /// - private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private void SetDetailsContent() { - var view = (MasterDetailsView)d; - - view.OnSelectionChanged(new SelectionChangedEventArgs(new List { e.OldValue }, new List { e.NewValue })); - - view.UpdateView(true); - - // If there is no selection, do not remove the DetailsPresenter content but let it animate out. - if (view.SelectedItem != null) + if (detailsPresenter != null) { - view.SetDetailsContent(); - } - } - - /// - /// Fired when the is changed. - /// - /// The sender - /// The event args - private static void OnMasterHeaderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var view = (MasterDetailsView)d; - view.SetMasterHeaderVisibility(); - } - - /// - /// Fired when the DetailsCommandBar changes. - /// - /// The sender - /// The event args - private static void OnDetailsCommandBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var view = (MasterDetailsView)d; - view.OnDetailsCommandBarChanged(); - } + // Update the content template: + if (!(detailsPresenter.ContentTemplateSelector is null)) + { + detailsPresenter.ContentTemplate = detailsPresenter.ContentTemplateSelector.SelectTemplate(SelectedItem, detailsPresenter); + } + // Update the content: + detailsPresenter.Content = MapDetails is null + ? SelectedItem + : !(SelectedItem is null) ? MapDetails(SelectedItem) : null; - /// - /// Fired when CompactModeThresholdWIdthChanged - /// - /// The sender - /// The event args - private static void OnCompactModeThresholdWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((MasterDetailsView)d).HandleStateChanges(); + } } - private static void OnBackButtonBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private void SetMasterHeaderVisibility() { - var view = (MasterDetailsView)d; - view.SetBackButtonVisibility(); + if (GetTemplateChild(PART_HEADER_CONTENT_PRESENTER) is FrameworkElement headerPresenter) + { + headerPresenter.Visibility = MasterHeader != null + ? Visibility.Visible + : Visibility.Collapsed; + } } /// - /// Fired when the MasterCommandBar changes. + /// Clears the and prevent flickering of the UI if only the order of the items changed. /// - /// The sender - /// The event args - private static void OnMasterCommandBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + public void ClearSelectedItem() { - var view = (MasterDetailsView)d; - view.OnMasterCommandBarChanged(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - if (DesignMode.DesignModeEnabled == false) - { - SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested; - if (_frame != null) - { - _frame.Navigating -= OnFrameNavigating; - } - - _navigationView = this.FindAscendants().FirstOrDefault(p => p.GetType().FullName == "Microsoft.UI.Xaml.Controls.NavigationView"); - _frame = this.FindAscendant(); - if (_frame != null) - { - _frame.Navigating += OnFrameNavigating; - } - - _selectionStateGroup = (VisualStateGroup)GetTemplateChild(SelectionStates); - if (_selectionStateGroup != null) - { - _selectionStateGroup.CurrentStateChanged += OnSelectionStateChanged; - } - - UpdateView(true); - } + ignoreClearSelectedItem = true; + SelectedItem = null; + ignoreClearSelectedItem = false; } - private void OnUnloaded(object sender, RoutedEventArgs e) + private void OnCommandBarChanged(string panelName, CommandBar commandbar) { - if (DesignMode.DesignModeEnabled == false) + if (!(GetTemplateChild(panelName) is Panel panel)) { - SystemNavigationManager.GetForCurrentView().BackRequested -= OnBackRequested; - if (_frame != null) - { - _frame.Navigating -= OnFrameNavigating; - } - - _selectionStateGroup = (VisualStateGroup)GetTemplateChild(SelectionStates); - if (_selectionStateGroup != null) - { - _selectionStateGroup.CurrentStateChanged -= OnSelectionStateChanged; - _selectionStateGroup = null; - } + return; } - } - private void MasterDetailsView_SizeChanged(object sender, SizeChangedEventArgs e) - { - // if size is changing - if ((e.PreviousSize.Width < CompactModeThresholdWidth && e.NewSize.Width >= CompactModeThresholdWidth) || - (e.PreviousSize.Width >= CompactModeThresholdWidth && e.NewSize.Width < CompactModeThresholdWidth)) + panel.Children.Clear(); + if (commandbar != null) { - HandleStateChanges(); + panel.Children.Add(commandbar); } } - private void OnInlineBackButtonClicked(object sender, RoutedEventArgs e) + private void OnMasterCommandBarChanged() { - SelectedItem = null; + OnCommandBarChanged(PART_MASTER_COMMAND_BAR, MasterCommandBar); } - private void HandleStateChanges() + private void OnDetailsCommandBarChanged() { - UpdateView(true); - SetListSelectionWithKeyboardFocusOnVisualStateChanged(ViewState); + OnCommandBarChanged(PART_DETAILS_COMMAND_BAR, DetailsCommandBar); } - /// - /// Closes the details pane if we are in narrow state - /// - /// The sender - /// The event args - private void OnFrameNavigating(object sender, NavigatingCancelEventArgs args) + private void OnSelectedItemChanged(DependencyPropertyChangedEventArgs e) { - if ((args.NavigationMode == NavigationMode.Back) && (ViewState == MasterDetailsViewState.Details)) + // Prevent setting the SelectedItem to null if only the order changed (=> collection reset got triggered). + if (!ignoreClearSelectedItem && !(e.OldValue is null) && e.NewValue is null && Items.Contains(e.OldValue)) { - SelectedItem = null; - args.Cancel = true; + SelectedItem = e.OldValue; + return; } - } - /// - /// Closes the details pane if we are in narrow state - /// - /// The sender - /// The event args - private void OnBackRequested(object sender, BackRequestedEventArgs args) - { - if (ViewState == MasterDetailsViewState.Details) - { - // let the OnFrameNavigating method handle it if - if (_frame == null || !_frame.CanGoBack) - { - SelectedItem = null; - } + OnSelectionChanged(new SelectionChangedEventArgs(new List { e.OldValue }, new List { e.NewValue })); - args.Handled = true; - } - } + UpdateView(true); - private void SetMasterHeaderVisibility() - { - if (GetTemplateChild(PartHeaderContentPresenter) is FrameworkElement headerPresenter) + // If there is no selection, do not remove the DetailsPresenter content but let it animate out. + if (!(SelectedItem is null)) { - headerPresenter.Visibility = MasterHeader != null - ? Visibility.Visible - : Visibility.Collapsed; + SetDetailsContent(); } } @@ -283,90 +175,17 @@ private void UpdateView(bool animate) SetVisualState(animate); } - /// - /// Sets the back button visibility based on the current visual state and selected item - /// - private void SetBackButtonVisibility(MasterDetailsViewState? previousState = null) - { - const int backButtonVisible = 1; - - if (DesignMode.DesignModeEnabled) - { - return; - } - - if (ViewState == MasterDetailsViewState.Details) - { - if ((BackButtonBehavior == BackButtonBehavior.Inline) && (_inlineBackButton != null)) - { - _inlineBackButton.Visibility = Visibility.Visible; - } - else if (BackButtonBehavior == BackButtonBehavior.Automatic) - { - // Continue to support the system back button if it is being used - var navigationManager = SystemNavigationManager.GetForCurrentView(); - if (navigationManager.AppViewBackButtonVisibility == AppViewBackButtonVisibility.Visible) - { - // Setting this indicates that the system back button is being used - _previousSystemBackButtonVisibility = navigationManager.AppViewBackButtonVisibility; - } - else if ((_inlineBackButton != null) && ((_navigationView == null) || (_frame == null))) - { - // We can only use the new NavigationView if we also have a Frame - // If there is no frame we have to use the inline button - _inlineBackButton.Visibility = Visibility.Visible; - } - else - { - SetNavigationViewBackButtonState(backButtonVisible, true); - } - } - else if (BackButtonBehavior != BackButtonBehavior.Manual) - { - var navigationManager = SystemNavigationManager.GetForCurrentView(); - _previousSystemBackButtonVisibility = navigationManager.AppViewBackButtonVisibility; - - navigationManager.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible; - } - } - else if (previousState == MasterDetailsViewState.Details) - { - if ((BackButtonBehavior == BackButtonBehavior.Inline) && (_inlineBackButton != null)) - { - _inlineBackButton.Visibility = Visibility.Collapsed; - } - else if (BackButtonBehavior == BackButtonBehavior.Automatic) - { - if (_previousSystemBackButtonVisibility.HasValue == false) - { - if ((_inlineBackButton != null) && ((_navigationView == null) || (_frame == null))) - { - _inlineBackButton.Visibility = Visibility.Collapsed; - } - else - { - SetNavigationViewBackButtonState(_previousNavigationViewBackVisibilty, _previousNavigationViewBackEnabled); - } - } - } - - if (_previousSystemBackButtonVisibility.HasValue) - { - // Make sure we show the back button if the stack can navigate back - SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = _previousSystemBackButtonVisibility.Value; - _previousSystemBackButtonVisibility = null; - } - } - } - private void UpdateViewState() { - var previousState = ViewState; + MasterDetailsViewState previousState = ViewState; - if (ActualWidth < CompactModeThresholdWidth) + // Single pane: + if (twoPaneView.Mode == Microsoft.UI.Xaml.Controls.TwoPaneViewMode.SinglePane) { - ViewState = SelectedItem == null ? MasterDetailsViewState.Master : MasterDetailsViewState.Details; + ViewState = SelectedItem is null ? MasterDetailsViewState.Master : MasterDetailsViewState.Details; + twoPaneView.PanePriority = SelectedItem is null ? Microsoft.UI.Xaml.Controls.TwoPaneViewPriority.Pane1 : Microsoft.UI.Xaml.Controls.TwoPaneViewPriority.Pane2; } + // Dual pane: else { ViewState = MasterDetailsViewState.Both; @@ -379,83 +198,42 @@ private void UpdateViewState() } } - private void SetVisualState(bool animate) + /// + /// Sets focus to the relevant control based on the viewState. + /// + /// the view state + private void SetFocus(MasterDetailsViewState viewState) { - string state; - string noSelectionState; - string hasSelectionState; - if (ActualWidth < CompactModeThresholdWidth) + if (viewState != MasterDetailsViewState.Details) { - state = NarrowState; - noSelectionState = NoSelectionNarrowState; - hasSelectionState = HasSelectionNarrowState; + FocusItemList(); } else { - state = WideState; - noSelectionState = NoSelectionWideState; - hasSelectionState = HasSelectionWideState; - } - - VisualStateManager.GoToState(this, state, animate); - VisualStateManager.GoToState(this, SelectedItem == null ? noSelectionState : hasSelectionState, animate); - } - - private void SetNavigationViewBackButtonState(int visible, bool enabled) - { - if (_navigationView == null) - { - return; - } - - var navType = _navigationView.GetType(); - var visibleProperty = navType.GetProperty("IsBackButtonVisible"); - if (visibleProperty != null) - { - _previousNavigationViewBackVisibilty = (int)visibleProperty.GetValue(_navigationView); - visibleProperty.SetValue(_navigationView, visible); - } - - var enabledProperty = navType.GetProperty("IsBackEnabled"); - if (enabledProperty != null) - { - _previousNavigationViewBackEnabled = (bool)enabledProperty.GetValue(_navigationView); - enabledProperty.SetValue(_navigationView, enabled); + FocusFirstFocusableElementInDetails(); } } - private void SetDetailsContent() + /// + /// Sets focus to the first focusable element in the details template + /// + private void FocusFirstFocusableElementInDetails() { - if (_detailsPresenter != null) + if (GetTemplateChild(PART_DETAILS_PANE) is DependencyObject details) { - _detailsPresenter.Content = MapDetails == null - ? SelectedItem - : SelectedItem != null ? MapDetails(SelectedItem) : null; + DependencyObject focusableElement = FocusManager.FindFirstFocusableElement(details); + (focusableElement as Control)?.Focus(FocusState.Programmatic); } } - private void OnMasterCommandBarChanged() - { - OnCommandBarChanged("MasterCommandBarPanel", MasterCommandBar); - } - - private void OnDetailsCommandBarChanged() - { - OnCommandBarChanged("DetailsCommandBarPanel", DetailsCommandBar); - } - - private void OnCommandBarChanged(string panelName, CommandBar commandbar) + /// + /// Sets focus to the item list + /// + private void FocusItemList() { - var panel = GetTemplateChild(panelName) as Panel; - if (panel == null) + if (GetTemplateChild(PART_MASTER_LIST) is Control masterList) { - return; - } - - panel.Children.Clear(); - if (commandbar != null) - { - panel.Children.Add(commandbar); + masterList.Focus(FocusState.Programmatic); } } @@ -480,64 +258,113 @@ private void SetListSelectionWithKeyboardFocusOnVisualStateChanged(MasterDetails /// private void SetListSelectionWithKeyboardFocus(bool singleSelectionFollowsFocus) { - if (GetTemplateChild("MasterList") is Windows.UI.Xaml.Controls.ListViewBase masterList) + if (GetTemplateChild(PART_MASTER_COMMAND_BAR) is ListViewBase masterList) { masterList.SingleSelectionFollowsFocus = singleSelectionFollowsFocus; } } /// - /// Fires when the selection state of the control changes + /// Invoked whenever application code or internal processes (such as a rebuilding layout pass) call + /// ApplyTemplate. In simplest terms, this means the method is called just before a UI element displays + /// in your app. Override this method to influence the default post-template logic of a class. /// - /// the sender - /// the event args - /// - /// Sets focus to the item list when the viewState is not Details. - /// Sets whether the selected item should change when focused with the keyboard. - /// - private void OnSelectionStateChanged(object sender, VisualStateChangedEventArgs e) + protected override void OnApplyTemplate() { - SetFocus(ViewState); - SetListSelectionWithKeyboardFocusOnVisualStateChanged(ViewState); - } + base.OnApplyTemplate(); - /// - /// Sets focus to the relevant control based on the viewState. - /// - /// the view state - private void SetFocus(MasterDetailsViewState viewState) - { - if (viewState != MasterDetailsViewState.Details) + if (!(inlineBackButton is null)) { - FocusItemList(); + inlineBackButton.Click -= OnInlineBackButtonClicked; } - else + + inlineBackButton = (Button)GetTemplateChild(PART_BACK_BUTTON); + if (!(inlineBackButton is null)) { - FocusFirstFocusableElementInDetails(); + inlineBackButton.Click += OnInlineBackButtonClicked; } + + twoPaneView = (Microsoft.UI.Xaml.Controls.TwoPaneView)GetTemplateChild(PART_ROOT_PANE); + if (!(twoPaneView is null)) + { + twoPaneView.ModeChanged += OnModeChanged; + } + + detailsPresenter = (ContentPresenter)GetTemplateChild(PART_DETAILS_PRESENTER); + + SetDetailsContent(); + + SetMasterHeaderVisibility(); + OnDetailsCommandBarChanged(); + OnMasterCommandBarChanged(); + + UpdateView(true); } - /// - /// Sets focus to the first focusable element in the details template - /// - private void FocusFirstFocusableElementInDetails() + private void OnUnloaded(object sender, RoutedEventArgs e) { - if (GetTemplateChild(PartDetailsPanel) is DependencyObject details) + if (!DesignMode.DesignModeEnabled) { - var focusableElement = FocusManager.FindFirstFocusableElement(details); - (focusableElement as Control)?.Focus(FocusState.Programmatic); + SystemNavigationManager.GetForCurrentView().BackRequested -= OnBackRequested; + if (!(frame is null)) + { + frame.Navigating -= OnFrameNavigating; + } + + selectionStateGroup = (VisualStateGroup)GetTemplateChild(SELECTION_STATES); + if (!(selectionStateGroup is null)) + { + selectionStateGroup.CurrentStateChanged -= OnSelectionStateChanged; + selectionStateGroup = null; + } } } - /// - /// Sets focus to the item list - /// - private void FocusItemList() + private void OnLoaded(object sender, RoutedEventArgs e) { - if (GetTemplateChild("MasterList") is Control masterList) + if (!DesignMode.DesignModeEnabled) { - masterList.Focus(FocusState.Programmatic); + SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested; + if (!(frame is null)) + { + frame.Navigating -= OnFrameNavigating; + } + + navigationView = this.FindAscendants().FirstOrDefault(p => p.GetType().FullName == "Microsoft.UI.Xaml.Controls.NavigationView"); + frame = this.FindAscendant(); + if (!(frame is null)) + { + frame.Navigating += OnFrameNavigating; + } + + selectionStateGroup = (VisualStateGroup)GetTemplateChild(SELECTION_STATES); + if (!(selectionStateGroup is null)) + { + selectionStateGroup.CurrentStateChanged += OnSelectionStateChanged; + } + + UpdateView(true); } } + + private void OnModeChanged(Microsoft.UI.Xaml.Controls.TwoPaneView sender, object args) + { + UpdateView(true); + } + + /// + /// Fires when the selection state of the control changes + /// + /// the sender + /// the event args + /// + /// Sets focus to the item list when the viewState is not Details. + /// Sets whether the selected item should change when focused with the keyboard. + /// + private void OnSelectionStateChanged(object sender, VisualStateChangedEventArgs e) + { + SetFocus(ViewState); + SetListSelectionWithKeyboardFocusOnVisualStateChanged(ViewState); + } } -} \ No newline at end of file +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.xaml index dd37399662f..ce5ef93749c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.xaml @@ -1,6 +1,7 @@  + xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls">