From 5b26a03ef00328ef8259eac70140e44912e7a357 Mon Sep 17 00:00:00 2001 From: Ravindu Gajanayaka Date: Mon, 9 Mar 2026 09:22:49 +0100 Subject: [PATCH 1/9] implemented full keyboard navigation support --- src/Presentation/QBD.WPF/MainWindow.xaml | 8 ++ src/Presentation/QBD.WPF/MainWindow.xaml.cs | 112 ++++++++++++++++++ .../QBD.WPF/Themes/DataTemplates.xaml | 53 ++++++--- 3 files changed, 155 insertions(+), 18 deletions(-) diff --git a/src/Presentation/QBD.WPF/MainWindow.xaml b/src/Presentation/QBD.WPF/MainWindow.xaml index 38ac16e..db29e72 100644 --- a/src/Presentation/QBD.WPF/MainWindow.xaml +++ b/src/Presentation/QBD.WPF/MainWindow.xaml @@ -11,6 +11,14 @@ + + + + + + + + new RelayCommand(obj => _navigationService.OpenHomePage()); + public System.Windows.Input.ICommand FocusSearchCommand => new RelayCommand(obj => FocusSearch()); + public System.Windows.Input.ICommand CloseCurrentTabCommand => new RelayCommand(obj => CloseCurrentTab()); + public System.Windows.Input.ICommand SaveCommand => new RelayCommand(obj => ExecuteGlobalSave()); + public System.Windows.Input.ICommand NewCommand => new RelayCommand(obj => ExecuteGlobalNew()); + public MainWindow(INavigationService navigationService, HomePageViewModel homePageViewModel) { InitializeComponent(); _navigationService = navigationService; + this.DataContext = this; + try { var appDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "QBD", "QBD.WPF"); @@ -148,4 +156,108 @@ private void ToggleDarkMode_Click(object sender, RoutedEventArgs e) System.Diagnostics.Debug.WriteLine($"Error saving theme: {ex.Message}"); } } + + private void CloseCurrentTab() + { + if (WorkspaceTabs.SelectedItem != null) + { + CloseTab(WorkspaceTabs.SelectedItem); + } + } + + private void FocusSearch() + { + StatusText.Text = "Searching for search box..."; + + var currentContent = WorkspaceTabs.SelectedContent; + var container = WorkspaceTabs.ItemContainerGenerator.ContainerFromItem(WorkspaceTabs.SelectedItem) as FrameworkElement; + + if (currentContent != null) + { + var searchBox = FindVisualChild(WorkspaceTabs); + + if (searchBox != null) + { + searchBox.Focus(); + System.Windows.Input.Keyboard.Focus(searchBox); + StatusText.Text = "Search box focused."; + } + else + { + StatusText.Text = "No search box found in this view."; + } + } + } + + private void ExecuteGlobalNew() + { + var selectedItem = WorkspaceTabs.SelectedItem; + if (selectedItem != null) + { + try + { + dynamic viewModel = selectedItem; + StatusText.Text = "Opening new entry form..."; + + if (viewModel.NewEntityCommand != null) viewModel.NewEntityCommand.Execute(null); + else if (viewModel.NewItemCommand != null) viewModel.NewItemCommand.Execute(null); + else if (viewModel.NewCommand != null) viewModel.NewCommand.Execute(null); + + (WorkspaceTabs.SelectedContent as FrameworkElement)?.Focus(); + } + catch (Exception ex) + { + StatusText.Text = "New entry not available here."; + System.Diagnostics.Debug.WriteLine($"New error: {ex.Message}"); + } + } + } + + private void ExecuteGlobalSave() + { + var selectedItem = WorkspaceTabs.SelectedItem; + if (selectedItem != null) + { + try + { + dynamic viewModel = selectedItem; + if (viewModel.SaveCommand != null) + { + viewModel.SaveCommand.Execute(null); + StatusText.Text = "Transaction saved successfully (Ctrl+S)."; + + (WorkspaceTabs.SelectedContent as FrameworkElement)?.Focus(); + } + } + catch (Exception ex) + { + StatusText.Text = "Save failed or not supported."; + System.Diagnostics.Debug.WriteLine($"Save error: {ex.Message}"); + } + } + } + + private T? FindVisualChild(DependencyObject? obj) where T : DependencyObject + { + if (obj == null) return null; + + for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(obj); i++) + { + var child = System.Windows.Media.VisualTreeHelper.GetChild(obj, i); + if (child != null && child is T t) return t; + + var childOfChild = FindVisualChild(child); + if (childOfChild != null) return childOfChild; + } + return null; + } +} + +public class RelayCommand : System.Windows.Input.ICommand +{ + private readonly Action _execute; + public RelayCommand(Action execute) => _execute = execute; + public bool CanExecute(object? parameter) => true; + public void Execute(object? parameter) => _execute(parameter); + public event EventHandler? CanExecuteChanged { add { } remove { } } } \ No newline at end of file diff --git a/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml b/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml index a2a5563..732ed61 100644 --- a/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml +++ b/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml @@ -107,8 +107,8 @@ - diff --git a/src/Presentation/QBD.WPF/MainWindow.xaml.cs b/src/Presentation/QBD.WPF/MainWindow.xaml.cs index a60f06e..e2c31e0 100644 --- a/src/Presentation/QBD.WPF/MainWindow.xaml.cs +++ b/src/Presentation/QBD.WPF/MainWindow.xaml.cs @@ -20,6 +20,7 @@ public partial class MainWindow : Window public ICommand CloseCurrentTabCommand { get; } public ICommand SaveCommand { get; } public ICommand NewCommand { get; } + public ICommand ShowKeyboardShortcutsCommand { get; } public MainWindow(INavigationService navigationService, HomePageViewModel homePageViewModel) { @@ -30,6 +31,7 @@ public MainWindow(INavigationService navigationService, HomePageViewModel homePa CloseCurrentTabCommand = new ActionCommand(() => CloseCurrentTab()); SaveCommand = new ActionCommand(() => ExecuteGlobalSave()); NewCommand = new ActionCommand(() => ExecuteGlobalNew()); + ShowKeyboardShortcutsCommand = new ActionCommand(() => ShowKeyboardShortcuts()); InitializeComponent(); @@ -126,6 +128,101 @@ private void CloseTab_Click(object sender, RoutedEventArgs e) } private void CloseAllTabs_Click(object sender, RoutedEventArgs e) => CloseAllTabs(); + private void KeyboardShortcuts_Click(object sender, RoutedEventArgs e) => ShowKeyboardShortcuts(); + + private void ShowKeyboardShortcuts() + { + var shortcuts = new Window + { + Title = "Keyboard Shortcuts", + Width = 450, + Height = 480, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + Owner = this, + ResizeMode = ResizeMode.NoResize, + Background = (System.Windows.Media.Brush)FindResource("ThemeWindowBackground") + }; + + var grid = new Grid { Margin = new Thickness(20) }; + grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); + grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + + var header = new TextBlock + { + Text = "Keyboard Shortcuts", + FontSize = 18, + FontWeight = FontWeights.Bold, + Margin = new Thickness(0, 0, 0, 12), + Foreground = (System.Windows.Media.Brush)FindResource("ThemeForeground") + }; + Grid.SetRow(header, 0); + + var items = new[] + { + ("F1", "Show this help dialog"), + ("F5", "Go to Home Page"), + ("Ctrl+N", "New transaction (context-aware)"), + ("Ctrl+S", "Save current form"), + ("Ctrl+F", "Focus search box"), + ("Ctrl+A", "Chart of Accounts"), + ("Esc", "Close current tab"), + ("Tab", "Move to next field"), + ("Shift+Tab", "Move to previous field"), + ("Alt+S", "Save (in forms)"), + ("Alt+P", "Save & Post (in forms)"), + ("Alt+L", "Clear (in forms)"), + ("Alt+V", "Void (in forms)"), + ("Alt+R", "Print (in forms)"), + ("Alt+N", "New (in lists)"), + ("Alt+E", "Edit (in lists)"), + ("Alt+D", "Delete (in lists)"), + }; + + var listView = new ListView + { + BorderThickness = new Thickness(1), + BorderBrush = (System.Windows.Media.Brush)FindResource("ThemeBorderBrush"), + Background = (System.Windows.Media.Brush)FindResource("ThemeControlBackground") + }; + + var gridView = new GridView(); + gridView.Columns.Add(new GridViewColumn + { + Header = "Shortcut", + Width = 120, + DisplayMemberBinding = new System.Windows.Data.Binding("Item1") + }); + gridView.Columns.Add(new GridViewColumn + { + Header = "Action", + Width = 280, + DisplayMemberBinding = new System.Windows.Data.Binding("Item2") + }); + listView.View = gridView; + + foreach (var item in items) + listView.Items.Add(item); + + Grid.SetRow(listView, 1); + + var closeBtn = new Button + { + Content = "Close", + Padding = new Thickness(20, 6, 20, 6), + HorizontalAlignment = HorizontalAlignment.Right, + Margin = new Thickness(0, 12, 0, 0) + }; + closeBtn.Click += (_, _) => shortcuts.Close(); + Grid.SetRow(closeBtn, 2); + + grid.Children.Add(header); + grid.Children.Add(listView); + grid.Children.Add(closeBtn); + shortcuts.Content = grid; + shortcuts.ShowDialog(); + } + private void About_Click(object sender, RoutedEventArgs e) { MessageBox.Show("QuickBooks Desktop Enterprise Clone\nVersion 1.0\n\nA full-featured accounting application.", "About", MessageBoxButton.OK, MessageBoxImage.Information); From 68990990ad35d29311302cde5b4776cc515a1d2b Mon Sep 17 00:00:00 2001 From: Ravindu Gajanayaka Date: Mon, 9 Mar 2026 10:23:13 +0100 Subject: [PATCH 5/9] enabled access keys --- .../QBD.WPF/Themes/DataTemplates.xaml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml b/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml index 4422e79..3ac50a6 100644 --- a/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml +++ b/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml @@ -111,8 +111,8 @@