592 строки
18 KiB
C#
592 строки
18 KiB
C#
using System;
|
|
using System.Collections.Specialized;
|
|
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 Xamarin.Forms.Internals;
|
|
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;
|
|
using WGrid = Windows.UI.Xaml.Controls.Grid;
|
|
using WTextAlignment = Windows.UI.Xaml.TextAlignment;
|
|
using WHorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment;
|
|
using WVisibility = Windows.UI.Xaml.Visibility;
|
|
using WStackPanel = Windows.UI.Xaml.Controls.StackPanel;
|
|
using WImage = Windows.UI.Xaml.Controls.Image;
|
|
using WTextBlock = Windows.UI.Xaml.Controls.TextBlock;
|
|
using Specifics = Xamarin.Forms.PlatformConfiguration.WindowsSpecific.TabbedPage;
|
|
using VisualElementSpecifics = Xamarin.Forms.PlatformConfiguration.WindowsSpecific.VisualElement;
|
|
using PageSpecifics = Xamarin.Forms.PlatformConfiguration.WindowsSpecific.Page;
|
|
using Windows.UI.Xaml.Input;
|
|
using System.Linq;
|
|
using WSelectionChangedEventArgs = Windows.UI.Xaml.Controls.SelectionChangedEventArgs;
|
|
|
|
namespace Xamarin.Forms.Platform.UWP
|
|
{
|
|
public class TabbedPageRenderer : IVisualElementRenderer, ITitleProvider, IToolbarProvider,
|
|
IToolBarForegroundBinder
|
|
{
|
|
const string TabBarHeaderStackPanelName = "TabbedPageHeaderStackPanel";
|
|
const string TabBarHeaderImageName = "TabbedPageHeaderImage";
|
|
const string TabBarHeaderTextBlockName = "TabbedPageHeaderTextBlock";
|
|
const string TabBarHeaderGridName = "TabbedPageHeaderGrid";
|
|
|
|
Color _barBackgroundColor;
|
|
Color _barTextColor;
|
|
bool _disposed;
|
|
bool _showTitle;
|
|
Brush _defaultSelectedColor;
|
|
Brush _defaultUnselectedColor;
|
|
|
|
WTextAlignment _oldBarTextBlockTextAlignment = WTextAlignment.Center;
|
|
WHorizontalAlignment _oldBarTextBlockHorinzontalAlignment = WHorizontalAlignment.Center;
|
|
|
|
VisualElementTracker<Page, Pivot> _tracker;
|
|
|
|
ITitleProvider TitleProvider => this;
|
|
|
|
public FormsPivot Control { get; private set; }
|
|
|
|
public TabbedPage Element { get; private set; }
|
|
|
|
protected VisualElementTracker<Page, Pivot> Tracker
|
|
{
|
|
get { return _tracker; }
|
|
set
|
|
{
|
|
if (_tracker == value)
|
|
return;
|
|
|
|
if (_tracker != null)
|
|
_tracker.Dispose();
|
|
|
|
_tracker = value;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
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;
|
|
|
|
UpdateTitleVisibility();
|
|
}
|
|
}
|
|
|
|
string ITitleProvider.Title
|
|
{
|
|
get { return (string)Control?.Title; }
|
|
|
|
set
|
|
{
|
|
if (Control != null && _showTitle)
|
|
Control.Title = value;
|
|
}
|
|
}
|
|
|
|
public Task<CommandBar> GetCommandBarAsync()
|
|
{
|
|
return (Control as IToolbarProvider)?.GetCommandBarAsync();
|
|
}
|
|
|
|
public FrameworkElement ContainerElement
|
|
{
|
|
get { return Control; }
|
|
}
|
|
|
|
VisualElement IVisualElementRenderer.Element
|
|
{
|
|
get { return Element; }
|
|
}
|
|
|
|
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
|
|
|
|
public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
|
|
{
|
|
var constraint = new Windows.Foundation.Size(widthConstraint, heightConstraint);
|
|
|
|
double oldWidth = Control.Width;
|
|
double oldHeight = Control.Height;
|
|
|
|
Control.Height = double.NaN;
|
|
Control.Width = double.NaN;
|
|
|
|
Control.Measure(constraint);
|
|
var result = new Size(Math.Ceiling(Control.DesiredSize.Width), Math.Ceiling(Control.DesiredSize.Height));
|
|
|
|
Control.Width = oldWidth;
|
|
Control.Height = oldHeight;
|
|
|
|
return new SizeRequest(result);
|
|
}
|
|
|
|
UIElement IVisualElementRenderer.GetNativeElement()
|
|
{
|
|
return Control;
|
|
}
|
|
|
|
public void SetElement(VisualElement element)
|
|
{
|
|
if (element != null && !(element is TabbedPage))
|
|
throw new ArgumentException("Element must be a TabbedPage", "element");
|
|
|
|
TabbedPage oldElement = Element;
|
|
Element = (TabbedPage)element;
|
|
|
|
if (oldElement != null)
|
|
{
|
|
oldElement.PropertyChanged -= OnElementPropertyChanged;
|
|
((INotifyCollectionChanged)oldElement.Children).CollectionChanged -= OnPagesChanged;
|
|
Control?.GetDescendantsByName<TextBlock>(TabBarHeaderTextBlockName).ForEach(t => { t.AccessKeyInvoked -= AccessKeyInvokedForTab; });
|
|
}
|
|
|
|
if (element != null)
|
|
{
|
|
if (Control == null)
|
|
{
|
|
Control = new FormsPivot {
|
|
Style = (Windows.UI.Xaml.Style)Windows.UI.Xaml.Application.Current.Resources["TabbedPageStyle"],
|
|
};
|
|
|
|
Control.SelectionChanged += OnSelectionChanged;
|
|
|
|
Tracker = new BackgroundTracker<Pivot>(Windows.UI.Xaml.Controls.Control.BackgroundProperty) {
|
|
Element = (Page)element,
|
|
Control = Control,
|
|
Container = Control
|
|
};
|
|
|
|
Control.Loaded += OnLoaded;
|
|
Control.Unloaded += OnUnloaded;
|
|
}
|
|
|
|
Control.DataContext = Element;
|
|
OnPagesChanged(Element.Children,
|
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|
|
|
UpdateCurrentPage();
|
|
UpdateToolbarPlacement();
|
|
UpdateToolbarDynamicOverflowEnabled();
|
|
|
|
((INotifyCollectionChanged)Element.Children).CollectionChanged += OnPagesChanged;
|
|
element.PropertyChanged += OnElementPropertyChanged;
|
|
|
|
if (!string.IsNullOrEmpty(element.AutomationId))
|
|
Control.SetValue(Windows.UI.Xaml.Automation.AutomationProperties.AutomationIdProperty, element.AutomationId);
|
|
}
|
|
|
|
OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposing || _disposed)
|
|
return;
|
|
|
|
_disposed = true;
|
|
Element?.SendDisappearing();
|
|
SetElement(null);
|
|
Tracker = null;
|
|
}
|
|
|
|
protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
|
|
{
|
|
ElementChanged?.Invoke(this, e);
|
|
}
|
|
|
|
void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(TabbedPage.CurrentPage))
|
|
{
|
|
UpdateCurrentPage();
|
|
UpdateBarTextColor();
|
|
UpdateBarBackgroundColor();
|
|
}
|
|
else if (e.PropertyName == TabbedPage.BarTextColorProperty.PropertyName)
|
|
UpdateBarTextColor();
|
|
else if (e.PropertyName == TabbedPage.BarBackgroundColorProperty.PropertyName)
|
|
UpdateBarBackgroundColor();
|
|
else if (e.PropertyName == PlatformConfiguration.WindowsSpecific.Page.ToolbarPlacementProperty.PropertyName)
|
|
UpdateToolbarPlacement();
|
|
else if (e.PropertyName == PlatformConfiguration.WindowsSpecific.Page.ToolbarDynamicOverflowEnabledProperty.PropertyName)
|
|
UpdateToolbarDynamicOverflowEnabled();
|
|
else if (e.PropertyName == Specifics.HeaderIconsEnabledProperty.PropertyName)
|
|
UpdateBarIcons();
|
|
else if (e.PropertyName == Specifics.HeaderIconsSizeProperty.PropertyName)
|
|
UpdateBarIcons();
|
|
else if (e.PropertyName == PageSpecifics.ToolbarPlacementProperty.PropertyName)
|
|
UpdateToolbarPlacement();
|
|
else if (e.PropertyName == TabbedPage.SelectedTabColorProperty.PropertyName || e.PropertyName == TabbedPage.UnselectedTabColorProperty.PropertyName)
|
|
UpdateSelectedTabColors();
|
|
}
|
|
|
|
void OnLoaded(object sender, RoutedEventArgs args)
|
|
{
|
|
Element?.SendAppearing();
|
|
|
|
UpdateBarTextColor();
|
|
UpdateBarBackgroundColor();
|
|
UpdateBarIcons();
|
|
UpdateAccessKeys();
|
|
UpdateSelectedTabColors();
|
|
}
|
|
|
|
void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
e.Apply(Element.Children, Control.Items);
|
|
switch (e.Action)
|
|
{
|
|
case NotifyCollectionChangedAction.Add:
|
|
case NotifyCollectionChangedAction.Remove:
|
|
case NotifyCollectionChangedAction.Replace:
|
|
if (e.NewItems != null)
|
|
for (int i = 0; i< e.NewItems.Count; i++)
|
|
((Page)e.NewItems[i]).PropertyChanged += OnChildPagePropertyChanged;
|
|
if (e.OldItems != null)
|
|
for (int i = 0; i < e.OldItems.Count; i++)
|
|
((Page)e.OldItems[i]).PropertyChanged -= OnChildPagePropertyChanged;
|
|
break;
|
|
case NotifyCollectionChangedAction.Reset:
|
|
foreach (var page in Element.Children)
|
|
page.PropertyChanged += OnChildPagePropertyChanged;
|
|
break;
|
|
}
|
|
|
|
Control.UpdateLayout();
|
|
EnsureBarColors(e.Action);
|
|
}
|
|
|
|
void EnsureBarColors(NotifyCollectionChangedAction action)
|
|
{
|
|
switch (action)
|
|
{
|
|
case NotifyCollectionChangedAction.Add:
|
|
case NotifyCollectionChangedAction.Replace:
|
|
case NotifyCollectionChangedAction.Reset:
|
|
// Need to make sure any new items have the correct fore/background colors
|
|
ApplyBarBackgroundColor(true);
|
|
ApplyBarTextColor(true);
|
|
UpdateSelectedTabColors();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OnChildPagePropertyChanged(object sender, PropertyChangedEventArgs e) {
|
|
var page = sender as Page;
|
|
if (page != null)
|
|
{
|
|
// If AccessKeys properties are updated on a child (tab) we want to
|
|
// update the access key on the native control.
|
|
if (e.PropertyName == VisualElementSpecifics.AccessKeyProperty.PropertyName ||
|
|
e.PropertyName == VisualElementSpecifics.AccessKeyPlacementProperty.PropertyName ||
|
|
e.PropertyName == VisualElementSpecifics.AccessKeyHorizontalOffsetProperty.PropertyName ||
|
|
e.PropertyName == VisualElementSpecifics.AccessKeyVerticalOffsetProperty.PropertyName)
|
|
UpdateAccessKeys();
|
|
}
|
|
}
|
|
|
|
void OnSelectionChanged(object sender, WSelectionChangedEventArgs e)
|
|
{
|
|
if (Element == null)
|
|
return;
|
|
|
|
Page page = e.AddedItems.Count > 0 ? (Page)e.AddedItems[0] : null;
|
|
Page currentPage = Element.CurrentPage;
|
|
if (currentPage == page)
|
|
return;
|
|
currentPage?.SendDisappearing();
|
|
Element.CurrentPage = page;
|
|
|
|
UpdateSelectedTabColors();
|
|
|
|
page?.SendAppearing();
|
|
}
|
|
|
|
void OnUnloaded(object sender, RoutedEventArgs args)
|
|
{
|
|
Element?.SendDisappearing();
|
|
}
|
|
|
|
Brush GetBarBackgroundBrush()
|
|
{
|
|
object defaultColor = new SolidColorBrush(Windows.UI.Colors.Transparent);
|
|
|
|
if (Element.BarBackgroundColor.IsDefault && defaultColor != null)
|
|
return (Brush)defaultColor;
|
|
return Element.BarBackgroundColor.ToBrush();
|
|
}
|
|
|
|
Brush GetBarForegroundBrush()
|
|
{
|
|
object defaultColor = Windows.UI.Xaml.Application.Current.Resources["ApplicationForegroundThemeBrush"];
|
|
if (Element.BarTextColor.IsDefault && defaultColor != null)
|
|
return (Brush)defaultColor;
|
|
return Element.BarTextColor.ToBrush();
|
|
}
|
|
|
|
void UpdateBarBackgroundColor()
|
|
{
|
|
if (Element == null) return;
|
|
var barBackgroundColor = Element.BarBackgroundColor;
|
|
|
|
if (barBackgroundColor == _barBackgroundColor) return;
|
|
_barBackgroundColor = barBackgroundColor;
|
|
|
|
ApplyBarBackgroundColor();
|
|
}
|
|
|
|
void ApplyBarBackgroundColor(bool force = false)
|
|
{
|
|
var controlToolbarBackground = Control.ToolbarBackground;
|
|
if (controlToolbarBackground == null && _barBackgroundColor.IsDefault) return;
|
|
|
|
var brush = GetBarBackgroundBrush();
|
|
if (brush == controlToolbarBackground && !force)
|
|
return;
|
|
|
|
TitleProvider.BarBackgroundBrush = brush;
|
|
|
|
foreach (WGrid tabBarGrid in Control.GetDescendantsByName<WGrid>(TabBarHeaderGridName))
|
|
{
|
|
tabBarGrid.Background = brush;
|
|
}
|
|
}
|
|
|
|
void UpdateBarTextColor()
|
|
{
|
|
if (Element == null) return;
|
|
var barTextColor = Element.BarTextColor;
|
|
|
|
if (barTextColor == _barTextColor) return;
|
|
_barTextColor = barTextColor;
|
|
|
|
ApplyBarTextColor();
|
|
}
|
|
|
|
void ApplyBarTextColor(bool force = false)
|
|
{
|
|
var controlToolbarForeground = Control.ToolbarForeground;
|
|
if (controlToolbarForeground == null && _barTextColor.IsDefault) return;
|
|
|
|
var brush = GetBarForegroundBrush();
|
|
if (brush == controlToolbarForeground && !force)
|
|
return;
|
|
|
|
TitleProvider.BarForegroundBrush = brush;
|
|
|
|
foreach (WTextBlock tabBarTextBlock in Control.GetDescendantsByName<WTextBlock>(TabBarHeaderTextBlockName))
|
|
{
|
|
tabBarTextBlock.Foreground = brush;
|
|
}
|
|
}
|
|
|
|
void UpdateTitleVisibility()
|
|
{
|
|
Control.TitleVisibility = _showTitle ? WVisibility.Visible : WVisibility.Collapsed;
|
|
}
|
|
|
|
void UpdateCurrentPage()
|
|
{
|
|
Page page = Element.CurrentPage;
|
|
|
|
var nav = page as NavigationPage;
|
|
TitleProvider.ShowTitle = nav != null;
|
|
|
|
// Enforce consistency rules on toolbar (show toolbar if visible Tab is Navigation Page)
|
|
Control.ShouldShowToolbar = nav != null;
|
|
|
|
if (page == null)
|
|
return;
|
|
|
|
Control.SelectedItem = page;
|
|
}
|
|
|
|
void UpdateBarIcons()
|
|
{
|
|
if (Control == null || Element == null)
|
|
return;
|
|
|
|
bool headerIconsEnabled = Element.OnThisPlatform().GetHeaderIconsEnabled();
|
|
bool invalidateMeasure = false;
|
|
|
|
// Get all stack panels affected by update.
|
|
var stackPanels = Control.GetDescendantsByName<WStackPanel>(TabBarHeaderStackPanelName);
|
|
foreach (var stackPanel in stackPanels)
|
|
{
|
|
int stackPanelChildCount = stackPanel.Children.Count;
|
|
for (int i = 0; i < stackPanelChildCount; i++)
|
|
{
|
|
var stackPanelItem = stackPanel.Children[i];
|
|
if (stackPanelItem is WImage tabBarImage)
|
|
{
|
|
// Update icon image.
|
|
if (tabBarImage.GetValue(FrameworkElement.NameProperty).ToString() == TabBarHeaderImageName)
|
|
{
|
|
if (headerIconsEnabled)
|
|
{
|
|
if (Element.IsSet(Specifics.HeaderIconsSizeProperty))
|
|
{
|
|
Size iconSize = Element.OnThisPlatform().GetHeaderIconsSize();
|
|
tabBarImage.Height = iconSize.Height;
|
|
tabBarImage.Width = iconSize.Width;
|
|
}
|
|
tabBarImage.HorizontalAlignment = WHorizontalAlignment.Center;
|
|
tabBarImage.Visibility = WVisibility.Visible;
|
|
}
|
|
else
|
|
{
|
|
tabBarImage.Visibility = WVisibility.Collapsed;
|
|
}
|
|
|
|
invalidateMeasure = true;
|
|
}
|
|
}
|
|
else if (stackPanelItem is WTextBlock tabBarTextblock)
|
|
{
|
|
// Update text block.
|
|
if (tabBarTextblock.GetValue(FrameworkElement.NameProperty).ToString() == TabBarHeaderTextBlockName)
|
|
{
|
|
if (headerIconsEnabled)
|
|
{
|
|
// Remember old values so we can restore them if icons are collapsed.
|
|
// NOTE, since all Textblock instances in this stack panel comes from the same
|
|
// style, we just keep one copy of the value (since they should be identical).
|
|
if (tabBarTextblock.TextAlignment != WTextAlignment.Center)
|
|
{
|
|
_oldBarTextBlockTextAlignment = tabBarTextblock.TextAlignment;
|
|
tabBarTextblock.TextAlignment = WTextAlignment.Center;
|
|
}
|
|
|
|
if (tabBarTextblock.HorizontalAlignment != WHorizontalAlignment.Center)
|
|
{
|
|
_oldBarTextBlockHorinzontalAlignment = tabBarTextblock.HorizontalAlignment;
|
|
tabBarTextblock.HorizontalAlignment = WHorizontalAlignment.Center;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Restore old values.
|
|
tabBarTextblock.TextAlignment = _oldBarTextBlockTextAlignment;
|
|
tabBarTextblock.HorizontalAlignment = _oldBarTextBlockHorinzontalAlignment;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If items have been made visible or collapsed in panel, invalidate current control measures.
|
|
if (invalidateMeasure)
|
|
Control.InvalidateMeasure();
|
|
}
|
|
|
|
void UpdateToolbarPlacement()
|
|
{
|
|
Control.ToolbarPlacement = Element.OnThisPlatform().GetToolbarPlacement();
|
|
}
|
|
|
|
void UpdateToolbarDynamicOverflowEnabled()
|
|
{
|
|
Control.ToolbarDynamicOverflowEnabled = Element.OnThisPlatform().GetToolbarDynamicOverflowEnabled();
|
|
}
|
|
|
|
protected void UpdateAccessKeys()
|
|
{
|
|
Control?.GetDescendantsByName<TextBlock>(TabBarHeaderTextBlockName).ForEach(UpdateAccessKey);
|
|
}
|
|
|
|
void AccessKeyInvokedForTab(UIElement sender, AccessKeyInvokedEventArgs arg)
|
|
{
|
|
var tab = sender as TextBlock;
|
|
if (tab != null && tab.DataContext is Page page)
|
|
Element.CurrentPage = page;
|
|
}
|
|
|
|
protected void UpdateAccessKey(TextBlock control) {
|
|
|
|
if (control != null && control.DataContext is Page page)
|
|
{
|
|
var windowsElement = page.On<PlatformConfiguration.Windows>();
|
|
if (page.IsSet(VisualElementSpecifics.AccessKeyProperty))
|
|
{
|
|
control.AccessKeyInvoked += AccessKeyInvokedForTab;
|
|
}
|
|
AccessKeyHelper.UpdateAccessKey(control, page);
|
|
}
|
|
}
|
|
|
|
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("ToolbarForeground"),
|
|
Source = Control, RelativeSource = new RelativeSource { Mode = RelativeSourceMode.TemplatedParent } });
|
|
}
|
|
|
|
void UpdateSelectedTabColors()
|
|
{
|
|
// Retrieve all tab header textblocks
|
|
var allTabHeaderTextBlocks = Control.GetDescendantsByName<WTextBlock>(TabBarHeaderTextBlockName).ToArray();
|
|
if (allTabHeaderTextBlocks.Length != Control.Items.Count)
|
|
return;
|
|
|
|
// Loop through all pages in the Pivot control
|
|
foreach (Page page in Control.Items)
|
|
{
|
|
// Fetch just the textblock for the current page
|
|
var tabBarTextBlock = allTabHeaderTextBlocks[Control.Items.IndexOf(page)];
|
|
|
|
// Apply selected or unselected style to the current textblock
|
|
if (page == Element.CurrentPage)
|
|
{
|
|
if (_defaultSelectedColor == null)
|
|
_defaultSelectedColor = tabBarTextBlock.Foreground;
|
|
|
|
if (Element.IsSet(TabbedPage.SelectedTabColorProperty) && Element.SelectedTabColor != Color.Default)
|
|
tabBarTextBlock.Foreground = Element.SelectedTabColor.ToBrush();
|
|
else
|
|
tabBarTextBlock.Foreground = _defaultSelectedColor;
|
|
}
|
|
else
|
|
{
|
|
if (_defaultUnselectedColor == null)
|
|
_defaultUnselectedColor = tabBarTextBlock.Foreground;
|
|
|
|
if (Element.IsSet(TabbedPage.SelectedTabColorProperty) && Element.UnselectedTabColor != Color.Default)
|
|
tabBarTextBlock.Foreground = Element.UnselectedTabColor.ToBrush();
|
|
else
|
|
tabBarTextBlock.Foreground = _defaultUnselectedColor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |