diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2016-09-08 20:39:05 +0200 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-09-08 11:39:05 -0700 |
commit | 85426c5d9495eb1d55b3128bf97e50c68a73b53f (patch) | |
tree | 2f81e5868ce61eb90d15c6c51a354603b8395627 | |
parent | 11326e1c182b3ff5c3d82c6ef7d09c193bc19891 (diff) | |
download | xamarin-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
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" /> |