Skip to content

Commit

Permalink
feat: global exception handler
Browse files Browse the repository at this point in the history
chore: separate `Settings view` from `MainWindow.xaml` via `SettingsView.xaml`
  • Loading branch information
realybin committed Oct 24, 2024
1 parent 61fc0d3 commit 5e9110f
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 189 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A application brings the ability to draw on your screen
1. Follow the `.editorconfig`
2. Use `Xaml Styler` to format xaml (Xaml Styler file at [Settings.XamlStyler](Settings.XamlStyler))
3. Follow [`Conventional Commits`](https://www.conventionalcommits.org/en/v1.0.0/)
4. Follow [`Semantic Versioning`](https://semver.org/)
### Build
- Windows 10 or greater
- Visual Studio 2022/JetBrains Rider
Expand Down
130 changes: 105 additions & 25 deletions SketchNow/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using CommunityToolkit.Mvvm.Messaging;
using System.Text;

using CommunityToolkit.Mvvm.Messaging;

using MaterialDesignThemes.Wpf;

Expand All @@ -12,6 +14,8 @@
using System.Windows;
using System.Windows.Threading;

using Microsoft.VisualBasic;

using Velopack;
using Velopack.Sources;

Expand All @@ -22,6 +26,73 @@ namespace SketchNow;
/// </summary>
public partial class App : Application
{
#if !DEBUG
public App()
{
this.Startup += AppStartup;
this.Exit += AppExit;
}

void AppStartup(object sender, StartupEventArgs e)
{
//UI Thread
this.DispatcherUnhandledException += AppDispatcherUnhandledException;
//Task Thread
TaskScheduler.UnobservedTaskException += TaskSchedulerUnobservedTaskException;
//Non-UI Thread
AppDomain.CurrentDomain.UnhandledException += CurrentDomainUnhandledException;
}

static void AppExit(object sender, ExitEventArgs e)
{
}

static void AppDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
try
{
e.Handled = true;
MessageBox.Show(
$"UI thread meets an exception: {e.Exception.Message}{Environment.NewLine}{e.Exception.StackTrace}",
"Unhandled Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show($"UI thread meets a fatal exception! {ex.Message}{Environment.NewLine}{ex.StackTrace}",
"Unhandled Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

static void CurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
StringBuilder sbEx = new();
if (e.IsTerminating)
{
sbEx.Append("Non-UI thread meets fatal exception.");
}

sbEx.Append("Non-UI thread exception: ");
if (e.ExceptionObject is Exception exception)
{
sbEx.Append(exception.Message + "\n" + exception.StackTrace);
}
else
{
sbEx.Append(e.ExceptionObject);
}

MessageBox.Show(sbEx.ToString());
}

static void TaskSchedulerUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
MessageBox.Show(
$"Task thread meets exception:{e.Exception.Message}{Environment.NewLine}{e.Exception.StackTrace}",
"Unobserved Task", MessageBoxButton.OK, MessageBoxImage.Error);
e.SetObserved();
}

#endif
[STAThread]
private static void Main(string[] args)
{
Expand All @@ -34,18 +105,26 @@ private static void Main(string[] args)
#if !DEBUG
private static async Task UpdateMyApp()
{
var mgr = new UpdateManager(new GithubSource("https://github.com/SketchNow/SketchNow.WPF", null, false ,null));
try
{
var mgr = new UpdateManager(new GithubSource("https://github.com/SketchNow/SketchNow.WPF", null, false));

// check for new version
var newVersion = await mgr.CheckForUpdatesAsync();
if (newVersion == null)
return; // no update available
// check for new version
var newVersion = await mgr.CheckForUpdatesAsync();
if (newVersion == null)
return; // no update available

// download new version
await mgr.DownloadUpdatesAsync(newVersion);
// download new version
await mgr.DownloadUpdatesAsync(newVersion);

// install new version and restart app
mgr.ApplyUpdatesAndRestart(newVersion);
// install new version and restart app
mgr.ApplyUpdatesAndRestart(newVersion);
}
catch (Exception e)
{
MessageBox.Show("Updated failed: " + e.Message + Environment.NewLine + e.StackTrace, "Update Failed",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
#endif
private static async Task MainAsync(string[] args)
Expand All @@ -64,22 +143,23 @@ private static async Task MainAsync(string[] args)

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder)
=> configurationBuilder.AddUserSecrets(typeof(App).Assembly))
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<MainWindow>();
services.AddSingleton<MainWindowViewModel>();
.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder)
=> configurationBuilder.AddUserSecrets(typeof(App).Assembly))
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<MainWindow>();
services.AddSingleton<MainWindowViewModel>();

services.AddSingleton<WeakReferenceMessenger>();
services.AddSingleton<IMessenger, WeakReferenceMessenger>(provider => provider.GetRequiredService<WeakReferenceMessenger>());
services.AddSingleton<WeakReferenceMessenger>();
services.AddSingleton<IMessenger, WeakReferenceMessenger>(provider =>
provider.GetRequiredService<WeakReferenceMessenger>());

services.AddSingleton(_ => Current.Dispatcher);
services.AddSingleton(_ => Current.Dispatcher);

services.AddTransient<ISnackbarMessageQueue>(provider =>
{
Dispatcher dispatcher = provider.GetRequiredService<Dispatcher>();
return new SnackbarMessageQueue(TimeSpan.FromSeconds(3.0), dispatcher);
services.AddTransient<ISnackbarMessageQueue>(provider =>
{
Dispatcher dispatcher = provider.GetRequiredService<Dispatcher>();
return new SnackbarMessageQueue(TimeSpan.FromSeconds(3.0), dispatcher);
});
});
});
}
}
29 changes: 19 additions & 10 deletions SketchNow/Models/CanvasPages.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using System.Windows.Ink;

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

Expand All @@ -10,6 +11,9 @@ public partial class CanvasPage : ObservableObject
[ObservableProperty]
private StrokeCollection _strokes = [];

/// <summary>
/// To Notify the Not ObservableProperty <see cref="_strokes"/>
/// </summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(UndoCommand), nameof(RedoCommand), nameof(ClearCommand))]
private int _counter;
Expand All @@ -22,12 +26,16 @@ public CanvasPage()
Strokes.StrokesChanged += Strokes_Changed;
_undoStack.Add(CloneStrokeCollection(_strokes));
}

private void ChangeCounter()
{
Counter++;
Counter=0;
}
private void Strokes_Changed(object sender, StrokeCollectionChangedEventArgs e)
{
_undoStack.Add(CloneStrokeCollection(Strokes));
_redoStack.Clear();
Counter++;
ChangeCounter();
}

[RelayCommand(CanExecute = nameof(CanUndo))]
Expand All @@ -36,7 +44,7 @@ private void Undo()
_redoStack.Add(CloneStrokeCollection(Strokes));
_undoStack.RemoveAt(_undoStack.Count - 1);
Strokes = CloneStrokeCollection(_undoStack[^1]);
Counter--;
ChangeCounter();
}

private bool CanUndo() => _undoStack.Count > 1;
Expand All @@ -47,7 +55,7 @@ private void Redo()
_undoStack.Add(CloneStrokeCollection(Strokes));
Strokes = CloneStrokeCollection(_redoStack[^1]);
_redoStack.RemoveAt(_redoStack.Count - 1);
Counter++;
ChangeCounter();
}

