refactored the pagesequence class to be event-based. sequenced pages no longer need a reference to the sequence controller

This commit is contained in:
David Sparer
2017-02-11 10:49:19 -07:00
parent af6e2abdb2
commit aafa383f56
13 changed files with 315 additions and 105 deletions

View File

@@ -0,0 +1,59 @@
using System.Linq;
using System.Windows.Forms;
using mRemoteNG.UI.Controls.PageSequence;
using NSubstitute;
using NUnit.Framework;
namespace mRemoteNGTests.UI.Controls
{
public class PageSequenceTests
{
private PageSequence _pageSequence;
private Control _parentControl;
[SetUp]
public void Setup()
{
_parentControl = new Control();
}
[Test]
public void PageListAddedToSequence()
{
var pages = new[] {new SequencedControl(), new SequencedControl(), new SequencedControl()};
_pageSequence = new PageSequence(_parentControl, pages);
Assert.That(_pageSequence.Pages, Is.EquivalentTo(pages));
}
[Test]
public void PageParamsAddedToSequence()
{
_pageSequence = new PageSequence(_parentControl,
new SequencedControl(),
new SequencedControl(),
new SequencedControl()
);
Assert.That(_pageSequence.Pages.Count(), Is.EqualTo(3));
}
[Test]
public void CallingNextAdvancesPage()
{
var pages = new[] { new SequencedControl(), new SequencedControl(), new SequencedControl() };
_pageSequence = new PageSequence(_parentControl, pages);
_pageSequence.NextPage();
Assert.That(_pageSequence.CurrentPageIndex, Is.EqualTo(1));
}
[Test]
public void CallingPreviousGoesBackAPage()
{
var pages = new[] { new SequencedControl(), new SequencedControl(), new SequencedControl() };
_pageSequence = new PageSequence(_parentControl, pages);
_pageSequence.NextPage();
_pageSequence.NextPage();
_pageSequence.PreviousPage();
Assert.That(_pageSequence.CurrentPageIndex, Is.EqualTo(1));
}
}
}

View File

@@ -177,6 +177,7 @@
<Compile Include="Tree\RootNodeInfoTests.cs" />
<Compile Include="Tree\ClickHandlers\SwitchToConnectionClickHandlerTests.cs" />
<Compile Include="Tree\SelectedConnectionDeletionConfirmerTests.cs" />
<Compile Include="UI\Controls\PageSequenceTests.cs" />
<Compile Include="UI\Controls\SecureTextBoxTestForm.cs">
<SubType>Form</SubType>
</Compile>

View File

