Phase 2-3: UI layout, theme switching, CRUD tools, process execution
- Phase 2: MainWindow 3-section layout (sidebar/content/log bar), Dark/Light theme with ThemeHelper, MainViewModel with ObservableProperty/RelayCommand, tool card filtering by search + category - Phase 3: ToolEditWindow for add/edit tools, ProcessExecutionService (Process.Start + error handling), double-click + right-click context menu (run/edit), path browse dialog - Bugfix: ContextMenu commands now use PlacementTarget.Tag binding (ContextMenu in separate visual tree) - Bugfix: StaticResource converters moved to XAML before DataTemplate to fix XamlParseException on tool card render - Bugfix: Pure filenames (no path separators) treated as PATH commands, not marked invalid - Bugfix: RefreshData preserves SelectedCategory; Load() catches all exceptions; Save() wrapped in try-catch; auto-scroll log to newest entry - Tests: xUnit project with 55 tests covering models, services, converters, and view models
This commit is contained in:
@@ -1,11 +1,63 @@
|
||||
using System.Windows;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using PersonalToolBox.ViewModels;
|
||||
|
||||
namespace PersonalToolBox.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
private readonly MainViewModel _viewModel;
|
||||
|
||||
public MainWindow(MainViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_viewModel = viewModel;
|
||||
DataContext = viewModel;
|
||||
|
||||
viewModel.Logs.CollectionChanged += OnLogsCollectionChanged;
|
||||
}
|
||||
|
||||
private void OnLogsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Add && LogListBox.Items.Count > 0)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
LogListBox.ScrollIntoView(LogListBox.Items[^1]);
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bool → double: true=1.0, false=0.4 (工具卡片失效时置灰)
|
||||
/// </summary>
|
||||
public class BoolToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is bool b && b ? 1.0 : 0.4;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string → string: 取字符串首字符作为图标占位
|
||||
/// </summary>
|
||||
public class FirstCharConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var s = value as string;
|
||||
return string.IsNullOrEmpty(s) ? "?" : s[0].ToString();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user