private bool CanRedo() => _redoStack.Count > 0;
Expand All @@ -72,14 +80,15 @@ private void Clear()
}
public partial class CanvasPages : ObservableObject
{
/// <summary>
/// Page 1 was reserved by default desktop canvas. For the convenience of managing <see cref="CanvasPages"/>, the latter <see cref="CanvasPages"/> are Board pages.
/// </summary>
[ObservableProperty] private ObservableCollection<CanvasPage> _pages = [new()];
[ObservableProperty] private int _length;
[ObservableProperty]
private ObservableCollection<CanvasPage> _pages = [new()];
[ObservableProperty]
private int _length;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(PreviousCommand), nameof(NextCommand))]
[NotifyPropertyChangedFor(nameof(SelectedPage))]
private int _selectedIndex = 0;
[NotifyCanExecuteChangedFor(nameof(PreviousCommand), nameof(NextCommand))]
private int _selectedIndex;
public CanvasPage SelectedPage
{
get => Pages[SelectedIndex];
Expand Down
15 changes: 15 additions & 0 deletions SketchNow/Properties/Settings.settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="SketchNow.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="FitToCurve" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="IgnorePressure" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="EraseByStroke" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
</Settings>
</SettingsFile>
Binary file modified SketchNow/Resources/AppIcon.ico
Binary file not shown.
16 changes: 15 additions & 1 deletion SketchNow/SketchNow.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@

<ItemGroup>
<ApplicationDefinition Remove="App.xaml" />
<Content Include="Resources\AppIcon.ico" />
<Page Include="App.xaml" />
</ItemGroup>

Expand All @@ -44,4 +43,19 @@
<PackageReference Include="Velopack" />
<PackageReference Include="WPF.JoshSmith.Controls.DragCanvas" />
</ItemGroup>

<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>

<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>
45 changes: 28 additions & 17 deletions SketchNow/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using CommunityToolkit.Mvvm.Input;

using SketchNow.Models;
using SketchNow.Properties;

Check failure on line 11 in SketchNow/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Properties' does not exist in the namespace 'SketchNow' (are you missing an assembly reference?)

Check failure on line 11 in SketchNow/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Properties' does not exist in the namespace 'SketchNow' (are you missing an assembly reference?)

namespace SketchNow.ViewModels;

Expand All @@ -16,7 +17,8 @@ public partial class MainWindowViewModel : ObservableObject
[ObservableProperty] private CanvasPages _canvasPages = new();
private int _previousPageIndex = 0;

[ObservableProperty] private ObservableCollection<Color> _colorList =
[ObservableProperty]
private ObservableCollection<Color> _colorList =
[
Color.FromRgb(28, 27, 31), Colors.White, Color.FromRgb(255, 26, 0), Color.FromRgb(47, 47, 255),
Color.FromRgb(0, 174, 128), Color.FromRgb(157, 118, 241), Color.FromRgb(255, 219, 29),
Expand All @@ -27,15 +29,16 @@ public partial class MainWindowViewModel : ObservableObject
[ObservableProperty] private ObservableCollection<double> _strokeSizeList = [5, 7, 9, 11, 13, 20];
[ObservableProperty] private double _selectedStrokeSize;

[ObservableProperty] private DrawingAttributes _currentDrawingAttributes = new()
[ObservableProperty]
private DrawingAttributes _currentDrawingAttributes = new()
{
Color = Colors.Transparent,
IgnorePressure = false,
FitToCurve = true,
IgnorePressure = Settings.Default.IgnorePressure,
FitToCurve = Settings.Default.FitToCurve,
Height = 5,
Width = 5
Width = 5,
IsHighlighter = true
};

[ObservableProperty] private InkCanvasEditingMode _selectedEditingMode = InkCanvasEditingMode.None;

public enum WhiteBoardMode
Expand All @@ -47,17 +50,18 @@ public enum WhiteBoardMode
[ObservableProperty] private int _selectedToolIndex = 0;
[ObservableProperty] private int _selectedCanvasModeIndex = (int)WhiteBoardMode.Screen;
[ObservableProperty] private bool _isMultiPageMode = false;
[ObservableProperty] private bool _useEraseByStroke = true;
[ObservableProperty] private bool _useFitToCurve = true;
[ObservableProperty] private bool _useEraseByStroke = Settings.Default.EraseByStroke;
[ObservableProperty] private Brush _inkCanvasBackground = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));

partial void OnSelectedStrokeSizeChanged(double value) =>
CurrentDrawingAttributes.Width = CurrentDrawingAttributes.Height = value;

partial void OnSelectedColorChanged(Color value) => CurrentDrawingAttributes.Color = value;
partial void OnUseEraseByStrokeChanged(bool value) => OnSelectedToolIndexChanged(SelectedToolIndex);
partial void OnUseFitToCurveChanged(bool value) => CurrentDrawingAttributes.FitToCurve = value;

partial void OnUseEraseByStrokeChanged(bool value)
{
Settings.Default.EraseByStroke = value;
Settings.Default.Save();
OnSelectedToolIndexChanged(SelectedToolIndex);
}
partial void OnSelectedToolIndexChanged(int value)
{
SelectedEditingMode = value switch
Expand Down Expand Up @@ -94,14 +98,21 @@ partial void OnSelectedCanvasModeIndexChanged(int value)
break;
}
}

[RelayCommand]
private static void Close()
{
Application.Current.Shutdown();
}
[RelayCommand]
private void ToggleMultiPageMode(bool value) => SelectedCanvasModeIndex =
value ? (int)WhiteBoardMode.MultiPages : (int)WhiteBoardMode.Screen;

[RelayCommand]
private static void CloseProgram()
public MainWindowViewModel()
{
Application.Current.Shutdown();
CurrentDrawingAttributes.AttributeChanged += (sender, e) =>
{
Settings.Default.FitToCurve = CurrentDrawingAttributes.FitToCurve;
Settings.Default.IgnorePressure = CurrentDrawingAttributes.IgnorePressure;
Settings.Default.Save();
};
}
}
Loading

0 comments on commit 5e9110f

Please sign in to comment.