diff --git a/MathCore.WPF.sln.DotSettings b/MathCore.WPF.sln.DotSettings index f23f904..bf06b76 100644 --- a/MathCore.WPF.sln.DotSettings +++ b/MathCore.WPF.sln.DotSettings @@ -20,6 +20,7 @@ True True True + True True True True diff --git a/MathCore.WPF/Commands/BindingGroupCommit.cs b/MathCore.WPF/Commands/BindingGroupCommit.cs new file mode 100644 index 0000000..3702e35 --- /dev/null +++ b/MathCore.WPF/Commands/BindingGroupCommit.cs @@ -0,0 +1,11 @@ +using System.Windows.Data; + +namespace MathCore.WPF.Commands; + +/// Команда для подтверждения изменений в BindingGroup, если они были изменены +public class BindingGroupCommit : Command +{ + public override bool CanExecute(object? parameter) => parameter is BindingGroup { IsDirty: true }; + + public override void Execute(object? parameter) => (parameter as BindingGroup).CommitEdit(); +} diff --git a/MathCore.WPF/Commands/BindingGroupRollback.cs b/MathCore.WPF/Commands/BindingGroupRollback.cs new file mode 100644 index 0000000..85ba582 --- /dev/null +++ b/MathCore.WPF/Commands/BindingGroupRollback.cs @@ -0,0 +1,11 @@ +using System.Windows.Data; + +namespace MathCore.WPF.Commands; + +/// Команда для отмены изменений в BindingGroup, если они были изменены +public class BindingGroupRollback : Command +{ + public override bool CanExecute(object? parameter) => parameter is BindingGroup { IsDirty: true }; + + public override void Execute(object? parameter) => (parameter as BindingGroup).CancelEdit(); +} diff --git a/MathCore.WPF/Extensions/WindowExtensions.cs b/MathCore.WPF/Extensions/WindowExtensions.cs index c849b84..9651f45 100644 --- a/MathCore.WPF/Extensions/WindowExtensions.cs +++ b/MathCore.WPF/Extensions/WindowExtensions.cs @@ -11,23 +11,26 @@ public static class WindowExtensions { public static IntPtr GetWindowHandle(this Window window) => new WindowInteropHelper(window).Handle; - public static void ForWindowFromChild(this object ChildDependencyObject, Action action) + public static void ForWindowFromChild(this T ChildDependencyObject, Action action) + where T : DependencyObject { if (action is null) throw new ArgumentNullException(nameof(action)); - var element = ChildDependencyObject as DependencyObject; + DependencyObject? element = ChildDependencyObject; while (element != null) { element = VisualTreeHelper.GetParent(element); - if(element is not Window window) continue; - action(window); + if (element is not Window window) continue; + action(window); break; } } - public static void ForWindowFromTemplate(this object TemplateFrameworkElement, Action action) + public static void ForWindowFromTemplate(this T TemplateFrameworkElement, Action action) + where T : DependencyObject { if (action is null) throw new ArgumentNullException(nameof(action)); - if (((FrameworkElement)TemplateFrameworkElement)?.TemplatedParent is Window window) action(window); + if (TemplateFrameworkElement is FrameworkElement { TemplatedParent: Window window }) + action(window); } public static void AddHook(this Window window, HwndSourceHook WndProc) @@ -39,7 +42,7 @@ public static void AddHook(this Window window, HwndSourceHook WndProc) source.AddHook(WndProc); } else - window.SourceInitialized += (_,_) => window.AddHook(WndProc); + window.SourceInitialized += (_, _) => window.AddHook(WndProc); } public static void RemoveHook(this Window window, HwndSourceHook WndProc) diff --git a/MathCore.WPF/MathCore.WPF.csproj b/MathCore.WPF/MathCore.WPF.csproj index dbc5f06..4a22d4b 100644 --- a/MathCore.WPF/MathCore.WPF.csproj +++ b/MathCore.WPF/MathCore.WPF.csproj @@ -24,10 +24,12 @@ enable preview + true + true - 0.0.48.1 + 0.0.48.2 Добавлены стили для кнопок и полей ввода текста в стилистике Bootstrap 5 @@ -49,24 +51,24 @@ - + - + - + - + @@ -75,7 +77,7 @@ - + diff --git a/MathCore.WPF/TextBoxEx.cs b/MathCore.WPF/TextBoxEx.cs index 3cdf2c7..4ea12a9 100644 --- a/MathCore.WPF/TextBoxEx.cs +++ b/MathCore.WPF/TextBoxEx.cs @@ -20,9 +20,10 @@ public static class TextBoxEx typeof(TextBoxEx), new(default(bool), OnUpdateBindingOnEnterChanged)); + /// Обработчик изменения свойства UpdateBindingSourceOnEnter private static void OnUpdateBindingOnEnterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { - if(sender is not TextBox text_box || e.NewValue is not bool value) return; + if (sender is not TextBox text_box || e.NewValue is not bool value) return; if (value) text_box.KeyDown += OnTextBoxKeyDown; @@ -30,30 +31,31 @@ private static void OnUpdateBindingOnEnterChanged(DependencyObject sender, Depen text_box.KeyDown -= OnTextBoxKeyDown; } + /// Обработчик события нажатия клавиши в TextBox private static void OnTextBoxKeyDown(object Sender, KeyEventArgs E) { - if(E.Key != Key.Enter) return; + if (E.Key != Key.Enter) return; var text_box = (TextBox)Sender; - if (text_box.AcceptsReturn && !E.KeyboardDevice.IsKeyDown(Key.LeftCtrl)) + if (text_box.AcceptsReturn && !E.KeyboardDevice.IsKeyDown(Key.LeftCtrl)) return; - if (BindingOperations.GetBindingExpression(text_box, TextBox.TextProperty) is not { } binding) + if (BindingOperations.GetBindingExpression(text_box, TextBox.TextProperty) is not { } binding) return; - if(binding.ParentBinding.UpdateSourceTrigger != UpdateSourceTrigger.Explicit && !E.KeyboardDevice.IsKeyDown(Key.LeftCtrl)) + if (binding.ParentBinding.UpdateSourceTrigger != UpdateSourceTrigger.Explicit && !E.KeyboardDevice.IsKeyDown(Key.LeftCtrl)) return; binding.UpdateSource(); E.Handled = true; } - /// Обновить привязку при нажатии Enter + /// Установить значение свойства UpdateBindingSourceOnEnter [AttachedPropertyBrowsableForType(typeof(TextBox))] public static void SetUpdateBindingSourceOnEnter(DependencyObject d, bool value) => d.SetValue(UpdateBindingSourceOnEnterProperty, value); - /// Обновить привязку при нажатии Enter + /// Получить значение свойства UpdateBindingSourceOnEnter public static bool GetUpdateBindingSourceOnEnter(DependencyObject d) => (bool)d.GetValue(UpdateBindingSourceOnEnterProperty); #endregion @@ -66,11 +68,12 @@ private static void OnTextBoxKeyDown(object Sender, KeyEventArgs E) "ValidateInputScope", typeof(bool), typeof(TextBoxEx), - new(default(bool), OnValidateInputScopeChanged)); + new(false, OnValidateInputScopeChanged)); + /// Обработчик изменения свойства ValidateInputScope private static void OnValidateInputScopeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { - if(obj is not TextBox text_box || e.NewValue is not bool value) return; + if (obj is not TextBox text_box || e.NewValue is not bool value) return; if (value) text_box.PreviewTextInput += ValidateScopes_OnPreviewTextInput; @@ -78,9 +81,10 @@ private static void OnValidateInputScopeChanged(DependencyObject obj, Dependency text_box.PreviewTextInput -= ValidateScopes_OnPreviewTextInput; } + /// Обработчик события предварительного ввода текста в TextBox private static void ValidateScopes_OnPreviewTextInput(object Sender, TextCompositionEventArgs E) { - if(Sender is not TextBox { InputScope: { } input_scope, Text: var current_text } text_box) return; + if (Sender is not TextBox { InputScope: { } input_scope, Text: var current_text } text_box) return; var input_text = E.Text; var full_text = current_text + input_text; @@ -95,20 +99,20 @@ private static void ValidateScopes_OnPreviewTextInput(object Sender, TextComposi case InputScopeNameValue.FullFilePath when Path.GetInvalidPathChars().Any(c => input_text.Contains(c)): case InputScopeNameValue.FileName when Path.GetInvalidFileNameChars().Any(c => input_text.Contains(c)): case InputScopeNameValue.DateDayName when full_text.ToLower() is not ( - "sunday" or "sun" or "su" or + "sunday" or "sun" or "su" or "monday" or "mon" or "mo" or "tuesday" or "tue" or "tu" or "wednesday" or "wed" or "wd" or "thursday" or "thu" or "th" or "friday" or "fri" or "fr" or - "saturday" or "sat" or "st" or + "saturday" or "sat" or "st" or "понедельник" or "пон" or "пн" or "вторник" or "втр" or "вт" or "среда" or "срд" or "ср" or "четверг" or "чет" or "чт" or "пятница" or "пят" or "пт" or "суббота" or "суб" or "сб" or - "воскресенье" or "вос" or "вс" + "воскресенье" or "вос" or "вс" ): case InputScopeNameValue.DateDay when !int.TryParse(full_text, out var day) && day is not (>= 1 and <= 31): case InputScopeNameValue.DateMonth when !int.TryParse(full_text, out var month) && month is not (>= 1 and <= 12): @@ -120,15 +124,15 @@ private static void ValidateScopes_OnPreviewTextInput(object Sender, TextComposi return; } - if (full_text.Length > 0 && input_scope.RegularExpression is { Length: > 0 } regex_str && !Regex.IsMatch(full_text, regex_str)) + if (full_text.Length > 0 && input_scope.RegularExpression is { Length: > 0 } regex_str && !Regex.IsMatch(full_text, regex_str)) E.Handled = true; } - /// Проверять корректность правил InputScope + /// Установить значение свойства ValidateInputScope [AttachedPropertyBrowsableForType(typeof(TextBox))] public static void SetValidateInputScope(DependencyObject d, bool value) => d.SetValue(ValidateInputScopeProperty, value); - /// Проверять корректность правил InputScope + /// Получить значение свойства ValidateInputScope public static bool GetValidateInputScope(DependencyObject d) => (bool)d.GetValue(ValidateInputScopeProperty); #endregion @@ -143,19 +147,25 @@ private static void ValidateScopes_OnPreviewTextInput(object Sender, TextComposi typeof(TextBoxEx), new(decimal.Zero, OnMouseWheelIncrementChanged)); + /// Обработчик изменения свойства MouseWheelIncrement private static void OnMouseWheelIncrementChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) { - if (e is { OldValue: decimal.Zero, NewValue: not decimal.Zero }) - ((TextBox)s).MouseWheel += OnMouseWheel; - else if (e is { OldValue: not decimal.Zero, NewValue: decimal.Zero }) - ((TextBox)s).MouseWheel -= OnMouseWheel; + switch (e) + { + case { OldValue: decimal.Zero, NewValue: not decimal.Zero }: + ((TextBox)s).MouseWheel += OnMouseWheel; + break; + case { OldValue: not decimal.Zero, NewValue: decimal.Zero }: + ((TextBox)s).MouseWheel -= OnMouseWheel; + break; + } } - /// Инкремент колёсика мышки + /// Установить значение свойства MouseWheelIncrement [AttachedPropertyBrowsableForType(typeof(TextBox))] public static void SetMouseWheelIncrement(DependencyObject d, decimal value) => d.SetValue(MouseWheelIncrementProperty, value); - /// Инкремент колёсика мышки + /// Получить значение свойства MouseWheelIncrement public static decimal GetMouseWheelIncrement(DependencyObject d) => (decimal)d.GetValue(MouseWheelIncrementProperty); #endregion @@ -170,15 +180,16 @@ private static void OnMouseWheelIncrementChanged(DependencyObject s, DependencyP typeof(TextBoxEx), new(0.1m)); - /// Инкремент колёсика мышки + /// Установить значение свойства MouseWheelIncrementCtrlRatio [AttachedPropertyBrowsableForType(typeof(TextBox))] public static void SetMouseWheelIncrementCtrlRatio(DependencyObject d, decimal value) => d.SetValue(MouseWheelIncrementCtrlRatioProperty, value); - /// Инкремент колёсика мышки + /// Получить значение свойства MouseWheelIncrementCtrlRatio public static decimal GetMouseWheelIncrementCtrlRatio(DependencyObject d) => (decimal)d.GetValue(MouseWheelIncrementCtrlRatioProperty); #endregion + /// Обработчик события прокрутки колёсика мыши в TextBox private static void OnMouseWheel(object Sender, MouseWheelEventArgs E) { if (Sender is not TextBox { Text: var text } text_block) return; @@ -195,4 +206,58 @@ private static void OnMouseWheel(object Sender, MouseWheelEventArgs E) var new_value_text = new_value.ToString(CultureInfo.InvariantCulture); text_block.Text = new_value_text; } + + #region Attached property AutoSelectAll : bool - Автовыбор всего содержимого TextBox + + /// Авто выбор всего содержимого TextBox + public static readonly DependencyProperty AutoSelectAllProperty = + DependencyProperty.RegisterAttached( + "AutoSelectAll", + typeof(bool), + typeof(TextBoxEx), + new(false, OnAutoSelectAllChanged)); + + /// Обработчик изменения свойства AutoSelectAll + private static void OnAutoSelectAllChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not TextBox text_box) return; + + if ((bool)e.NewValue) + { + text_box.PreviewMouseDown += OnTextBoxPreviewMouseDown; + text_box.GotKeyboardFocus += OnTextBoxGotKeyboardFocus; + } + else + { + text_box.PreviewMouseDown -= OnTextBoxPreviewMouseDown; + text_box.GotKeyboardFocus -= OnTextBoxGotKeyboardFocus; + } + } + + /// Обработчик события предварительного нажатия мыши в TextBox + private static void OnTextBoxPreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (sender is not TextBox text_box || text_box.IsKeyboardFocusWithin) return; + + text_box.Focus(); + e.Handled = true; + } + + /// Обработчик события получения фокуса клавиатуры в TextBox + private static void OnTextBoxGotKeyboardFocus(object sender, RoutedEventArgs e) + { + if (sender is not TextBox text_box) return; + + text_box.SelectAll(); + e.Handled = true; + } + + /// Установить значение свойства AutoSelectAll + [AttachedPropertyBrowsableForType(typeof(TextBox))] + public static void SetAutoSelectAll(DependencyObject d, bool value) => d.SetValue(AutoSelectAllProperty, value); + + /// Получить значение свойства AutoSelectAll + public static bool GetAutoSelectAll(DependencyObject d) => (bool)d.GetValue(AutoSelectAllProperty); + + #endregion } diff --git a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj index df6c2a9..5bf2908 100644 --- a/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj +++ b/Tests/MathCore.WPF.Tests/MathCore.WPF.Tests.csproj @@ -10,9 +10,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/MathCore.WPF.WindowTest/App.xaml b/Tests/MathCore.WPF.WindowTest/App.xaml index 218626a..3c1362d 100644 --- a/Tests/MathCore.WPF.WindowTest/App.xaml +++ b/Tests/MathCore.WPF.WindowTest/App.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:l="clr-namespace:MathCore.WPF.WindowTest" - StartupUri="TestWindow5.xaml"> + StartupUri="TestWindow7.xaml"> diff --git a/Tests/MathCore.WPF.WindowTest/App.xaml.cs b/Tests/MathCore.WPF.WindowTest/App.xaml.cs index c2eb576..3a43487 100644 --- a/Tests/MathCore.WPF.WindowTest/App.xaml.cs +++ b/Tests/MathCore.WPF.WindowTest/App.xaml.cs @@ -45,8 +45,8 @@ protected override async void OnStartup(StartupEventArgs e) base.OnStartup(e); await host.StartAsync(); - var main_thread_cancellation = __MainThreadWatcherCancellation.Token; - _ = Task.Run(() => MainThreadLoadingWatcher(main_thread_cancellation), main_thread_cancellation); + //var main_thread_cancellation = __MainThreadWatcherCancellation.Token; + //_ = Task.Run(() => MainThreadLoadingWatcher(main_thread_cancellation), main_thread_cancellation); } private static readonly CancellationTokenSource __MainThreadWatcherCancellation = new(); diff --git a/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj b/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj index aa1ad62..544ab5e 100644 --- a/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj +++ b/Tests/MathCore.WPF.WindowTest/MathCore.WPF.WindowTest.csproj @@ -27,7 +27,7 @@ - + diff --git a/Tests/MathCore.WPF.WindowTest/TestWindow6.xaml b/Tests/MathCore.WPF.WindowTest/TestWindow6.xaml index 92ecae7..9db5524 100644 --- a/Tests/MathCore.WPF.WindowTest/TestWindow6.xaml +++ b/Tests/MathCore.WPF.WindowTest/TestWindow6.xaml @@ -1,15 +1,139 @@  + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + +