diff --git a/Nuclex.Windows.Forms (net-4.0).csproj b/Nuclex.Windows.Forms (net-4.0).csproj index b77720e..f77e696 100644 --- a/Nuclex.Windows.Forms (net-4.0).csproj +++ b/Nuclex.Windows.Forms (net-4.0).csproj @@ -55,6 +55,14 @@ + + + DialogViewModel.cs + + + + ThreadedDialogViewModel.cs + ThreadedViewModel.cs diff --git a/Source/ViewModels/DialogViewModel.Test.cs b/Source/ViewModels/DialogViewModel.Test.cs new file mode 100644 index 0000000..00b1ba1 --- /dev/null +++ b/Source/ViewModels/DialogViewModel.Test.cs @@ -0,0 +1,132 @@ +using System; + +using NUnit.Framework; + +namespace Nuclex.Windows.Forms.ViewModels { + + /// Unit test for the dialog view model + [TestFixture] + public class DialogViewModelTest { + + #region class DialogViewModelSubscriber + + /// Subscriber for the events offered by a dialog view model + private class DialogViewModelSubscriber { + + /// Indicates that the user has accepted the dialog + public void Confirmed(object sender, EventArgs arguments) { + ++this.confirmCallCount; + } + + /// Indicates that the user has cancelled the dialog + public void Cancelled(object sender, EventArgs arguments) { + ++this.cancelCallCount; + } + + /// Indicates that the dialog was simply closed + public void Submitted(object sender, EventArgs arguments) { + ++this.submitCallCount; + } + + /// How many times the Confirmed() method was called + public int ConfirmCallCount { + get { return this.confirmCallCount; } + } + + /// How many times the Cancelled() method was called + public int CancelCallCount { + get { return this.cancelCallCount; } + } + + /// How many times the Submitted() method was called + public int SubmitCallCount { + get { return this.submitCallCount; } + } + + /// How many times the Confirmed() method was called + private int confirmCallCount; + /// How many times the Cancelled() method was called + private int cancelCallCount; + /// How many times the Submitted() method was called + private int submitCallCount; + + } + + #endregion // class DialogViewModelSubscriber + + /// Verifies that the dialog view model has a default constructor + [Test] + public void HasDefaultConstructor() { + Assert.DoesNotThrow( + delegate() { new DialogViewModel(); } + ); + } + + /// + /// Verifies that calling Confirm() on the dialog view model triggers + /// the 'Confirmed' event + /// + [Test] + public void ConfirmTriggersConfirmedEvent() { + var viewModel = new DialogViewModel(); + var subscriber = createSubscriber(viewModel); + + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + viewModel.Confirm(); + Assert.AreEqual(1, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + } + + /// + /// Verifies that calling Cancel() on the dialog view model triggers + /// the 'Cancelled' event + /// + [Test] + public void CancelTriggersCancelledEvent() { + var viewModel = new DialogViewModel(); + var subscriber = createSubscriber(viewModel); + + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + viewModel.Cancel(); + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(1, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + } + + /// + /// Verifies that calling Submitm() on the dialog view model triggers + /// the 'Submitted' event + /// + [Test] + public void SubmitTriggersSubmittedEvent() { + var viewModel = new DialogViewModel(); + var subscriber = createSubscriber(viewModel); + + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + viewModel.Submit(); + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(1, subscriber.SubmitCallCount); + } + + /// Constructs a new subscriber for the dialog view model's events + /// View model a subscriber will be created for + /// A subscriber for the events of the specified view model + private DialogViewModelSubscriber createSubscriber(DialogViewModel viewModel) { + var subscriber = new DialogViewModelSubscriber(); + viewModel.Confirmed += subscriber.Confirmed; + viewModel.Canceled += subscriber.Cancelled; + viewModel.Submitted += subscriber.Submitted; + return subscriber; + } + + } + +} // namespace Nuclex.Windows.Forms.ViewModels diff --git a/Source/ViewModels/DialogViewModel.cs b/Source/ViewModels/DialogViewModel.cs new file mode 100644 index 0000000..2cecb00 --- /dev/null +++ b/Source/ViewModels/DialogViewModel.cs @@ -0,0 +1,76 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2019 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; + +using Nuclex.Support; + +namespace Nuclex.Windows.Forms.ViewModels { + + /// Base class for the view model of dialogs (typically modal ones) + public class DialogViewModel : Observable { + + /// Indicates that the view should close with a positive result + /// + /// This event typically corresponds to the 'Ok' button in a dialog. + /// + public event EventHandler Confirmed; + + /// Indicates that the view should close with a negative result + /// + /// This event typically corresponds to the 'Cancel' button in a dialog. + /// + public event EventHandler Canceled; + + /// Indicates that the view should close + /// + /// This closes the view with a neutral result, used when the view doesn't follow + /// an ok/cancel scheme or the result is transmitted in some other way. + /// + public event EventHandler Submitted; + + /// + /// Indicates that the dialog should be closed with a positive outcome + /// + public virtual void Confirm() { + if(Confirmed != null) { + Confirmed(this, EventArgs.Empty); + } + } + + /// + /// Indicates that the dialog should be closed with a negative outcome + /// + public virtual void Cancel() { + if(Canceled != null) { + Canceled(this, EventArgs.Empty); + } + } + + /// Indicates that the dialog should be closed + public virtual void Submit() { + if(Submitted != null) { + Submitted(this, EventArgs.Empty); + } + } + + } + +} // namespace Nuclex.Windows.Forms.ViewModels diff --git a/Source/ViewModels/ThreadedDialogViewModel.Test.cs b/Source/ViewModels/ThreadedDialogViewModel.Test.cs new file mode 100644 index 0000000..ae9eda2 --- /dev/null +++ b/Source/ViewModels/ThreadedDialogViewModel.Test.cs @@ -0,0 +1,150 @@ +using System; + +using NUnit.Framework; + +namespace Nuclex.Windows.Forms.ViewModels { + + /// Unit test for the threaded dialog view model + [TestFixture] + public class ThreadedDialogViewModelTest { + + #region class DialogViewModelSubscriber + + /// Subscriber for the events offered by a dialog view model + private class DialogViewModelSubscriber { + + /// Indicates that the user has accepted the dialog + public void Confirmed(object sender, EventArgs arguments) { + ++this.confirmCallCount; + } + + /// Indicates that the user has cancelled the dialog + public void Cancelled(object sender, EventArgs arguments) { + ++this.cancelCallCount; + } + + /// Indicates that the dialog was simply closed + public void Submitted(object sender, EventArgs arguments) { + ++this.submitCallCount; + } + + /// How many times the Confirmed() method was called + public int ConfirmCallCount { + get { return this.confirmCallCount; } + } + + /// How many times the Cancelled() method was called + public int CancelCallCount { + get { return this.cancelCallCount; } + } + + /// How many times the Submitted() method was called + public int SubmitCallCount { + get { return this.submitCallCount; } + } + + /// How many times the Confirmed() method was called + private int confirmCallCount; + /// How many times the Cancelled() method was called + private int cancelCallCount; + /// How many times the Submitted() method was called + private int submitCallCount; + + } + + #endregion // class DialogViewModelSubscriber + + #region class TestViewModel + + private class TestViewModel : ThreadedDialogViewModel { + + public Exception ReportedError { + get { return this.reportedError; } + } + + protected override void ReportError(Exception exception) { + this.reportedError = exception; + } + + private Exception reportedError; + + } + + #endregion // class TestViewModel + + /// Verifies that the dialog view model has a default constructor + [Test] + public void HasDefaultConstructor() { + Assert.DoesNotThrow( + delegate() { new TestViewModel(); } + ); + } + + /// + /// Verifies that calling Confirm() on the dialog view model triggers + /// the 'Confirmed' event + /// + [Test] + public void ConfirmTriggersConfirmedEvent() { + var viewModel = new TestViewModel(); + var subscriber = createSubscriber(viewModel); + + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + viewModel.Confirm(); + Assert.AreEqual(1, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + } + + /// + /// Verifies that calling Cancel() on the dialog view model triggers + /// the 'Cancelled' event + /// + [Test] + public void CancelTriggersCancelledEvent() { + var viewModel = new TestViewModel(); + var subscriber = createSubscriber(viewModel); + + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + viewModel.Cancel(); + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(1, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + } + + /// + /// Verifies that calling Submitm() on the dialog view model triggers + /// the 'Submitted' event + /// + [Test] + public void SubmitTriggersSubmittedEvent() { + var viewModel = new TestViewModel(); + var subscriber = createSubscriber(viewModel); + + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(0, subscriber.SubmitCallCount); + viewModel.Submit(); + Assert.AreEqual(0, subscriber.ConfirmCallCount); + Assert.AreEqual(0, subscriber.CancelCallCount); + Assert.AreEqual(1, subscriber.SubmitCallCount); + } + + /// Constructs a new subscriber for the dialog view model's events + /// View model a subscriber will be created for + /// A subscriber for the events of the specified view model + private DialogViewModelSubscriber createSubscriber(ThreadedDialogViewModel viewModel) { + var subscriber = new DialogViewModelSubscriber(); + viewModel.Confirmed += subscriber.Confirmed; + viewModel.Canceled += subscriber.Cancelled; + viewModel.Submitted += subscriber.Submitted; + return subscriber; + } + + } + +} // namespace Nuclex.Windows.Forms.ViewModels diff --git a/Source/ViewModels/ThreadedDialogViewModel.cs b/Source/ViewModels/ThreadedDialogViewModel.cs new file mode 100644 index 0000000..f0d8364 --- /dev/null +++ b/Source/ViewModels/ThreadedDialogViewModel.cs @@ -0,0 +1,74 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2019 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; + +namespace Nuclex.Windows.Forms.ViewModels { + + /// View model for a dialog that can execute tasks in a background thread + public abstract class ThreadedDialogViewModel : ThreadedViewModel { + + /// Indicates that the view should close with a positive result + /// + /// This event typically corresponds to the 'Ok' button in a dialog. + /// + public event EventHandler Confirmed; + + /// Indicates that the view should close with a negative result + /// + /// This event typically corresponds to the 'Cancel' button in a dialog. + /// + public event EventHandler Canceled; + + /// Indicates that the view should close + /// + /// This closes the view with a neutral result, used when the view doesn't follow + /// an ok/cancel scheme or the result is transmitted in some other way. + /// + public event EventHandler Submitted; + + /// + /// Indicates that the dialog should be closed with a positive outcome + /// + public virtual void Confirm() { + if(Confirmed != null) { + Confirmed(this, EventArgs.Empty); + } + } + + /// + /// Indicates that the dialog should be closed with a negative outcome + /// + public virtual void Cancel() { + if(Canceled != null) { + Canceled(this, EventArgs.Empty); + } + } + + /// Indicates that the dialog should be closed + public virtual void Submit() { + if(Submitted != null) { + Submitted(this, EventArgs.Empty); + } + } + + } + +} // namespace Nuclex.Windows.Forms.ViewModels