@@ -1,60 +0,0 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace mRemoteNG.UI.Controls
{
public class PageSequence
{
private readonly Control _pageContainer;
private int _currentPageIndex;
public IList<Control> Pages { get; set; }
public PageSequence(Control pageContainer) : this(pageContainer, new List<Control>())
{
}
public PageSequence(Control pageContainer, params Control[] pages) : this(pageContainer, new List<Control>())
{
foreach (var page in pages)
Pages.Add(page);
}
public PageSequence(Control pageContainer, IList<Control> pages)
{
if (pageContainer == null)
throw new ArgumentNullException(nameof(pageContainer));
if (pages == null)
throw new ArgumentNullException(nameof(pages));
_pageContainer = pageContainer;
Pages = pages;
}
public void Next()
{
if (_currentPageIndex == Pages.Count-1) return;
_currentPageIndex++;
ActivatePage(_currentPageIndex);
}
public void Previous()
{
if (_currentPageIndex == 0) return;
_currentPageIndex--;
ActivatePage(_currentPageIndex);
}
public void ReplaceNextPage(Control newPage)
{
Pages[_currentPageIndex + 1] = newPage;
}
private void ActivatePage(int sequenceNumber)
{
_pageContainer.Controls.Clear();
_pageContainer.Controls.Add(Pages[sequenceNumber]);
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace mRemoteNG.UI.Controls.PageSequence
{
public interface ISequenceChangingNotifier
{
event EventHandler Next;
event EventHandler Previous;
event SequencedPageReplcementRequestHandler PageReplacementRequested;
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace mRemoteNG.UI.Controls.PageSequence
{
public class PageSequence : IDisposable
{
private readonly Control _pageContainer;
private readonly IList<SequencedControl> _pages = new List<SequencedControl>();
public IEnumerable<SequencedControl> Pages => _pages;
public int CurrentPageIndex { get; private set; }
public PageSequence(Control pageContainer, IEnumerable<SequencedControl> pages) : this(pageContainer, pages.ToArray())
{
}
public PageSequence(Control pageContainer, params SequencedControl[] pages)
{
if (pageContainer == null)
throw new ArgumentNullException(nameof(pageContainer));
if (pages == null)
throw new ArgumentNullException(nameof(pages));
_pageContainer = pageContainer;
foreach (var page in pages)
{
SubscribeToPageEvents(page);
_pages.Add(page);
}
}
public virtual void NextPage()
{
CurrentPageIndex++;
ActivatePage(CurrentPageIndex);
if (CurrentPageIndex == _pages.Count - 1)
Dispose();
}
public virtual void PreviousPage()
{
CurrentPageIndex--;
ActivatePage(CurrentPageIndex);
if (CurrentPageIndex == 0)
Dispose();
}
public virtual void ReplacePage(SequencedControl newPage, RelativePagePosition pageToReplace)
{
var indexModifier = 0;
// ReSharper disable once SwitchStatementMissingSomeCases
switch (pageToReplace)
{
case RelativePagePosition.PreviousPage:
indexModifier--;
break;
case RelativePagePosition.NextPage:
indexModifier++;
break;
}
var pageIndexToReplace = CurrentPageIndex + indexModifier;
UnsubscribeFromPageEvents(_pages[pageIndexToReplace]);
SubscribeToPageEvents(newPage);
_pages[pageIndexToReplace] = newPage;
}
private void ActivatePage(int sequenceNumber)
{
_pageContainer.Controls.Clear();
_pageContainer.Controls.Add(_pages[sequenceNumber]);
}
private void SubscribeToPageEvents(ISequenceChangingNotifier page)
{
if (_pages.Contains(page)) return;
page.Next += PageOnNext;
page.Previous += PageOnPrevious;
page.PageReplacementRequested += PageOnPageReplacementRequested;
}
private void UnsubscribeFromPageEvents(ISequenceChangingNotifier page)
{
if (!_pages.Contains(page)) return;
page.Next -= PageOnNext;
page.Previous -= PageOnPrevious;
page.PageReplacementRequested -= PageOnPageReplacementRequested;
}
private void PageOnNext(object sender, EventArgs eventArgs)
{
NextPage();
}
private void PageOnPrevious(object sender, EventArgs eventArgs)
{
PreviousPage();
}
private void PageOnPageReplacementRequested(object sender, SequencedPageReplcementRequestArgs args)
{
ReplacePage(args.NewControl, args.PagePosition);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
foreach (var page in _pages)
{
UnsubscribeFromPageEvents(page);
}
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Windows.Forms;
namespace mRemoteNG.UI.Controls.PageSequence
{
public class SequencedControl : UserControl, ISequenceChangingNotifier
{
public event EventHandler Next;
public event EventHandler Previous;
public event SequencedPageReplcementRequestHandler PageReplacementRequested;
protected virtual void RaiseNextPageEvent()
{
Next?.Invoke(this, EventArgs.Empty);
}
protected virtual void RaisePreviousPageEvent()
{
Previous?.Invoke(this, EventArgs.Empty);
}
protected virtual void RaisePageReplacementEvent(SequencedControl control, RelativePagePosition pagetoReplace)
{
PageReplacementRequested?.Invoke(this, new SequencedPageReplcementRequestArgs(control, pagetoReplace));
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
namespace mRemoteNG.UI.Controls.PageSequence
{
public delegate void SequencedPageReplcementRequestHandler(object sender, SequencedPageReplcementRequestArgs args);
public enum RelativePagePosition
{
PreviousPage,
CurrentPage,
NextPage
}
public class SequencedPageReplcementRequestArgs
{
public SequencedControl NewControl { get; }
public RelativePagePosition PagePosition { get; }
public SequencedPageReplcementRequestArgs(SequencedControl newControl, RelativePagePosition pageToReplace)
{
if (newControl == null)
throw new ArgumentNullException(nameof(newControl));
NewControl = newControl;
PagePosition = pageToReplace;
}
}
}

View File

@@ -33,6 +33,7 @@
this.objectListView1 = new BrightIdeasSoftware.ObjectListView();
this.olvColumnProvider = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
this.olvColumnSource = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
this.buttonEdit = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.objectListView1)).BeginInit();
this.SuspendLayout();
//
@@ -41,7 +42,7 @@
this.buttonAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonAdd.Image = global::mRemoteNG.Resources.key_add;
this.buttonAdd.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.buttonAdd.Location = new System.Drawing.Point(210, 269);
this.buttonAdd.Location = new System.Drawing.Point(105, 269);
this.buttonAdd.Name = "buttonAdd";
this.buttonAdd.Size = new System.Drawing.Size(99, 32);
this.buttonAdd.TabIndex = 5;
@@ -106,10 +107,23 @@
this.olvColumnSource.Groupable = false;
this.olvColumnSource.Text = "Source";
//
// buttonEdit
//
this.buttonEdit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonEdit.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.buttonEdit.Location = new System.Drawing.Point(210, 269);
this.buttonEdit.Name = "buttonEdit";
this.buttonEdit.Size = new System.Drawing.Size(99, 32);
this.buttonEdit.TabIndex = 8;
this.buttonEdit.Text = "Edit";
this.buttonEdit.UseVisualStyleBackColor = true;
this.buttonEdit.Click += new System.EventHandler(this.buttonEdit_Click);
//
// CredentialRepositoriesPage
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.buttonEdit);
this.Controls.Add(this.objectListView1);
this.Controls.Add(this.buttonAdd);
this.Controls.Add(this.buttonRemove);
@@ -127,5 +141,6 @@
private BrightIdeasSoftware.ObjectListView objectListView1;
private BrightIdeasSoftware.OLVColumn olvColumnProvider;
private BrightIdeasSoftware.OLVColumn olvColumnSource;
private System.Windows.Forms.Button buttonEdit;
}
}

View File

@@ -1,16 +1,17 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.UI.Controls;
using mRemoteNG.UI.Controls.PageSequence;
using mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositoryEditorPages;
using mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositorySelectors;
namespace mRemoteNG.UI.Forms.CredentialManagerPages
{
public partial class CredentialRepositoriesPage : UserControl, ICredentialManagerPage
public partial class CredentialRepositoriesPage : SequencedControl, ICredentialManagerPage
{
private readonly ICredentialRepositoryList _providerCatalog;
@@ -49,9 +50,7 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages
private void buttonAdd_Click(object sender, EventArgs e)
{
var addRepoSequence = new PageSequence(Parent);
var pages = new List<Control>
{
var addRepoSequence = new PageSequence(Parent,
this,
new CredentialRepositorySelectionPage(
new ISelectionTarget<ICredentialRepositoryConfig>[]
@@ -59,14 +58,25 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages
new XmlCredentialRepositorySelector(),
new KeePassRepositorySelector()
},
_providerCatalog,
addRepoSequence
) {Dock = DockStyle.Fill},
new Control(),
_providerCatalog
)
{ Dock = DockStyle.Fill },
new SequencedControl(),
this
};
addRepoSequence.Pages = pages;
addRepoSequence.Next();
);
RaiseNextPageEvent();
}
private void buttonEdit_Click(object sender, EventArgs e)
{
var selectedRepository = objectListView1.SelectedObject as ICredentialRepository;
if (selectedRepository == null) return;
var nextPage = CredentialRepositoryPageEditorFactory.BuildXmlCredentialRepositoryEditorPage(selectedRepository.Config, _providerCatalog);
var pageSequence = new PageSequence(Parent,
this,
nextPage,
this
);
}
private void buttonRemove_Click(object sender, EventArgs e)
@@ -77,4 +87,4 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages
_providerCatalog.RemoveProvider(selectedRepository);
}
}
}
}

View File

@@ -1,16 +1,14 @@
using System.Collections.Generic;
using System.Windows.Forms;
using mRemoteNG.Credential;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.UI.Controls;
using mRemoteNG.UI.Controls.PageSequence;
namespace mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositoryEditorPages
{
public class CredentialRepositoryPageEditorFactory
{
public static Control BuildXmlCredentialRepositoryEditorPage<T>(T config, ICredentialRepositoryList repositoryList, PageSequence pageSequence) where T : ICredentialRepositoryConfig
public static SequencedControl BuildXmlCredentialRepositoryEditorPage<T>(T config, ICredentialRepositoryList repositoryList) where T : ICredentialRepositoryConfig
{
return new XmlCredentialRepositoryEditorPage(config, repositoryList, pageSequence);
return new XmlCredentialRepositoryEditorPage(config, repositoryList);
}
}
}

View File

@@ -5,17 +5,16 @@ using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.UI.Controls;
using mRemoteNG.UI.Controls.PageSequence;
namespace mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositoryEditorPages
{
public partial class XmlCredentialRepositoryEditorPage : UserControl
public partial class XmlCredentialRepositoryEditorPage : SequencedControl
{
private readonly ICredentialRepositoryConfig _repositoryConfig;
private readonly ICredentialRepositoryList _repositoryList;
private readonly PageSequence _pageSequence;
public XmlCredentialRepositoryEditorPage(ICredentialRepositoryConfig repositoryConfig, ICredentialRepositoryList repositoryList, PageSequence pageSequence)
public XmlCredentialRepositoryEditorPage(ICredentialRepositoryConfig repositoryConfig, ICredentialRepositoryList repositoryList)
{
if (repositoryConfig == null)
throw new ArgumentNullException(nameof(repositoryConfig));
@@ -24,7 +23,6 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositoryEditorPa
_repositoryConfig = repositoryConfig;
_repositoryList = repositoryList;
_pageSequence = pageSequence;
InitializeComponent();
PopulateFields();
textBoxFilePath.TextChanged += SaveValuesToConfig;
@@ -62,7 +60,7 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositoryEditorPa
var repository = new XmlCredentialRepository(_repositoryConfig, dataProvider, deserializer);
if (!_repositoryList.Contains(repository))
_repositoryList.AddProvider(repository);
_pageSequence.Next();
RaiseNextPageEvent();
}
private bool AllRequiredFieldsFilledOut()
@@ -72,7 +70,7 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositoryEditorPa
private void buttonBack_Click(object sender, EventArgs e)
{
_pageSequence.Previous();
RaisePreviousPageEvent();
}
}
}

View File

@@ -5,25 +5,22 @@ using BrightIdeasSoftware;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.UI.Controls;
using mRemoteNG.UI.Controls.PageSequence;
using mRemoteNG.UI.Forms.CredentialManagerPages.CredentialRepositoryEditorPages;
namespace mRemoteNG.UI.Forms.CredentialManagerPages
{
public partial class CredentialRepositorySelectionPage : UserControl
public partial class CredentialRepositorySelectionPage : SequencedControl
{
private readonly PageSequence _pageSequence;
private readonly ICredentialRepositoryList _repositoryList;
public CredentialRepositorySelectionPage(IEnumerable<ISelectionTarget<ICredentialRepositoryConfig>> selectionTargets, ICredentialRepositoryList repositoryList, PageSequence pageSequence)
public CredentialRepositorySelectionPage(IEnumerable<ISelectionTarget<ICredentialRepositoryConfig>> selectionTargets, ICredentialRepositoryList repositoryList)
{
if (selectionTargets == null)
throw new ArgumentNullException(nameof(selectionTargets));
if (pageSequence == null)
throw new ArgumentNullException(nameof(pageSequence));
if (repositoryList == null)
throw new ArgumentNullException(nameof(repositoryList));
_pageSequence = pageSequence;
_repositoryList = repositoryList;
InitializeComponent();
SetupListView(selectionTargets);
@@ -46,13 +43,6 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages
return imgHash;
}
private void BuildNextPage(ISelectionTarget<ICredentialRepositoryConfig> selection)
{
var editorPage = CredentialRepositoryPageEditorFactory.BuildXmlCredentialRepositoryEditorPage(selection.Config, _repositoryList, _pageSequence);
editorPage.Dock = DockStyle.Fill;
_pageSequence.ReplaceNextPage(editorPage);
}
private void ObjectListViewOnMouseDoubleClick(object sender, MouseEventArgs mouseEventArgs)
{
if (mouseEventArgs.Clicks < 2) return;
@@ -60,20 +50,28 @@ namespace mRemoteNG.UI.Forms.CredentialManagerPages
var listItem = objectListView.GetItemAt(mouseEventArgs.X, mouseEventArgs.Y, out column);
var clickedNode = listItem.RowObject as ISelectionTarget<ICredentialRepositoryConfig>;
if (clickedNode == null) return;
BuildNextPage(clickedNode);
BuildEditorPage(clickedNode);
}
private void buttonContinue_Click(object sender, EventArgs e)
{
var selection = objectListView.SelectedObject as ISelectionTarget<ICredentialRepositoryConfig>;
if (selection == null) return;
BuildNextPage(selection);
_pageSequence.Next();
var editorPage = BuildEditorPage(selection);
RaisePageReplacementEvent(editorPage, RelativePagePosition.NextPage);
RaiseNextPageEvent();
}
private SequencedControl BuildEditorPage(ISelectionTarget<ICredentialRepositoryConfig> selection)
{
var editorPage = CredentialRepositoryPageEditorFactory.BuildXmlCredentialRepositoryEditorPage(selection.Config, _repositoryList);
editorPage.Dock = DockStyle.Fill;
return editorPage;
}
private void buttonBack_Click(object sender, EventArgs e)
{
_pageSequence.Previous();
RaisePreviousPageEvent();
}
}
}

View File

@@ -316,6 +316,7 @@
<Compile Include="UI\Controls\ConnectionTree\NameColumn.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="UI\Controls\PageSequence\ISequenceChangingNotifier.cs" />
<Compile Include="UI\Controls\NewPasswordWithVerification.cs">
<SubType>UserControl</SubType>
</Compile>
@@ -323,7 +324,11 @@
<DependentUpon>NewPasswordWithVerification.cs</DependentUpon>
</Compile>
<Compile Include="UI\Controls\NotificationMessageListViewItem.cs" />
<Compile Include="UI\Controls\PageSequence.cs" />
<Compile Include="UI\Controls\PageSequence\PageSequence.cs" />
<Compile Include="UI\Controls\PageSequence\SequencedControl.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\Controls\PageSequence\SequencedPageReplcementRequestArgs.cs" />
<Compile Include="UI\Controls\QuickConnectComboBox.cs">
<SubType>Component</SubType>
</Compile>