From c79a8933c823f973a6f713aa15fc009b1e041bba Mon Sep 17 00:00:00 2001 From: Oddbjorn Bakke Date: Sun, 28 Aug 2016 18:17:36 +0200 Subject: [PATCH 1/5] Added a navigation command that ignores multiple invocations until it's finished executing. --- .../Fixtures/FreshNavigationCommandFixture.cs | 41 ++++++ src/FreshMvvm.Tests/FreshMvvm.Tests.csproj | 1 + .../Mocks/MockPageModelCoreMethods.cs | 118 ++++++++++++++++- src/FreshMvvm/FreshMvvm.csproj | 2 + src/FreshMvvm/FreshNavigationCommand.cs | 125 ++++++++++++++++++ src/FreshMvvm/IFreshNavigationCommand.cs | 11 ++ src/FreshMvvm/IPageModelCoreMethods.cs | 9 ++ src/FreshMvvm/PageModelCoreMethods.cs | 49 +++++++ 8 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 src/FreshMvvm.Tests/Fixtures/FreshNavigationCommandFixture.cs create mode 100644 src/FreshMvvm/FreshNavigationCommand.cs create mode 100644 src/FreshMvvm/IFreshNavigationCommand.cs diff --git a/src/FreshMvvm.Tests/Fixtures/FreshNavigationCommandFixture.cs b/src/FreshMvvm.Tests/Fixtures/FreshNavigationCommandFixture.cs new file mode 100644 index 0000000..65d7199 --- /dev/null +++ b/src/FreshMvvm.Tests/Fixtures/FreshNavigationCommandFixture.cs @@ -0,0 +1,41 @@ +using System; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace FreshMvvm.Tests.Fixtures +{ + [TestFixture] + public class FreshNavigationCommandFixture + { + SharedLock _sharedLock; + + [SetUp] + public void Setup() + { + _sharedLock = new SharedLock(); + } + + [Test] + public async Task OnlyOneCommandWillExecuteTests() + { + //Flow: A executes, B executes, A starts, B ignored, A completes. + int countBefore = 0; + int countAfter = 0; + Func execute = async (obj) => + { + countBefore++; + await Task.Delay(100); + countAfter++; + }; + + //Execute is async void, so this will run in the background. + var first = new FreshNavigationCommand(execute, _sharedLock).ExecuteAsync(null); + var second = new FreshNavigationCommand(execute, _sharedLock).ExecuteAsync(null); + + await Task.WhenAll(first, second); //Need to let execute finish. + + Assert.AreEqual(1, countBefore); + Assert.AreEqual(1, countAfter); + } + } +} diff --git a/src/FreshMvvm.Tests/FreshMvvm.Tests.csproj b/src/FreshMvvm.Tests/FreshMvvm.Tests.csproj index f545741..911c6b5 100644 --- a/src/FreshMvvm.Tests/FreshMvvm.Tests.csproj +++ b/src/FreshMvvm.Tests/FreshMvvm.Tests.csproj @@ -87,6 +87,7 @@ + diff --git a/src/FreshMvvm.Tests/Mocks/MockPageModelCoreMethods.cs b/src/FreshMvvm.Tests/Mocks/MockPageModelCoreMethods.cs index 45466aa..dfc7568 100644 --- a/src/FreshMvvm.Tests/Mocks/MockPageModelCoreMethods.cs +++ b/src/FreshMvvm.Tests/Mocks/MockPageModelCoreMethods.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; +using System.Windows.Input; +using Xamarin.Forms; namespace FreshMvvm.Tests.Mocks { @@ -114,5 +116,119 @@ public Task PushNewNavigationServiceModal (IFreshNavigationService newNavigation { throw new NotImplementedException (); } - } + + public Task PushPageModel(object data, bool modal = false, bool animate = true) where T : FreshBasePageModel + { + throw new NotImplementedException(); + } + + public Task PushPageModel(object data, bool modal = false, bool animate = true) + where T : FreshBasePageModel + where TPage : Page + { + throw new NotImplementedException(); + } + + public Task PopPageModel(bool modal = false, bool animate = true) + { + throw new NotImplementedException(); + } + + public Task PopPageModel(object data, bool modal = false, bool animate = true) + { + throw new NotImplementedException(); + } + + public Task PushPageModel(bool animate = true) where T : FreshBasePageModel + { + throw new NotImplementedException(); + } + + public Task PushPageModel(bool animate = true) + where T : FreshBasePageModel + where TPage : Page + { + throw new NotImplementedException(); + } + + public Task PushPageModel(Type pageModelType, bool animate = true) + { + throw new NotImplementedException(); + } + + public void RemoveFromNavigation() + { + throw new NotImplementedException(); + } + + public void RemoveFromNavigation(bool removeAll = false) where TPageModel : FreshBasePageModel + { + throw new NotImplementedException(); + } + + public Task PushPageModelWithNewNavigation(object data, bool animate = true) where T : FreshBasePageModel + { + throw new NotImplementedException(); + } + + public Task PushNewNavigationServiceModal(IFreshNavigationService newNavigationService, FreshBasePageModel[] basePageModels, bool animate = true) + { + throw new NotImplementedException(); + } + + public Task PushNewNavigationServiceModal(FreshTabbedNavigationContainer tabbedNavigationContainer, FreshBasePageModel basePageModel = null, bool animate = true) + { + throw new NotImplementedException(); + } + + public Task PushNewNavigationServiceModal(FreshMasterDetailNavigationContainer masterDetailContainer, FreshBasePageModel basePageModel = null, bool animate = true) + { + throw new NotImplementedException(); + } + + public Task PushNewNavigationServiceModal(IFreshNavigationService newNavigationService, FreshBasePageModel basePageModels, bool animate = true) + { + throw new NotImplementedException(); + } + + public Task PopModalNavigationService(bool animate = true) + { + throw new NotImplementedException(); + } + + public Task SwitchSelectedRootPageModel() where T : FreshBasePageModel + { + throw new NotImplementedException(); + } + + public Task SwitchSelectedTab() where T : FreshBasePageModel + { + throw new NotImplementedException(); + } + + public Task SwitchSelectedMaster() where T : FreshBasePageModel + { + throw new NotImplementedException(); + } + + public ICommand CreateCommand(Func execute, SharedLock sharedLock = null) + { + throw new NotImplementedException(); + } + + public ICommand CreateCommand(Func execute, SharedLock sharedLock = null) + { + throw new NotImplementedException(); + } + + public ICommand CreateCommand(Func execute, SharedLock sharedLock = null) + { + throw new NotImplementedException(); + } + + public ICommand CreateCommand(Func execute, Func stringConverter, SharedLock sharedLock = null) + { + throw new NotImplementedException(); + } + } } diff --git a/src/FreshMvvm/FreshMvvm.csproj b/src/FreshMvvm/FreshMvvm.csproj index bcf7c26..b76cb32 100644 --- a/src/FreshMvvm/FreshMvvm.csproj +++ b/src/FreshMvvm/FreshMvvm.csproj @@ -49,6 +49,8 @@ + + diff --git a/src/FreshMvvm/FreshNavigationCommand.cs b/src/FreshMvvm/FreshNavigationCommand.cs new file mode 100644 index 0000000..0fcaaa7 --- /dev/null +++ b/src/FreshMvvm/FreshNavigationCommand.cs @@ -0,0 +1,125 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FreshMvvm +{ + /// + /// Command that will execute and ignore all new events until it's finished. + /// Typicaly used to ignore multiple events from doubleclicking by the user. + /// + public class FreshNavigationCommand : IFreshNavigationCommand + { + public event EventHandler CanExecuteChanged; + + private readonly SharedLock _sharedLock; + private readonly Func _execute; + + public FreshNavigationCommand(Func execute, SharedLock sharedLock = null) + { + if (execute == null) + throw new ArgumentException(nameof(execute)); + + _execute = execute; + _sharedLock = sharedLock ?? new SharedLock(); + } + + public FreshNavigationCommand(Func execute, SharedLock sharedLock = null) + : this((obj) => execute(), sharedLock) + { + if (execute == null) + throw new ArgumentException(nameof(execute)); + } + + public bool CanExecute(object parameter) + { + return !_sharedLock.IsLocked; + } + + /// + /// Execute the command with specified parameter. + /// The caller will not be able to await this command. + /// + /// Parameter. +#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void + public async void Execute(object parameter) +#pragma warning restore RECS0165 + { + await ExecuteAsync(parameter); + } + + /// + /// Execute the async command with specified parameter. + /// If more than one is executed at the same time, all other than the first one will be ignored. + /// + /// The async task, that can be awaited. + /// Parameter. + public async Task ExecuteAsync(object parameter) + { + if (_sharedLock.TakeLock()) //Ignores code block if lock already taken in SharedLock. + { + var events = CanExecuteChanged; + try + { + if (events != null) + events(this, EventArgs.Empty); + + await _execute(parameter); + } + finally + { + _sharedLock.ReleaseLock(); + if (events != null) + events(this, EventArgs.Empty); + } + } + } + } + + public class FreshNavigationCommand : FreshNavigationCommand + { + public FreshNavigationCommand(Func execute, SharedLock sharedLock = null) + : base(obj => execute((TValue)obj), sharedLock) + { + // + } + } + + /// + /// Locking object that can be shared between commands. + /// If multiple commands use the same shared object, only one of them can run simultanious. + /// + public class SharedLock + { + private int _lock; + + public bool IsLocked => _lock != 0; + + public SharedLock() + { + _lock = 0; + } + + /// + /// Will take a threadsafe lock on the object. + /// + /// true, if lock was taken, false otherwise. + public bool TakeLock() + { + var oldVal = Interlocked.Exchange(ref _lock, 1); //Atomic swap values. + var lockTaken = oldVal == 0; //If the old value was 0, no one else is executing at this time. + return lockTaken; + } + + /// + /// Will release the treadsafe lock on the object. + /// + /// true, if lock was released, false otherwise. + public bool ReleaseLock() + { + var oldVal = Interlocked.Exchange(ref _lock, 0); + var lockReleased = oldVal == 1; + return lockReleased; + } + } +} \ No newline at end of file diff --git a/src/FreshMvvm/IFreshNavigationCommand.cs b/src/FreshMvvm/IFreshNavigationCommand.cs new file mode 100644 index 0000000..f6d5c68 --- /dev/null +++ b/src/FreshMvvm/IFreshNavigationCommand.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace FreshMvvm +{ + public interface IFreshNavigationCommand : ICommand + { + Task ExecuteAsync(object parameter); + } +} diff --git a/src/FreshMvvm/IPageModelCoreMethods.cs b/src/FreshMvvm/IPageModelCoreMethods.cs index 1b3c0dc..95a02cd 100644 --- a/src/FreshMvvm/IPageModelCoreMethods.cs +++ b/src/FreshMvvm/IPageModelCoreMethods.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using System.Windows.Input; using Xamarin.Forms; namespace FreshMvvm @@ -82,6 +83,14 @@ public interface IPageModelCoreMethods void BatchBegin(); void BatchCommit(); + + ICommand CreateCommand(Func execute, SharedLock sharedLock = null); + + ICommand CreateCommand(Func execute, SharedLock sharedLock = null); + + ICommand CreateCommand(Func execute, SharedLock sharedLock = null); + + ICommand CreateCommand(Func execute, Func stringConverter, SharedLock sharedLock = null); } } diff --git a/src/FreshMvvm/PageModelCoreMethods.cs b/src/FreshMvvm/PageModelCoreMethods.cs index bd6405b..48d6269 100644 --- a/src/FreshMvvm/PageModelCoreMethods.cs +++ b/src/FreshMvvm/PageModelCoreMethods.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Xamarin.Forms; using System.Linq; +using System.Windows.Input; namespace FreshMvvm { @@ -272,6 +273,54 @@ public void RemoveFromNavigation (bool removeAll = false) where TPag } } } + + /// + /// Creates a command without parameter. + /// + /// The command. + /// Execute method. + /// Shared lock. + public ICommand CreateCommand(Func execute, SharedLock sharedLock = null) + { + return new FreshNavigationCommand(execute, sharedLock); + } + + /// + /// Creates a command with a object parameter. + /// + /// The command. + /// Execute method. + /// Shared lock. + public ICommand CreateCommand(Func execute, SharedLock sharedLock = null) + { + return new FreshNavigationCommand(execute, sharedLock); + } + + /// + /// Creates a genric command. + /// + /// The command. + /// Execute method. + /// Shared lock. + /// Parameter type, ex. string. + public ICommand CreateCommand(Func execute, SharedLock sharedLock = null) + { + return new FreshNavigationCommand(execute, sharedLock); + } + + /// + /// Creates a command, with a conversion function from string to given type. + /// Typicaly used in xaml parameters to convert command parameters from string. + /// + /// The command. + /// Execute method. + /// int.Parse, "0" to 0 + /// Shared lock. + /// Parameter type to convert to from string. + public ICommand CreateCommand(Func execute, Func stringConverter, SharedLock sharedLock = null) + { + return new FreshNavigationCommand((obj) => execute(stringConverter(obj)), sharedLock); + } } } From 44c409e13638dcafa3de770f9e2b20729c95a78e Mon Sep 17 00:00:00 2001 From: Oddbjorn Bakke Date: Sun, 28 Aug 2016 23:04:27 +0200 Subject: [PATCH 2/5] Removing double tap issue from the sample project. --- .../PageModels/ContactListPageModel.cs | 10 +++----- .../PageModels/MainMenuPageModel.cs | 25 ++++++------------- .../PageModels/QuoteListPageModel.cs | 10 +++----- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactListPageModel.cs b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactListPageModel.cs index 06a3669..2c95e75 100644 --- a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactListPageModel.cs +++ b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactListPageModel.cs @@ -3,6 +3,7 @@ using PropertyChanged; using System.Collections.ObjectModel; using FreshMvvm; +using System.Windows.Input; namespace FreshMvvmSampleApp { @@ -21,6 +22,7 @@ public ContactListPageModel (IDatabaseService databaseService) public override void Init (object initData) { Contacts = new ObservableCollection (_databaseService.GetContacts ()); + AddContact = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel()); } protected override void ViewIsAppearing (object sender, EventArgs e) @@ -49,13 +51,7 @@ public Contact SelectedContact { } } - public Command AddContact { - get { - return new Command (async () => { - await CoreMethods.PushPageModel (); - }); - } - } + public ICommand AddContact { get; private set; } public Command ContactSelected { get { diff --git a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/MainMenuPageModel.cs b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/MainMenuPageModel.cs index c83f2b5..d7cad7d 100644 --- a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/MainMenuPageModel.cs +++ b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/MainMenuPageModel.cs @@ -1,28 +1,19 @@ using Xamarin.Forms; using FreshMvvm; +using System.Windows.Input; namespace FreshMvvmSampleApp { public class MainMenuPageModel : FreshBasePageModel { - public MainMenuPageModel () - { - } + public ICommand ShowQuotes { get; private set; } + public ICommand ShowContacts { get; private set; } - public Command ShowQuotes { - get { - return new Command (async () => { - await CoreMethods.PushPageModel (); - }); - } - } - - public Command ShowContacts { - get { - return new Command (async () => { - await CoreMethods.PushPageModel (); - }); - } + public override void Init(object initData) + { + var sharedLock = new SharedLock(); + ShowQuotes = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(), sharedLock); + ShowContacts = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(), sharedLock); } } } diff --git a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuoteListPageModel.cs b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuoteListPageModel.cs index b420c9b..c52c537 100644 --- a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuoteListPageModel.cs +++ b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuoteListPageModel.cs @@ -3,6 +3,7 @@ using FreshMvvm; using PropertyChanged; using System.Diagnostics; +using System.Windows.Input; namespace FreshMvvmSampleApp { @@ -21,6 +22,7 @@ public QuoteListPageModel (IDatabaseService databaseService) public override void Init (object initData) { Quotes = new ObservableCollection (_databaseService.GetQuotes ()); + AddQuote = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel()); } protected override void ViewIsAppearing (object sender, System.EventArgs e) @@ -42,13 +44,7 @@ public override void ReverseInit (object value) } } - public Command AddQuote { - get { - return new Command (async () => { - await CoreMethods.PushPageModel (); - }); - } - } + public ICommand AddQuote { get; private set; } Quote _selectedQuote; From 46bea71f78851126b49493ff580460d55eaa6733 Mon Sep 17 00:00:00 2001 From: Oddbjorn Bakke Date: Mon, 29 Aug 2016 21:33:52 +0200 Subject: [PATCH 3/5] Updated more of the sample commands to navigation commands. --- .../PageModels/ContactPageModel.cs | 81 ++++++++----------- .../PageModels/QuotePageModel.cs | 33 ++++---- 2 files changed, 48 insertions(+), 66 deletions(-) diff --git a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs index 497f6a2..d0c62fa 100644 --- a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs +++ b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs @@ -2,6 +2,8 @@ using PropertyChanged; using FreshMvvm; using System; +using System.Windows.Input; +using System.Threading.Tasks; namespace FreshMvvmSampleApp { @@ -10,6 +12,12 @@ public class ContactPageModel : FreshBasePageModel { IDatabaseService _dataService; + public ICommand SaveCommand { get; private set; } + public ICommand TestModal { get; private set; } + public ICommand TestModalNavigationBasic { get; private set; } + public ICommand TestModalNavigationTabbed { get; private set; } + public ICommand TestModalNavigationMasterDetail { get; private set; } + public ContactPageModel (IDatabaseService dataService) { _dataService = dataService; @@ -31,62 +39,41 @@ public override void Init (object initData) } else { Contact = new Contact (); } - } - public Command SaveCommand { - get { - return new Command (() => { - _dataService.UpdateContact (Contact); - CoreMethods.PopPageModel (Contact); - } - ); - } + SaveCommand = CoreMethods.CreateCommand(SaveCommandLogic); + TestModal = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(null, true)); + TestModalNavigationBasic = CoreMethods.CreateCommand(TestModalNavigationBasicLogic); + TestModalNavigationTabbed = CoreMethods.CreateCommand(TestModalNavigationTabbedLogic); + TestModalNavigationMasterDetail = CoreMethods.CreateCommand(TestModalNavigationMasterDetailLogic); } - public Command TestModal { - get { - return new Command (async () => { - await CoreMethods.PushPageModel (null, true); - }); - } + private async Task SaveCommandLogic() + { + _dataService.UpdateContact(Contact); + await CoreMethods.PopPageModel(Contact); } - public Command TestModalNavigationBasic { - get { - return new Command (async () => { - - var page = FreshPageModelResolver.ResolvePageModel (); - var basicNavContainer = new FreshNavigationContainer (page, Guid.NewGuid ().ToString ()); - await CoreMethods.PushNewNavigationServiceModal(basicNavContainer, new FreshBasePageModel[] { page.GetModel() }); - }); - } + private async Task TestModalNavigationBasicLogic() + { + var page = FreshPageModelResolver.ResolvePageModel(); + var basicNavContainer = new FreshNavigationContainer(page, Guid.NewGuid().ToString()); + await CoreMethods.PushNewNavigationServiceModal(basicNavContainer, new FreshBasePageModel[] { page.GetModel() }); } - - public Command TestModalNavigationTabbed { - get { - return new Command (async () => { - - var tabbedNavigation = new FreshTabbedNavigationContainer (Guid.NewGuid ().ToString ()); - tabbedNavigation.AddTab ("Contacts", "contacts.png", null); - tabbedNavigation.AddTab ("Quotes", "document.png", null); - await CoreMethods.PushNewNavigationServiceModal(tabbedNavigation); - }); - } + public async Task TestModalNavigationTabbedLogic() { + var tabbedNavigation = new FreshTabbedNavigationContainer (Guid.NewGuid ().ToString ()); + tabbedNavigation.AddTab ("Contacts", "contacts.png", null); + tabbedNavigation.AddTab ("Quotes", "document.png", null); + await CoreMethods.PushNewNavigationServiceModal(tabbedNavigation); } - public Command TestModalNavigationMasterDetail { - get { - return new Command (async () => { - - var masterDetailNav = new FreshMasterDetailNavigationContainer (Guid.NewGuid ().ToString ()); - masterDetailNav.Init ("Menu", "Menu.png"); - masterDetailNav.AddPage ("Contacts", null); - masterDetailNav.AddPage ("Quotes", null); - await CoreMethods.PushNewNavigationServiceModal(masterDetailNav); - - }); - } + public async Task TestModalNavigationMasterDetailLogic() + { + var masterDetailNav = new FreshMasterDetailNavigationContainer(Guid.NewGuid().ToString()); + masterDetailNav.Init("Menu", "Menu.png"); + masterDetailNav.AddPage("Contacts", null); + masterDetailNav.AddPage("Quotes", null); + await CoreMethods.PushNewNavigationServiceModal(masterDetailNav); } } } diff --git a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs index 814a9e0..a913289 100644 --- a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs +++ b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs @@ -1,6 +1,8 @@ using Xamarin.Forms; using PropertyChanged; using FreshMvvm; +using System.Windows.Input; +using System.Threading.Tasks; namespace FreshMvvmSampleApp { @@ -9,6 +11,9 @@ public class QuotePageModel : FreshBasePageModel { IDatabaseService _databaseService; + public ICommand SaveCommand { get; private set; } + public ICommand TestModal { get; private set; } + public Quote Quote { get; set; } public QuotePageModel (IDatabaseService databaseService) @@ -17,27 +22,17 @@ public QuotePageModel (IDatabaseService databaseService) } public override void Init (object initData) - { - Quote = initData as Quote; - if (Quote == null) - Quote = new Quote (); - } - - public Command SaveCommand { - get { - return new Command (async () => { - _databaseService.UpdateQuote (Quote); - await CoreMethods.PopPageModel (Quote); - }); - } + { + Quote = (initData as Quote) ?? new Quote(); + + SaveCommand = CoreMethods.CreateCommand(SaveCommandLogic); + TestModal = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(null, true)); } - public Command TestModal { - get { - return new Command (async () => { - await CoreMethods.PushPageModel (null, true); - }); - } + private async Task SaveCommandLogic() + { + _databaseService.UpdateQuote (Quote); + await CoreMethods.PopPageModel (Quote); } } } From 656a14d2993fb9ba018d7732d709a3d16fb00e2d Mon Sep 17 00:00:00 2001 From: Oddbjorn Bakke Date: Mon, 29 Aug 2016 21:48:57 +0200 Subject: [PATCH 4/5] Added sharedlock in sample, to synchronise multiple commands (tap on A, will lock B for tapping until A is done). --- .../FreshMvvmSampleApp/PageModels/ContactPageModel.cs | 11 ++++++----- .../FreshMvvmSampleApp/PageModels/QuotePageModel.cs | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs index d0c62fa..369e88c 100644 --- a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs +++ b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/ContactPageModel.cs @@ -40,11 +40,12 @@ public override void Init (object initData) Contact = new Contact (); } - SaveCommand = CoreMethods.CreateCommand(SaveCommandLogic); - TestModal = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(null, true)); - TestModalNavigationBasic = CoreMethods.CreateCommand(TestModalNavigationBasicLogic); - TestModalNavigationTabbed = CoreMethods.CreateCommand(TestModalNavigationTabbedLogic); - TestModalNavigationMasterDetail = CoreMethods.CreateCommand(TestModalNavigationMasterDetailLogic); + var sharedLock = new SharedLock(); + SaveCommand = CoreMethods.CreateCommand(SaveCommandLogic, sharedLock); + TestModal = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(null, true), sharedLock); + TestModalNavigationBasic = CoreMethods.CreateCommand(TestModalNavigationBasicLogic, sharedLock); + TestModalNavigationTabbed = CoreMethods.CreateCommand(TestModalNavigationTabbedLogic, sharedLock); + TestModalNavigationMasterDetail = CoreMethods.CreateCommand(TestModalNavigationMasterDetailLogic, sharedLock); } private async Task SaveCommandLogic() diff --git a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs index a913289..32553e1 100644 --- a/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs +++ b/samples/FreshMvvmSampleApp/FreshMvvmSampleApp/PageModels/QuotePageModel.cs @@ -24,9 +24,10 @@ public QuotePageModel (IDatabaseService databaseService) public override void Init (object initData) { Quote = (initData as Quote) ?? new Quote(); - - SaveCommand = CoreMethods.CreateCommand(SaveCommandLogic); - TestModal = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(null, true)); + + var sharedLock = new SharedLock(); + SaveCommand = CoreMethods.CreateCommand(SaveCommandLogic, sharedLock); + TestModal = CoreMethods.CreateCommand(() => CoreMethods.PushPageModel(null, true), sharedLock); } private async Task SaveCommandLogic() From 99c8db9f4facb174aec8eca217531f1951b2aa97 Mon Sep 17 00:00:00 2001 From: Oddbjorn Bakke Date: Fri, 23 Sep 2016 14:25:46 +0200 Subject: [PATCH 5/5] Shared CanExecuted state between commands (for cases like global lock in buttons). --- src/FreshMvvm/FreshNavigationCommand.cs | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/FreshMvvm/FreshNavigationCommand.cs b/src/FreshMvvm/FreshNavigationCommand.cs index 0fcaaa7..2adb3f3 100644 --- a/src/FreshMvvm/FreshNavigationCommand.cs +++ b/src/FreshMvvm/FreshNavigationCommand.cs @@ -10,7 +10,11 @@ namespace FreshMvvm /// public class FreshNavigationCommand : IFreshNavigationCommand { - public event EventHandler CanExecuteChanged; + public event EventHandler CanExecuteChanged + { + add { _sharedLock.SharedCanExecuteChanged += value; } + remove { _sharedLock.SharedCanExecuteChanged -= value; } + } private readonly SharedLock _sharedLock; private readonly Func _execute; @@ -58,19 +62,13 @@ public async Task ExecuteAsync(object parameter) { if (_sharedLock.TakeLock()) //Ignores code block if lock already taken in SharedLock. { - var events = CanExecuteChanged; try { - if (events != null) - events(this, EventArgs.Empty); - await _execute(parameter); } finally { _sharedLock.ReleaseLock(); - if (events != null) - events(this, EventArgs.Empty); } } } @@ -81,7 +79,8 @@ public class FreshNavigationCommand : FreshNavigationCommand public FreshNavigationCommand(Func execute, SharedLock sharedLock = null) : base(obj => execute((TValue)obj), sharedLock) { - // + if (execute == null) + throw new ArgumentException(nameof(execute)); } } @@ -95,6 +94,8 @@ public class SharedLock public bool IsLocked => _lock != 0; + public event EventHandler SharedCanExecuteChanged; + public SharedLock() { _lock = 0; @@ -108,6 +109,11 @@ public bool TakeLock() { var oldVal = Interlocked.Exchange(ref _lock, 1); //Atomic swap values. var lockTaken = oldVal == 0; //If the old value was 0, no one else is executing at this time. + + var events = SharedCanExecuteChanged; + if (events != null) + events(this, EventArgs.Empty); + return lockTaken; } @@ -119,6 +125,11 @@ public bool ReleaseLock() { var oldVal = Interlocked.Exchange(ref _lock, 0); var lockReleased = oldVal == 1; + + var events = SharedCanExecuteChanged; + if (events != null) + events(this, EventArgs.Empty); + return lockReleased; } }