From bd96a9709fa529469e6aba67be201a8e28ca9450 Mon Sep 17 00:00:00 2001 From: leo Date: Sun, 12 Jul 2020 22:24:59 +0800 Subject: [PATCH] Auto scroll when text changed or selection changed in a TextBox --- SourceGit/Helpers/TextBoxHelper.cs | 368 +++++++++++++++--------- SourceGit/Resources/Styles/TextBox.xaml | 230 +++++++-------- SourceGit/SourceGit.csproj | 34 +-- SourceGit/UI/WorkingCopy.xaml | 3 +- SourceGit/UI/WorkingCopy.xaml.cs | 4 - 5 files changed, 360 insertions(+), 279 deletions(-) diff --git a/SourceGit/Helpers/TextBoxHelper.cs b/SourceGit/Helpers/TextBoxHelper.cs index 3f34aed8..df6d901d 100644 --- a/SourceGit/Helpers/TextBoxHelper.cs +++ b/SourceGit/Helpers/TextBoxHelper.cs @@ -1,143 +1,225 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace SourceGit.Helpers { - - /// - /// Attached properties to TextBox. - /// - public static class TextBoxHelper { - - /// - /// Placeholder property - /// - public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached( - "Placeholder", - typeof(string), - typeof(TextBoxHelper), - new PropertyMetadata(string.Empty, OnPlaceholderChanged)); - - /// - /// Vertical alignment for placeholder. - /// - public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.RegisterAttached( - "PlaceholderBaseline", - typeof(AlignmentY), - typeof(TextBoxHelper), - new PropertyMetadata(AlignmentY.Center)); - - /// - /// Property to store generated placeholder brush. - /// - public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached( - "PlaceholderBrush", - typeof(Brush), - typeof(TextBoxHelper), - new PropertyMetadata(Brushes.Transparent)); - - /// - /// Triggered when placeholder changed. - /// - /// - /// - private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var textBox = d as TextBox; - if (textBox != null) textBox.Loaded += OnTextLoaded; - } - - /// - /// Setter for Placeholder property - /// - /// - /// - public static void SetPlaceholder(UIElement element, string value) { - element.SetValue(PlaceholderProperty, value); - } - - /// - /// Getter for Placeholder property - /// - /// - /// - public static string GetPlaceholder(UIElement element) { - return (string)element.GetValue(PlaceholderProperty); - } - - /// - /// Setter for PlaceholderBaseline property - /// - /// - /// - public static void SetPlaceholderBaseline(UIElement element, AlignmentY align) { - element.SetValue(PlaceholderBaselineProperty, align); - } - - /// - /// Setter for PlaceholderBaseline property. - /// - /// - /// - public static AlignmentY GetPlaceholderBaseline(UIElement element) { - return (AlignmentY)element.GetValue(PlaceholderBaselineProperty); - } - - /// - /// Setter for PlaceholderBrush property. - /// - /// - /// - public static void SetPlaceholderBrush(UIElement element, Brush value) { - element.SetValue(PlaceholderBrushProperty, value); - } - - /// - /// Getter for PlaceholderBrush property. - /// - /// - /// - public static Brush GetPlaceholderBrush(UIElement element) { - return (Brush)element.GetValue(PlaceholderBrushProperty); - } - - /// - /// Set placeholder as background when TextBox was loaded. - /// - /// - /// - private static void OnTextLoaded(object sender, RoutedEventArgs e) { - var textBox = sender as TextBox; - if (textBox == null) return; - - Label placeholder = new Label(); - placeholder.Content = textBox.GetValue(PlaceholderProperty); - - VisualBrush brush = new VisualBrush(); - brush.AlignmentX = AlignmentX.Left; - brush.AlignmentY = GetPlaceholderBaseline(textBox); - brush.TileMode = TileMode.None; - brush.Stretch = Stretch.None; - brush.Opacity = 0.3; - brush.Visual = placeholder; - - textBox.SetValue(PlaceholderBrushProperty, brush); - textBox.Background = brush; - textBox.TextChanged += OnTextChanged; - OnTextChanged(textBox, null); - } - - /// - /// Dynamically hide/show placeholder. - /// - /// - /// - private static void OnTextChanged(object sender, RoutedEventArgs e) { - var textBox = sender as TextBox; - if (string.IsNullOrEmpty(textBox.Text)) { - textBox.Background = textBox.GetValue(PlaceholderBrushProperty) as Brush; - } else { - textBox.Background = Brushes.Transparent; - } - } - } -} +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace SourceGit.Helpers { + + /// + /// Attached properties to TextBox. + /// + public static class TextBoxHelper { + + /// + /// Auto scroll on text changed. + /// + public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.RegisterAttached( + "AutoScroll", + typeof(bool), + typeof(TextBoxHelper), + new PropertyMetadata(false, OnAutoScrollChanged)); + + /// + /// Placeholder property + /// + public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached( + "Placeholder", + typeof(string), + typeof(TextBoxHelper), + new PropertyMetadata(string.Empty, OnPlaceholderChanged)); + + /// + /// Vertical alignment for placeholder. + /// + public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.RegisterAttached( + "PlaceholderBaseline", + typeof(AlignmentY), + typeof(TextBoxHelper), + new PropertyMetadata(AlignmentY.Center)); + + /// + /// Property to store generated placeholder brush. + /// + public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached( + "PlaceholderBrush", + typeof(Brush), + typeof(TextBoxHelper), + new PropertyMetadata(Brushes.Transparent)); + + /// + /// Setter for AutoScrollProperty + /// + /// + /// + public static void SetAutoScroll(UIElement element, bool enabled) { + element.SetValue(AutoScrollProperty, enabled); + } + + /// + /// Getter for AutoScrollProperty + /// + /// + /// + public static bool GetAutoScroll(UIElement element) { + return (bool)element.GetValue(AutoScrollProperty); + } + + /// + /// Triggered when AutoScroll property changed. + /// + /// + /// + public static void OnAutoScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var textBox = d as TextBox; + if (textBox == null) return; + + textBox.SelectionChanged -= UpdateScrollOnSelectionChanged; + if ((bool)e.NewValue == true) { + textBox.SelectionChanged += UpdateScrollOnSelectionChanged; + } + } + + /// + /// Triggered when placeholder changed. + /// + /// + /// + private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var textBox = d as TextBox; + if (textBox != null) textBox.Loaded += OnTextLoaded; + } + + /// + /// Setter for Placeholder property + /// + /// + /// + public static void SetPlaceholder(UIElement element, string value) { + element.SetValue(PlaceholderProperty, value); + } + + /// + /// Getter for Placeholder property + /// + /// + /// + public static string GetPlaceholder(UIElement element) { + return (string)element.GetValue(PlaceholderProperty); + } + + /// + /// Setter for PlaceholderBaseline property + /// + /// + /// + public static void SetPlaceholderBaseline(UIElement element, AlignmentY align) { + element.SetValue(PlaceholderBaselineProperty, align); + } + + /// + /// Setter for PlaceholderBaseline property. + /// + /// + /// + public static AlignmentY GetPlaceholderBaseline(UIElement element) { + return (AlignmentY)element.GetValue(PlaceholderBaselineProperty); + } + + /// + /// Setter for PlaceholderBrush property. + /// + /// + /// + public static void SetPlaceholderBrush(UIElement element, Brush value) { + element.SetValue(PlaceholderBrushProperty, value); + } + + /// + /// Getter for PlaceholderBrush property. + /// + /// + /// + public static Brush GetPlaceholderBrush(UIElement element) { + return (Brush)element.GetValue(PlaceholderBrushProperty); + } + + /// + /// Set placeholder as background when TextBox was loaded. + /// + /// + /// + private static void OnTextLoaded(object sender, RoutedEventArgs e) { + var textBox = sender as TextBox; + if (textBox == null) return; + + Label placeholder = new Label(); + placeholder.Content = textBox.GetValue(PlaceholderProperty); + + VisualBrush brush = new VisualBrush(); + brush.AlignmentX = AlignmentX.Left; + brush.AlignmentY = GetPlaceholderBaseline(textBox); + brush.TileMode = TileMode.None; + brush.Stretch = Stretch.None; + brush.Opacity = 0.3; + brush.Visual = placeholder; + + textBox.SetValue(PlaceholderBrushProperty, brush); + textBox.Background = brush; + textBox.TextChanged += UpdatePlaceholder; + UpdatePlaceholder(textBox, null); + } + + /// + /// Dynamically hide/show placeholder. + /// + /// + /// + private static void UpdatePlaceholder(object sender, RoutedEventArgs e) { + var textBox = sender as TextBox; + if (string.IsNullOrEmpty(textBox.Text)) { + textBox.Background = textBox.GetValue(PlaceholderBrushProperty) as Brush; + } else { + textBox.Background = Brushes.Transparent; + } + } + + /// + /// + /// + /// + /// + private static void UpdateScrollOnSelectionChanged(object sender, RoutedEventArgs e) { + var textBox = sender as TextBox; + if (textBox != null && textBox.IsFocused) { + if (Mouse.LeftButton == MouseButtonState.Pressed && textBox.SelectionLength > 0) { + var p = Mouse.GetPosition(textBox); + if (p.X <= 8) { + textBox.LineLeft(); + } else if (p.X >= textBox.ActualWidth - 8) { + textBox.LineRight(); + } + + if (p.Y <= 8) { + textBox.LineUp(); + } else if (p.Y >= textBox.ActualHeight - 8) { + textBox.LineDown(); + } + } else { + var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex); + if (rect.Left <= 0) { + textBox.ScrollToHorizontalOffset(textBox.HorizontalOffset + rect.Left); + } else if (rect.Right >= textBox.ActualWidth) { + textBox.ScrollToHorizontalOffset(textBox.HorizontalOffset + rect.Right); + } + + if (rect.Top <= 0) { + textBox.ScrollToVerticalOffset(textBox.VerticalOffset + rect.Top); + } else if (rect.Bottom >= textBox.ActualHeight) { + textBox.ScrollToVerticalOffset(textBox.VerticalOffset + rect.Bottom); + } + + } + } + } + } +} diff --git a/SourceGit/Resources/Styles/TextBox.xaml b/SourceGit/Resources/Styles/TextBox.xaml index 728587fd..91578281 100644 --- a/SourceGit/Resources/Styles/TextBox.xaml +++ b/SourceGit/Resources/Styles/TextBox.xaml @@ -1,114 +1,118 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SourceGit/SourceGit.csproj b/SourceGit/SourceGit.csproj index b538fee1..ac32ebc3 100644 --- a/SourceGit/SourceGit.csproj +++ b/SourceGit/SourceGit.csproj @@ -1,18 +1,18 @@ - - - net461 - WinExe - true - true - App.ico - sourcegit - OpenSource GIT client for Windows - Copyright © sourcegit 2020. All rights reserved. - App.manifest - 2.0.0-preview - MIT - - - - + + + net46 + WinExe + true + true + App.ico + sourcegit + OpenSource GIT client for Windows + Copyright © sourcegit 2020. All rights reserved. + App.manifest + 2.0.0-preview + MIT + + + + \ No newline at end of file diff --git a/SourceGit/UI/WorkingCopy.xaml b/SourceGit/UI/WorkingCopy.xaml index 6d4b735d..c4957829 100644 --- a/SourceGit/UI/WorkingCopy.xaml +++ b/SourceGit/UI/WorkingCopy.xaml @@ -343,8 +343,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" helpers:TextBoxHelper.Placeholder="Enter commit message" - helpers:TextBoxHelper.PlaceholderBaseline="Top" - TextChanged="CommitMessageChanged"> + helpers:TextBoxHelper.PlaceholderBaseline="Top"> diff --git a/SourceGit/UI/WorkingCopy.xaml.cs b/SourceGit/UI/WorkingCopy.xaml.cs index 4dd0ad48..6e0345b8 100644 --- a/SourceGit/UI/WorkingCopy.xaml.cs +++ b/SourceGit/UI/WorkingCopy.xaml.cs @@ -729,10 +729,6 @@ namespace SourceGit.UI { e.Handled = true; } - private void CommitMessageChanged(object sender, TextChangedEventArgs e) { - (sender as TextBox).ScrollToEnd(); - } - private void StartAmend(object sender, RoutedEventArgs e) { var commits = Repo.Commits("-n 1"); if (commits.Count == 0) {