fix: ConvertWindow ItemTemplate XamlReader 실패 + 진단/예외 핸들러
- ConvertWindow의 ItemTemplate을 코드비하인드 XamlReader.Parse 대신 XAML 안에 <ItemsControl.ItemTemplate>으로 인라인 정의. XamlReader는 code-behind 메서드를 wire-up할 수 없어 Click="OnRemoveEntry"가 매번 실패 → ConvertWindow 생성 시 예외 발생, GUI 상태가 깨져 변환 버튼 클릭이 처리되지 않던 근본 원인. - OnConvertClick에 진단 로그(%TEMP%\EverythingToJpeg_dialog.log) 추가 - App에 전역 unhandled exception 핸들러 — DispatcherUnhandled, AppDomain.UnhandledException, TaskScheduler.UnobservedTaskException 모두 %TEMP%\EverythingToJpeg_unhandled.log에 기록 + 메시지박스로 즉시 노출 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e663921460
commit
8065bf7373
3 changed files with 124 additions and 51 deletions
|
|
@ -13,6 +13,8 @@ public partial class App : Application
|
||||||
{
|
{
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
WireGlobalExceptionLogging();
|
||||||
|
|
||||||
var parsed = CliRouter.Parse(e.Args);
|
var parsed = CliRouter.Parse(e.Args);
|
||||||
|
|
||||||
switch (parsed.Mode)
|
switch (parsed.Mode)
|
||||||
|
|
@ -116,4 +118,38 @@ public partial class App : Application
|
||||||
try { File.WriteAllText(logPath, log.ToString()); } catch { }
|
try { File.WriteAllText(logPath, log.ToString()); } catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void WireGlobalExceptionLogging()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Path.GetTempPath(), "EverythingToJpeg_unhandled.log");
|
||||||
|
|
||||||
|
void Append(string source, Exception? ex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.AppendAllText(path,
|
||||||
|
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {source}\n{ex}\n\n");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
|
||||||
|
Append("AppDomain.UnhandledException", e.ExceptionObject as Exception);
|
||||||
|
|
||||||
|
Current.DispatcherUnhandledException += (_, e) =>
|
||||||
|
{
|
||||||
|
Append("Application.DispatcherUnhandledException", e.Exception);
|
||||||
|
MessageBox.Show(
|
||||||
|
"예기치 못한 오류:\n\n" + e.Exception.Message + "\n\n로그: " + path,
|
||||||
|
"EverythingToJpeg",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
e.Handled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
System.Threading.Tasks.TaskScheduler.UnobservedTaskException += (_, e) =>
|
||||||
|
{
|
||||||
|
Append("TaskScheduler.UnobservedTaskException", e.Exception);
|
||||||
|
e.SetObserved();
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,51 @@
|
||||||
<TextBlock Text="대상 파일" Style="{StaticResource TextBodyStrong}"/>
|
<TextBlock Text="대상 파일" Style="{StaticResource TextBodyStrong}"/>
|
||||||
</Border>
|
</Border>
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="20,8,20,16">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="20,8,20,16">
|
||||||
<ItemsControl x:Name="FilesList"/>
|
<ItemsControl x:Name="FilesList">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border Margin="0,0,0,6" Padding="12,8" CornerRadius="6"
|
||||||
|
Background="{DynamicResource SubtleFillColorTransparentBrush}"
|
||||||
|
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="44"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Border Width="40" Height="40" CornerRadius="4"
|
||||||
|
Background="{DynamicResource ControlFillColorDefaultBrush}">
|
||||||
|
<Image Source="{Binding Thumbnail}" Stretch="UniformToFill"/>
|
||||||
|
</Border>
|
||||||
|
<StackPanel Grid.Column="1" Margin="12,0,8,0" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding FileName}" FontWeight="SemiBold"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
<TextBlock Text="{Binding SubText}" FontSize="11"
|
||||||
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
<TextBlock Text="{Binding State}" FontSize="11" Margin="0,2,0,0">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground"
|
||||||
|
Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsFailed}" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="#F87171"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<ui:Button Grid.Column="2" Icon="{ui:SymbolIcon Dismiss20}"
|
||||||
|
Appearance="Transparent" Click="OnRemoveEntry"
|
||||||
|
ToolTip="목록에서 제거"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</ui:Card>
|
</ui:Card>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ public partial class ConvertWindow : FluentWindow
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
FilesList.ItemsSource = _entries;
|
FilesList.ItemsSource = _entries;
|
||||||
FilesList.ItemTemplate = (DataTemplate)CreateFileEntryTemplate();
|
|
||||||
|
|
||||||
AddFiles(initialFiles);
|
AddFiles(initialFiles);
|
||||||
|
|
||||||
|
|
@ -113,19 +112,51 @@ public partial class ConvertWindow : FluentWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly string DialogLogPath =
|
||||||
|
Path.Combine(Path.GetTempPath(), "EverythingToJpeg_dialog.log");
|
||||||
|
|
||||||
|
private static void DiagLog(string line)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.AppendAllText(DialogLogPath,
|
||||||
|
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {line}{Environment.NewLine}");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
private async void OnConvertClick(object sender, RoutedEventArgs e)
|
private async void OnConvertClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
DiagLog($"OnConvertClick: entries={_entries.Count}");
|
||||||
|
|
||||||
if (_entries.Count == 0)
|
if (_entries.Count == 0)
|
||||||
{
|
{
|
||||||
|
DiagLog(" → no entries, showing info");
|
||||||
ShowInfo("변환할 파일이 없습니다.");
|
ShowInfo("변환할 파일이 없습니다.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConvertButton.IsEnabled = false;
|
ConvertButton.IsEnabled = false;
|
||||||
CancelButton.Content = "취소";
|
CancelButton.Content = "취소";
|
||||||
|
ProgressStatusText.Text = "준비 중…";
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
|
|
||||||
var options = BuildOptions();
|
ConvertOptions options;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
options = BuildOptions();
|
||||||
|
DiagLog($" options: Quality={options.Quality} OutputLocation={options.OutputLocation} Custom={options.CustomOutputDirectory} Collision={options.OnCollision} MaxLong={options.MaxLongEdgePixels} PdfDpi={options.PdfDpi}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DiagLog(" BuildOptions threw: " + ex);
|
||||||
|
ProgressStatusText.Text = "옵션 처리 오류: " + ex.Message;
|
||||||
|
ConvertButton.IsEnabled = true;
|
||||||
|
CancelButton.Content = "닫기";
|
||||||
|
_cts = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var reporter = new Progress<ConvertProgress>(p =>
|
var reporter = new Progress<ConvertProgress>(p =>
|
||||||
{
|
{
|
||||||
var overall = p.Total == 0 ? 0 : (p.Index + p.FileProgress) / p.Total;
|
var overall = p.Total == 0 ? 0 : (p.Index + p.FileProgress) / p.Total;
|
||||||
|
|
@ -137,17 +168,27 @@ public partial class ConvertWindow : FluentWindow
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sources = _entries.Select(en => en.Path).ToList();
|
var sources = _entries.Select(en => en.Path).ToList();
|
||||||
|
DiagLog($" starting ConvertManyAsync, {sources.Count} files");
|
||||||
var results = await _engine.ConvertManyAsync(sources, options, reporter, _cts.Token);
|
var results = await _engine.ConvertManyAsync(sources, options, reporter, _cts.Token);
|
||||||
|
DiagLog($" finished, {results.Count} results");
|
||||||
|
foreach (var r in results)
|
||||||
|
DiagLog($" [{r.Status}] {Path.GetFileName(r.SourcePath)} msg={r.Message}");
|
||||||
ApplyResults(results);
|
ApplyResults(results);
|
||||||
ProgressStatusText.Text = SummarizeResults(results);
|
ProgressStatusText.Text = SummarizeResults(results);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
DiagLog(" canceled");
|
||||||
ProgressStatusText.Text = "변환이 취소되었습니다.";
|
ProgressStatusText.Text = "변환이 취소되었습니다.";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
DiagLog(" EXCEPTION: " + ex);
|
||||||
ProgressStatusText.Text = "오류: " + ex.Message;
|
ProgressStatusText.Text = "오류: " + ex.Message;
|
||||||
|
MessageBox.Show(this,
|
||||||
|
"변환 중 오류가 발생했습니다:\n\n" + ex.Message + "\n\n로그: " + DialogLogPath,
|
||||||
|
"EverythingToJpeg",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -234,54 +275,6 @@ public partial class ConvertWindow : FluentWindow
|
||||||
=> MessageBox.Show(this, message, "EverythingToJpeg",
|
=> MessageBox.Show(this, message, "EverythingToJpeg",
|
||||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
|
||||||
private object CreateFileEntryTemplate()
|
|
||||||
{
|
|
||||||
const string xaml = """
|
|
||||||
<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml">
|
|
||||||
<Border Margin="0,0,0,6" Padding="12,8" CornerRadius="6"
|
|
||||||
Background="{DynamicResource SubtleFillColorTransparentBrush}"
|
|
||||||
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
|
|
||||||
BorderThickness="1">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="44"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Border Width="40" Height="40" CornerRadius="4"
|
|
||||||
Background="{DynamicResource ControlFillColorDefaultBrush}">
|
|
||||||
<Image Source="{Binding Thumbnail}" Stretch="UniformToFill"/>
|
|
||||||
</Border>
|
|
||||||
<StackPanel Grid.Column="1" Margin="12,0,8,0" VerticalAlignment="Center">
|
|
||||||
<TextBlock Text="{Binding FileName}" FontWeight="SemiBold"
|
|
||||||
TextTrimming="CharacterEllipsis"/>
|
|
||||||
<TextBlock Text="{Binding SubText}" FontSize="11"
|
|
||||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
|
||||||
TextTrimming="CharacterEllipsis"/>
|
|
||||||
<TextBlock Text="{Binding State}" FontSize="11" Margin="0,2,0,0">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding IsFailed}" Value="True">
|
|
||||||
<Setter Property="Foreground" Value="#F87171"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
<ui:Button Grid.Column="2" Icon="{ui:SymbolIcon Dismiss20}"
|
|
||||||
Appearance="Transparent" Click="OnRemoveEntry"
|
|
||||||
ToolTip="목록에서 제거"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
""";
|
|
||||||
return System.Windows.Markup.XamlReader.Parse(xaml);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FileEntry : System.ComponentModel.INotifyPropertyChanged
|
public sealed class FileEntry : System.ComponentModel.INotifyPropertyChanged
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue