202 lines
6.3 KiB
C#
202 lines
6.3 KiB
C#
using System.Text.RegularExpressions;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using UtopiaCanteenSystem.ViewModels;
|
|
|
|
namespace UtopiaCanteenSystem.Views;
|
|
|
|
/// <summary>
|
|
/// Minimal code-behind for focus management and Enter-key submission.
|
|
/// </summary>
|
|
public partial class ScannerDashboardView : UserControl
|
|
{
|
|
private bool _isUnloaded;
|
|
private bool _suppressScannerFocus;
|
|
private DispatcherTimer? _focusSuppressionTimer;
|
|
|
|
public ScannerDashboardView()
|
|
{
|
|
InitializeComponent();
|
|
Loaded += OnLoaded;
|
|
Unloaded += OnUnloaded;
|
|
}
|
|
|
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
|
{
|
|
_isUnloaded = false;
|
|
FocusRfidInput(selectAll: true, DispatcherPriority.Input);
|
|
}
|
|
|
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
|
{
|
|
_isUnloaded = true;
|
|
|
|
// Clean up timer
|
|
_focusSuppressionTimer?.Stop();
|
|
_focusSuppressionTimer = null;
|
|
}
|
|
|
|
private void FocusRfidInput(bool selectAll, DispatcherPriority priority)
|
|
{
|
|
if (_isUnloaded)
|
|
return;
|
|
|
|
Dispatcher.BeginInvoke(() =>
|
|
{
|
|
if (_isUnloaded || !IsVisible || !IsEnabled)
|
|
return;
|
|
|
|
// Don't auto-focus scanner input if focus suppression is active
|
|
if (_suppressScannerFocus)
|
|
return;
|
|
|
|
// Don't auto-focus scanner input if the Order History modal is open
|
|
if (DataContext is ScannerDashboardViewModel vm && vm.IsOrderHistoryModalOpen)
|
|
return;
|
|
|
|
RfidInputTextBox.Focus();
|
|
Keyboard.Focus(RfidInputTextBox);
|
|
|
|
if (selectAll)
|
|
RfidInputTextBox.SelectAll();
|
|
}, priority);
|
|
}
|
|
|
|
private void RfidInputTextBox_OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
|
{
|
|
// Don't auto-focus scanner input if focus suppression is active
|
|
if (_suppressScannerFocus)
|
|
return;
|
|
|
|
// Don't auto-focus scanner input if the Order History modal is open
|
|
if (DataContext is ScannerDashboardViewModel vm && vm.IsOrderHistoryModalOpen)
|
|
return;
|
|
|
|
// If focus moved into the menu panel (e.g. Site number field), don't steal it back.
|
|
if (IsDescendantOf(MenuPanel, e.NewFocus as DependencyObject))
|
|
return;
|
|
|
|
// SIMPLE FIX: Just add a small delay before refocusing
|
|
// This gives buttons time to process their click events
|
|
DispatcherTimer focusTimer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(150) // 150ms is enough for click to process
|
|
};
|
|
|
|
focusTimer.Tick += (s, args) =>
|
|
{
|
|
focusTimer.Stop();
|
|
|
|
// Check conditions again after delay
|
|
if (_suppressScannerFocus)
|
|
return;
|
|
|
|
if (DataContext is ScannerDashboardViewModel viewModel && viewModel.IsOrderHistoryModalOpen)
|
|
return;
|
|
|
|
// Only refocus if we're not already focused somewhere else
|
|
// If modal opened, it will have focus now
|
|
if (Keyboard.FocusedElement != RfidInputTextBox)
|
|
{
|
|
FocusRfidInput(selectAll: false, DispatcherPriority.ApplicationIdle);
|
|
}
|
|
};
|
|
|
|
focusTimer.Start();
|
|
}
|
|
|
|
private static bool IsDescendantOf(DependencyObject? ancestor, DependencyObject? element)
|
|
{
|
|
if (ancestor == null || element == null) return false;
|
|
while (element != null)
|
|
{
|
|
if (element == ancestor) return true;
|
|
element = VisualTreeHelper.GetParent(element);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void RfidInputTextBox_OnKeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.Key != Key.Enter)
|
|
return;
|
|
|
|
if (DataContext is ScannerDashboardViewModel vm && vm.ScanCommand.CanExecute(null))
|
|
{
|
|
vm.ScanCommand.Execute(null);
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void SiteNumberTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
|
{
|
|
e.Handled = !Regex.IsMatch(e.Text, @"^\d+$");
|
|
}
|
|
|
|
private void MenuToggleButton_Unchecked(object sender, RoutedEventArgs e)
|
|
{
|
|
// When user closes the menu, focus scanner input so they can scan without clicking it.
|
|
FocusRfidInput(selectAll: false, DispatcherPriority.ApplicationIdle);
|
|
}
|
|
|
|
private void OrderHistoryModalOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
// Only close if the user actually clicked the dark overlay background,
|
|
// not when the event is bubbling from inside the dialog content.
|
|
if (!ReferenceEquals(e.OriginalSource, sender))
|
|
return;
|
|
|
|
CloseOrderHistoryModal();
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void OrderHistoryModalContent_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
// No special behavior required here for now; this hook exists to satisfy XAML and
|
|
// is a good place to move focus inside the dialog if needed later.
|
|
}
|
|
|
|
private void OrderHistoryModalClose_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
CloseOrderHistoryModal();
|
|
}
|
|
|
|
private void CloseOrderHistoryModal()
|
|
{
|
|
if (DataContext is ScannerDashboardViewModel vm)
|
|
{
|
|
vm.IsOrderHistoryModalOpen = false;
|
|
// Suppress scanner focus temporarily after closing modal
|
|
SuppressScannerFocusTemporarily();
|
|
}
|
|
}
|
|
|
|
private void SuppressScannerFocusTemporarily()
|
|
{
|
|
_suppressScannerFocus = true;
|
|
|
|
// Clear any existing timer
|
|
_focusSuppressionTimer?.Stop();
|
|
|
|
// Create a timer to clear suppression after UI settles
|
|
_focusSuppressionTimer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(300) // Short delay to let UI settle
|
|
};
|
|
_focusSuppressionTimer.Tick += (s, e) =>
|
|
{
|
|
_suppressScannerFocus = false;
|
|
_focusSuppressionTimer.Stop();
|
|
_focusSuppressionTimer = null;
|
|
|
|
// Now restore scanner focus after suppression period
|
|
FocusRfidInput(selectAll: false, DispatcherPriority.ApplicationIdle);
|
|
};
|
|
_focusSuppressionTimer.Start();
|
|
}
|
|
}
|
|
|