From bdccb96d278c094a3874785b300bc6f47d7064f8 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Fri, 1 Nov 2019 14:20:32 -0600 Subject: [PATCH] Prevent UWP error/crash when updating binding off the main thread; fixes #8167 (#8311) --- .../Issue8167.cs | 123 +++++++++++++++++++++ .../Xamarin.Forms.Controls.Issues.Shared.projitems | 1 + Xamarin.Forms.Platform.UAP/Dispatcher.cs | 5 + Xamarin.Forms.Platform.UAP/DispatcherProvider.cs | 25 ++++- 4 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8167.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8167.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8167.cs new file mode 100644 index 0000000..5bdadff --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8167.cs @@ -0,0 +1,123 @@ +using System.ComponentModel; +using System.Threading.Tasks; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [NUnit.Framework.Category(UITestCategories.ManualReview)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 8167, "[Bug] XF 4.3 UWP Crash - Element not found", PlatformAffected.UWP)] + public class Issue8167 : TestContentPage + { + const string Run = "Update Text"; + const string Success = "Success"; + + protected override void Init() + { + var layout = new StackLayout(); + + var instructions = new Label + { + Text = $"Tap the button marked {Run}. If the Label below reads {Success} then the test has passed." + }; + + layout.Children.Add(instructions); + + var label = new Label(); + label.SetBinding(Label.TextProperty, new Binding(nameof(_8167ViewModel.Text))); + + layout.Children.Add(label); + + var presenter = new ContentPresenter(); + presenter.SetBinding(ContentPresenter.WidthRequestProperty, new Binding(nameof(_8167ViewModel.Width))); + + layout.Children.Add(presenter); + + var model = new _8167ViewModel(); + + var button = new Button() { Text = Run }; + + button.Clicked += (obj, args) => + { + model.UpdateText(); + model.UpdateWidth(); + }; + + layout.Children.Add(button); + + Content = layout; + + BindingContext = model; + } + + [Preserve(AllMembers = true)] + public class _8167ViewModel : INotifyPropertyChanged + { + string _text; + double _width; + + public _8167ViewModel() + { + _text = "Starting value"; + } + + public void UpdateText() + { + Task.Run(() => { Text = Success; }); + } + + public void UpdateWidth() + { + Task.Run(() => { Width = 200; }); + } + + public string Text + { + get => _text; + set + { + _text = value; + RaisePropertyChanged(nameof(Text)); + } + } + + public double Width + { + get => _width; + set + { + _width = value; + RaisePropertyChanged(nameof(Width)); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + +#if UITEST + [Test] + public void ThreadpoolBindingUpdateShouldNotCrash() + { + RunningApp.WaitForElement(Run); + RunningApp.Tap(Run); + RunningApp.WaitForElement(Success); + } +#endif + } + + +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 32f55d7..dd59c33 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -94,6 +94,7 @@ Issue7803.xaml + diff --git a/Xamarin.Forms.Platform.UAP/Dispatcher.cs b/Xamarin.Forms.Platform.UAP/Dispatcher.cs index fbc9189..5cad1e3 100644 --- a/Xamarin.Forms.Platform.UAP/Dispatcher.cs +++ b/Xamarin.Forms.Platform.UAP/Dispatcher.cs @@ -20,6 +20,11 @@ namespace Xamarin.Forms.Platform.UWP _coreDispatcher = CoreApplication.GetCurrentView().Dispatcher; } + public Dispatcher(CoreDispatcher coreDispatcher) + { + _coreDispatcher = coreDispatcher; + } + bool IDispatcher.IsInvokeRequired => Device.IsInvokeRequired; } } diff --git a/Xamarin.Forms.Platform.UAP/DispatcherProvider.cs b/Xamarin.Forms.Platform.UAP/DispatcherProvider.cs index 549a705..f41a0f5 100644 --- a/Xamarin.Forms.Platform.UAP/DispatcherProvider.cs +++ b/Xamarin.Forms.Platform.UAP/DispatcherProvider.cs @@ -1,10 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xamarin.Forms; -using Xamarin.Forms.Internals; using Xamarin.Forms.Platform.UWP; [assembly: Dependency(typeof(DispatcherProvider))] @@ -17,6 +12,26 @@ namespace Xamarin.Forms.Platform.UWP public IDispatcher GetDispatcher(object context) { + if (s_current != null) + { + return s_current; + } + + if (context is BindableObject) + { + if (context is VisualElement element) + { + var renderer = Platform.GetRenderer(element); + if (renderer?.ContainerElement != null) + { + s_current = new Dispatcher(renderer.ContainerElement.Dispatcher); + return s_current; + } + } + + return null; + } + return s_current = s_current ?? new Dispatcher(); } } -- 2.7.4