TwoFactorInput now handles paste event

This commit is contained in:
Haacked 2015-03-01 00:05:42 -08:00
Родитель e8a88ecfec
Коммит d74db93d66
4 изменённых файлов: 176 добавлений и 17 удалений

Просмотреть файл

@ -4,7 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DesignTimeStyleHelper" xmlns:local="clr-namespace:DesignTimeStyleHelper"
xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"> Title="MainWindow" Height="350" Width="525">
@ -31,5 +31,9 @@
<StackPanel x:Name="container"> <StackPanel x:Name="container">
</StackPanel> </StackPanel>
<Border Margin="20">
<ui:TwoFactorInput />
</Border>
</StackPanel> </StackPanel>
</Window> </Window>

Просмотреть файл

@ -1,7 +1,9 @@
using System; using System;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using NullGuard;
namespace GitHub.UI namespace GitHub.UI
{ {
@ -13,26 +15,68 @@ namespace GitHub.UI
public static readonly DependencyProperty TextProperty = public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TwoFactorInput), new PropertyMetadata("")); DependencyProperty.Register("Text", typeof(string), typeof(TwoFactorInput), new PropertyMetadata(""));
TextBox[] TextBoxes;
public TwoFactorInput() public TwoFactorInput()
{ {
InitializeComponent(); InitializeComponent();
SetupTextBox(one); TextBoxes = new[]
SetupTextBox(two); {
SetupTextBox(three); one,
SetupTextBox(four); two,
SetupTextBox(five); three,
SetupTextBox(six); four,
five,
six
};
foreach(var textBox in TextBoxes)
{
SetupTextBox(textBox);
}
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
var isText = e.SourceDataObject.GetDataPresent(DataFormats.Text, true);
if (!isText) return;
var text = e.SourceDataObject.GetData(DataFormats.Text) as string;
if (text == null) return;
e.CancelCommand();
SetText(text);
}
void SetText(string text)
{
if (String.IsNullOrEmpty(text))
{
foreach (var textBox in TextBoxes)
{
textBox.Text = "";
}
SetValue(TextProperty, text);
return;
}
var digits = text.Where(Char.IsDigit).ToList();
for (int i = 0; i < Math.Min(6, digits.Count); i++)
{
TextBoxes[i].Text = digits[i].ToString();
}
SetValue(TextProperty, String.Join("", digits));
} }
public string Text public string Text
{ {
get { return (string)GetValue(TextProperty); } get { return (string)GetValue(TextProperty); }
private set { SetValue(TextProperty, value); } set { SetText(value); }
} }
private void SetupTextBox(TextBox textBox) private void SetupTextBox(TextBox textBox)
{ {
DataObject.AddPastingHandler(textBox, new DataObjectPastingEventHandler(OnPaste));
textBox.GotFocus += (sender, args) => textBox.SelectAll(); textBox.GotFocus += (sender, args) => textBox.SelectAll();
textBox.PreviewKeyDown += (sender, args) => textBox.PreviewKeyDown += (sender, args) =>
@ -48,18 +92,30 @@ namespace GitHub.UI
&& args.Key != Key.D8 && args.Key != Key.D8
&& args.Key != Key.D9 && args.Key != Key.D9
&& args.Key != Key.Tab && args.Key != Key.Tab
&& args.Key != Key.Escape) && args.Key != Key.Escape
&& (!(args.Key == Key.V && args.KeyboardDevice.Modifiers == ModifierKeys.Control))
&& (!(args.Key == Key.Insert && args.KeyboardDevice.Modifiers == ModifierKeys.Shift)))
{ {
args.Handled = true; args.Handled = true;
} }
}; };
textBox.SelectionChanged += (sender, args) =>
{
// Make sure we can't insert additional text into a textbox.
// Each textbox should only allow one character.
if (textBox.SelectionLength == 0 && textBox.Text.Any())
{
textBox.SelectAll();
}
};
textBox.TextChanged += (sender, args) => textBox.TextChanged += (sender, args) =>
{ {
var tRequest = new TraversalRequest(FocusNavigationDirection.Next); var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
var keyboardFocus = Keyboard.FocusedElement as UIElement; var keyboardFocus = Keyboard.FocusedElement as UIElement;
Text = GetTwoFactorCode(); SetValue(TextProperty, String.Join("", GetTwoFactorCode()));
if (keyboardFocus != null) if (keyboardFocus != null)
{ {
@ -68,19 +124,14 @@ namespace GitHub.UI
}; };
} }
private string GetTextBoxValue(TextBox textBox) private static string GetTextBoxValue(TextBox textBox)
{ {
return String.IsNullOrEmpty(textBox.Text) ? " " : textBox.Text; return String.IsNullOrEmpty(textBox.Text) ? " " : textBox.Text;
} }
private string GetTwoFactorCode() private string GetTwoFactorCode()
{ {
return GetTextBoxValue(one) return String.Join("", TextBoxes.Select(textBox => textBox.Text));
+ GetTextBoxValue(two)
+ GetTextBoxValue(three)
+ GetTextBoxValue(four)
+ GetTextBoxValue(five)
+ GetTextBoxValue(six);
} }
} }
} }

Просмотреть файл

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using GitHub.UI;
using Xunit;
using Xunit.Extensions;
public class TwoFactorInputTests
{
public class TheTextProperty
{
[Fact]
public void SetsTextBoxesToIndividualCharacters()
{
var twoFactorInput = new TwoFactorInput();
var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList();
twoFactorInput.Text = "012345";
Assert.Equal("012345", twoFactorInput.Text);
Assert.Equal("0", textBoxes[0].Text);
Assert.Equal("1", textBoxes[1].Text);
Assert.Equal("2", textBoxes[2].Text);
Assert.Equal("3", textBoxes[3].Text);
Assert.Equal("4", textBoxes[4].Text);
Assert.Equal("5", textBoxes[5].Text);
}
[Fact]
public void IgnoresNonDigitCharacters()
{
var twoFactorInput = new TwoFactorInput();
var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList();
twoFactorInput.Text = "01xyz2345";
Assert.Equal("012345", twoFactorInput.Text);
Assert.Equal("0", textBoxes[0].Text);
Assert.Equal("1", textBoxes[1].Text);
Assert.Equal("2", textBoxes[2].Text);
Assert.Equal("3", textBoxes[3].Text);
Assert.Equal("4", textBoxes[4].Text);
Assert.Equal("5", textBoxes[5].Text);
}
[Fact]
public void HandlesNotEnoughCharacters()
{
var twoFactorInput = new TwoFactorInput();
var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList();
twoFactorInput.Text = "012";
Assert.Equal("012", twoFactorInput.Text);
Assert.Equal("0", textBoxes[0].Text);
Assert.Equal("1", textBoxes[1].Text);
Assert.Equal("2", textBoxes[2].Text);
Assert.Equal("", textBoxes[3].Text);
Assert.Equal("", textBoxes[4].Text);
Assert.Equal("", textBoxes[5].Text);
}
[Theory]
[InlineData(null, null)]
[InlineData("", "")]
[InlineData("xxxx", "")]
public void HandlesNullAndStringsWithNoDigits(string input, string expected)
{
var twoFactorInput = new TwoFactorInput();
var textBoxes = GetChildrenRecursive(twoFactorInput).OfType<TextBox>().ToList();
twoFactorInput.Text = input;
Assert.Equal(expected, twoFactorInput.Text);
Assert.Equal("", textBoxes[0].Text);
Assert.Equal("", textBoxes[1].Text);
Assert.Equal("", textBoxes[2].Text);
Assert.Equal("", textBoxes[3].Text);
Assert.Equal("", textBoxes[4].Text);
Assert.Equal("", textBoxes[5].Text);
}
static IEnumerable<FrameworkElement> GetChildrenRecursive(FrameworkElement element)
{
yield return element;
foreach (var child in LogicalTreeHelper.GetChildren(element)
.Cast<FrameworkElement>()
.SelectMany(GetChildrenRecursive))
{
yield return child;
}
}
}
}

Просмотреть файл

@ -58,6 +58,8 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\NSubstitute.1.8.1.0\lib\net45\NSubstitute.dll</HintPath> <HintPath>..\..\packages\NSubstitute.1.8.1.0\lib\net45\NSubstitute.dll</HintPath>
</Reference> </Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="ReactiveUI, Version=6.3.1.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="ReactiveUI, Version=6.3.1.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\reactiveui-core.6.3.1\lib\Net45\ReactiveUI.dll</HintPath> <HintPath>..\..\packages\reactiveui-core.6.3.1\lib\Net45\ReactiveUI.dll</HintPath>
@ -73,11 +75,13 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath> <HintPath>..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<Reference Include="xunit"> <Reference Include="xunit">
<HintPath>..\..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath> <HintPath>..\..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
</Reference> </Reference>
@ -88,6 +92,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Args.cs" /> <Compile Include="Args.cs" />
<Compile Include="GitHub.App\ViewModels\LoginControlViewModelTests.cs" /> <Compile Include="GitHub.App\ViewModels\LoginControlViewModelTests.cs" />
<Compile Include="GitHub.UI\TwoFactorInputTests.cs" />
<Compile Include="GitHubPackageTests.cs" /> <Compile Include="GitHubPackageTests.cs" />
<Compile Include="Helpers\LazySubstitute.cs" /> <Compile Include="Helpers\LazySubstitute.cs" />
<Compile Include="TestDoubles\FakeMenuCommandService.cs" /> <Compile Include="TestDoubles\FakeMenuCommandService.cs" />
@ -106,6 +111,10 @@
<Project>{158B05E8-FDBC-4D71-B871-C96E28D5ADF5}</Project> <Project>{158B05E8-FDBC-4D71-B871-C96E28D5ADF5}</Project>
<Name>GitHub.UI.Reactive</Name> <Name>GitHub.UI.Reactive</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\GitHub.UI\GitHub.UI.csproj">
<Project>{346384dd-2445-4a28-af22-b45f3957bd89}</Project>
<Name>GitHub.UI</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.VisualStudio\GitHub.VisualStudio.csproj"> <ProjectReference Include="..\GitHub.VisualStudio\GitHub.VisualStudio.csproj">
<Project>{11569514-5ae5-4b5b-92a2-f10b0967de5f}</Project> <Project>{11569514-5ae5-4b5b-92a2-f10b0967de5f}</Project>
<Name>GitHub.VisualStudio</Name> <Name>GitHub.VisualStudio</Name>