refactor(P5b-7): 잔여 이벤트 핸들러 전부 커맨드/behavior로 — XAML 이벤트 핸들러 0개
Microsoft.Xaml.Behaviors.Wpf 도입. MainWindow.xaml에 직접 이벤트 핸들러(Click/SelectionChanged/Drop/ MouseUp 등) 0개 — 전부 선언적 바인딩·커맨드·InvokeCommandAction으로 전환. - 드래그앤드롭(DragOver/DragLeave/Drop) → i:EventTrigger + InvokeCommandAction(PassEventArgsToCommand), HandleDragOver/HandleFilesDropped(DragEventArgs) 추출. - 동적 포맷 콤보 SelectionChanged → OutputFormatChangedCommand(OnOutputFormatSelected). - 큐·이력 행 PreviewMouseLeftButtonUp → QueueRow/PastRowCommand(MouseButtonEventArgs, IsInsideButton 가드 보존 + e.OriginalSource.DataContext로 항목 해소). - ProcessQueueButton Click → ProcessQueueCommand. - 검증: 빌드 0/0, 83 테스트 그린, publish 후 메인 창 크래시 없이 로드(behaviors self-contained 정상). 드래그앤드롭·행 미리보기·포맷 변경 실동작은 GUI 스모크 권장(비치명적 실패 모드).
This commit is contained in:
parent
ccba168f9a
commit
944187c314
4 changed files with 58 additions and 25 deletions
|
|
@ -32,6 +32,7 @@
|
||||||
<!-- App -->
|
<!-- App -->
|
||||||
<PackageVersion Include="WPF-UI" Version="4.3.0" />
|
<PackageVersion Include="WPF-UI" Version="4.3.0" />
|
||||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||||
|
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||||
|
|
||||||
<!-- Tests -->
|
<!-- Tests -->
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="WPF-UI" />
|
<PackageReference Include="WPF-UI" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
|
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
Title="FormatShift Utility"
|
Title="FormatShift Utility"
|
||||||
Icon="pack://application:,,,/Assets/app-icon.png"
|
Icon="pack://application:,,,/Assets/app-icon.png"
|
||||||
Width="1280" Height="960"
|
Width="1280" Height="960"
|
||||||
|
|
@ -11,9 +12,6 @@
|
||||||
WindowCornerPreference="Round"
|
WindowCornerPreference="Round"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
AllowDrop="True"
|
AllowDrop="True"
|
||||||
Drop="OnFilesDropped"
|
|
||||||
DragOver="OnDragOver"
|
|
||||||
DragLeave="OnDragLeave"
|
|
||||||
TextOptions.TextFormattingMode="Display"
|
TextOptions.TextFormattingMode="Display"
|
||||||
UseLayoutRounding="True">
|
UseLayoutRounding="True">
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
|
|
@ -31,6 +29,18 @@
|
||||||
<KeyBinding Key="F5" Command="{Binding RefreshCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
<KeyBinding Key="F5" Command="{Binding RefreshCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
||||||
</Window.InputBindings>
|
</Window.InputBindings>
|
||||||
|
|
||||||
|
<i:Interaction.Triggers>
|
||||||
|
<i:EventTrigger EventName="DragOver">
|
||||||
|
<i:InvokeCommandAction Command="{Binding DragOverCommand, RelativeSource={RelativeSource AncestorType=Window}}" PassEventArgsToCommand="True"/>
|
||||||
|
</i:EventTrigger>
|
||||||
|
<i:EventTrigger EventName="DragLeave">
|
||||||
|
<i:InvokeCommandAction Command="{Binding DragLeaveCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
||||||
|
</i:EventTrigger>
|
||||||
|
<i:EventTrigger EventName="Drop">
|
||||||
|
<i:InvokeCommandAction Command="{Binding DropCommand, RelativeSource={RelativeSource AncestorType=Window}}" PassEventArgsToCommand="True"/>
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
|
|
||||||
<Grid Background="{StaticResource FsBgBase}">
|
<Grid Background="{StaticResource FsBgBase}">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="32"/>
|
<RowDefinition Height="32"/>
|
||||||
|
|
@ -97,8 +107,13 @@
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
</Border>
|
</Border>
|
||||||
<ComboBox x:Name="OutputFormatCombo" Grid.Column="1"
|
<ComboBox x:Name="OutputFormatCombo" Grid.Column="1"
|
||||||
Margin="8,0,0,0" VerticalAlignment="Center"
|
Margin="8,0,0,0" VerticalAlignment="Center">
|
||||||
SelectionChanged="OnOutputFormatChanged"/>
|
<i:Interaction.Triggers>
|
||||||
|
<i:EventTrigger EventName="SelectionChanged">
|
||||||
|
<i:InvokeCommandAction Command="{Binding OutputFormatChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
|
</ComboBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
<TextBlock x:Name="OutputFormatHint" Margin="0,6,0,0"
|
<TextBlock x:Name="OutputFormatHint" Margin="0,6,0,0"
|
||||||
Style="{StaticResource FsCaptionStyle}"
|
Style="{StaticResource FsCaptionStyle}"
|
||||||
|
|
@ -315,7 +330,7 @@
|
||||||
Content="Idle — drop files to begin"
|
Content="Idle — drop files to begin"
|
||||||
Style="{StaticResource FsPrimaryButtonStyle}"
|
Style="{StaticResource FsPrimaryButtonStyle}"
|
||||||
IsEnabled="False"
|
IsEnabled="False"
|
||||||
Click="OnProcessQueueClick"/>
|
Command="{Binding ProcessQueueCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -456,9 +471,12 @@
|
||||||
Background="{StaticResource FsBgPanel}"
|
Background="{StaticResource FsBgPanel}"
|
||||||
BorderBrush="{StaticResource FsBorderSubtle}"
|
BorderBrush="{StaticResource FsBorderSubtle}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Cursor="Hand"
|
Cursor="Hand">
|
||||||
PreviewMouseLeftButtonUp="OnQueueRowClick"
|
<i:Interaction.Triggers>
|
||||||
Tag="{Binding}">
|
<i:EventTrigger EventName="PreviewMouseLeftButtonUp">
|
||||||
|
<i:InvokeCommandAction Command="{Binding QueueRowCommand, RelativeSource={RelativeSource AncestorType=Window}}" PassEventArgsToCommand="True"/>
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="2"/>
|
<ColumnDefinition Width="2"/>
|
||||||
|
|
@ -570,9 +588,12 @@
|
||||||
<Border BorderBrush="#0DFFFFFF"
|
<Border BorderBrush="#0DFFFFFF"
|
||||||
BorderThickness="0,0,0,1" Padding="0,12"
|
BorderThickness="0,0,0,1" Padding="0,12"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
Background="Transparent"
|
Background="Transparent">
|
||||||
PreviewMouseLeftButtonUp="OnPastRowClick"
|
<i:Interaction.Triggers>
|
||||||
Tag="{Binding}">
|
<i:EventTrigger EventName="PreviewMouseLeftButtonUp">
|
||||||
|
<i:InvokeCommandAction Command="{Binding PastRowCommand, RelativeSource={RelativeSource AncestorType=Window}}" PassEventArgsToCommand="True"/>
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="40"/>
|
<ColumnDefinition Width="40"/>
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
public ICommand TabCommand { get; }
|
public ICommand TabCommand { get; }
|
||||||
public ICommand ConflictRuleCommand { get; }
|
public ICommand ConflictRuleCommand { get; }
|
||||||
public ICommand CombineToggleCommand { get; }
|
public ICommand CombineToggleCommand { get; }
|
||||||
|
public ICommand OutputFormatChangedCommand { get; }
|
||||||
|
public ICommand DragOverCommand { get; }
|
||||||
|
public ICommand DragLeaveCommand { get; }
|
||||||
|
public ICommand DropCommand { get; }
|
||||||
|
public ICommand QueueRowCommand { get; }
|
||||||
|
public ICommand PastRowCommand { get; }
|
||||||
|
|
||||||
public MainWindow(ConversionEngine engine, ISettingsStore settings, IReadOnlyList<string>? initialFiles = null)
|
public MainWindow(ConversionEngine engine, ISettingsStore settings, IReadOnlyList<string>? initialFiles = null)
|
||||||
{
|
{
|
||||||
|
|
@ -78,6 +84,12 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
TabCommand = new RelayCommand(p => ShowTab(p as string ?? "Past"));
|
TabCommand = new RelayCommand(p => ShowTab(p as string ?? "Past"));
|
||||||
ConflictRuleCommand = new RelayCommand(p => SetConflictRule(p as string));
|
ConflictRuleCommand = new RelayCommand(p => SetConflictRule(p as string));
|
||||||
CombineToggleCommand = new RelayCommand(_ => UpdateCombineState(SelectedOutputExtension));
|
CombineToggleCommand = new RelayCommand(_ => UpdateCombineState(SelectedOutputExtension));
|
||||||
|
OutputFormatChangedCommand = new RelayCommand(_ => OnOutputFormatSelected());
|
||||||
|
DragOverCommand = new RelayCommand(p => HandleDragOver(p as DragEventArgs));
|
||||||
|
DragLeaveCommand = new RelayCommand(_ => DropHintOverlay.Visibility = Visibility.Collapsed);
|
||||||
|
DropCommand = new RelayCommand(p => HandleFilesDropped(p as DragEventArgs));
|
||||||
|
QueueRowCommand = new RelayCommand(p => HandleQueueRowClick(p as MouseButtonEventArgs));
|
||||||
|
PastRowCommand = new RelayCommand(p => HandlePastRowClick(p as MouseButtonEventArgs));
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
|
@ -164,8 +176,9 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
|
|
||||||
// ============== Drag & Drop ==============
|
// ============== Drag & Drop ==============
|
||||||
|
|
||||||
private void OnDragOver(object sender, DragEventArgs e)
|
private void HandleDragOver(DragEventArgs? e)
|
||||||
{
|
{
|
||||||
|
if (e is null) return;
|
||||||
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop)
|
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop)
|
||||||
? DragDropEffects.Copy : DragDropEffects.None;
|
? DragDropEffects.Copy : DragDropEffects.None;
|
||||||
DropHintOverlay.Visibility = e.Effects == DragDropEffects.Copy
|
DropHintOverlay.Visibility = e.Effects == DragDropEffects.Copy
|
||||||
|
|
@ -173,15 +186,10 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDragLeave(object sender, DragEventArgs e)
|
private void HandleFilesDropped(DragEventArgs? e)
|
||||||
{
|
{
|
||||||
DropHintOverlay.Visibility = Visibility.Collapsed;
|
DropHintOverlay.Visibility = Visibility.Collapsed;
|
||||||
}
|
if (e is null || !e.Data.GetDataPresent(DataFormats.FileDrop)) return;
|
||||||
|
|
||||||
private void OnFilesDropped(object sender, DragEventArgs e)
|
|
||||||
{
|
|
||||||
DropHintOverlay.Visibility = Visibility.Collapsed;
|
|
||||||
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
|
|
||||||
if (e.Data.GetData(DataFormats.FileDrop) is not string[] paths) return;
|
if (e.Data.GetData(DataFormats.FileDrop) is not string[] paths) return;
|
||||||
|
|
||||||
AddToQueue(ExpandPaths(paths));
|
AddToQueue(ExpandPaths(paths));
|
||||||
|
|
@ -308,20 +316,22 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
private string? _selectedPreviewPath;
|
private string? _selectedPreviewPath;
|
||||||
private CancellationTokenSource? _previewCts;
|
private CancellationTokenSource? _previewCts;
|
||||||
|
|
||||||
private async void OnQueueRowClick(object sender, MouseButtonEventArgs e)
|
private async void HandleQueueRowClick(MouseButtonEventArgs? e)
|
||||||
{
|
{
|
||||||
|
if (e is null) return;
|
||||||
if (e.OriginalSource is DependencyObject src && IsInsideButton(src)) return;
|
if (e.OriginalSource is DependencyObject src && IsInsideButton(src)) return;
|
||||||
if (sender is not FrameworkElement fe || fe.Tag is not QueueItem item) return;
|
if ((e.OriginalSource as FrameworkElement)?.DataContext is not QueueItem item) return;
|
||||||
|
|
||||||
_selectedPreviewItem = item;
|
_selectedPreviewItem = item;
|
||||||
await LoadPreviewAsync(item);
|
await LoadPreviewAsync(item);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnPastRowClick(object sender, MouseButtonEventArgs e)
|
private async void HandlePastRowClick(MouseButtonEventArgs? e)
|
||||||
{
|
{
|
||||||
|
if (e is null) return;
|
||||||
if (e.OriginalSource is DependencyObject src && IsInsideButton(src)) return;
|
if (e.OriginalSource is DependencyObject src && IsInsideButton(src)) return;
|
||||||
if (sender is not FrameworkElement fe || fe.Tag is not HistoryRow row) return;
|
if ((e.OriginalSource as FrameworkElement)?.DataContext is not HistoryRow row) return;
|
||||||
|
|
||||||
if (row.SourcePath == "<demo>")
|
if (row.SourcePath == "<demo>")
|
||||||
{
|
{
|
||||||
|
|
@ -1036,7 +1046,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnOutputFormatChanged(object sender, SelectionChangedEventArgs e)
|
private void OnOutputFormatSelected()
|
||||||
{
|
{
|
||||||
if (_suppressFormatChanged) return;
|
if (_suppressFormatChanged) return;
|
||||||
if (OutputFormatCombo.SelectedItem is ComboBoxItem item && item.Tag is string ext)
|
if (OutputFormatCombo.SelectedItem is ComboBoxItem item && item.Tag is string ext)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue