summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephane Delcroix <stephane@delcroix.org>2016-09-08 20:39:05 +0200
committerJason Smith <jason.smith@xamarin.com>2016-09-08 11:39:05 -0700
commit85426c5d9495eb1d55b3128bf97e50c68a73b53f (patch)
tree2f81e5868ce61eb90d15c6c51a354603b8395627
parent11326e1c182b3ff5c3d82c6ef7d09c193bc19891 (diff)
downloadxamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.tar.gz
xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.tar.bz2
xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.zip
Native Bindings (#278)
* [C, I, A, W] Support Native Bindings * fix tabs
-rw-r--r--Xamarin.Forms.ControlGallery.Android/Activity1.cs61
-rw-r--r--Xamarin.Forms.ControlGallery.Android/ColorPicker.cs160
-rw-r--r--Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj1
-rw-r--r--Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs75
-rw-r--r--Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs68
-rw-r--r--Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj9
-rw-r--r--Xamarin.Forms.Controls/ControlGalleryPages/NativeBindingGalleryPage.cs101
-rw-r--r--Xamarin.Forms.Controls/CoreGallery.cs3
-rw-r--r--Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj1
-rw-r--r--Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs487
-rw-r--r--Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj1
-rw-r--r--Xamarin.Forms.Core/Binding.cs11
-rw-r--r--Xamarin.Forms.Core/NativeBindingHelpers.cs191
-rw-r--r--Xamarin.Forms.Core/Xamarin.Forms.Core.csproj1
-rw-r--r--Xamarin.Forms.Platform.Android/ColorExtensions.cs5
-rw-r--r--Xamarin.Forms.Platform.Android/Extensions/NativeBindingExtensions.cs33
-rw-r--r--Xamarin.Forms.Platform.Android/NativeViewWrapper.cs10
-rw-r--r--Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj6
-rw-r--r--Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj9
-rw-r--r--Xamarin.Forms.Platform.WinRT/FrameworkElementExtensions.cs16
-rw-r--r--Xamarin.Forms.Platform.WinRT/NativeBindingExtensions.cs58
-rw-r--r--Xamarin.Forms.Platform.WinRT/NativeEventWrapper.cs45
-rw-r--r--Xamarin.Forms.Platform.WinRT/NativePropertyListener.cs47
-rw-r--r--Xamarin.Forms.Platform.WinRT/NativeViewWrapper.cs8
-rw-r--r--Xamarin.Forms.Platform.WinRT/Xamarin.Forms.Platform.WinRT.csproj5
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/LayoutExtensions.cs (renamed from Xamarin.Forms.Platform.iOS/LayoutExtensions.cs)0
-rw-r--r--Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs43
-rw-r--r--Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs29
-rw-r--r--Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs8
-rw-r--r--Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj3
-rw-r--r--Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj3
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs4
-rw-r--r--docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml/BindingExtension.xml16
33 files changed, 1510 insertions, 8 deletions
diff --git a/Xamarin.Forms.ControlGallery.Android/Activity1.cs b/Xamarin.Forms.ControlGallery.Android/Activity1.cs
index bc1772c6..35f877dc 100644
--- a/Xamarin.Forms.ControlGallery.Android/Activity1.cs
+++ b/Xamarin.Forms.ControlGallery.Android/Activity1.cs
@@ -20,6 +20,7 @@ using System.IO;
using System.IO.IsolatedStorage;
using Droid = Android;
+using System.Globalization;
[assembly: Dependency (typeof (CacheService))]
@@ -337,6 +338,14 @@ namespace Xamarin.Forms.ControlGallery.Android
if (nncgPage != null) {
AddNativeControls (nncgPage);
}
+
+ var nncgPage1 = args.Page as NativeBindingGalleryPage;
+
+ if (nncgPage1 != null)
+ {
+ AddNativeBindings(nncgPage1);
+ }
+
};
}
@@ -408,6 +417,58 @@ namespace Xamarin.Forms.ControlGallery.Android
var size = new Size (nativeView.MeasuredWidth, nativeView.MeasuredHeight);
return new SizeRequest (size);
}
+
+ void AddNativeBindings(NativeBindingGalleryPage page)
+ {
+ if (page.NativeControlsAdded)
+ return;
+
+ StackLayout sl = page.Layout;
+
+ var textView = new TextView(this)
+ {
+ TextSize = 14,
+ Text = "This will be text"
+ };
+
+ var viewGroup = new LinearLayout(this);
+ viewGroup.AddView(textView);
+
+ var buttonColor = new global::Android.Widget.Button(this) { Text = "Change label Color" };
+ buttonColor.Click += (sender, e) => textView.SetTextColor(Color.Blue.ToAndroid());
+
+ var colorPicker = new ColorPickerView(this, 200, 200);
+
+ textView.SetBinding(nameof(textView.Text), new Binding("NativeLabel"));
+ //this doesn't work because there's not TextColor property
+ //textView.SetBinding("TextColor", new Binding("NativeLabelColor", converter: new ColorConverter()));
+ colorPicker.SetBinding(nameof(colorPicker.SelectedColor), new Binding("NativeLabelColor", BindingMode.TwoWay, new ColorConverter()), "ColorPicked");
+
+ sl?.Children.Add(viewGroup);
+ sl?.Children.Add(buttonColor.ToView());
+ sl?.Children.Add(colorPicker);
+
+ page.NativeControlsAdded = true;
+ }
+
+ public class ColorConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Color)
+ return ((Color)value).ToAndroid();
+
+ return null;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is global::Android.Graphics.Color)
+ return ((global::Android.Graphics.Color)value).ToColor();
+
+ return null;
+ }
+ }
}
#endif
}
diff --git a/Xamarin.Forms.ControlGallery.Android/ColorPicker.cs b/Xamarin.Forms.ControlGallery.Android/ColorPicker.cs
new file mode 100644
index 00000000..8e047870
--- /dev/null
+++ b/Xamarin.Forms.ControlGallery.Android/ColorPicker.cs
@@ -0,0 +1,160 @@
+using System;
+using System.ComponentModel;
+using Android.Content;
+using Android.Views;
+using Android.Widget;
+using Xamarin.Forms.Platform.Android;
+using Droid = Android;
+
+namespace Xamarin.Forms.ControlGallery.Android
+{
+ public class ColorPickerView : ViewGroup
+ //, INotifyPropertyChanged
+ {
+ static readonly int[] COLORS = new[] {
+ new Droid.Graphics.Color(255,0,0,255).ToArgb(), new Droid.Graphics.Color(255,0,255,255).ToArgb(), new Droid.Graphics.Color(0,0,255,255).ToArgb(),
+ new Droid.Graphics.Color(0,255,255,255).ToArgb(), new Droid.Graphics.Color(0,255,0,255).ToArgb(), new Droid.Graphics.Color(255,255,0,255).ToArgb(),
+ new Droid.Graphics.Color(255,0,0,255).ToArgb()
+ };
+ Droid.Graphics.Point currentPoint;
+ ColorPointer colorPointer;
+ ImageView imageViewSelectedColor;
+ ImageView imageViewPallete;
+ Droid.Graphics.Color selectedColor;
+ Droid.Graphics.Color previewColor;
+
+ //public event PropertyChangedEventHandler PropertyChanged;
+ public event EventHandler ColorPicked;
+
+ public ColorPickerView(Context context, int minWidth, int minHeight) : base(context)
+ {
+ SelectedColor = Color.Black.ToAndroid();
+
+ SetMinimumHeight(minHeight);
+ SetMinimumWidth(minWidth);
+
+ imageViewPallete = new ImageView(context);
+ imageViewPallete.DrawingCacheEnabled = true;
+ imageViewPallete.Background = new Droid.Graphics.Drawables.GradientDrawable(Droid.Graphics.Drawables.GradientDrawable.Orientation.LeftRight, COLORS);
+
+ imageViewPallete.Touch += (object sender, TouchEventArgs e) =>
+ {
+ if (e.Event.Action == MotionEventActions.Down || e.Event.Action == MotionEventActions.Move)
+ {
+ currentPoint = new Droid.Graphics.Point((int)e.Event.GetX(), (int)e.Event.GetY());
+
+ previewColor = GetCurrentColor(imageViewPallete.GetDrawingCache(false), (int)e.Event.GetX(), (int)e.Event.GetY());
+ }
+ if (e.Event.Action == MotionEventActions.Up)
+ {
+ SelectedColor = previewColor;
+ }
+ };
+
+ imageViewSelectedColor = new ImageView(context);
+ colorPointer = new ColorPointer(context);
+
+ AddView(imageViewPallete);
+ AddView(imageViewSelectedColor);
+ AddView(colorPointer);
+ }
+
+ public Droid.Graphics.Color SelectedColor
+ {
+ get
+ {
+ return selectedColor;
+ }
+
+ set
+ {
+ if (selectedColor == value)
+ return;
+
+ selectedColor = value;
+ UpdateUi();
+ OnPropertyChanged();
+ OnColorPicked();
+ }
+ }
+
+ protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
+ {
+ var half = (bottom - top) / 2;
+ var margin = 20;
+
+ var palleteY = top + half;
+
+ imageViewSelectedColor.Layout(left, top, right, bottom - half - margin);
+ imageViewPallete.Layout(left, palleteY, right, bottom);
+ colorPointer.Layout(left, palleteY, right, bottom);
+ }
+
+ void UpdateUi()
+ {
+ imageViewSelectedColor?.SetBackgroundColor(selectedColor);
+ colorPointer?.UpdatePoint(currentPoint);
+ }
+
+ Droid.Graphics.Color GetCurrentColor(Droid.Graphics.Bitmap bitmap, int x, int y)
+ {
+ if (bitmap == null)
+ return new Droid.Graphics.Color(255, 255, 255, 255);
+
+ if (x < 0)
+ x = 0;
+ if (y < 0)
+ y = 0;
+ if (x >= bitmap.Width)
+ x = bitmap.Width - 1;
+ if (y >= bitmap.Height)
+ y = bitmap.Height - 1;
+
+ int color = bitmap.GetPixel(x, y);
+ return new Droid.Graphics.Color(color);
+ }
+
+ void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
+ {
+ //PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ void OnColorPicked()
+ {
+ ColorPicked?.Invoke(this, new EventArgs());
+ }
+ }
+
+ public class ColorPointer : Droid.Views.View
+ {
+ Droid.Graphics.Paint colorPointerPaint;
+ Droid.Graphics.Point currentPoint;
+ Droid.Graphics.Point nextPoint;
+
+ public ColorPointer(Context context) : base(context)
+ {
+
+ colorPointerPaint = new Droid.Graphics.Paint();
+ colorPointerPaint.SetStyle(Droid.Graphics.Paint.Style.Stroke);
+ colorPointerPaint.StrokeWidth = 5f;
+ colorPointerPaint.SetARGB(255, 0, 0, 0);
+
+ }
+
+ public void UpdatePoint(Droid.Graphics.Point p)
+ {
+ if (p == null)
+ return;
+
+ if (currentPoint == null)
+ currentPoint = nextPoint;
+
+ nextPoint = p;
+ }
+
+ protected override void OnDraw(Droid.Graphics.Canvas canvas)
+ {
+ base.OnDraw(canvas);
+ }
+ }
+}
diff --git a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
index 25f896f8..4a2e5288 100644
--- a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
+++ b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
@@ -175,6 +175,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="CustomRenderers.cs" />
+ <Compile Include="ColorPicker.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\default.css" />
diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
index 7e16e83c..84767a55 100644
--- a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
+++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs
@@ -1,6 +1,7 @@
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
using System;
+using System.Globalization;
using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.UI.ViewManagement;
@@ -33,6 +34,12 @@ namespace Xamarin.Forms.ControlGallery.WindowsUniversal
if (nncgPage != null) {
AddNativeControls (nncgPage);
}
+
+ var nncgPage1 = args.Page as NativeBindingGalleryPage;
+
+ if (nncgPage1 != null) {
+ AddNativeBindings(nncgPage1);
+ }
};
}
@@ -105,5 +112,71 @@ namespace Xamarin.Forms.ControlGallery.WindowsUniversal
page.NativeControlsAdded = true;
}
- }
+
+ void AddNativeBindings(NativeBindingGalleryPage page)
+ {
+ if (page.NativeControlsAdded)
+ return;
+
+ StackLayout sl = page.Layout;
+
+ var txbLabel = new TextBlock {
+ FontSize = 14,
+ FontFamily = new FontFamily("HelveticaNeue")
+ };
+
+ var txbBox = new TextBox {
+ FontSize = 14,
+ FontFamily = new FontFamily("HelveticaNeue")
+ };
+
+ var btnColor = new Windows.UI.Xaml.Controls.Button { Content = "Toggle Label Color", Height = 80 };
+ btnColor.Click += (sender, args) => txbLabel.Foreground = new SolidColorBrush(Windows.UI.Colors.Pink);
+
+ var btnTextBox = new Windows.UI.Xaml.Controls.Button { Content = "Change text textbox", Height = 80 };
+ btnTextBox.Click += (sender, args) => txbBox.Text = "Hello 2 way native";
+
+ txbLabel.SetBinding("Text", new Binding("NativeLabel"));
+ txbBox.SetBinding("Text", new Binding("NativeLabel", BindingMode.TwoWay), "TextChanged");
+ txbLabel.SetBinding("Foreground", new Binding("NativeLabelColor", BindingMode.TwoWay, new ColorToBrushNativeBindingConverter()));
+
+ var grd = new StackPanel();
+ grd.Children.Add(txbLabel);
+ grd.Children.Add(btnColor);
+
+ sl?.Children.Add(grd.ToView());
+
+ sl?.Children.Add(txbBox);
+ sl?.Children.Add(btnTextBox.ToView());
+
+ page.NativeControlsAdded = true;
+ }
+
+ class ColorToBrushNativeBindingConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Color)
+ return new SolidColorBrush(ToWindowsColor((Color)value));
+
+ return null;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is SolidColorBrush)
+ return ToColor(((SolidColorBrush)value).Color);
+
+ return null;
+ }
+ public static Windows.UI.Color ToWindowsColor(Color color)
+ {
+ return Windows.UI.Color.FromArgb((byte)(color.A * 255), (byte)(color.R * 255), (byte)(color.G * 255), (byte)(color.B * 255));
+ }
+ public static Color ToColor(Windows.UI.Color color)
+ {
+ return Color.FromRgba(color.R, color.G, color.B, color.A);
+ }
+ }
+ }
}
diff --git a/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs b/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs
index 8f064d34..c4d8eaf0 100644
--- a/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs
+++ b/Xamarin.Forms.ControlGallery.iOS/AppDelegate.cs
@@ -8,15 +8,18 @@ using System.IO;
using UIKit;
using Foundation;
using CoreGraphics;
+using AdvancedColorPicker;
#else
using MonoTouch.UIKit;
using MonoTouch.Foundation;
+using MonoTouch.CoreGraphics;
#endif
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.iOS;
using Xamarin.Forms.Controls;
using Xamarin.Forms.Maps.iOS;
using Xamarin.Forms.Platform.iOS;
+using System.Globalization;
[assembly: Dependency (typeof (TestCloudService))]
[assembly: Dependency (typeof (StringProvider))]
@@ -163,6 +166,13 @@ namespace Xamarin.Forms.ControlGallery.iOS
if (nncgPage != null) {
AddNativeControls (nncgPage);
}
+
+ var nncgPage1 = args.Page as NativeBindingGalleryPage;
+
+ if (nncgPage1 != null)
+ {
+ AddNativeBindings(nncgPage1);
+ }
};
}
@@ -271,6 +281,64 @@ namespace Xamarin.Forms.ControlGallery.iOS
// And we'll use the width (which is fine) and substitute our own height
return new SizeRequest (new Size (badRect.Width, 20));
}
+
+ void AddNativeBindings(NativeBindingGalleryPage page)
+ {
+ if (page.NativeControlsAdded)
+ return;
+
+ StackLayout sl = page.Layout;
+
+ int width = (int)sl.Width;
+ int heightCustomLabelView = 100;
+
+ var uilabel = new UILabel(new RectangleF(0, 0, width, heightCustomLabelView))
+ {
+ MinimumFontSize = 14f,
+ Lines = 0,
+ LineBreakMode = UILineBreakMode.WordWrap,
+ Font = UIFont.FromName("Helvetica", 24f),
+ Text = "DefaultText"
+ };
+
+ var uibuttonColor = new UIButton(UIButtonType.RoundedRect);
+ uibuttonColor.SetTitle("Toggle Text Color Binding", UIControlState.Normal);
+ uibuttonColor.Font = UIFont.FromName("Helvetica", 14f);
+ uibuttonColor.TouchUpInside += (sender, args) => uilabel.TextColor = UIColor.Blue;
+
+ var nativeColorConverter = new ColorConverter();
+
+ uilabel.SetBinding("Text", new Binding("NativeLabel"));
+ uilabel.SetBinding(nameof(uilabel.TextColor), new Binding("NativeLabelColor", converter: nativeColorConverter));
+
+ var uiView = new UIView(new RectangleF(0, 0, width, heightCustomLabelView));
+ uiView.Add(uilabel);
+ sl?.Children.Add(uiView);
+ sl?.Children.Add(uibuttonColor.ToView());
+#if !_CLASSIC_
+ var colorPicker = new ColorPickerView(new CGRect(0, 0, width, 300));
+ colorPicker.SetBinding("SelectedColor", new Binding("NativeLabelColor", BindingMode.TwoWay, nativeColorConverter), "ColorPicked");
+ sl?.Children.Add(colorPicker);
+#endif
+ page.NativeControlsAdded = true;
+ }
+ }
+
+ public class ColorConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Color)
+ return ((Color)value).ToUIColor();
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is UIColor)
+ return ((UIColor)value).ToColor();
+ return value;
+ }
}
#endif
}
diff --git a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
index d3dc36b2..35951c56 100644
--- a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
+++ b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
@@ -250,6 +250,9 @@
<Reference Include="Calabash">
<HintPath>..\packages\Xamarin.TestCloud.Agent.0.19.1\lib\Xamarin.iOS10\Calabash.dll</HintPath>
</Reference>
+ <Reference Include="AdvancedColorPicker">
+ <HintPath>..\Components\advancedcolorpicker-2.0\lib\ios-unified\AdvancedColorPicker.dll</HintPath>
+ </Reference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
@@ -261,4 +264,10 @@
<Error Condition="!Exists('..\packages\Xamarin.Insights.1.11.4\build\Xamarin.iOS10\Xamarin.Insights.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.Insights.1.11.4\build\Xamarin.iOS10\Xamarin.Insights.targets'))" />
</Target>
<ItemGroup />
+ <ItemGroup>
+ <XamarinComponentReference Include="advancedcolorpicker">
+ <Version>2.0</Version>
+ <Visible>False</Visible>
+ </XamarinComponentReference>
+ </ItemGroup>
</Project> \ No newline at end of file
diff --git a/Xamarin.Forms.Controls/ControlGalleryPages/NativeBindingGalleryPage.cs b/Xamarin.Forms.Controls/ControlGalleryPages/NativeBindingGalleryPage.cs
new file mode 100644
index 00000000..51d9b970
--- /dev/null
+++ b/Xamarin.Forms.Controls/ControlGalleryPages/NativeBindingGalleryPage.cs
@@ -0,0 +1,101 @@
+using System;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Controls
+{
+ public class NativeBindingGalleryPage : ContentPage
+ {
+ public StackLayout Layout { get; set; }
+ public bool NativeControlsAdded { get; set; }
+
+ NestedNativeViewModel ViewModel { get; set; }
+
+ public NativeBindingGalleryPage()
+ {
+
+ var vm = new NestedNativeViewModel();
+ vm.FormsLabel = "Forms Label Binding";
+ vm.NativeLabel = "Native Label Binding";
+ vm.NativeLabelColor = Color.Red;
+ vm.Age = 45;
+
+ Layout = new StackLayout { Padding = 20, VerticalOptions = LayoutOptions.FillAndExpand };
+
+ var buttonNav = new Button { Text = "New Page" };
+ buttonNav.Clicked += (object sender, EventArgs e) =>
+ {
+ App.Current.MainPage = new ContentPage { Content = new Label { Text = "New page" } };
+ };
+
+ var button = new Button { Text = "Change BindingContext " };
+ button.Clicked += (object sender, EventArgs e) =>
+ {
+ vm = new NestedNativeViewModel();
+ vm.FormsLabel = "Forms Label Binding Changed";
+ vm.NativeLabel = "Native Label Binding Changed";
+ vm.NativeLabelColor = Color.Pink;
+ vm.Age = 10;
+
+ BindingContext = ViewModel = vm; ;
+ };
+
+ var boxView = new BoxView { HeightRequest = 50 };
+ boxView.SetBinding(BoxView.BackgroundColorProperty, "NativeLabelColor");
+
+ var label = new Label();
+ label.SetBinding(Label.TextProperty, "FormsLabel");
+
+ Layout.Children.Add(buttonNav);
+
+ Layout.Children.Add(label);
+
+ Layout.Children.Add(boxView);
+ Layout.Children.Add(button);
+
+ BindingContext = ViewModel = vm; ;
+
+ Content = new ScrollView { Content = Layout };
+ }
+ }
+
+
+ [Preserve(AllMembers = true)]
+ public class NestedNativeViewModel : ViewModelBase
+ {
+ string _formsLabel;
+ public string FormsLabel
+ {
+ get { return _formsLabel; }
+ set { _formsLabel = value; OnPropertyChanged(); }
+ }
+
+ string _nativeLabel;
+ public string NativeLabel
+ {
+ get { return _nativeLabel; }
+ set { _nativeLabel = value; OnPropertyChanged(); }
+ }
+
+ Color _nativeLabelColor;
+ public Color NativeLabelColor
+ {
+ get { return _nativeLabelColor; }
+ set { _nativeLabelColor = value; OnPropertyChanged(); }
+ }
+
+ int _age;
+ public int Age
+ {
+ get { return _age; }
+ set { _age = value; OnPropertyChanged(); }
+ }
+
+ bool _selected;
+ public bool Selected
+ {
+ get { return _selected; }
+ set { _selected = value; OnPropertyChanged(); }
+ }
+ }
+
+}
diff --git a/Xamarin.Forms.Controls/CoreGallery.cs b/Xamarin.Forms.Controls/CoreGallery.cs
index 7986a45c..06f214b4 100644
--- a/Xamarin.Forms.Controls/CoreGallery.cs
+++ b/Xamarin.Forms.Controls/CoreGallery.cs
@@ -221,6 +221,7 @@ namespace Xamarin.Forms.Controls
{
var pages = new List<Page> {
new PlatformSpecificsGallery() {Title = "Platform Specifics"},
+ new NativeBindingGalleryPage {Title = "Native Binding Controls Gallery"},
new AppLinkPageGallery {Title = "App Link Page Gallery"},
new NestedNativeControlGalleryPage {Title = "Nested Native Controls Gallery"},
new CellForceUpdateSizeGalleryPage {Title = "Cell Force Update Size Gallery"},
@@ -446,4 +447,4 @@ namespace Xamarin.Forms.Controls
return new CoreNavigationPage ();
}
}
-} \ No newline at end of file
+}
diff --git a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
index 825d157d..3b02adbe 100644
--- a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
+++ b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
@@ -235,6 +235,7 @@
<Compile Include="ControlGalleryPages\AppearingGalleryPage.cs" />
<Compile Include="ControlGalleryPages\AutomationIDGallery.cs" />
<Compile Include="GalleryPages\AppLinkPageGallery.cs" />
+ <Compile Include="ControlGalleryPages\NativeBindingGalleryPage.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\.nuspec\Xamarin.Forms.targets" />
diff --git a/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs b/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs
new file mode 100644
index 00000000..e7c7f9a8
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs
@@ -0,0 +1,487 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class MockNativeView
+ {
+ public IList<MockNativeView> SubViews { get; set; }
+ public string Foo { get; set; }
+ public int Bar { get; set; }
+ public string Baz { get; set; }
+
+ public void FireBazChanged()
+ {
+ BazChanged?.Invoke(this, new TappedEventArgs(null));
+ }
+
+ public event EventHandler<TappedEventArgs> BazChanged;
+
+ public event EventHandler SelectedColorChanged;
+
+ MockNativeColor _selectedColor;
+ public MockNativeColor SelectedColor
+ {
+ get { return _selectedColor; }
+ set
+ {
+ if (_selectedColor == value)
+ return;
+ _selectedColor = value;
+ SelectedColorChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ }
+
+ class MockNativeViewWrapper : View
+ {
+ public MockNativeView NativeView { get; }
+
+ public MockNativeViewWrapper(MockNativeView nativeView)
+ {
+ NativeView = nativeView;
+ nativeView.TransferbindablePropertiesToWrapper(this);
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ NativeView.SetBindingContext(BindingContext, nv => nv.SubViews);
+ base.OnBindingContextChanged();
+ }
+
+ }
+
+ public class MockNativeColor
+ {
+
+ public MockNativeColor(Color color)
+ {
+ FormsColor = color;
+ }
+
+ public Color FormsColor
+ {
+ get;
+ set;
+ }
+ }
+
+ public static class MockNativeViewExtensions
+ {
+ public static View ToView(this MockNativeView nativeView)
+ {
+ return new MockNativeViewWrapper(nativeView);
+ }
+
+ public static void SetBinding(this MockNativeView target, string targetProperty, BindingBase binding, string updateSourceEventName = null)
+ {
+ NativeBindingHelpers.SetBinding(target, targetProperty, binding, updateSourceEventName);
+ }
+
+ internal static void SetBinding(this MockNativeView target, string targetProperty, BindingBase binding, INotifyPropertyChanged propertyChanged)
+ {
+ NativeBindingHelpers.SetBinding(target, targetProperty, binding, propertyChanged);
+ }
+
+ public static void SetBinding(this MockNativeView target, BindableProperty targetProperty, BindingBase binding)
+ {
+ NativeBindingHelpers.SetBinding(target, targetProperty, binding);
+ }
+
+ public static void SetValue(this MockNativeView target, BindableProperty targetProperty, object value)
+ {
+ NativeBindingHelpers.SetValue(target, targetProperty, value);
+ }
+
+ public static void SetBindingContext(this MockNativeView target, object bindingContext, Func<MockNativeView, IEnumerable<MockNativeView>> getChild = null)
+ {
+ NativeBindingHelpers.SetBindingContext(target, bindingContext, getChild);
+ }
+
+ internal static void TransferbindablePropertiesToWrapper(this MockNativeView target, MockNativeViewWrapper wrapper)
+ {
+ NativeBindingHelpers.TransferBindablePropertiesToWrapper(target, wrapper);
+ }
+ }
+
+ class MockCustomColorConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Color)
+ return new MockNativeColor((Color)value);
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is MockNativeColor)
+ return ((MockNativeColor)value).FormsColor;
+ return value;
+ }
+ }
+
+ class MockINPC : INotifyPropertyChanged
+ {
+ public void FireINPC(object sender, string propertyName)
+ {
+ PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+
+ class MockVMForNativeBinding : INotifyPropertyChanged
+ {
+ string fFoo;
+ public string FFoo {
+ get { return fFoo; }
+ set {
+ fFoo = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FFoo"));
+ }
+ }
+
+ int bBar;
+ public int BBar {
+ get { return bBar; }
+ set {
+ bBar = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("BBar"));
+ }
+ }
+
+ Color cColor;
+ public Color CColor
+ {
+ get { return cColor; }
+ set
+ {
+ if (cColor == value)
+ return;
+ cColor = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CColor"));
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+
+ [TestFixture]
+ public class NativeBindingTests
+ {
+ [SetUp]
+ public void SetUp()
+ {
+ Device.PlatformServices = new MockPlatformServices();
+
+ //this should collect the ConditionalWeakTable
+ GC.Collect();
+ }
+
+ [Test]
+ public void SetOneWayBinding()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+
+ nativeView.SetBinding("Foo", new Binding("FFoo", mode:BindingMode.OneWay));
+ nativeView.SetBinding("Bar", new Binding("BBar", mode:BindingMode.OneWay));
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+
+ nativeView.SetBindingContext(new { FFoo = "Foo", BBar = 42 });
+ Assert.AreEqual("Foo", nativeView.Foo);
+ Assert.AreEqual(42, nativeView.Bar);
+ }
+
+ [Test]
+ public void AttachedPropertiesAreTransferredFromTheBackpack()
+ {
+ var nativeView = new MockNativeView();
+ nativeView.SetValue(Grid.ColumnProperty, 3);
+ nativeView.SetBinding(Grid.RowProperty, new Binding("foo"));
+
+ var view = nativeView.ToView();
+ view.BindingContext = new { foo = 42 };
+ Assert.AreEqual(3, view.GetValue(Grid.ColumnProperty));
+ Assert.AreEqual(42, view.GetValue(Grid.RowProperty));
+ }
+
+ [Test]
+ public void Set2WayBindings()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+
+ var vm = new MockVMForNativeBinding();
+ nativeView.SetBindingContext(vm);
+ var inpc = new MockINPC();
+ nativeView.SetBinding("Foo", new Binding("FFoo", mode:BindingMode.TwoWay), inpc);
+ nativeView.SetBinding("Bar", new Binding("BBar", mode:BindingMode.TwoWay), inpc);
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+ Assert.AreEqual(null, vm.FFoo);
+ Assert.AreEqual(0, vm.BBar);
+
+ nativeView.Foo = "oof";
+ inpc.FireINPC(nativeView, "Foo");
+ nativeView.Bar = -42;
+ inpc.FireINPC(nativeView, "Bar");
+ Assert.AreEqual("oof", nativeView.Foo);
+ Assert.AreEqual(-42, nativeView.Bar);
+ Assert.AreEqual("oof", vm.FFoo);
+ Assert.AreEqual(-42, vm.BBar);
+
+ vm.FFoo = "foo";
+ vm.BBar = 42;
+ Assert.AreEqual("foo", nativeView.Foo);
+ Assert.AreEqual(42, nativeView.Bar);
+ Assert.AreEqual("foo", vm.FFoo);
+ Assert.AreEqual(42, vm.BBar);
+ }
+
+ [Test]
+ public void Set2WayBindingsWithUpdateSourceEvent()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Baz);
+
+ var vm = new MockVMForNativeBinding();
+ nativeView.SetBindingContext(vm);
+
+ nativeView.SetBinding("Baz", new Binding("FFoo", mode: BindingMode.TwoWay), "BazChanged");
+ Assert.AreEqual(null, nativeView.Baz);
+ Assert.AreEqual(null, vm.FFoo);
+
+ nativeView.Baz = "oof";
+ nativeView.FireBazChanged();
+ Assert.AreEqual("oof", nativeView.Baz);
+ Assert.AreEqual("oof", vm.FFoo);
+
+ vm.FFoo = "foo";
+ Assert.AreEqual("foo", nativeView.Baz);
+ Assert.AreEqual("foo", vm.FFoo);
+ }
+
+ [Test]
+ public void Set2WayBindingsWithUpdateSourceEventInBindingObject()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Baz);
+
+ var vm = new MockVMForNativeBinding();
+ nativeView.SetBindingContext(vm);
+
+ nativeView.SetBinding("Baz", new Binding("FFoo", mode: BindingMode.TwoWay) { UpdateSourceEventName = "BazChanged"});
+ Assert.AreEqual(null, nativeView.Baz);
+ Assert.AreEqual(null, vm.FFoo);
+
+ nativeView.Baz = "oof";
+ nativeView.FireBazChanged();
+ Assert.AreEqual("oof", nativeView.Baz);
+ Assert.AreEqual("oof", vm.FFoo);
+
+ vm.FFoo = "foo";
+ Assert.AreEqual("foo", nativeView.Baz);
+ Assert.AreEqual("foo", vm.FFoo);
+ }
+
+ [Test]
+ public void NativeViewsAreCollected()
+ {
+ WeakReference wr = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ var nativeView = new MockNativeView();
+ nativeView.SetBinding("fooBar", new Binding("Foo", BindingMode.TwoWay));
+ nativeView.SetBinding("Baz", new Binding("Qux", BindingMode.TwoWay), "BazChanged");
+
+ wr = new WeakReference(nativeView);
+ nativeView = null;
+
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ Assert.False(wr.IsAlive);
+ }
+
+ [Test]
+ public void ProxiesAreCollected()
+ {
+ WeakReference wr = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ var nativeView = new MockNativeView();
+ nativeView.SetBinding("fooBar", new Binding("Foo", BindingMode.TwoWay));
+ nativeView.SetBinding("Baz", new Binding("Qux", BindingMode.TwoWay), "BazChanged");
+
+ NativeBindingHelpers.BindableObjectProxy<MockNativeView> proxy;
+ if (!NativeBindingHelpers.BindableObjectProxy<MockNativeView>.BindableObjectProxies.TryGetValue(nativeView, out proxy))
+ Assert.Fail();
+
+ wr = new WeakReference(proxy);
+ nativeView = null;
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ Assert.False(wr.IsAlive);
+ }
+
+ [Test]
+ public void SetBindingContextToSubviews()
+ {
+ var nativeView = new MockNativeView { SubViews = new List<MockNativeView> ()};
+ var nativeViewChild = new MockNativeView();
+
+ nativeViewChild.SetBinding("Foo", new Binding("FFoo", mode: BindingMode.OneWay));
+ nativeViewChild.SetBinding("Bar", new Binding("BBar", mode: BindingMode.OneWay));
+
+ nativeView.SubViews.Add(nativeViewChild);
+
+ var vm = new MockVMForNativeBinding();
+ nativeView.SetBindingContext(vm, v => v.SubViews);
+
+ Assert.AreEqual(null, nativeViewChild.Foo);
+ Assert.AreEqual(0, nativeViewChild.Bar);
+
+ nativeView.SetBindingContext(new { FFoo = "Foo", BBar = 42 }, v => v.SubViews);
+ Assert.AreEqual("Foo", nativeViewChild.Foo);
+ Assert.AreEqual(42, nativeViewChild.Bar);
+ }
+
+ [Test]
+ public void ThrowsNeedsConverter()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+ var vm = new MockVMForNativeBinding();
+ nativeView.SetBinding("SelectedColor", new Binding("CColor"));
+ Assert.Throws<ArgumentException>(() => nativeView.SetBindingContext(vm));
+ }
+
+ [Test]
+ public void TestConverterDoesNotThrow()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+ var vm = new MockVMForNativeBinding();
+ var converter = new MockCustomColorConverter();
+ nativeView.SetBinding("SelectedColor", new Binding("CColor", converter: converter));
+ Assert.DoesNotThrow(() => nativeView.SetBindingContext(vm));
+ }
+
+ [Test]
+ public void TestConverterWorks()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+ var vm = new MockVMForNativeBinding();
+ vm.CColor = Color.Red;
+ var converter = new MockCustomColorConverter();
+ nativeView.SetBinding("SelectedColor", new Binding("CColor", converter: converter));
+ nativeView.SetBindingContext(vm);
+ Assert.AreEqual(vm.CColor, nativeView.SelectedColor.FormsColor);
+ }
+
+ [Test]
+ public void TestConverter2WayWorks()
+ {
+ var nativeView = new MockNativeView();
+ Assert.AreEqual(null, nativeView.Foo);
+ Assert.AreEqual(0, nativeView.Bar);
+ var inpc = new MockINPC();
+ var vm = new MockVMForNativeBinding();
+ vm.CColor = Color.Red;
+ var converter = new MockCustomColorConverter();
+ nativeView.SetBinding("SelectedColor", new Binding("CColor", BindingMode.TwoWay, converter), inpc);
+ nativeView.SetBindingContext(vm);
+ Assert.AreEqual(vm.CColor, nativeView.SelectedColor.FormsColor);
+
+ var newFormsColor = Color.Blue;
+ var newColor = new MockNativeColor(newFormsColor);
+ nativeView.SelectedColor = newColor;
+ inpc.FireINPC(nativeView, nameof(nativeView.SelectedColor));
+
+ Assert.AreEqual(newFormsColor, vm.CColor);
+
+ }
+
+ [Test]
+ public void Binding2WayWithConvertersDoNotLoop()
+ {
+ var nativeView = new MockNativeView();
+ int count = 0;
+
+ nativeView.SelectedColorChanged += (o, e) => {
+ if (++count > 5)
+ Assert.Fail("Probable loop detected");
+ };
+
+ var vm = new MockVMForNativeBinding { CColor = Color.Red };
+
+ nativeView.SetBinding("SelectedColor", new Binding("CColor", BindingMode.TwoWay, new MockCustomColorConverter()), "SelectedColorChanged");
+ nativeView.SetBindingContext(vm);
+
+ Assert.AreEqual(count, 1);
+ }
+
+ [Test]
+ public void ThrowsOnMissingProperty()
+ {
+ var nativeView = new MockNativeView();
+ nativeView.SetBinding("Qux", new Binding("Foo"));
+ Assert.Throws<InvalidOperationException>(()=>nativeView.SetBindingContext(new { Foo = 42 }));
+ }
+
+ [Test]
+ public void ThrowsOnMissingEvent()
+ {
+ var nativeView = new MockNativeView();
+ Assert.Throws<ArgumentException>(()=>nativeView.SetBinding("Foo", new Binding("Foo", BindingMode.TwoWay), "missingEvent"));
+ }
+
+ [Test]
+ public void OneWayToSourceAppliedOnSetBC()
+ {
+ var nativeView = new MockNativeView { Foo = "foobar" };
+ nativeView.SetBinding("Foo", new Binding("FFoo", BindingMode.OneWayToSource));
+ var vm = new MockVMForNativeBinding { FFoo = "qux" };
+ nativeView.SetBindingContext(vm);
+ Assert.AreEqual("foobar", vm.FFoo);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
index 74544605..2c7490d1 100644
--- a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
+++ b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
@@ -178,6 +178,7 @@
<Compile Include="TriggerTests.cs" />
<Compile Include="PinchGestureRecognizerTests.cs" />
<Compile Include="AppLinkEntryTests.cs" />
+ <Compile Include="NativeBindingTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">
diff --git a/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms.Core/Binding.cs
index 5fa1dd65..b3baa0df 100644
--- a/Xamarin.Forms.Core/Binding.cs
+++ b/Xamarin.Forms.Core/Binding.cs
@@ -17,6 +17,7 @@ namespace Xamarin.Forms
BindingExpression _expression;
string _path;
object _source;
+ string _updateSourceEventName;
public Binding()
{
@@ -81,6 +82,14 @@ namespace Xamarin.Forms
}
}
+ internal string UpdateSourceEventName {
+ get { return _updateSourceEventName; }
+ set {
+ ThrowIfApplied();
+ _updateSourceEventName = value;
+ }
+ }
+
public static Binding Create<TSource>(Expression<Func<TSource, object>> propertyGetter, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null,
string stringFormat = null)
{
@@ -115,7 +124,7 @@ namespace Xamarin.Forms
internal override BindingBase Clone()
{
- return new Binding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat, Source = Source };
+ return new Binding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat, Source = Source, UpdateSourceEventName = UpdateSourceEventName };
}
internal override object GetSourceValue(object value, Type targetPropertyType)
diff --git a/Xamarin.Forms.Core/NativeBindingHelpers.cs b/Xamarin.Forms.Core/NativeBindingHelpers.cs
new file mode 100644
index 00000000..c732efd5
--- /dev/null
+++ b/Xamarin.Forms.Core/NativeBindingHelpers.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms.Internals;
+
+using static System.String;
+
+namespace Xamarin.Forms
+{
+ static class NativeBindingHelpers
+ {
+ public static void SetBinding<TNativeView>(TNativeView target, string targetProperty, BindingBase bindingBase, string updateSourceEventName=null) where TNativeView : class
+ {
+ var binding = bindingBase as Binding;
+ //This will allow setting bindings from Xaml by reusing the MarkupExtension
+ if (IsNullOrEmpty(updateSourceEventName) && binding != null && !IsNullOrEmpty(binding.UpdateSourceEventName))
+ updateSourceEventName = binding.UpdateSourceEventName;
+ INotifyPropertyChanged eventWrapper = null;
+ if (!IsNullOrEmpty(updateSourceEventName))
+ eventWrapper = new EventWrapper(target, targetProperty, updateSourceEventName);
+
+ SetBinding(target, targetProperty, bindingBase, eventWrapper);
+ }
+
+ internal static void SetBinding<TNativeView>(TNativeView target, string targetProperty, BindingBase bindingBase, INotifyPropertyChanged propertyChanged) where TNativeView : class
+ {
+ if (target == null)
+ throw new ArgumentNullException(nameof(target));
+ if (IsNullOrEmpty(targetProperty))
+ throw new ArgumentNullException(nameof(targetProperty));
+
+ var binding = bindingBase as Binding;
+ var proxy = BindableObjectProxy<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(key));
+ BindableProperty bindableProperty = null;
+ propertyChanged = propertyChanged ?? target as INotifyPropertyChanged;
+ bindableProperty = CreateBindableProperty<TNativeView>(targetProperty);
+ if (binding != null && binding.Mode != BindingMode.OneWay && propertyChanged != null)
+ propertyChanged.PropertyChanged += (sender, e) => {
+ if (e.PropertyName != targetProperty)
+ return;
+ SetValueFromNative<TNativeView>(sender as TNativeView, targetProperty, bindableProperty);
+ };
+
+ if (binding != null && binding.Mode != BindingMode.OneWay)
+ SetValueFromNative(target, targetProperty, bindableProperty);
+
+ proxy.SetBinding(bindableProperty, bindingBase);
+ }
+
+ static BindableProperty CreateBindableProperty<TNativeView>(string targetProperty) where TNativeView : class
+ {
+ return BindableProperty.Create(
+ targetProperty,
+ typeof(object),
+ typeof(BindableObjectProxy<TNativeView>),
+ defaultBindingMode: BindingMode.Default,
+ propertyChanged: (bindable, oldValue, newValue) => {
+ TNativeView nativeView;
+ if ((bindable as BindableObjectProxy<TNativeView>).TargetReference.TryGetTarget(out nativeView))
+ SetNativeValue(nativeView, targetProperty, newValue);
+ }
+ );
+ }
+
+ static void SetNativeValue<TNativeView>(TNativeView target, string targetProperty, object newValue) where TNativeView : class
+ {
+ var mi = target.GetType().GetProperty(targetProperty)?.SetMethod;
+ if (mi == null)
+ throw new InvalidOperationException(Format("Native Binding on {0}.{1} failed due to missing or inaccessible property", target.GetType(), targetProperty));
+ mi.Invoke(target, new[] { newValue });
+ }
+
+ static void SetValueFromNative<TNativeView>(TNativeView target, string targetProperty, BindableProperty bindableProperty) where TNativeView : class
+ {
+ BindableObjectProxy<TNativeView> proxy;
+ if (!BindableObjectProxy<TNativeView>.BindableObjectProxies.TryGetValue(target, out proxy))
+ return;
+ SetValueFromRenderer(proxy, bindableProperty, target.GetType().GetProperty(targetProperty)?.GetMethod.Invoke(target, new object [] { }));
+ }
+
+ static void SetValueFromRenderer(BindableObject bindable, BindableProperty property, object value)
+ {
+ bindable.SetValueCore(property, value);
+ }
+
+ public static void SetBinding<TNativeView>(TNativeView target, BindableProperty targetProperty, BindingBase binding) where TNativeView : class
+ {
+ if (target == null)
+ throw new ArgumentNullException(nameof(target));
+ if (targetProperty == null)
+ throw new ArgumentNullException(nameof(targetProperty));
+ if (binding == null)
+ throw new ArgumentNullException(nameof(binding));
+
+ var proxy = BindableObjectProxy<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(key));
+ proxy.BindingsBackpack.Add(new KeyValuePair<BindableProperty, BindingBase>(targetProperty, binding));
+ }
+
+ public static void SetValue<TNativeView>(TNativeView target, BindableProperty targetProperty, object value) where TNativeView : class
+ {
+ if (target == null)
+ throw new ArgumentNullException(nameof(target));
+ if (targetProperty == null)
+ throw new ArgumentNullException(nameof(targetProperty));
+
+ var proxy = BindableObjectProxy<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(key));
+ proxy.ValuesBackpack.Add(new KeyValuePair<BindableProperty, object>(targetProperty, value));
+ }
+
+ public static void SetBindingContext<TNativeView>(TNativeView target, object bindingContext, Func<TNativeView, IEnumerable<TNativeView>> getChild = null) where TNativeView : class
+ {
+ if (target == null)
+ throw new ArgumentNullException(nameof(target));
+
+ var proxy = BindableObjectProxy<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(key));
+ proxy.BindingContext = bindingContext;
+ if (getChild == null)
+ return;
+ var children = getChild(target);
+ if (children == null)
+ return;
+ foreach (var child in children)
+ if (child != null)
+ SetBindingContext(child, bindingContext, getChild);
+ }
+
+ internal static void TransferBindablePropertiesToWrapper<TNativeView, TNativeWrapper>(TNativeView nativeView, TNativeWrapper wrapper)
+ where TNativeView : class
+ where TNativeWrapper : View
+ {
+ BindableObjectProxy<TNativeView> proxy;
+ if (!BindableObjectProxy<TNativeView>.BindableObjectProxies.TryGetValue(nativeView, out proxy))
+ return;
+ proxy.TransferAttachedPropertiesTo(wrapper);
+ }
+
+ class EventWrapper : INotifyPropertyChanged
+ {
+ string TargetProperty { get; set; }
+ static readonly MethodInfo s_handlerinfo = typeof(EventWrapper).GetRuntimeMethods().Single(mi => mi.Name == "OnPropertyChanged" && mi.IsPublic == false);
+
+ public EventWrapper(object target, string targetProperty, string updateSourceEventName)
+ {
+ TargetProperty = targetProperty;
+ Delegate handlerDelegate = null;
+ EventInfo updateSourceEvent=null;
+ try {
+ updateSourceEvent = target.GetType().GetRuntimeEvent(updateSourceEventName);
+ handlerDelegate = s_handlerinfo.CreateDelegate(updateSourceEvent.EventHandlerType, this);
+ } catch (Exception){
+ throw new ArgumentException(Format("No declared or accessible event {0} on {1}",updateSourceEventName,target.GetType()), nameof(updateSourceEventName));
+ }
+ if (updateSourceEvent != null && handlerDelegate != null)
+ updateSourceEvent.AddEventHandler(target, handlerDelegate);
+ }
+
+ [Preserve]
+ void OnPropertyChanged(object sender, EventArgs e)
+ {
+ PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(TargetProperty));
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+
+ //This needs to be internal for testing purposes
+ internal class BindableObjectProxy<TNativeView> : BindableObject where TNativeView : class
+ {
+ public static ConditionalWeakTable<TNativeView, BindableObjectProxy<TNativeView>> BindableObjectProxies { get; } = new ConditionalWeakTable<TNativeView, BindableObjectProxy<TNativeView>>();
+ public WeakReference<TNativeView> TargetReference { get; set; }
+ public IList<KeyValuePair<BindableProperty, BindingBase>> BindingsBackpack { get; } = new List<KeyValuePair<BindableProperty, BindingBase>>();
+ public IList<KeyValuePair<BindableProperty, object>> ValuesBackpack { get; } = new List<KeyValuePair<BindableProperty, object>>();
+
+ public BindableObjectProxy(TNativeView target)
+ {
+ TargetReference = new WeakReference<TNativeView>(target);
+ }
+
+ public void TransferAttachedPropertiesTo(View wrapper)
+ {
+ foreach (var kvp in BindingsBackpack)
+ wrapper.SetBinding(kvp.Key, kvp.Value);
+ foreach (var kvp in ValuesBackpack)
+ wrapper.SetValue(kvp.Key, kvp.Value);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
index 4fa202aa..9e961521 100644
--- a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
+++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
@@ -432,6 +432,7 @@
<Compile Include="IAppIndexingProvider.cs" />
<Compile Include="ListStringTypeConverter.cs" />
<Compile Include="PoppedToRootEventArgs.cs" />
+ <Compile Include="NativeBindingHelpers.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<ItemGroup>
diff --git a/Xamarin.Forms.Platform.Android/ColorExtensions.cs b/Xamarin.Forms.Platform.Android/ColorExtensions.cs
index db190b4a..93e23124 100644
--- a/Xamarin.Forms.Platform.Android/ColorExtensions.cs
+++ b/Xamarin.Forms.Platform.Android/ColorExtensions.cs
@@ -36,5 +36,10 @@ namespace Xamarin.Forms.Platform.Android
int disabled = defaults.GetColorForState(States[1], color.ToAndroid());
return new ColorStateList(States, new[] { color.ToAndroid().ToArgb(), disabled });
}
+
+ public static Color ToColor(this AColor color)
+ {
+ return Color.FromUint((uint)color.ToArgb());
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Extensions/NativeBindingExtensions.cs b/Xamarin.Forms.Platform.Android/Extensions/NativeBindingExtensions.cs
new file mode 100644
index 00000000..fd072461
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/Extensions/NativeBindingExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public static class NativeBindingExtensions
+ {
+ public static void SetBinding(this global::Android.Views.View view, string propertyName, BindingBase binding, string updateSourceEventName = null)
+ {
+ NativeBindingHelpers.SetBinding(view, propertyName, binding, updateSourceEventName);
+ }
+
+ public static void SetBinding(this global::Android.Views.View view, BindableProperty targetProperty, BindingBase binding)
+ {
+ NativeBindingHelpers.SetBinding(view, targetProperty, binding);
+ }
+
+ public static void SetValue(this global::Android.Views.View target, BindableProperty targetProperty, object value)
+ {
+ NativeBindingHelpers.SetValue(target, targetProperty, value);
+ }
+
+ public static void SetBindingContext(this global::Android.Views.View target, object bindingContext, Func<global::Android.Views.View, IEnumerable<global::Android.Views.View>> getChildren = null)
+ {
+ NativeBindingHelpers.SetBindingContext(target, bindingContext, getChildren);
+ }
+
+ internal static void TransferBindablePropertiesToWrapper(this global::Android.Views.View target, View wrapper)
+ {
+ NativeBindingHelpers.TransferBindablePropertiesToWrapper(target, wrapper);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/NativeViewWrapper.cs b/Xamarin.Forms.Platform.Android/NativeViewWrapper.cs
index eecef9bc..53b5253c 100644
--- a/Xamarin.Forms.Platform.Android/NativeViewWrapper.cs
+++ b/Xamarin.Forms.Platform.Android/NativeViewWrapper.cs
@@ -1,3 +1,5 @@
+using Android.Views;
+
namespace Xamarin.Forms.Platform.Android
{
public class NativeViewWrapper : View
@@ -9,6 +11,8 @@ namespace Xamarin.Forms.Platform.Android
NativeView = nativeView;
OnLayoutDelegate = onLayoutDelegate;
OnMeasureDelegate = onMeasureDelegate;
+
+ nativeView.TransferBindablePropertiesToWrapper(this);
}
public GetDesiredSizeDelegate GetDesiredSizeDelegate { get; }
@@ -18,5 +22,11 @@ namespace Xamarin.Forms.Platform.Android
public OnLayoutDelegate OnLayoutDelegate { get; }
public OnMeasureDelegate OnMeasureDelegate { get; }
+
+ protected override void OnBindingContextChanged()
+ {
+ NativeView.SetBindingContext(BindingContext, (view) => (view as ViewGroup)?.GetChildrenOfType<global::Android.Views.View>());
+ base.OnBindingContextChanged();
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
index 33bfa03f..00179717 100644
--- a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
+++ b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
@@ -241,6 +241,7 @@
<Compile Include="AppCompat\FormsFragmentPagerAdapter.cs" />
<Compile Include="AndroidAppIndexProvider.cs" />
<Compile Include="Renderers\FormsSeekBar.cs" />
+ <Compile Include="Extensions\NativeBindingExtensions.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
@@ -263,6 +264,9 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Extensions\" />
+ </ItemGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Import Project="..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -271,4 +275,4 @@
</PropertyGroup>
<Error Condition="!Exists('..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets'))" />
</Target>
-</Project> \ No newline at end of file
+</Project>
diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
index ce1b1066..81ec3ff3 100644
--- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
+++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
@@ -158,6 +158,15 @@
<Compile Include="..\Xamarin.Forms.Platform.WinRT\IWrapperAware.cs">
<Link>IWrapperAware.cs</Link>
</Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.WinRT\NativeBindingExtensions.cs">
+ <Link>NativeBindingExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.WinRT\NativeEventWrapper.cs">
+ <Link>NativeEventWrapper.cs</Link>
+ </Compile>
+ <Compile Include="..\Xamarin.Forms.Platform.WinRT\NativePropertyListener.cs">
+ <Link>NativePropertyListener.cs</Link>
+ </Compile>
<Compile Include="..\Xamarin.Forms.Platform.WinRT\NativeViewWrapper.cs">
<Link>NativeViewWrapper.cs</Link>
</Compile>
diff --git a/Xamarin.Forms.Platform.WinRT/FrameworkElementExtensions.cs b/Xamarin.Forms.Platform.WinRT/FrameworkElementExtensions.cs
index eb040e02..f94f3d62 100644
--- a/Xamarin.Forms.Platform.WinRT/FrameworkElementExtensions.cs
+++ b/Xamarin.Forms.Platform.WinRT/FrameworkElementExtensions.cs
@@ -128,5 +128,21 @@ namespace Xamarin.Forms.Platform.WinRT
return foregroundProperty;
}
+
+ internal static IEnumerable<T> GetChildren<T>(this DependencyObject parent) where T : DependencyObject
+ {
+ int myChildrenCount = VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < myChildrenCount; i++)
+ {
+ var child = VisualTreeHelper.GetChild(parent, i);
+ if (child is T)
+ yield return child as T;
+ else
+ {
+ foreach (var subChild in child.GetChildren<T>())
+ yield return subChild;
+ }
+ }
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WinRT/NativeBindingExtensions.cs b/Xamarin.Forms.Platform.WinRT/NativeBindingExtensions.cs
new file mode 100644
index 00000000..cc88c87e
--- /dev/null
+++ b/Xamarin.Forms.Platform.WinRT/NativeBindingExtensions.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Windows.UI.Xaml;
+using static System.String;
+#if WINDOWS_UWP
+
+namespace Xamarin.Forms.Platform.UWP
+#else
+
+namespace Xamarin.Forms.Platform.WinRT
+#endif
+{
+ public static class NativeBindingExtensions
+ {
+ public static void SetBinding(this FrameworkElement view, string propertyName, BindingBase bindingBase, string updateSourceEventName = null)
+ {
+ var binding = bindingBase as Binding;
+ updateSourceEventName = updateSourceEventName ?? binding?.UpdateSourceEventName;
+
+ if (IsNullOrEmpty(updateSourceEventName))
+ {
+ NativePropertyListener nativePropertyListener = null;
+ if (bindingBase.Mode == BindingMode.TwoWay)
+ nativePropertyListener = new NativePropertyListener(view, propertyName);
+
+ NativeBindingHelpers.SetBinding(view, propertyName, bindingBase, nativePropertyListener as INotifyPropertyChanged);
+ return;
+ }
+
+ NativeEventWrapper eventE = null;
+ if (binding.Mode == BindingMode.TwoWay && !(view is INotifyPropertyChanged))
+ eventE = new NativeEventWrapper(view, propertyName, updateSourceEventName);
+
+ NativeBindingHelpers.SetBinding(view, propertyName, binding, eventE);
+ }
+
+ public static void SetBinding(this FrameworkElement view, BindableProperty targetProperty, BindingBase binding)
+ {
+ NativeBindingHelpers.SetBinding(view, targetProperty, binding);
+ }
+
+ public static void SetValue(this FrameworkElement target, BindableProperty targetProperty, object value)
+ {
+ NativeBindingHelpers.SetValue(target, targetProperty, value);
+ }
+
+ public static void SetBindingContext(this FrameworkElement target, object bindingContext, Func<FrameworkElement, IEnumerable<FrameworkElement>> getChildren = null)
+ {
+ NativeBindingHelpers.SetBindingContext(target, bindingContext, getChildren);
+ }
+
+ internal static void TransferbindablePropertiesToWrapper(this FrameworkElement target, View wrapper)
+ {
+ NativeBindingHelpers.TransferBindablePropertiesToWrapper(target, wrapper);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WinRT/NativeEventWrapper.cs b/Xamarin.Forms.Platform.WinRT/NativeEventWrapper.cs
new file mode 100644
index 00000000..e28ca0eb
--- /dev/null
+++ b/Xamarin.Forms.Platform.WinRT/NativeEventWrapper.cs
@@ -0,0 +1,45 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.UI.Xaml;
+
+#if WINDOWS_UWP
+namespace Xamarin.Forms.Platform.UWP
+#else
+namespace Xamarin.Forms.Platform.WinRT
+#endif
+{
+ class NativeEventWrapper : INotifyPropertyChanged
+ {
+ static string TargetProperty { get; set; }
+ static readonly MethodInfo s_handlerinfo = typeof(NativeEventWrapper).GetRuntimeMethods().Single(mi => mi.Name == nameof(OnPropertyChanged) && mi.IsPublic == false);
+
+ public NativeEventWrapper(object target, string targetProperty, string updateSourceEventName)
+ {
+ TargetProperty = targetProperty;
+ try {
+ var updateSourceEvent = target.GetType().GetRuntimeEvent(updateSourceEventName);
+ MethodInfo addMethod = updateSourceEvent.AddMethod;
+ MethodInfo removeMethod = updateSourceEvent.RemoveMethod;
+ ParameterInfo[] addParameters = addMethod.GetParameters();
+ Type delegateType = addParameters[0].ParameterType;
+ var handlerDelegate = s_handlerinfo.CreateDelegate(delegateType, this);
+ Func<object, EventRegistrationToken> add = a => (EventRegistrationToken)addMethod.Invoke(target, new object[] { handlerDelegate });
+ Action<EventRegistrationToken> remove = t => removeMethod.Invoke(target, new object[] { t });
+ WindowsRuntimeMarshal.AddEventHandler(add, remove, s_handlerinfo);
+ }
+ catch (Exception) {
+ Log.Warning(nameof(NativeEventWrapper), "Can not attach NativeEventWrapper.");
+ }
+ }
+
+ void OnPropertyChanged(object sender, RoutedEventArgs e)
+ {
+ PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(TargetProperty));
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+}
diff --git a/Xamarin.Forms.Platform.WinRT/NativePropertyListener.cs b/Xamarin.Forms.Platform.WinRT/NativePropertyListener.cs
new file mode 100644
index 00000000..5a1b294c
--- /dev/null
+++ b/Xamarin.Forms.Platform.WinRT/NativePropertyListener.cs
@@ -0,0 +1,47 @@
+using System;
+using System.ComponentModel;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Data;
+
+#if WINDOWS_UWP
+namespace Xamarin.Forms.Platform.UWP
+#else
+namespace Xamarin.Forms.Platform.WinRT
+#endif
+{
+ class NativePropertyListener : DependencyObject, INotifyPropertyChanged
+ {
+ readonly DependencyObject _target;
+ readonly string _targetProperty;
+
+ public static readonly DependencyProperty TargetPropertyValueProperty = DependencyProperty.Register(nameof(TargetPropertyValue), typeof(object), typeof(NativePropertyListener), new PropertyMetadata(null, OnNativePropertyChanged));
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public NativePropertyListener(DependencyObject target, string propertyName)
+ {
+ _target = target;
+ _targetProperty = propertyName;
+ BindingOperations.SetBinding(this, TargetPropertyValueProperty, new Windows.UI.Xaml.Data.Binding() { Source = _target, Path = new PropertyPath(_targetProperty), Mode = Windows.UI.Xaml.Data.BindingMode.OneWay });
+ }
+
+ public void Dispose()
+ {
+ ClearValue(TargetPropertyValueProperty);
+ }
+
+ public object TargetPropertyValue
+ {
+ get
+ {
+ return GetValue(TargetPropertyValueProperty);
+ }
+ }
+
+ static void OnNativePropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
+ {
+ NativePropertyListener source = (NativePropertyListener)sender;
+ source?.PropertyChanged?.Invoke(source._target, new PropertyChangedEventArgs(source._targetProperty));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WinRT/NativeViewWrapper.cs b/Xamarin.Forms.Platform.WinRT/NativeViewWrapper.cs
index 824f0eed..22cca62e 100644
--- a/Xamarin.Forms.Platform.WinRT/NativeViewWrapper.cs
+++ b/Xamarin.Forms.Platform.WinRT/NativeViewWrapper.cs
@@ -1,4 +1,5 @@
using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
#if WINDOWS_UWP
@@ -17,6 +18,7 @@ namespace Xamarin.Forms.Platform.WinRT
ArrangeOverrideDelegate = arrangeOverrideDelegate;
MeasureOverrideDelegate = measureOverrideDelegate;
NativeElement = nativeElement;
+ nativeElement.TransferbindablePropertiesToWrapper(this);
}
public ArrangeOverrideDelegate ArrangeOverrideDelegate { get; set; }
@@ -26,5 +28,11 @@ namespace Xamarin.Forms.Platform.WinRT
public MeasureOverrideDelegate MeasureOverrideDelegate { get; set; }
public FrameworkElement NativeElement { get; }
+
+ protected override void OnBindingContextChanged()
+ {
+ NativeElement.SetBindingContext(BindingContext, nv => nv.GetChildren<FrameworkElement>());
+ base.OnBindingContextChanged();
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.WinRT/Xamarin.Forms.Platform.WinRT.csproj b/Xamarin.Forms.Platform.WinRT/Xamarin.Forms.Platform.WinRT.csproj
index fd73aae0..ea456c00 100644
--- a/Xamarin.Forms.Platform.WinRT/Xamarin.Forms.Platform.WinRT.csproj
+++ b/Xamarin.Forms.Platform.WinRT/Xamarin.Forms.Platform.WinRT.csproj
@@ -69,7 +69,10 @@
<Compile Include="PlatformConfigurationExtensions.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(OS)' != 'Unix' ">
- <Compile Include="BrushHelpers.cs" />
+ <Compile Include="BrushHelpers.cs" />
+ <Compile Include="NativeBindingExtensions.cs" />
+ <Compile Include="NativeEventWrapper.cs" />
+ <Compile Include="NativePropertyListener.cs" />
<Compile Include="NativeViewWrapper.cs" />
<Compile Include="NativeViewWrapperRenderer.cs" />
<Compile Include="ViewExtensions.cs" />
diff --git a/Xamarin.Forms.Platform.iOS/LayoutExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/LayoutExtensions.cs
index 6e7f5738..6e7f5738 100644
--- a/Xamarin.Forms.Platform.iOS/LayoutExtensions.cs
+++ b/Xamarin.Forms.Platform.iOS/Extensions/LayoutExtensions.cs
diff --git a/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs
index 9847867c..73475b13 100644
--- a/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs
+++ b/Xamarin.Forms.Platform.iOS/Extensions/UIViewExtensions.cs
@@ -2,6 +2,8 @@ using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System;
+using static System.String;
+
#if __UNIFIED__
using UIKit;
@@ -28,6 +30,47 @@ namespace Xamarin.Forms.Platform.iOS
return new SizeRequest(request, minimum);
}
+ public static void SetBinding(this UIView view, string propertyName, BindingBase bindingBase, string updateSourceEventName = null)
+ {
+ var binding = bindingBase as Binding;
+ //This will allow setting bindings from Xaml by reusing the MarkupExtension
+ updateSourceEventName = updateSourceEventName ?? binding?.UpdateSourceEventName;
+
+ if (!IsNullOrEmpty(updateSourceEventName))
+ {
+ NativeBindingHelpers.SetBinding(view, propertyName, bindingBase, updateSourceEventName);
+ return;
+ }
+
+ NativeViewPropertyListener nativePropertyListener = null;
+ if (bindingBase.Mode == BindingMode.TwoWay) {
+ nativePropertyListener = new NativeViewPropertyListener(propertyName);
+ view.AddObserver(nativePropertyListener, propertyName, 0, IntPtr.Zero);
+ }
+
+ NativeBindingHelpers.SetBinding(view, propertyName, bindingBase, nativePropertyListener);
+ }
+
+ public static void SetBinding(this UIView self, BindableProperty targetProperty, BindingBase binding)
+ {
+ NativeBindingHelpers.SetBinding(self, targetProperty, binding);
+ }
+
+ public static void SetValue(this UIView target, BindableProperty targetProperty, object value)
+ {
+ NativeBindingHelpers.SetValue(target, targetProperty, value);
+ }
+
+ public static void SetBindingContext(this UIView target, object bindingContext, Func<UIView, IEnumerable<UIView>> getChildren = null)
+ {
+ NativeBindingHelpers.SetBindingContext(target, bindingContext, getChildren);
+ }
+
+ internal static void TransferbindablePropertiesToWrapper(this UIView target, View wrapper)
+ {
+ NativeBindingHelpers.TransferBindablePropertiesToWrapper(target, wrapper);
+ }
+
internal static T FindDescendantView<T>(this UIView view) where T : UIView
{
var queue = new Queue<UIView>();
diff --git a/Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs b/Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs
new file mode 100644
index 00000000..ffe117f4
--- /dev/null
+++ b/Xamarin.Forms.Platform.iOS/NativeViewPropertyListener.cs
@@ -0,0 +1,29 @@
+using System;
+using System.ComponentModel;
+#if __UNIFIED__
+using Foundation;
+
+#else
+using MonoTouch.Foundation;
+#endif
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ class NativeViewPropertyListener : NSObject, INotifyPropertyChanged
+ {
+ string TargetProperty { get; set; }
+
+ public NativeViewPropertyListener(string targetProperty)
+ {
+ TargetProperty = targetProperty;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
+ {
+ if (keyPath == TargetProperty)
+ PropertyChanged?.Invoke(ofObject, new PropertyChangedEventArgs(TargetProperty));
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs b/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs
index 209aca80..5474c65b 100644
--- a/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs
+++ b/Xamarin.Forms.Platform.iOS/NativeViewWrapper.cs
@@ -22,6 +22,8 @@ namespace Xamarin.Forms.Platform.iOS
SizeThatFitsDelegate = sizeThatFitsDelegate;
LayoutSubViews = layoutSubViews;
NativeView = nativeView;
+
+ nativeView.TransferbindablePropertiesToWrapper(this);
}
public GetDesiredSizeDelegate GetDesiredSizeDelegate { get; }
@@ -31,5 +33,11 @@ namespace Xamarin.Forms.Platform.iOS
public UIView NativeView { get; }
public SizeThatFitsDelegate SizeThatFitsDelegate { get; set; }
+
+ protected override void OnBindingContextChanged()
+ {
+ NativeView.SetBindingContext(BindingContext, nv => nv.Subviews);
+ base.OnBindingContextChanged();
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj
index 5b705d01..45699dae 100644
--- a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj
+++ b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj
@@ -76,7 +76,6 @@
<Compile Include="NativeViewWrapper.cs" />
<Compile Include="NativeViewWrapperRenderer.cs" />
<Compile Include="PlatformEffect.cs" />
- <Compile Include="LayoutExtensions.cs" />
<Compile Include="Renderers\AlignmentExtensions.cs" />
<Compile Include="PageExtensions.cs" />
<Compile Include="Renderers\ExportCellAttribute.cs" />
@@ -84,6 +83,8 @@
<Compile Include="Renderers\ExportRendererAttribute.cs" />
<Compile Include="Resources\StringResources.Designer.cs" />
<Compile Include="ViewInitializedEventArgs.cs" />
+ <Compile Include="NativeViewPropertyListener.cs" />
+ <Compile Include="Extensions\LayoutExtensions.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Deserializer.cs" />
diff --git a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
index d2364175..8fde4c81 100644
--- a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
+++ b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
@@ -137,7 +137,6 @@
<Compile Include="NativeViewWrapper.cs" />
<Compile Include="NativeViewWrapperRenderer.cs" />
<Compile Include="PlatformEffect.cs" />
- <Compile Include="LayoutExtensions.cs" />
<Compile Include="Renderers\AlignmentExtensions.cs" />
<Compile Include="Forms.cs" />
<Compile Include="PageExtensions.cs" />
@@ -149,6 +148,8 @@
<Compile Include="ViewInitializedEventArgs.cs" />
<Compile Include="IOSAppIndexingProvider.cs" />
<Compile Include="IOSAppLinks.cs" />
+ <Compile Include="NativeViewPropertyListener.cs" />
+ <Compile Include="Extensions\LayoutExtensions.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\StringResources.ar.resx" />
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs
index f9af72a4..5b519e62 100644
--- a/Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs
@@ -23,9 +23,11 @@ namespace Xamarin.Forms.Xaml
public object Source { get; set; }
+ public string UpdateSourceEventName { get; set; }
+
BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
{
- return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source);
+ return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source) { UpdateSourceEventName = UpdateSourceEventName };
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
diff --git a/docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml/BindingExtension.xml b/docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml/BindingExtension.xml
index fd4e25be..f040bb89 100644
--- a/docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml/BindingExtension.xml
+++ b/docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml/BindingExtension.xml
@@ -148,6 +148,22 @@
<remarks>To be added.</remarks>
</Docs>
</Member>
+ <Member MemberName="UpdateSourceEventName">
+ <MemberSignature Language="C#" Value="public string UpdateSourceEventName { get; set; }" />
+ <MemberSignature Language="ILAsm" Value=".property instance string UpdateSourceEventName" />
+ <MemberType>Property</MemberType>
+ <AssemblyInfo>
+ <AssemblyVersion>2.0.0.0</AssemblyVersion>
+ </AssemblyInfo>
+ <ReturnValue>
+ <ReturnType>System.String</ReturnType>
+ </ReturnValue>
+ <Docs>
+ <summary>To be added.</summary>
+ <value>To be added.</value>
+ <remarks>To be added.</remarks>
+ </Docs>
+ </Member>
<Member MemberName="Xamarin.Forms.Xaml.IMarkupExtension.ProvideValue">
<MemberSignature Language="C#" Value="object IMarkupExtension.ProvideValue (IServiceProvider serviceProvider);" />
<MemberSignature Language="ILAsm" Value=".method hidebysig newslot virtual instance object Xamarin.Forms.Xaml.IMarkupExtension.ProvideValue(class System.IServiceProvider serviceProvider) cil managed" />