diff --git a/src/Presentation/QBD.WPF/MainWindow.xaml b/src/Presentation/QBD.WPF/MainWindow.xaml
index 38ac16e..cacdd78 100644
--- a/src/Presentation/QBD.WPF/MainWindow.xaml
+++ b/src/Presentation/QBD.WPF/MainWindow.xaml
@@ -11,6 +11,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Presentation/QBD.WPF/MainWindow.xaml.cs b/src/Presentation/QBD.WPF/MainWindow.xaml.cs
index 03fe618..e2c31e0 100644
--- a/src/Presentation/QBD.WPF/MainWindow.xaml.cs
+++ b/src/Presentation/QBD.WPF/MainWindow.xaml.cs
@@ -1,7 +1,9 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Input;
using QBD.Application.Interfaces;
using QBD.Application.ViewModels;
@@ -13,18 +15,32 @@ public partial class MainWindow : Window
private bool _isDarkMode = false;
private string _themeConfigFile = string.Empty;
+ public ICommand HomePageCommand { get; }
+ public ICommand FocusSearchCommand { get; }
+ public ICommand CloseCurrentTabCommand { get; }
+ public ICommand SaveCommand { get; }
+ public ICommand NewCommand { get; }
+ public ICommand ShowKeyboardShortcutsCommand { get; }
+
public MainWindow(INavigationService navigationService, HomePageViewModel homePageViewModel)
{
- InitializeComponent();
_navigationService = navigationService;
+ HomePageCommand = new ActionCommand(() => _navigationService.OpenHomePage());
+ FocusSearchCommand = new ActionCommand(() => FocusSearch());
+ CloseCurrentTabCommand = new ActionCommand(() => CloseCurrentTab());
+ SaveCommand = new ActionCommand(() => ExecuteGlobalSave());
+ NewCommand = new ActionCommand(() => ExecuteGlobalNew());
+ ShowKeyboardShortcutsCommand = new ActionCommand(() => ShowKeyboardShortcuts());
+
+ InitializeComponent();
+
+ this.DataContext = this;
+
try
{
var appDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "QBD", "QBD.WPF");
- if (!Directory.Exists(appDataDirectory))
- {
- Directory.CreateDirectory(appDataDirectory);
- }
+ if (!Directory.Exists(appDataDirectory)) Directory.CreateDirectory(appDataDirectory);
_themeConfigFile = Path.Combine(appDataDirectory, "theme.cfg");
@@ -51,8 +67,7 @@ public void OpenTab(ViewModelBase viewModel)
{
foreach (var item in WorkspaceTabs.Items)
{
- if (item is ViewModelBase existing && existing.GetType() == viewModel.GetType()
- && existing.Title == viewModel.Title)
+ if (item is ViewModelBase existing && existing.GetType() == viewModel.GetType() && existing.Title == viewModel.Title)
{
WorkspaceTabs.SelectedItem = item;
return;
@@ -62,15 +77,8 @@ public void OpenTab(ViewModelBase viewModel)
WorkspaceTabs.SelectedItem = viewModel;
}
- public void CloseTab(object viewModel)
- {
- WorkspaceTabs.Items.Remove(viewModel);
- }
-
- public void CloseAllTabs()
- {
- WorkspaceTabs.Items.Clear();
- }
+ public void CloseTab(object viewModel) => WorkspaceTabs.Items.Remove(viewModel);
+ public void CloseAllTabs() => WorkspaceTabs.Items.Clear();
private void Exit_Click(object sender, RoutedEventArgs e) => System.Windows.Application.Current.Shutdown();
private void HomePage_Click(object sender, RoutedEventArgs e) => _navigationService.OpenHomePage();
@@ -116,36 +124,243 @@ public void CloseAllTabs()
private void CloseTab_Click(object sender, RoutedEventArgs e)
{
- if (sender is Button btn && btn.DataContext is ViewModelBase vm)
- {
- CloseTab(vm);
- }
+ if (sender is Button btn && btn.DataContext is ViewModelBase vm) CloseTab(vm);
}
-
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);
+ MessageBox.Show("QuickBooks Desktop Enterprise Clone\nVersion 1.0\n\nA full-featured accounting application.", "About", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void ToggleDarkMode_Click(object sender, RoutedEventArgs e)
{
_isDarkMode = !_isDarkMode;
- var app = (App)System.Windows.Application.Current;
- app.ChangeTheme(_isDarkMode);
+ ((App)System.Windows.Application.Current).ChangeTheme(_isDarkMode);
+ try { if (!string.IsNullOrEmpty(_themeConfigFile)) File.WriteAllText(_themeConfigFile, _isDarkMode.ToString()); }
+ catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error saving theme: {ex.Message}"); }
+ }
- try
+ private void CloseCurrentTab()
+ {
+ if (WorkspaceTabs.SelectedItem != null) CloseTab(WorkspaceTabs.SelectedItem);
+ }
+
+ private void FocusSearch()
+ {
+ var contentPresenter = FindVisualChild(WorkspaceTabs);
+ var searchRoot = contentPresenter ?? (DependencyObject)WorkspaceTabs;
+ var searchBox = FindSearchTextBox(searchRoot);
+
+ if (searchBox != null)
+ {
+ searchBox.Focus();
+ Keyboard.Focus(searchBox);
+ StatusText.Text = "Search box focused (Ctrl+F).";
+ }
+ else
{
- if (!string.IsNullOrEmpty(_themeConfigFile))
+ StatusText.Text = "No search box found in this view.";
+ }
+ }
+
+ private void ExecuteGlobalNew()
+ {
+ var selectedItem = WorkspaceTabs.SelectedItem;
+ if (selectedItem == null) return;
+
+ StatusText.Text = "Opening new entry form...";
+
+ var itemType = selectedItem.GetType();
+ string[] commandNames = { "NewEntityCommand", "NewItemCommand", "NewCommand" };
+
+ foreach (var name in commandNames)
+ {
+ var prop = itemType.GetProperty(name);
+ if (prop != null && prop.GetValue(selectedItem) is ICommand command && command.CanExecute(null))
{
- File.WriteAllText(_themeConfigFile, _isDarkMode.ToString());
+ command.Execute(null);
+ (WorkspaceTabs.SelectedContent as FrameworkElement)?.Focus();
+ return;
}
}
- catch (Exception ex)
+ StatusText.Text = "New entry not available here.";
+ }
+
+ private async void ExecuteGlobalSave()
+ {
+ var selectedItem = WorkspaceTabs.SelectedItem;
+ if (selectedItem == null) return;
+
+ var prop = selectedItem.GetType().GetProperty("SaveCommand");
+ if (prop != null && prop.GetValue(selectedItem) is ICommand command && command.CanExecute(null))
+ {
+ StatusText.Text = "Saving...";
+ try
+ {
+ var asyncMethod = command.GetType().GetMethod("ExecuteAsync");
+ if (asyncMethod != null && asyncMethod.Invoke(command, new object?[] { null }) is Task task)
+ {
+ await task;
+ }
+ else
+ {
+ command.Execute(null);
+ }
+ StatusText.Text = "Transaction saved successfully (Ctrl+S).";
+ }
+ catch (Exception ex)
+ {
+ StatusText.Text = "Save failed.";
+ System.Diagnostics.Debug.WriteLine($"Save error: {ex.Message}");
+ }
+
+ (WorkspaceTabs.SelectedContent as FrameworkElement)?.Focus();
+ }
+ else
+ {
+ StatusText.Text = "Save not supported in this view.";
+ }
+ }
+
+ private TextBox? FindSearchTextBox(DependencyObject? obj)
+ {
+ 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 is TextBox tb)
+ {
+ var binding = tb.GetBindingExpression(TextBox.TextProperty);
+ var path = binding?.ParentBinding?.Path?.Path;
+ if (path is "FilterText" or "SearchText")
+ return tb;
+ }
+
+ var result = FindSearchTextBox(child);
+ if (result != null) return result;
+ }
+ return null;
+ }
+
+ 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++)
{
- System.Diagnostics.Debug.WriteLine($"Error saving theme: {ex.Message}");
+ var child = System.Windows.Media.VisualTreeHelper.GetChild(obj, i);
+ if (child is T match) return match;
+
+ var result = FindVisualChild(child);
+ if (result != null) return result;
}
+ return null;
}
+}
+
+public class ActionCommand : ICommand
+{
+ private readonly Action _execute;
+ public ActionCommand(Action execute) => _execute = execute;
+ public bool CanExecute(object? parameter) => true;
+ public void Execute(object? parameter) => _execute();
+ 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..4422e79 100644
--- a/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml
+++ b/src/Presentation/QBD.WPF/Themes/DataTemplates.xaml
@@ -107,8 +107,8 @@
-
+ Width="160" Margin="0,0,4,0" VerticalContentAlignment="Center" TabIndex="1"/>
+
@@ -119,7 +119,7 @@
DisplayMemberPath="DisplayName"
Background="{DynamicResource ThemeControlBackground}"
Foreground="{DynamicResource ThemeForeground}"
- Margin="4" BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderBrush}"/>
+ Margin="4" BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderBrush}" TabIndex="3"/>
@@ -189,13 +189,22 @@
-
-
-
-
+
+
+
+
-
+
+
@@ -241,11 +250,14 @@
+
-
+
+
-
-
+
+
+
@@ -300,14 +312,19 @@
-
-
-
+
+
+
+
+
+
-
+ VerticalContentAlignment="Center" TabIndex="1"/>
+
+
@@ -350,9 +367,11 @@
-
+
+
+