Fix ESC handling in ticket completion dropdowns

This commit is contained in:
Meik
2026-02-20 14:55:35 +01:00
parent dd0e7bdc99
commit 234eacaecf
4 changed files with 3489 additions and 3422 deletions

View File

@@ -1,7 +1,7 @@
using C4IT.FASD.Base; using C4IT.FASD.Base;
using C4IT.MultiLanguage; using C4IT.MultiLanguage;
using FasdDesktopUi.Basics.Helper; using FasdDesktopUi.Basics.Helper;
using FasdDesktopUi.Basics.Models; using FasdDesktopUi.Basics.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -125,25 +125,25 @@ namespace FasdDesktopUi.Basics.UserControls
} }
public static readonly DependencyProperty SearchDataChangedProperty = public static readonly DependencyProperty SearchDataChangedProperty =
DependencyProperty.Register("SearchDataChanged", typeof(EventHandler<cF4sdHealthSelectionDataRequest>), typeof(ComboBoxPageable), new PropertyMetadata(null)); DependencyProperty.Register("SearchDataChanged", typeof(EventHandler<cF4sdHealthSelectionDataRequest>), typeof(ComboBoxPageable), new PropertyMetadata(null));
#endregion #endregion
#region RestoreParentScrollFocusOnDropDownClose #region RestoreParentScrollFocusOnDropDownClose
public bool RestoreParentScrollFocusOnDropDownClose public bool RestoreParentScrollFocusOnDropDownClose
{ {
get { return (bool)GetValue(RestoreParentScrollFocusOnDropDownCloseProperty); } get { return (bool)GetValue(RestoreParentScrollFocusOnDropDownCloseProperty); }
set { SetValue(RestoreParentScrollFocusOnDropDownCloseProperty, value); } set { SetValue(RestoreParentScrollFocusOnDropDownCloseProperty, value); }
} }
public static readonly DependencyProperty RestoreParentScrollFocusOnDropDownCloseProperty = public static readonly DependencyProperty RestoreParentScrollFocusOnDropDownCloseProperty =
DependencyProperty.Register("RestoreParentScrollFocusOnDropDownClose", typeof(bool), typeof(ComboBoxPageable), new PropertyMetadata(false)); DependencyProperty.Register("RestoreParentScrollFocusOnDropDownClose", typeof(bool), typeof(ComboBoxPageable), new PropertyMetadata(false));
#endregion #endregion
#region ItemData #region ItemData
public ObservableCollection<KeyValuePair<string, object>> ItemData public ObservableCollection<KeyValuePair<string, object>> ItemData
{ {
@@ -222,6 +222,15 @@ namespace FasdDesktopUi.Basics.UserControls
return null; return null;
} }
public bool CloseDropDownIfOpen()
{
if (ComboBoxControl?.IsDropDownOpen != true)
return false;
ComboBoxControl.IsDropDownOpen = false;
return true;
}
#region Paging Events #region Paging Events
#region PageBack #region PageBack
@@ -316,33 +325,33 @@ namespace FasdDesktopUi.Basics.UserControls
} }
} }
private void ComboBoxControl_DropDownClosed(object sender, EventArgs e) private void ComboBoxControl_DropDownClosed(object sender, EventArgs e)
{ {
timer.Stop(); timer.Stop();
cFocusInvoker.InvokeLostFocus(this, e); cFocusInvoker.InvokeLostFocus(this, e);
if (RestoreParentScrollFocusOnDropDownClose) if (RestoreParentScrollFocusOnDropDownClose)
{ {
_ = Dispatcher.BeginInvoke((Action)(() => _ = Dispatcher.BeginInvoke((Action)(() =>
{ {
try try
{ {
var parentScrollViewer = cUiElementHelper.GetFirstParentOfType<ScrollViewer>(this); var parentScrollViewer = cUiElementHelper.GetFirstParentOfType<ScrollViewer>(this);
Keyboard.ClearFocus(); Keyboard.ClearFocus();
if (parentScrollViewer != null) if (parentScrollViewer != null)
{ {
parentScrollViewer.Focus(); parentScrollViewer.Focus();
Keyboard.Focus(parentScrollViewer); Keyboard.Focus(parentScrollViewer);
} }
} }
catch (Exception exception) catch (Exception exception)
{ {
LogException(exception); LogException(exception);
} }
}), System.Windows.Threading.DispatcherPriority.Input); }), System.Windows.Threading.DispatcherPriority.Input);
} }
} }
private void ComboBoxControl_DropDownOpened(object sender, EventArgs e) private void ComboBoxControl_DropDownOpened(object sender, EventArgs e)
{ {

View File

@@ -1,429 +1,438 @@
using FasdDesktopUi.Basics.Models; using FasdDesktopUi.Basics.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; 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 System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
namespace FasdDesktopUi.Basics.UserControls namespace FasdDesktopUi.Basics.UserControls
{ {
public partial class HierarchicalSelectionControl : UserControl public partial class HierarchicalSelectionControl : UserControl
{ {
private readonly ObservableCollection<HierarchicalSelectionItem> visibleItems = new ObservableCollection<HierarchicalSelectionItem>(); private readonly ObservableCollection<HierarchicalSelectionItem> visibleItems = new ObservableCollection<HierarchicalSelectionItem>();
private readonly Dictionary<string, HierarchicalSelectionItem> itemLookup = new Dictionary<string, HierarchicalSelectionItem>(); private readonly Dictionary<string, HierarchicalSelectionItem> itemLookup = new Dictionary<string, HierarchicalSelectionItem>();
private readonly DispatcherTimer searchDelayTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; private readonly DispatcherTimer searchDelayTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
private string lastSearchText = string.Empty; private string lastSearchText = string.Empty;
private bool suppressTreeSelectionChanged; private bool suppressTreeSelectionChanged;
private TextBox searchTextBox; private TextBox searchTextBox;
private TreeView treeViewControl; private TreeView treeViewControl;
private ScrollViewer itemsScrollViewer; private ScrollViewer itemsScrollViewer;
public ObservableCollection<HierarchicalSelectionItem> VisibleItems => visibleItems; public ObservableCollection<HierarchicalSelectionItem> VisibleItems => visibleItems;
public event EventHandler DropDownOpened; public event EventHandler DropDownOpened;
public event EventHandler DropDownClosed; public event EventHandler DropDownClosed;
public HierarchicalSelectionControl() public HierarchicalSelectionControl()
{ {
InitializeComponent(); InitializeComponent();
searchDelayTimer.Tick += SearchDelayTimer_Tick; searchDelayTimer.Tick += SearchDelayTimer_Tick;
} }
public override void OnApplyTemplate() public bool CloseDropDownIfOpen()
{ {
base.OnApplyTemplate(); if (ComboBoxControl?.IsDropDownOpen != true)
EnsureTemplateParts(); return false;
if (treeViewControl != null)
treeViewControl.ItemsSource = VisibleItems; ComboBoxControl.IsDropDownOpen = false;
} return true;
}
#region DependencyProperties
public override void OnApplyTemplate()
public ObservableCollection<HierarchicalSelectionItem> ItemsSource {
{ base.OnApplyTemplate();
get => (ObservableCollection<HierarchicalSelectionItem>)GetValue(ItemsSourceProperty); EnsureTemplateParts();
set => SetValue(ItemsSourceProperty, value); if (treeViewControl != null)
} treeViewControl.ItemsSource = VisibleItems;
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
nameof(ItemsSource), #region DependencyProperties
typeof(ObservableCollection<HierarchicalSelectionItem>),
typeof(HierarchicalSelectionControl), public ObservableCollection<HierarchicalSelectionItem> ItemsSource
new PropertyMetadata(null, OnItemsSourceChanged)); {
get => (ObservableCollection<HierarchicalSelectionItem>)GetValue(ItemsSourceProperty);
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) set => SetValue(ItemsSourceProperty, value);
{ }
if (d is HierarchicalSelectionControl control)
{ public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
control.RebuildLookup(); nameof(ItemsSource),
control.ApplyFilter(control.lastSearchText); typeof(ObservableCollection<HierarchicalSelectionItem>),
} typeof(HierarchicalSelectionControl),
} new PropertyMetadata(null, OnItemsSourceChanged));
public HierarchicalSelectionItem SelectedItem private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
get => (HierarchicalSelectionItem)GetValue(SelectedItemProperty); if (d is HierarchicalSelectionControl control)
set => SetValue(SelectedItemProperty, value); {
} control.RebuildLookup();
control.ApplyFilter(control.lastSearchText);
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register( }
nameof(SelectedItem), }
typeof(HierarchicalSelectionItem),
typeof(HierarchicalSelectionControl), public HierarchicalSelectionItem SelectedItem
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged)); {
get => (HierarchicalSelectionItem)GetValue(SelectedItemProperty);
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) set => SetValue(SelectedItemProperty, value);
{ }
if (d is HierarchicalSelectionControl control)
{ public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
control.TryExpandToSelectedItem(); nameof(SelectedItem),
control.SyncTreeSelectionWithSelectedItem(bringIntoView: false); typeof(HierarchicalSelectionItem),
} typeof(HierarchicalSelectionControl),
} new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
public Brush ComboBoxBackground private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
get => (Brush)GetValue(ComboBoxBackgroundProperty); if (d is HierarchicalSelectionControl control)
set => SetValue(ComboBoxBackgroundProperty, value); {
} control.TryExpandToSelectedItem();
control.SyncTreeSelectionWithSelectedItem(bringIntoView: false);
public static readonly DependencyProperty ComboBoxBackgroundProperty = }
DependencyProperty.Register(nameof(ComboBoxBackground), typeof(Brush), typeof(HierarchicalSelectionControl), new PropertyMetadata(Brushes.Transparent)); }
public string SearchPlaceholderText public Brush ComboBoxBackground
{ {
get => (string)GetValue(SearchPlaceholderTextProperty); get => (Brush)GetValue(ComboBoxBackgroundProperty);
set => SetValue(SearchPlaceholderTextProperty, value); set => SetValue(ComboBoxBackgroundProperty, value);
} }
public static readonly DependencyProperty SearchPlaceholderTextProperty = public static readonly DependencyProperty ComboBoxBackgroundProperty =
DependencyProperty.Register(nameof(SearchPlaceholderText), typeof(string), typeof(HierarchicalSelectionControl), new PropertyMetadata(string.Empty)); DependencyProperty.Register(nameof(ComboBoxBackground), typeof(Brush), typeof(HierarchicalSelectionControl), new PropertyMetadata(Brushes.Transparent));
#endregion public string SearchPlaceholderText
{
#region UI Event Handling get => (string)GetValue(SearchPlaceholderTextProperty);
set => SetValue(SearchPlaceholderTextProperty, value);
private void ComboBoxControl_DropDownOpened(object sender, EventArgs e) }
{
EnsureTemplateParts(); public static readonly DependencyProperty SearchPlaceholderTextProperty =
searchTextBox?.Focus(); DependencyProperty.Register(nameof(SearchPlaceholderText), typeof(string), typeof(HierarchicalSelectionControl), new PropertyMetadata(string.Empty));
searchTextBox?.SelectAll();
suppressTreeSelectionChanged = false; #endregion
SyncTreeSelectionWithSelectedItem(bringIntoView: true);
DropDownOpened?.Invoke(this, e); #region UI Event Handling
}
private void ComboBoxControl_DropDownOpened(object sender, EventArgs e)
private void ComboBoxControl_DropDownClosed(object sender, EventArgs e) {
{ EnsureTemplateParts();
searchDelayTimer.Stop(); searchTextBox?.Focus();
suppressTreeSelectionChanged = false; searchTextBox?.SelectAll();
DropDownClosed?.Invoke(this, e); suppressTreeSelectionChanged = false;
} SyncTreeSelectionWithSelectedItem(bringIntoView: true);
DropDownOpened?.Invoke(this, e);
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e) }
{
searchDelayTimer.Stop(); private void ComboBoxControl_DropDownClosed(object sender, EventArgs e)
searchDelayTimer.Start(); {
} searchDelayTimer.Stop();
suppressTreeSelectionChanged = false;
private void SearchDelayTimer_Tick(object sender, EventArgs e) DropDownClosed?.Invoke(this, e);
{ }
searchDelayTimer.Stop();
lastSearchText = searchTextBox?.Text ?? string.Empty; private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
ApplyFilter(lastSearchText); {
} searchDelayTimer.Stop();
searchDelayTimer.Start();
private void TreeViewControl_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) }
{
if (suppressTreeSelectionChanged) private void SearchDelayTimer_Tick(object sender, EventArgs e)
return; {
searchDelayTimer.Stop();
if (e.NewValue is HierarchicalSelectionItem selected) lastSearchText = searchTextBox?.Text ?? string.Empty;
{ ApplyFilter(lastSearchText);
var original = ResolveOriginalItem(selected); }
if (original != null && !Equals(SelectedItem, original))
{ private void TreeViewControl_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
SelectedItem = original; {
} if (suppressTreeSelectionChanged)
return;
suppressTreeSelectionChanged = true;
ComboBoxControl.IsDropDownOpen = false; if (e.NewValue is HierarchicalSelectionItem selected)
} {
} var original = ResolveOriginalItem(selected);
if (original != null && !Equals(SelectedItem, original))
#endregion {
SelectedItem = original;
#region Data Preparation and Filtering }
private HierarchicalSelectionItem ResolveOriginalItem(HierarchicalSelectionItem item) suppressTreeSelectionChanged = true;
{ ComboBoxControl.IsDropDownOpen = false;
if (item == null) }
return null; }
if (!string.IsNullOrWhiteSpace(item.Id) && itemLookup.TryGetValue(item.Id, out var original)) #endregion
return original;
#region Data Preparation and Filtering
return null;
} private HierarchicalSelectionItem ResolveOriginalItem(HierarchicalSelectionItem item)
{
private void RebuildLookup() if (item == null)
{ return null;
itemLookup.Clear();
if (!string.IsNullOrWhiteSpace(item.Id) && itemLookup.TryGetValue(item.Id, out var original))
if (ItemsSource == null) return original;
return;
return null;
foreach (var entry in ItemsSource.SelectMany(item => item.SelfAndDescendants())) }
{
if (string.IsNullOrWhiteSpace(entry.Id)) private void RebuildLookup()
continue; {
itemLookup.Clear();
itemLookup[entry.Id] = entry;
} if (ItemsSource == null)
} return;
private void ApplyFilter(string searchText) foreach (var entry in ItemsSource.SelectMany(item => item.SelfAndDescendants()))
{ {
visibleItems.Clear(); if (string.IsNullOrWhiteSpace(entry.Id))
continue;
if (ItemsSource == null)
return; itemLookup[entry.Id] = entry;
}
if (string.IsNullOrWhiteSpace(searchText)) }
{
foreach (var item in ItemsSource) private void ApplyFilter(string searchText)
{ {
item.SetExpandedRecursive(false); visibleItems.Clear();
visibleItems.Add(item);
} if (ItemsSource == null)
return;
TryExpandToSelectedItem();
SyncTreeSelectionWithSelectedItem(bringIntoView: false); if (string.IsNullOrWhiteSpace(searchText))
return; {
} foreach (var item in ItemsSource)
{
var comparison = StringComparison.CurrentCultureIgnoreCase; item.SetExpandedRecursive(false);
visibleItems.Add(item);
foreach (var root in ItemsSource) }
{
var clone = root.CloneBranch(node => TryExpandToSelectedItem();
node.FullPath?.IndexOf(searchText, comparison) >= 0 || SyncTreeSelectionWithSelectedItem(bringIntoView: false);
node.DisplayName?.IndexOf(searchText, comparison) >= 0); return;
}
if (clone == null)
continue; var comparison = StringComparison.CurrentCultureIgnoreCase;
clone.SetExpandedRecursive(true); foreach (var root in ItemsSource)
visibleItems.Add(clone); {
} var clone = root.CloneBranch(node =>
node.FullPath?.IndexOf(searchText, comparison) >= 0 ||
// If the selected item is part of current results, keep it visually selected. node.DisplayName?.IndexOf(searchText, comparison) >= 0);
SyncTreeSelectionWithSelectedItem(bringIntoView: false);
} if (clone == null)
continue;
private void TryExpandToSelectedItem()
{ clone.SetExpandedRecursive(true);
if (SelectedItem == null || string.IsNullOrWhiteSpace(SelectedItem.Id)) visibleItems.Add(clone);
return; }
if (!string.IsNullOrWhiteSpace(lastSearchText)) // If the selected item is part of current results, keep it visually selected.
return; SyncTreeSelectionWithSelectedItem(bringIntoView: false);
}
var chain = SelectedItem;
while (chain != null) private void TryExpandToSelectedItem()
{ {
chain.IsExpanded = true; if (SelectedItem == null || string.IsNullOrWhiteSpace(SelectedItem.Id))
chain = chain.Parent; return;
}
} if (!string.IsNullOrWhiteSpace(lastSearchText))
return;
#endregion
var chain = SelectedItem;
#region Tree Selection Sync while (chain != null)
{
private void SyncTreeSelectionWithSelectedItem(bool bringIntoView) chain.IsExpanded = true;
{ chain = chain.Parent;
if (SelectedItem == null || string.IsNullOrWhiteSpace(SelectedItem.Id)) }
return; }
EnsureTemplateParts(); #endregion
if (treeViewControl == null)
return; #region Tree Selection Sync
// Wait for popup/template layout to finish so item containers are generated. private void SyncTreeSelectionWithSelectedItem(bool bringIntoView)
Dispatcher.BeginInvoke(new Action(() => {
{ if (SelectedItem == null || string.IsNullOrWhiteSpace(SelectedItem.Id))
var target = FindVisibleItemById(VisibleItems, SelectedItem.Id); return;
if (target == null)
return; EnsureTemplateParts();
if (treeViewControl == null)
var ancestor = target.Parent; return;
while (ancestor != null)
{ // Wait for popup/template layout to finish so item containers are generated.
ancestor.IsExpanded = true; Dispatcher.BeginInvoke(new Action(() =>
ancestor = ancestor.Parent; {
} var target = FindVisibleItemById(VisibleItems, SelectedItem.Id);
if (target == null)
treeViewControl.UpdateLayout(); return;
var targetContainer = GetTreeViewItemContainer(treeViewControl, target);
if (targetContainer == null) var ancestor = target.Parent;
return; while (ancestor != null)
{
suppressTreeSelectionChanged = true; ancestor.IsExpanded = true;
try ancestor = ancestor.Parent;
{ }
targetContainer.IsSelected = true;
if (bringIntoView) treeViewControl.UpdateLayout();
targetContainer.BringIntoView(); var targetContainer = GetTreeViewItemContainer(treeViewControl, target);
} if (targetContainer == null)
finally return;
{
suppressTreeSelectionChanged = false; suppressTreeSelectionChanged = true;
} try
}), DispatcherPriority.Loaded); {
} targetContainer.IsSelected = true;
if (bringIntoView)
private static HierarchicalSelectionItem FindVisibleItemById(IEnumerable<HierarchicalSelectionItem> items, string id) targetContainer.BringIntoView();
{ }
if (items == null || string.IsNullOrWhiteSpace(id)) finally
return null; {
suppressTreeSelectionChanged = false;
foreach (var item in items) }
{ }), DispatcherPriority.Loaded);
if (item == null) }
continue;
private static HierarchicalSelectionItem FindVisibleItemById(IEnumerable<HierarchicalSelectionItem> items, string id)
if (string.Equals(item.Id, id, StringComparison.OrdinalIgnoreCase)) {
return item; if (items == null || string.IsNullOrWhiteSpace(id))
return null;
var childMatch = FindVisibleItemById(item.Children, id);
if (childMatch != null) foreach (var item in items)
return childMatch; {
} if (item == null)
continue;
return null;
} if (string.Equals(item.Id, id, StringComparison.OrdinalIgnoreCase))
return item;
private static TreeViewItem GetTreeViewItemContainer(ItemsControl root, object targetItem)
{ var childMatch = FindVisibleItemById(item.Children, id);
if (root == null || targetItem == null) if (childMatch != null)
return null; return childMatch;
}
var directContainer = root.ItemContainerGenerator.ContainerFromItem(targetItem) as TreeViewItem;
if (directContainer != null) return null;
return directContainer; }
foreach (var child in root.Items) private static TreeViewItem GetTreeViewItemContainer(ItemsControl root, object targetItem)
{ {
var childContainer = root.ItemContainerGenerator.ContainerFromItem(child) as TreeViewItem; if (root == null || targetItem == null)
if (childContainer == null) return null;
continue;
var directContainer = root.ItemContainerGenerator.ContainerFromItem(targetItem) as TreeViewItem;
childContainer.UpdateLayout(); if (directContainer != null)
var result = GetTreeViewItemContainer(childContainer, targetItem); return directContainer;
if (result != null)
return result; foreach (var child in root.Items)
} {
var childContainer = root.ItemContainerGenerator.ContainerFromItem(child) as TreeViewItem;
return null; if (childContainer == null)
} continue;
#endregion childContainer.UpdateLayout();
var result = GetTreeViewItemContainer(childContainer, targetItem);
#region Template and Scroll Handling if (result != null)
return result;
private void EnsureTemplateParts() }
{
if (treeViewControl == null) return null;
{ }
treeViewControl = ComboBoxControl.Template.FindName("PART_TreeView", ComboBoxControl) as TreeView;
if (treeViewControl != null) #endregion
{
treeViewControl.SelectedItemChanged += TreeViewControl_SelectedItemChanged; #region Template and Scroll Handling
treeViewControl.ItemsSource = VisibleItems;
} private void EnsureTemplateParts()
} {
if (treeViewControl == null)
if (searchTextBox == null) {
{ treeViewControl = ComboBoxControl.Template.FindName("PART_TreeView", ComboBoxControl) as TreeView;
searchTextBox = ComboBoxControl.Template.FindName("PART_SearchTextBox", ComboBoxControl) as TextBox; if (treeViewControl != null)
if (searchTextBox != null) {
searchTextBox.TextChanged += SearchTextBox_TextChanged; treeViewControl.SelectedItemChanged += TreeViewControl_SelectedItemChanged;
} treeViewControl.ItemsSource = VisibleItems;
}
if (itemsScrollViewer == null) }
{
itemsScrollViewer = ComboBoxControl.Template.FindName("PART_ItemsScrollViewer", ComboBoxControl) as ScrollViewer; if (searchTextBox == null)
} {
} searchTextBox = ComboBoxControl.Template.FindName("PART_SearchTextBox", ComboBoxControl) as TextBox;
if (searchTextBox != null)
private void ItemsScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) searchTextBox.TextChanged += SearchTextBox_TextChanged;
{ }
var scroller = itemsScrollViewer ?? sender as ScrollViewer;
if (scroller == null || scroller.ScrollableHeight <= 0) if (itemsScrollViewer == null)
return; {
itemsScrollViewer = ComboBoxControl.Template.FindName("PART_ItemsScrollViewer", ComboBoxControl) as ScrollViewer;
var lines = SystemParameters.WheelScrollLines; }
if (lines < 0) }
{
if (e.Delta < 0) private void ItemsScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
scroller.PageDown(); {
else var scroller = itemsScrollViewer ?? sender as ScrollViewer;
scroller.PageUp(); if (scroller == null || scroller.ScrollableHeight <= 0)
e.Handled = true; return;
return;
} var lines = SystemParameters.WheelScrollLines;
if (lines < 0)
if (lines == 0) {
{ if (e.Delta < 0)
scroller.ScrollToVerticalOffset(scroller.VerticalOffset - e.Delta); scroller.PageDown();
e.Handled = true; else
return; scroller.PageUp();
} e.Handled = true;
return;
var direction = e.Delta < 0 ? 1 : -1; }
var stepCount = Math.Max(1, Math.Abs(e.Delta) / 120) * lines;
for (var i = 0; i < stepCount; i++) if (lines == 0)
{ {
if (direction > 0) scroller.ScrollToVerticalOffset(scroller.VerticalOffset - e.Delta);
scroller.LineDown(); e.Handled = true;
else return;
scroller.LineUp(); }
}
var direction = e.Delta < 0 ? 1 : -1;
e.Handled = true; var stepCount = Math.Max(1, Math.Abs(e.Delta) / 120) * lines;
} for (var i = 0; i < stepCount; i++)
{
#endregion if (direction > 0)
scroller.LineDown();
#region Keyboard else
scroller.LineUp();
protected override void OnPreviewKeyDown(KeyEventArgs e) }
{
base.OnPreviewKeyDown(e); e.Handled = true;
}
if (!IsEnabled)
return; #endregion
if (!ComboBoxControl.IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Down || e.Key == Key.Space)) #region Keyboard
{
ComboBoxControl.IsDropDownOpen = true; protected override void OnPreviewKeyDown(KeyEventArgs e)
e.Handled = true; {
return; base.OnPreviewKeyDown(e);
}
if (!IsEnabled)
if (ComboBoxControl.IsDropDownOpen && e.Key == Key.Escape) return;
{
ComboBoxControl.IsDropDownOpen = false; if (!ComboBoxControl.IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Down || e.Key == Key.Space))
e.Handled = true; {
} ComboBoxControl.IsDropDownOpen = true;
} e.Handled = true;
return;
#endregion }
}
} if (ComboBoxControl.IsDropDownOpen && e.Key == Key.Escape)
{
ComboBoxControl.IsDropDownOpen = false;
e.Handled = true;
}
}
#endregion
}
}

