From a51aa6958b81ac165efa11cab8ee433fed44bacb Mon Sep 17 00:00:00 2001 From: lanyi Date: Sun, 22 Mar 2020 19:56:16 +0100 Subject: [PATCH] Custom input bar --- FileSystemSuggestions.cs | 184 +++++++++++++++++++++++++++++++++++ HashCalculator.GUI.csproj | 15 ++- InputBar.xaml | 115 ++++++++++++++++++++++ InputBar.xaml.cs | 150 ++++++++++++++++++++++++++++ InputEntry.cs | 39 ++++++++ MainWindow.xaml | 45 ++++----- MainWindow.xaml.cs | 122 ++++++++--------------- NullToVisibilityConverter.cs | 22 +++++ ShellLink.cs | 2 + TechnologyAssembler.Core.dll | Bin 0 -> 303616 bytes 10 files changed, 586 insertions(+), 108 deletions(-) create mode 100644 FileSystemSuggestions.cs create mode 100644 InputBar.xaml create mode 100644 InputBar.xaml.cs create mode 100644 InputEntry.cs create mode 100644 NullToVisibilityConverter.cs create mode 100644 TechnologyAssembler.Core.dll diff --git a/FileSystemSuggestions.cs b/FileSystemSuggestions.cs new file mode 100644 index 0000000..3d504ea --- /dev/null +++ b/FileSystemSuggestions.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Diagnostics.CodeAnalysis; + +namespace HashCalculator.GUI +{ + internal class FileSystemSuggestions + { + private static readonly Dictionary Mapping = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { ".big", InputEntryType.BigFile }, + { ".xml", InputEntryType.XmlFile }, + { ".w3x", InputEntryType.XmlFile }, + { ".manifest", InputEntryType.ManifestFile }, + }; + + private Search _search = new Search(); + + [SuppressMessage("Microsoft.Performance", "CA1031")] + public IEnumerable ProvideFileSystemSuggestions(string? path) + { + var empty = Enumerable.Empty(); + + if (string.IsNullOrWhiteSpace(path) || path == null) + { + return empty; + } + + path = Environment.ExpandEnvironmentVariables(path); + + _search.Update(path); + + if (!_search.IsValidPath) + { + return empty; + } + + var currentFiles = empty; + string? fileName; + string? currentFullPath; + try + { + currentFullPath = Path.GetFullPath(path); + fileName = Path.GetFileName(path); + if (File.Exists(currentFullPath)) + { + + var type = CheckExtension(currentFullPath); + if (type.HasValue) + { + currentFiles.Append(new InputEntry(type.Value, path, currentFullPath)); + } + currentFiles.Append(new InputEntry(InputEntryType.BinaryFile, path, currentFullPath)); + } + } + catch + { + return empty; + } + + var directories = from directory in _search.AllDirectories + where directory.Name.StartsWith(fileName, StringComparison.OrdinalIgnoreCase) + select new InputEntry(InputEntryType.Path, _search.GetInputStyleName(directory), directory.FullName); + + var otherFiles = from file in _search.AllFiles + where file.Name.StartsWith(fileName, StringComparison.OrdinalIgnoreCase) + where file.FullName != currentFullPath + select file; + + var supportedFiles = empty; + try + { + supportedFiles = from file in otherFiles + let type = CheckExtension(file.Extension) + where type.HasValue + select new InputEntry(type.Value, _search.GetInputStyleName(file), file.FullName); + } + catch { } + + var binaryFiles = empty; + try + { + binaryFiles = from file in otherFiles + select new InputEntry(InputEntryType.BinaryFile, _search.GetInputStyleName(file), file.FullName); + } + catch { } + + return currentFiles.Concat(supportedFiles).Concat(directories).Concat(binaryFiles); + } + + private static InputEntryType? CheckExtension(string path) + { + if (Mapping.TryGetValue(path, out var type)) + { + return type; + } + + return null; + } + } + + internal sealed class Search + { + private string? _inputBaseDirectory; + public bool IsValidPath => _inputBaseDirectory != null; + public IEnumerable AllDirectories { get; private set; } + public IEnumerable AllFiles { get; private set; } + + public Search() + { + AllDirectories = Array.Empty(); + AllFiles = Array.Empty(); + } + + public string GetInputStyleName(FileSystemInfo entry) + { + if (_inputBaseDirectory == null) + { + throw new InvalidOperationException(); + } + + return Path.Combine(_inputBaseDirectory, entry.Name); + } + + [SuppressMessage("Microsoft.Performance", "CA1031")] + public void Update(string path) + { + string? newBaseDirectory = null; + try + { + newBaseDirectory = Path.GetDirectoryName(path); + var rootDirectory = Path.GetPathRoot(path); + if(string.IsNullOrEmpty(newBaseDirectory) && !string.IsNullOrEmpty(rootDirectory)) + { + var last = rootDirectory.LastOrDefault(); + if (last != Path.DirectorySeparatorChar && last != Path.AltDirectorySeparatorChar) + { + rootDirectory += Path.DirectorySeparatorChar; + } + newBaseDirectory = rootDirectory; + } + } + catch + { + _inputBaseDirectory = null; + } + + if (newBaseDirectory == null) + { + return; + } + + if (newBaseDirectory == _inputBaseDirectory) + { + return; + } + + _inputBaseDirectory = newBaseDirectory; + Update(); + } + + [SuppressMessage("Microsoft.Performance", "CA1031")] + private void Update() + { + AllDirectories = Enumerable.Empty(); + AllFiles = Enumerable.Empty(); + try + { + var actualPath = _inputBaseDirectory!.Length == 0 + ? "." + : _inputBaseDirectory; + var directory = new DirectoryInfo(actualPath); + if (directory.Exists) + { + AllDirectories = directory.EnumerateDirectories(); + AllFiles = directory.EnumerateFiles(); + } + } + catch { } + } + } +} diff --git a/HashCalculator.GUI.csproj b/HashCalculator.GUI.csproj index 246463b..65353e7 100644 --- a/HashCalculator.GUI.csproj +++ b/HashCalculator.GUI.csproj @@ -2,14 +2,25 @@ WinExe - net461 + net472 HashCalculator.GUI 8.0 enable true + AnyCPU;x86 - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + TechnologyAssembler.Core.dll + true + \ No newline at end of file diff --git a/InputBar.xaml b/InputBar.xaml new file mode 100644 index 0000000..b1aadcd --- /dev/null +++ b/InputBar.xaml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InputBar.xaml.cs b/InputBar.xaml.cs new file mode 100644 index 0000000..9486a26 --- /dev/null +++ b/InputBar.xaml.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Diagnostics; + +namespace HashCalculator.GUI +{ + /// + /// InputBar.xaml 的交互逻辑 + /// + [SuppressMessage("Microsoft.Performance", "CA1812")] + internal partial class InputBar : UserControl + { + public static readonly DependencyProperty HintTextProperty = + DependencyProperty.Register(nameof(HintText), typeof(string), typeof(InputBar), new FrameworkPropertyMetadata + { + BindsTwoWayByDefault = true, + DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }); + public string? HintText + { + get => GetValue(HintTextProperty) as string; + set => SetValue(HintTextProperty, value); + } + + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(InputBar), new FrameworkPropertyMetadata + { + BindsTwoWayByDefault = true, + DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }); + public string? Text + { + get => GetValue(TextProperty) as string; + set => SetValue(TextProperty, value); + } + + public static readonly DependencyProperty CollectionProperty = + DependencyProperty.Register(nameof(Collection), typeof(IEnumerable), typeof(InputBar), new FrameworkPropertyMetadata + { + BindsTwoWayByDefault = true, + DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, + CoerceValueCallback = (d, baseObject) => + { + var value = baseObject as IEnumerable; + if (value?.Count() > 100) + { + value = value + .Take(99) + .Append(new InputEntry(InputEntryType.Path, $"Other {value.Count() - 50} items...", string.Empty)); + } + return value; + } + }); + public IEnumerable? Collection + { + get => GetValue(CollectionProperty) as IEnumerable; + set => SetValue(CollectionProperty, value); + } + + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register(nameof(SelectedItem), typeof(InputEntry), typeof(InputBar), new FrameworkPropertyMetadata + { + BindsTwoWayByDefault = true, + DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }); + public InputEntry? SelectedItem + { + get => GetValue(SelectedItemProperty) as InputEntry; + set => SetValue(SelectedItemProperty, value); + } + + public event TextChangedEventHandler? TextChanged; + + public InputBar() + { + InitializeComponent(); + } + + private void OnPreviewKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) + { + case Key.Down: + ListBox.SelectedIndex = NormalizeIndex(ListBox.SelectedIndex + 1); + ListBox.ScrollIntoView(ListBox.SelectedItem); + DropDown.IsOpen = true; + break; + case Key.Up: + ListBox.SelectedIndex = NormalizeIndex(ListBox.SelectedIndex - 1); + ListBox.ScrollIntoView(ListBox.SelectedItem); + break; + case Key.Escape: + case Key.Enter: + DropDown.IsOpen = false; + break; + default: + DropDown.IsOpen = true; + break; + } + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + SelectedItem = (InputEntry)ListBox.SelectedItem; + if (SelectedItem != null) + { + Text = SelectedItem.ToString(); + TextBox.Select(Text.Length, 0); + } + } + + private void OnTextChanged(object sender, TextChangedEventArgs e) + { + if (Text != SelectedItem?.ToString()) + { + TextChanged?.Invoke(this, e); + } + } + + private void OnLostFocus(object sender, RoutedEventArgs e) + { + DropDown.IsOpen = false; + } + + private int NormalizeIndex(int rawIndex) + { + if (Collection == null) + { + return -1; + } + + return Math.Min(Math.Max(rawIndex, -1), Collection.Count() - 1); + } + + + } +} diff --git a/InputEntry.cs b/InputEntry.cs new file mode 100644 index 0000000..5cc49e7 --- /dev/null +++ b/InputEntry.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; + +using System.Text; +using System.Threading.Tasks; +using TechnologyAssembler.Core.IO; + +namespace HashCalculator.GUI +{ + internal enum InputEntryType + { + Text, + BigFile, + ManifestFile, + XmlFile, + BinaryFile, + Path, + } + + internal sealed class InputEntry + { + public InputEntryType Type { get; } + public string Value { get; } + public string Text { get; } + + public InputEntry(InputEntryType type, string text, string value) + { + Type = type; + Text = text; + Value = value; + } + + public override string ToString() + { + return Text; + } + + } +} diff --git a/MainWindow.xaml b/MainWindow.xaml index 0956be6..7bc4812 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -3,10 +3,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:dotNetKitControls="clr-namespace:DotNetKit.Windows.Controls;assembly=DotNetKit.Wpf.AutoCompleteComboBox" xmlns:l="clr-namespace:HashCalculator.GUI" mc:Ignorable="d" Title="Sage FastHash 哈希计算器" Height="600" Width="600"> + + + @@ -18,36 +20,35 @@ - + + + +