зеркало из https://github.com/github/VisualStudio.git
TwoFactorInput now handles paste event
This commit is contained in:
Родитель
e8a88ecfec
Коммит
d74db93d66
|
@ -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>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче