forked from unoplatform/uno
/
ElementStub.cs
152 lines (130 loc) · 4.02 KB
/
ElementStub.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#if IS_UNO
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Uno.UI;
using Uno.UI.DataBinding;
#if XAMARIN_ANDROID
using View = Android.Views.View;
#elif XAMARIN_IOS_UNIFIED
using View = UIKit.UIView;
#elif __MACOS__
using View = AppKit.NSView;
#else
using View = System.Object;
#endif
namespace Windows.UI.Xaml
{
/// <summary>
/// A support element for the DeferLoadStrategy Lazy Xaml directive.
/// </summary>
/// <remarks>This control is added in the visual tree, in place of the original content.</remarks>
public partial class ElementStub : FrameworkElement
{
#if UNO_HAS_UIELEMENT_IMPLICIT_PINNING
[Weak]
#endif
private View _content;
public bool Load
{
get => (bool)GetValue(LoadProperty);
set => SetValue(LoadProperty, value);
}
// Using a DependencyProperty as the backing store for Load. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LoadProperty =
DependencyProperty.Register("Load", typeof(bool), typeof(ElementStub), new PropertyMetadata(
false, OnLoadChanged));
public ElementStub(Func<View> contentBuilder) : this()
{
#if UNO_HAS_UIELEMENT_IMPLICIT_PINNING
// In this context, the delegate provided here closes over other UIElement instances
// causing memory leaks unless the Target member of the delegate is weak.
// Here, we deconstruct the delegate to keep the MethodInfo, and invoking it
// via the resolution of the weak reference. This technique only works because
// the provided delegate and its target (the lambda's display class) is kept
// alive by the "ElementStub holder" variable provided by the XAML generator.
var delegateTarget = WeakReferencePool.RentWeakReference(this, contentBuilder.Target);
var methodInfo = contentBuilder.Method;
ContentBuilder = () => (View)methodInfo.Invoke(delegateTarget.Target, null);
#else
ContentBuilder = contentBuilder;
#endif
}
public ElementStub()
{
Visibility = Visibility.Collapsed;
}
private static void OnLoadChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if ((bool)args.NewValue)
{
((ElementStub)dependencyObject).Materialize();
}
else
{
((ElementStub)dependencyObject).Dematerialize();
}
}
/// <summary>
/// A function that will create the actual view.
/// </summary>
public Func<View> ContentBuilder { get; set; }
protected override void OnVisibilityChanged(Visibility oldValue, Visibility newValue)
{
base.OnVisibilityChanged(oldValue, newValue);
if (ContentBuilder != null
&& oldValue == Visibility.Collapsed
&& newValue == Visibility.Visible
&& Parent != null
)
{
Materialize(isVisibilityChanged: true);
}
}
private protected override void OnLoaded()
{
base.OnLoaded();
if (ContentBuilder != null
&& Visibility == Visibility.Visible
)
{
Materialize();
}
}
public void Materialize()
=> Materialize(isVisibilityChanged: false);
private void Materialize(bool isVisibilityChanged)
{
_content = SwapViews(oldView: (FrameworkElement)this, newViewProvider: ContentBuilder);
var targetDependencyObject = _content as DependencyObject;
if (isVisibilityChanged && targetDependencyObject != null)
{
var visibilityProperty = GetVisibilityProperty(_content);
// Set the visibility at the same precedence it was currently set with on the stub.
var precedence = this.GetCurrentHighestValuePrecedence(visibilityProperty);
targetDependencyObject.SetValue(visibilityProperty, Visibility.Visible, precedence);
}
}
private void Dematerialize()
{
var newView = SwapViews(oldView: (FrameworkElement)_content, newViewProvider: () => this as View);
if (newView != null)
{
_content = null;
}
}
private static DependencyProperty GetVisibilityProperty(View view)
{
if (view is FrameworkElement)
{
return VisibilityProperty;
}
else
{
return DependencyProperty.GetProperty(view.GetType(), nameof(Visibility));
}
}
}
}
#endif