View File

@@ -3,26 +3,26 @@ using FasdDesktopUi.Basics;
using FasdDesktopUi.Basics.Models; using FasdDesktopUi.Basics.Models;
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using WinForms = System.Windows.Forms; using WinForms = System.Windows.Forms;
using static C4IT.Logging.cLogManager; using static C4IT.Logging.cLogManager;
namespace FasdDesktopUi.Pages.TicketCompletion namespace FasdDesktopUi.Pages.TicketCompletion
{ {
public partial class TicketCompletion : Window, IBlurInvoker, INotifyPropertyChanged public partial class TicketCompletion : Window, IBlurInvoker, INotifyPropertyChanged
{ {
private const double MinWindowHeightDip = 220d; private const double MinWindowHeightDip = 220d;
private const double WindowWorkingAreaMarginDip = 12d; private const double WindowWorkingAreaMarginDip = 12d;
private const double DialogNonContentReserveDip = 180d; private const double DialogNonContentReserveDip = 180d;
private bool isUpdatingDialogBounds; private bool isUpdatingDialogBounds;
private bool isCanceled = false; private bool isCanceled = false;
private bool _WaitForClosing = false; private bool _WaitForClosing = false;
public bool WaitForClosing public bool WaitForClosing
@@ -49,31 +49,31 @@ namespace FasdDesktopUi.Pages.TicketCompletion
CloseCaseDialogUc.DataProvider = _dataProvider; CloseCaseDialogUc.DataProvider = _dataProvider;
} }
protected override void OnInitialized(EventArgs e) protected override void OnInitialized(EventArgs e)
{ {
base.OnInitialized(e); base.OnInitialized(e);
cFocusInvoker.GotFocus += ElementGotFocus; cFocusInvoker.GotFocus += ElementGotFocus;
cFocusInvoker.LostFocus += ElementLostFocus; cFocusInvoker.LostFocus += ElementLostFocus;
SizeChanged += TicketCompletion_SizeChanged; SizeChanged += TicketCompletion_SizeChanged;
Loaded += TicketCompletion_Loaded; Loaded += TicketCompletion_Loaded;
} }
protected override void OnSourceInitialized(EventArgs e) protected override void OnSourceInitialized(EventArgs e)
{ {
base.OnSourceInitialized(e); base.OnSourceInitialized(e);
UpdateDialogMaxHeightToScreen(); UpdateDialogMaxHeightToScreen();
} }
protected override void OnLocationChanged(EventArgs e) protected override void OnLocationChanged(EventArgs e)
{ {
base.OnLocationChanged(e); base.OnLocationChanged(e);
UpdateDialogMaxHeightToScreen(); UpdateDialogMaxHeightToScreen();
} }
private void TicketCompletion_Loaded(object sender, RoutedEventArgs e) => UpdateDialogMaxHeightToScreen(); private void TicketCompletion_Loaded(object sender, RoutedEventArgs e) => UpdateDialogMaxHeightToScreen();
private void TicketCompletion_SizeChanged(object sender, SizeChangedEventArgs e) => UpdateDialogMaxHeightToScreen(); private void TicketCompletion_SizeChanged(object sender, SizeChangedEventArgs e) => UpdateDialogMaxHeightToScreen();
#region ClosingBusy #region ClosingBusy
@@ -126,98 +126,98 @@ namespace FasdDesktopUi.Pages.TicketCompletion
return null; return null;
} }
#region Close_Click #region Close_Click
private void Close_Click()
{
isCanceled = true;
TrySetDialogResult(null);
Close();
}
private void CloseButton_Click(object sender, InputEventArgs e) => Close_Click(); private void Close_Click()
{
#endregion isCanceled = true;
TrySetDialogResult(null);
private void TrySetDialogResult(bool? result) Close();
{ }
try
{ private void CloseButton_Click(object sender, InputEventArgs e) => Close_Click();
DialogResult = result;
} #endregion
catch (InvalidOperationException)
{ private void TrySetDialogResult(bool? result)
// Window was not shown as dialog; ignore. {
} try
} {
DialogResult = result;
private void UpdateDialogMaxHeightToScreen() }
{ catch (InvalidOperationException)
if (isUpdatingDialogBounds) {
return; // Window was not shown as dialog; ignore.
}
isUpdatingDialogBounds = true; }
try
{ private void UpdateDialogMaxHeightToScreen()
WinForms.Screen screen = null; {
IntPtr currentHandle = new WindowInteropHelper(this).Handle; if (isUpdatingDialogBounds)
return;
if (currentHandle != IntPtr.Zero)
{ isUpdatingDialogBounds = true;
screen = WinForms.Screen.FromHandle(currentHandle); try
} {
else if (Owner != null) WinForms.Screen screen = null;
{ IntPtr currentHandle = new WindowInteropHelper(this).Handle;
IntPtr ownerHandle = new WindowInteropHelper(Owner).Handle;
if (ownerHandle != IntPtr.Zero) if (currentHandle != IntPtr.Zero)
screen = WinForms.Screen.FromHandle(ownerHandle); {
} screen = WinForms.Screen.FromHandle(currentHandle);
}
screen = screen ?? WinForms.Screen.PrimaryScreen; else if (Owner != null)
var workingArea = screen?.WorkingArea ?? WinForms.Screen.PrimaryScreen.WorkingArea; {
var dpiScaleY = VisualTreeHelper.GetDpi(this).DpiScaleY; IntPtr ownerHandle = new WindowInteropHelper(Owner).Handle;
var safeDpiScaleY = Math.Max(0.1, dpiScaleY); if (ownerHandle != IntPtr.Zero)
var workingAreaTopDip = workingArea.Top / safeDpiScaleY; screen = WinForms.Screen.FromHandle(ownerHandle);
var workingAreaBottomDip = workingArea.Bottom / safeDpiScaleY; }
var workingAreaHeightDip = workingArea.Height / safeDpiScaleY;
var availableWindowHeightDip = workingAreaHeightDip - (WindowWorkingAreaMarginDip * 2); screen = screen ?? WinForms.Screen.PrimaryScreen;
var workingArea = screen?.WorkingArea ?? WinForms.Screen.PrimaryScreen.WorkingArea;
MaxHeight = Math.Max(MinWindowHeightDip, availableWindowHeightDip); var dpiScaleY = VisualTreeHelper.GetDpi(this).DpiScaleY;
var safeDpiScaleY = Math.Max(0.1, dpiScaleY);
if (!double.IsNaN(Top)) var workingAreaTopDip = workingArea.Top / safeDpiScaleY;
{ var workingAreaBottomDip = workingArea.Bottom / safeDpiScaleY;
var minTop = workingAreaTopDip + WindowWorkingAreaMarginDip; var workingAreaHeightDip = workingArea.Height / safeDpiScaleY;
var maxBottom = workingAreaBottomDip - WindowWorkingAreaMarginDip; var availableWindowHeightDip = workingAreaHeightDip - (WindowWorkingAreaMarginDip * 2);
if (Top < minTop) MaxHeight = Math.Max(MinWindowHeightDip, availableWindowHeightDip);
Top = minTop;
if (!double.IsNaN(Top))
var currentBottom = Top + ActualHeight; {
if (currentBottom > maxBottom) var minTop = workingAreaTopDip + WindowWorkingAreaMarginDip;
Top = Math.Max(minTop, maxBottom - ActualHeight); var maxBottom = workingAreaBottomDip - WindowWorkingAreaMarginDip;
}
if (Top < minTop)
double nonDialogReserve = DialogNonContentReserveDip; Top = minTop;
if (CloseCaseDialogUc != null && CloseCaseDialogUc.IsLoaded)
{ var currentBottom = Top + ActualHeight;
var estimatedReserve = ActualHeight - CloseCaseDialogUc.ActualHeight; if (currentBottom > maxBottom)
if (estimatedReserve > 0) Top = Math.Max(minTop, maxBottom - ActualHeight);
nonDialogReserve = Math.Max(nonDialogReserve, estimatedReserve + WindowWorkingAreaMarginDip); }
}
double nonDialogReserve = DialogNonContentReserveDip;
CloseCaseDialogUc?.SetDialogContentMaxHeight(MaxHeight - nonDialogReserve); if (CloseCaseDialogUc != null && CloseCaseDialogUc.IsLoaded)
} {
catch (Exception ex) var estimatedReserve = ActualHeight - CloseCaseDialogUc.ActualHeight;
{ if (estimatedReserve > 0)
LogException(ex); nonDialogReserve = Math.Max(nonDialogReserve, estimatedReserve + WindowWorkingAreaMarginDip);
} }
finally
{ CloseCaseDialogUc?.SetDialogContentMaxHeight(MaxHeight - nonDialogReserve);
isUpdatingDialogBounds = false; }
} catch (Exception ex)
} {
LogException(ex);
#region Internal Focus Events }
finally
{
isUpdatingDialogBounds = false;
}
}
#region Internal Focus Events
private void ElementGotFocus(object sender, EventArgs e) private void ElementGotFocus(object sender, EventArgs e)
{ {
@@ -285,7 +285,14 @@ namespace FasdDesktopUi.Pages.TicketCompletion
switch (e.Key) switch (e.Key)
{ {
case Key.Escape: case Key.Escape:
if (CloseCaseDialogUc?.TryHandleEscapeKey() == true)
{
e.Handled = true;
return;
}
Close_Click(); Close_Click();
e.Handled = true;
break; break;
} }
} }
@@ -303,15 +310,15 @@ namespace FasdDesktopUi.Pages.TicketCompletion
bool closedSuccessfull = await CloseCaseDialogUc.CloseCaseAsync(_dataProvider.Identities.FirstOrDefault(identity => identity.Class == enumFasdInformationClass.User).Id); bool closedSuccessfull = await CloseCaseDialogUc.CloseCaseAsync(_dataProvider.Identities.FirstOrDefault(identity => identity.Class == enumFasdInformationClass.User).Id);
if (closedSuccessfull) if (closedSuccessfull)
{ {
SuccessPage.SuccessPage successPage = new SuccessPage.SuccessPage(); SuccessPage.SuccessPage successPage = new SuccessPage.SuccessPage();
successPage.Show(); successPage.Show();
await _dataProvider?.CloseCaseAsync(); await _dataProvider?.CloseCaseAsync();
TrySetDialogResult(true); TrySetDialogResult(true);
Close(); Close();
} }
} }
catch (Exception E) catch (Exception E)
{ {
LogException(E); LogException(E);