diff options
Diffstat (limited to 'Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs')
-rw-r--r-- | Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs b/Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs new file mode 100644 index 00000000..9ec274c5 --- /dev/null +++ b/Xamarin.Forms.Platform.WP8/VisualElementRenderer.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Xamarin.Forms.Platform.WinPhone +{ + public class VisualElementRenderer<TElement, TNativeElement> : Panel, IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement where TNativeElement : FrameworkElement + { + readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>(); + + Brush _initialBrush; + + VisualElementTracker _tracker; + + public TNativeElement Control { get; private set; } + + public TElement Element { get; private set; } + + protected bool AutoPackage { get; set; } = true; + + protected bool AutoTrack { get; set; } = true; + + protected VisualElementTracker Tracker + { + get { return _tracker; } + set + { + if (_tracker == value) + return; + + if (_tracker != null) + { + _tracker.Dispose(); + _tracker.Updated -= HandleTrackerUpdated; + } + + _tracker = value; + + if (_tracker != null) + _tracker.Updated += HandleTrackerUpdated; + } + } + + VisualElementPackager Packager { get; set; } + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect != null) + OnRegisterEffect(platformEffect); + } + + public UIElement ContainerElement + { + get { return this; } + } + + VisualElement IVisualElementRenderer.Element + { + get { return Element; } + } + + event EventHandler<VisualElementChangedEventArgs> IVisualElementRenderer.ElementChanged + { + add { _elementChangedHandlers.Add(value); } + remove { _elementChangedHandlers.Remove(value); } + } + + public virtual SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + if (Children.Count == 0) + return new SizeRequest(); + + var constraint = new System.Windows.Size(widthConstraint, heightConstraint); + var child = (FrameworkElement)Children[0]; + + child.Measure(constraint); + var result = new Size(Math.Ceiling(child.DesiredSize.Width), Math.Ceiling(child.DesiredSize.Height)); + + return new SizeRequest(result); + } + + public void SetElement(VisualElement element) + { + TElement oldElement = Element; + Element = (TElement)element; + + if (oldElement != null) + { + oldElement.PropertyChanged -= OnElementPropertyChanged; + oldElement.FocusChangeRequested -= OnModelFocusChangeRequested; + } + + Element.PropertyChanged += OnElementPropertyChanged; + Element.FocusChangeRequested += OnModelFocusChangeRequested; + + if (AutoPackage && Packager == null) + Packager = new VisualElementPackager(this); + + if (AutoTrack && Tracker == null) + { + Tracker = new VisualElementTracker<TElement, FrameworkElement> { Model = Element, Element = this }; + } + + // Disabled until reason for crashes with unhandled exceptions is discovered + // Without this some layouts may end up with improper sizes, however their children + // will position correctly + //Loaded += (sender, args) => { + if (Packager != null) + Packager.Load(); + //}; + + OnElementChanged(new ElementChangedEventArgs<TElement>(oldElement, Element)); + + var controller = (IElementController)oldElement; + if (controller != null && controller.EffectControlProvider == this) + controller.EffectControlProvider = null; + + controller = element; + if (controller != null) + controller.EffectControlProvider = this; + } + + public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged; + + protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) + { + if (Element == null || finalSize.Width * finalSize.Height == 0) + return finalSize; + + Element.IsInNativeLayout = true; + + if (Control != null) + { + Control.Measure(finalSize); + Control.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + + for (var i = 0; i < Element.LogicalChildren.Count; i++) + { + var child = Element.LogicalChildren[i] as VisualElement; + if (child == null) + continue; + IVisualElementRenderer renderer = child.GetRenderer(); + if (renderer == null) + continue; + Rectangle bounds = child.Bounds; + + renderer.ContainerElement.Arrange(new Rect(bounds.X, bounds.Y, Math.Max(0, bounds.Width), Math.Max(0, bounds.Height))); + } + + Element.IsInNativeLayout = false; + + return finalSize; + } + + protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) + { + if (Element == null || availableSize.Width * availableSize.Height == 0) + return new System.Windows.Size(0, 0); + + Element.IsInNativeLayout = true; + + for (var i = 0; i < Element.LogicalChildren.Count; i++) + { + var child = Element.LogicalChildren[i] as VisualElement; + if (child == null) + continue; + IVisualElementRenderer renderer = Platform.GetRenderer(child); + if (renderer == null) + continue; + + try + { + renderer.ContainerElement.Measure(availableSize); + } + catch (NullReferenceException) + { + if (!IsExpectedTabbedPageMeasurementException(renderer)) + throw; + } + } + + double width = Math.Max(0, Element.Width); + double height = Math.Max(0, Element.Height); + var result = new System.Windows.Size(width, height); + + if (Control != null) + { + double w = Element.Width; + double h = Element.Height; + if (w == -1) + w = availableSize.Width; + if (h == -1) + h = availableSize.Height; + w = Math.Max(0, w); + h = Math.Max(0, h); + Control.Measure(new System.Windows.Size(w, h)); + } + + Element.IsInNativeLayout = false; + + return result; + } + + protected virtual void OnElementChanged(ElementChangedEventArgs<TElement> e) + { + var args = new VisualElementChangedEventArgs(e.OldElement, e.NewElement); + for (var i = 0; i < _elementChangedHandlers.Count; i++) + _elementChangedHandlers[i](this, args); + + EventHandler<ElementChangedEventArgs<TElement>> changed = ElementChanged; + if (changed != null) + changed(this, e); + } + + protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateEnabled(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundColor(); + } + + protected virtual void OnGotFocus(object sender, RoutedEventArgs args) + { + ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true); + } + + protected virtual void OnLostFocus(object sender, RoutedEventArgs args) + { + ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false); + } + + protected virtual void OnRegisterEffect(PlatformEffect effect) + { + effect.Container = this; + effect.Control = Control; + } + + protected void SetNativeControl(TNativeElement element) + { + Control = element; + + Children.Add(element); + Element.IsNativeStateConsistent = false; + element.Loaded += (sender, args) => Element.IsNativeStateConsistent = true; + + element.GotFocus += OnGotFocus; + element.LostFocus += OnLostFocus; + + _tracker.Child = element; + + UpdateBackgroundColor(); + } + + protected virtual void UpdateBackgroundColor() + { + var control = Control as Control; + if (_initialBrush == null) + _initialBrush = control == null ? Background : control.Background; + if (control != null) + control.Background = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : _initialBrush; + else + Background = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : _initialBrush; + } + + protected virtual void UpdateNativeWidget() + { + UpdateEnabled(); + } + + internal virtual void OnModelFocusChangeRequested(object sender, VisualElement.FocusRequestArgs args) + { + var control = Control as Control; + if (control == null) + return; + + if (args.Focus) + args.Result = control.Focus(); + else + { + UnfocusControl(control); + args.Result = true; + } + } + + internal void UnfocusControl(Control control) + { + if (control == null || !control.IsEnabled) + return; + control.IsEnabled = false; + control.IsEnabled = true; + } + + void HandleTrackerUpdated(object sender, EventArgs e) + { + UpdateNativeWidget(); + } + + static bool IsExpectedTabbedPageMeasurementException(IVisualElementRenderer renderer) + { + // The TabbedPageRenderer's underlying Pivot control throws a NRE if the tabbed page + // does not have any toolbar items and it's measured during an animated transition + // from a page which does have toolbar items + + // The NRE happens before TabbedPageRenderer's MeasureOverride is even called, + // so unfortunately we have to handle it here + + var tpr = renderer.ContainerElement as TabbedPageRenderer; + + var tp = tpr?.Element as TabbedPage; + + return tp?.ToolbarItems.Count == 0; + } + + void UpdateEnabled() + { + if (Control is Control) + (Control as Control).IsEnabled = Element.IsEnabled; + } + } +}
\ No newline at end of file |