448 строки
13 KiB
C#
448 строки
13 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Threading.Tasks;
|
|
using Windows.UI.Xaml;
|
|
using Windows.UI.Xaml.Controls;
|
|
using Windows.UI.Xaml.Data;
|
|
using Windows.UI.Xaml.Media;
|
|
using Windows.UI.Xaml.Automation.Peers;
|
|
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
|
|
using Specifics = Xamarin.Forms.PlatformConfiguration.WindowsSpecific.MasterDetailPage;
|
|
using WImageSource = Windows.UI.Xaml.Media.ImageSource;
|
|
|
|
namespace Xamarin.Forms.Platform.UWP
|
|
{
|
|
public class MasterDetailPageRenderer : IVisualElementRenderer, IToolbarProvider, ITitleProvider, ITitleIconProvider, ITitleViewProvider, IToolBarForegroundBinder
|
|
{
|
|
Page _master;
|
|
Page _detail;
|
|
bool _showTitle;
|
|
|
|
string _defaultAutomationPropertiesName;
|
|
AccessibilityView? _defaultAutomationPropertiesAccessibilityView;
|
|
string _defaultAutomationPropertiesHelpText;
|
|
UIElement _defaultAutomationPropertiesLabeledBy;
|
|
|
|
VisualElementTracker<Page, FrameworkElement> _tracker;
|
|
|
|
public MasterDetailControl Control { get; private set; }
|
|
|
|
public MasterDetailPage Element { get; private set; }
|
|
|
|
protected VisualElementTracker<Page, FrameworkElement> Tracker
|
|
{
|
|
get { return _tracker; }
|
|
set
|
|
{
|
|
if (_tracker == value)
|
|
return;
|
|
|
|
if (_tracker != null)
|
|
_tracker.Dispose();
|
|
|
|
_tracker = value;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
ClearMaster();
|
|
ClearDetail();
|
|
|
|
Tracker = null;
|
|
if (Element != null)
|
|
SetElement(null);
|
|
}
|
|
|
|
Brush ITitleProvider.BarBackgroundBrush
|
|
{
|
|
set { Control.ToolbarBackground = value; }
|
|
}
|
|
|
|
Brush ITitleProvider.BarForegroundBrush
|
|
{
|
|
set { Control.ToolbarForeground = value; }
|
|
}
|
|
|
|
bool ITitleProvider.ShowTitle
|
|
{
|
|
get { return _showTitle; }
|
|
|
|
set
|
|
{
|
|
if (_showTitle == value)
|
|
return;
|
|
|
|
_showTitle = value;
|
|
Control.DetailTitleVisibility = _showTitle ? Visibility.Visible : Visibility.Collapsed;
|
|
}
|
|
}
|
|
|
|
string ITitleProvider.Title
|
|
{
|
|
get { return Element?.Title; }
|
|
|
|
set
|
|
{
|
|
if (Control != null)
|
|
Control.DetailTitle = value;
|
|
}
|
|
}
|
|
|
|
Task<CommandBar> IToolbarProvider.GetCommandBarAsync()
|
|
{
|
|
return ((IToolbarProvider)Control)?.GetCommandBarAsync();
|
|
}
|
|
|
|
FrameworkElement IVisualElementRenderer.ContainerElement
|
|
{
|
|
get { return Control; }
|
|
}
|
|
|
|
VisualElement IVisualElementRenderer.Element
|
|
{
|
|
get { return Element; }
|
|
}
|
|
|
|
WImageSource ITitleIconProvider.TitleIcon
|
|
{
|
|
get { return Control?.DetailTitleIcon; }
|
|
|
|
set
|
|
{
|
|
if (Control != null)
|
|
Control.DetailTitleIcon = value;
|
|
}
|
|
}
|
|
|
|
View ITitleViewProvider.TitleView
|
|
{
|
|
get => Control?.DetailTitleView;
|
|
set
|
|
{
|
|
if (Control != null)
|
|
Control.DetailTitleView = value;
|
|
}
|
|
}
|
|
|
|
#pragma warning disable 0067 // Revisit: Can't remove; required by interface
|
|
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
|
|
#pragma warning restore
|
|
|
|
public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
|
|
{
|
|
Size size = Device.Info.ScaledScreenSize;
|
|
return new SizeRequest(new Size(size.Width, size.Height));
|
|
}
|
|
|
|
UIElement IVisualElementRenderer.GetNativeElement()
|
|
{
|
|
return Control;
|
|
}
|
|
|
|
public void SetElement(VisualElement element)
|
|
{
|
|
MasterDetailPage old = Element;
|
|
Element = (MasterDetailPage)element;
|
|
|
|
if (element != old)
|
|
OnElementChanged(new ElementChangedEventArgs<MasterDetailPage>(old, Element));
|
|
}
|
|
|
|
protected virtual void OnElementChanged(ElementChangedEventArgs<MasterDetailPage> e)
|
|
{
|
|
if (e.OldElement != null)
|
|
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
|
|
|
|
if (e.NewElement != null)
|
|
{
|
|
if (Control == null)
|
|
{
|
|
Control = new MasterDetailControl();
|
|
Control.Loaded += OnControlLoaded;
|
|
Control.Unloaded += OnControlUnloaded;
|
|
Control.SizeChanged += OnNativeSizeChanged;
|
|
|
|
Control.RegisterPropertyChangedCallback(MasterDetailControl.IsPaneOpenProperty, OnIsPaneOpenChanged);
|
|
|
|
Tracker = new VisualElementTracker<Page, FrameworkElement> { Container = Control, Element = Element };
|
|
}
|
|
|
|
e.NewElement.PropertyChanged += OnElementPropertyChanged;
|
|
UpdateMode();
|
|
UpdateDetail();
|
|
UpdateMaster();
|
|
UpdateIsPresented();
|
|
|
|
if (!string.IsNullOrEmpty(e.NewElement.AutomationId))
|
|
Control.SetValue(Windows.UI.Xaml.Automation.AutomationProperties.AutomationIdProperty, e.NewElement.AutomationId);
|
|
|
|
((ITitleProvider)this).BarBackgroundBrush = (Brush)Windows.UI.Xaml.Application.Current.Resources["SystemControlBackgroundChromeMediumLowBrush"];
|
|
UpdateToolbarPlacement();
|
|
UpdateToolbarDynamicOverflowEnabled();
|
|
|
|
_defaultAutomationPropertiesName = Control.SetAutomationPropertiesName(Element, _defaultAutomationPropertiesName);
|
|
_defaultAutomationPropertiesHelpText = Control.SetAutomationPropertiesHelpText(Element, _defaultAutomationPropertiesHelpText);
|
|
_defaultAutomationPropertiesLabeledBy = Control.SetAutomationPropertiesLabeledBy(Element, _defaultAutomationPropertiesLabeledBy);
|
|
_defaultAutomationPropertiesAccessibilityView = Control.SetAutomationPropertiesAccessibilityView(Element, _defaultAutomationPropertiesAccessibilityView);
|
|
}
|
|
}
|
|
|
|
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName || e.PropertyName == MasterDetailPage.MasterBehaviorProperty.PropertyName)
|
|
UpdateIsPresented();
|
|
else if (e.PropertyName == "Master")
|
|
UpdateMaster();
|
|
else if (e.PropertyName == "Detail")
|
|
UpdateDetail();
|
|
else if (e.PropertyName == nameof(MasterDetailControl.ShouldShowSplitMode)
|
|
|| e.PropertyName == Specifics.CollapseStyleProperty.PropertyName
|
|
|| e.PropertyName == Specifics.CollapsedPaneWidthProperty.PropertyName)
|
|
UpdateMode();
|
|
else if (e.PropertyName == PlatformConfiguration.WindowsSpecific.Page.ToolbarPlacementProperty.PropertyName)
|
|
UpdateToolbarPlacement();
|
|
else if (e.PropertyName == PlatformConfiguration.WindowsSpecific.Page.ToolbarDynamicOverflowEnabledProperty.PropertyName)
|
|
UpdateToolbarDynamicOverflowEnabled();
|
|
else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
|
|
UpdateFlowDirection();
|
|
else if (e.PropertyName == AutomationProperties.NameProperty.PropertyName)
|
|
_defaultAutomationPropertiesName = Control.SetAutomationPropertiesName(Element, _defaultAutomationPropertiesName);
|
|
else if (e.PropertyName == AutomationProperties.HelpTextProperty.PropertyName)
|
|
_defaultAutomationPropertiesHelpText = Control.SetAutomationPropertiesHelpText(Element, _defaultAutomationPropertiesHelpText);
|
|
else if (e.PropertyName == AutomationProperties.LabeledByProperty.PropertyName)
|
|
_defaultAutomationPropertiesLabeledBy = Control.SetAutomationPropertiesLabeledBy(Element, _defaultAutomationPropertiesLabeledBy);
|
|
else if (e.PropertyName == AutomationProperties.IsInAccessibleTreeProperty.PropertyName)
|
|
_defaultAutomationPropertiesAccessibilityView = Control.SetAutomationPropertiesAccessibilityView(Element, _defaultAutomationPropertiesAccessibilityView);
|
|
}
|
|
|
|
void ClearDetail()
|
|
{
|
|
((ITitleProvider)this).ShowTitle = false;
|
|
|
|
var titleView = ((ITitleViewProvider)this).TitleView;
|
|
titleView?.ClearValue(Platform.RendererProperty);
|
|
titleView = null;
|
|
|
|
if (_detail == null)
|
|
return;
|
|
|
|
_detail.PropertyChanged -= OnDetailPropertyChanged;
|
|
|
|
IVisualElementRenderer renderer = Platform.GetRenderer(_detail);
|
|
renderer?.Dispose();
|
|
|
|
_detail.ClearValue(Platform.RendererProperty);
|
|
_detail = null;
|
|
}
|
|
|
|
void ClearMaster()
|
|
{
|
|
if (_master == null)
|
|
return;
|
|
|
|
_master.PropertyChanged -= OnMasterPropertyChanged;
|
|
|
|
IVisualElementRenderer renderer = Platform.GetRenderer(_master);
|
|
renderer?.Dispose();
|
|
|
|
_master.ClearValue(Platform.RendererProperty);
|
|
_master = null;
|
|
}
|
|
|
|
void OnControlLoaded(object sender, RoutedEventArgs routedEventArgs)
|
|
{
|
|
if (Element == null)
|
|
return;
|
|
|
|
Element.SendAppearing();
|
|
UpdateBounds();
|
|
UpdateFlowDirection();
|
|
}
|
|
|
|
void OnControlUnloaded(object sender, RoutedEventArgs routedEventArgs)
|
|
{
|
|
Element?.SendDisappearing();
|
|
}
|
|
|
|
void OnDetailPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == Page.TitleProperty.PropertyName)
|
|
UpdateDetailTitle();
|
|
else if (e.PropertyName == NavigationPage.TitleIconImageSourceProperty.PropertyName)
|
|
UpdateDetailTitleIcon();
|
|
else if (e.PropertyName == NavigationPage.TitleViewProperty.PropertyName)
|
|
UpdateDetailTitleView();
|
|
else if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName)
|
|
{
|
|
UpdateDetailTitle();
|
|
UpdateDetailTitleIcon();
|
|
}
|
|
}
|
|
|
|
void OnIsPaneOpenChanged(DependencyObject sender, DependencyProperty dp)
|
|
{
|
|
((IElementController)Element).SetValueFromRenderer(MasterDetailPage.IsPresentedProperty, Control.IsPaneOpen);
|
|
UpdateBounds();
|
|
}
|
|
|
|
void OnMasterPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == Page.TitleProperty.PropertyName)
|
|
Control.MasterTitle = _master?.Title;
|
|
}
|
|
|
|
void OnNativeSizeChanged(object sender, SizeChangedEventArgs e)
|
|
{
|
|
UpdateBounds();
|
|
}
|
|
|
|
void UpdateBounds()
|
|
{
|
|
Windows.Foundation.Size masterSize = Control.MasterSize;
|
|
Windows.Foundation.Size detailSize = Control.DetailSize;
|
|
|
|
Element.MasterBounds = new Rectangle(0, 0, masterSize.Width, masterSize.Height);
|
|
Element.DetailBounds = new Rectangle(0, 0, detailSize.Width, detailSize.Height);
|
|
}
|
|
|
|
void UpdateDetail()
|
|
{
|
|
ClearDetail();
|
|
|
|
FrameworkElement element = null;
|
|
|
|
_detail = Element.Detail;
|
|
if (_detail != null)
|
|
{
|
|
_detail.PropertyChanged += OnDetailPropertyChanged;
|
|
|
|
IVisualElementRenderer renderer = _detail.GetOrCreateRenderer();
|
|
element = renderer.ContainerElement;
|
|
|
|
UpdateToolbarVisibilty();
|
|
}
|
|
|
|
Control.Detail = element;
|
|
UpdateDetailTitle();
|
|
UpdateDetailTitleIcon();
|
|
UpdateDetailTitleView();
|
|
}
|
|
|
|
void UpdateDetailTitle()
|
|
{
|
|
if (_detail == null)
|
|
return;
|
|
|
|
Control.DetailTitle = GetCurrentPage().Title ?? Element?.Title;
|
|
(this as ITitleProvider).ShowTitle = !string.IsNullOrEmpty(Control.DetailTitle) || Element.MasterBehavior == MasterBehavior.Popover;
|
|
}
|
|
|
|
async void UpdateDetailTitleIcon()
|
|
{
|
|
if (_detail == null)
|
|
return;
|
|
|
|
Control.DetailTitleIcon = await NavigationPage.GetTitleIconImageSource(GetCurrentPage()).ToWindowsImageSourceAsync();
|
|
Control.InvalidateMeasure();
|
|
}
|
|
|
|
void UpdateDetailTitleView()
|
|
{
|
|
if (_detail == null)
|
|
return;
|
|
|
|
Control.DetailTitleView = NavigationPage.GetTitleView(GetCurrentPage()) as View;
|
|
Control.InvalidateMeasure();
|
|
}
|
|
|
|
void UpdateFlowDirection()
|
|
{
|
|
Control.UpdateFlowDirection(Element);
|
|
}
|
|
|
|
void UpdateIsPresented()
|
|
{
|
|
// Ignore the IsPresented value being set to false for Split mode on desktop and allow the master
|
|
// view to be made initially visible
|
|
if (Device.Idiom == TargetIdiom.Desktop && Control.IsPaneOpen && Element.MasterBehavior != MasterBehavior.Popover)
|
|
return;
|
|
|
|
Control.IsPaneOpen = Element.IsPresented;
|
|
}
|
|
|
|
void UpdateMaster()
|
|
{
|
|
ClearMaster();
|
|
|
|
FrameworkElement element = null;
|
|
_master = Element.Master;
|
|
if (_master != null)
|
|
{
|
|
_master.PropertyChanged += OnMasterPropertyChanged;
|
|
|
|
IVisualElementRenderer renderer = _master.GetOrCreateRenderer();
|
|
element = renderer.ContainerElement;
|
|
}
|
|
|
|
Control.Master = element;
|
|
Control.MasterTitle = _master?.Title;
|
|
|
|
UpdateToolbarVisibilty();
|
|
}
|
|
|
|
void UpdateMode()
|
|
{
|
|
UpdateDetailTitle();
|
|
UpdateDetailTitleIcon();
|
|
UpdateDetailTitleView();
|
|
Control.CollapseStyle = Element.OnThisPlatform().GetCollapseStyle();
|
|
Control.CollapsedPaneWidth = Element.OnThisPlatform().CollapsedPaneWidth();
|
|
Control.ShouldShowSplitMode = Element.ShouldShowSplitMode;
|
|
}
|
|
|
|
void UpdateToolbarPlacement()
|
|
{
|
|
Control.ToolbarPlacement = Element.OnThisPlatform().GetToolbarPlacement();
|
|
}
|
|
|
|
void UpdateToolbarDynamicOverflowEnabled()
|
|
{
|
|
Control.ToolbarDynamicOverflowEnabled = Element.OnThisPlatform().GetToolbarDynamicOverflowEnabled();
|
|
}
|
|
|
|
void UpdateToolbarVisibilty()
|
|
{
|
|
// Enforce consistency rules on toolbar
|
|
Control.ShouldShowToolbar = _detail is NavigationPage || _master is NavigationPage;
|
|
if (_detail is NavigationPage _detailNav)
|
|
Control.ShouldShowNavigationBar = NavigationPage.GetHasNavigationBar(_detailNav.CurrentPage);
|
|
|
|
}
|
|
|
|
public void BindForegroundColor(AppBar appBar)
|
|
{
|
|
SetAppBarForegroundBinding(appBar);
|
|
}
|
|
|
|
public void BindForegroundColor(AppBarButton button)
|
|
{
|
|
SetAppBarForegroundBinding(button);
|
|
}
|
|
|
|
void SetAppBarForegroundBinding(FrameworkElement element)
|
|
{
|
|
element.SetBinding(Windows.UI.Xaml.Controls.Control.ForegroundProperty,
|
|
new Windows.UI.Xaml.Data.Binding { Path = new PropertyPath("Control.ToolbarForeground"), Source = this, RelativeSource = new RelativeSource { Mode = RelativeSourceMode.TemplatedParent } });
|
|
}
|
|
|
|
Page GetCurrentPage()
|
|
{
|
|
if (_detail is NavigationPage page)
|
|
return page.CurrentPage;
|
|
|
|
return _detail;
|
|
}
|
|
}
|
|
}
|