Browse Source

Refactor

- Merges SettingsHelper into App
- Merges MainViewModel into MainWindow codebehind'
- Uses new MVVM Toolkit source generators
- Other cleanup
Daniel Chalmers 2 years ago
parent
commit
b87283e3a9

+ 44 - 2
DesktopClock/App.xaml.cs

@@ -1,5 +1,8 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Windows;
+using DesktopClock.Properties;
+using Microsoft.Win32;
 
 namespace DesktopClock;
 
@@ -8,7 +11,7 @@ namespace DesktopClock;
 /// </summary>
 public partial class App : Application
 {
-    // https://www.materialui.co/colors
+    // https://www.materialui.co/colors - A100, A700.
     public static IReadOnlyList<Theme> Themes { get; } = new[]
     {
         new Theme("Light Text", "#F5F5F5", "#212121"),
@@ -21,4 +24,43 @@ public partial class App : Application
         new Theme("Green", "#00C853", "#B9F6CA"),
         new Theme("Orange", "#FF6D00", "#FFD180"),
     };
+
+    /// <summary>
+    /// Gets the time zone selected in settings, or local by default.
+    /// </summary>
+    public static TimeZoneInfo GetTimeZone() =>
+        DateTimeUtil.TryGetTimeZoneById(Settings.Default.TimeZone, out var timeZoneInfo) ? timeZoneInfo : TimeZoneInfo.Local;
+
+    /// <summary>
+    /// Selects a time zone to use.
+    /// </summary>
+    public static void SetTimeZone(TimeZoneInfo timeZone) =>
+        Settings.Default.TimeZone = timeZone.Id;
+
+    /// <summary>
+    /// Sets a value in the registry determining whether the current executable should run on system startup.
+    /// </summary>
+    /// <param name="runOnStartup"></param>
+    public static void SetRunOnStartup(bool runOnStartup)
+    {
+        var exePath = ResourceAssembly.Location;
+        var keyName = GetSha256Hash(exePath);
+        using var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
+
+        if (runOnStartup)
+            key?.SetValue(keyName, exePath); // Use the path as the name so we can handle multiple exes, but hash it or Windows won't like it.
+        else
+            key?.DeleteValue(keyName, false);
+    }
+
+    internal static string GetSha256Hash(string text)
+    {
+        if (string.IsNullOrEmpty(text))
+            return string.Empty;
+
+        using var sha = new System.Security.Cryptography.SHA256Managed();
+        var textData = System.Text.Encoding.UTF8.GetBytes(text);
+        var hash = sha.ComputeHash(textData);
+        return BitConverter.ToString(hash).Replace("-", string.Empty);
+    }
 }

+ 0 - 81
DesktopClock/MainViewModel.cs

@@ -1,81 +0,0 @@
-using System;
-using System.ComponentModel;
-using System.Windows.Input;
-using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using DesktopClock.Properties;
-using Humanizer;
-
-namespace DesktopClock;
-
-public class MainViewModel : ObservableRecipient
-{
-    private readonly SystemClockTimer _systemClockTimer;
-    private TimeZoneInfo _timeZone;
-
-    public MainViewModel()
-    {
-        _timeZone = SettingsHelper.GetTimeZone();
-
-        Settings.Default.PropertyChanged += Settings_PropertyChanged;
-
-        _systemClockTimer = new();
-        _systemClockTimer.SecondChanged += SystemClockTimer_SecondChanged;
-        _systemClockTimer.Start();
-    }
-
-    /// <summary>
-    /// The current date and time in the selected time zone or countdown as a formatted string.
-    /// </summary>
-    public string CurrentTimeOrCountdownString =>
-        IsCountdown ?
-        Settings.Default.CountdownTo.Humanize(CurrentTimeInSelectedTimeZone) :
-        CurrentTimeInSelectedTimeZone.ToString(Settings.Default.Format);
-
-    /// <summary>
-    /// Sets app theme to parameter's value.
-    /// </summary>
-    public ICommand SetThemeCommand { get; } = new RelayCommand<Theme>((t) => Settings.Default.Theme = t);
-
-    /// <summary>
-    /// Sets format string in settings to parameter's string.
-    /// </summary>
-    public ICommand SetFormatCommand { get; } = new RelayCommand<string>((f) => Settings.Default.Format = f);
-
-    /// <summary>
-    /// Sets time zone ID in settings to parameter's time zone ID.
-    /// </summary>
-    public ICommand SetTimeZoneCommand { get; } = new RelayCommand<TimeZoneInfo>(SettingsHelper.SetTimeZone);
-
-    /// <summary>
-    /// The current date and time in the selected time zone.
-    /// </summary>
-    private DateTimeOffset CurrentTimeInSelectedTimeZone => TimeZoneInfo.ConvertTime(DateTimeOffset.Now, _timeZone);
-
-    /// <summary>
-    /// Should the clock be a countdown?
-    /// </summary>
-    private bool IsCountdown => Settings.Default.CountdownTo > DateTimeOffset.MinValue;
-
-    private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
-    {
-        switch (e.PropertyName)
-        {
-            case nameof(Settings.Default.TimeZone):
-                _timeZone = SettingsHelper.GetTimeZone();
-                UpdateTimeString();
-                break;
-
-            case nameof(Settings.Default.Format):
-                UpdateTimeString();
-                break;
-        }
-    }
-
-    private void SystemClockTimer_SecondChanged(object sender, EventArgs e)
-    {
-        UpdateTimeString();
-    }
-
-    private void UpdateTimeString() => OnPropertyChanged(nameof(CurrentTimeOrCountdownString));
-}

+ 6 - 10
DesktopClock/MainWindow.xaml

@@ -21,13 +21,9 @@
         MouseWheel="Window_MouseWheel"
         SourceInitialized="Window_SourceInitialized"
         Closing="Window_Closing">
-	<Window.DataContext>
-		<local:MainViewModel />
-	</Window.DataContext>
-
 	<Window.ContextMenu>
 		<ContextMenu>
-			<MenuItem Click="MenuItemCopy_OnClick" Header="_Copy" />
+			<MenuItem Command="{Binding CopyToClipboardCommand}" Header="_Copy" />
 
 			<Separator />
 
@@ -98,15 +94,15 @@
 
 			<Separator />
 
-			<MenuItem Click="MenuItemNew_OnClick" Header="_New clock..." />
+			<MenuItem Command="{Binding NewClockCommand}" Header="_New clock..." />
 
-			<MenuItem Click="MenuItemCountdown_OnClick" Header="_Countdown to..." />
+			<MenuItem Command="{Binding CountdownToCommand}" Header="_Countdown to..." />
 
-			<MenuItem Click="MenuItemSettings_OnClick" Header="Advanced _settings..." />
+			<MenuItem Command="{Binding OpenSettingsCommand}" Header="Advanced _settings..." />
 
-			<MenuItem Click="MenuItemCheckForUpdates_OnClick" Header="Check for _updates..." />
+			<MenuItem Command="{Binding CheckForUpdatesCommand}" Header="Check for _updates..." />
 
-			<MenuItem Click="MenuItemExit_OnClick" Header="E_xit" />
+			<MenuItem Command="{Binding ExitCommand}" Header="E_xit" />
 		</ContextMenu>
 	</Window.ContextMenu>
 

+ 129 - 41
DesktopClock/MainWindow.xaml.cs

@@ -1,9 +1,13 @@
 using System;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.IO;
 using System.Windows;
 using System.Windows.Input;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
 using DesktopClock.Properties;
+using Humanizer;
 using WpfWindowPlacement;
 
 namespace DesktopClock;
@@ -11,62 +15,91 @@ namespace DesktopClock;
 /// <summary>
 /// Interaction logic for MainWindow.xaml
 /// </summary>
+[ObservableObject]
 public partial class MainWindow : Window
 {
+    private readonly SystemClockTimer _systemClockTimer;
+    private TimeZoneInfo _timeZone;
+
     public MainWindow()
     {
         InitializeComponent();
-    }
+        DataContext = this;
 
-    private void Window_MouseDown(object sender, MouseButtonEventArgs e)
-    {
-        if (e.ChangedButton == MouseButton.Left)
-        {
-            DragMove();
-        }
-    }
-
-    private void CopyToClipboard() =>
-        Clipboard.SetText(TimeTextBlock.Text);
+        _timeZone = App.GetTimeZone();
 
-    private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
-    {
-        CopyToClipboard();
-    }
+        Settings.Default.PropertyChanged += Settings_PropertyChanged;
 
-    private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
-    {
-        if (Keyboard.Modifiers == ModifierKeys.Control)
-        {
-            // Scale size based on scroll amount, with one notch on a default PC mouse being a change of 15%.
-            var steps = e.Delta / (double)Mouse.MouseWheelDeltaForOneLine;
-            var change = Settings.Default.Height * steps * 0.15;
-            Settings.Default.Height = (int)Math.Min(Math.Max(Settings.Default.Height + change, 16), 160);
-        }
+        _systemClockTimer = new();
+        _systemClockTimer.SecondChanged += SystemClockTimer_SecondChanged;
+        _systemClockTimer.Start();
     }
 
-    private void MenuItemCopy_OnClick(object sender, RoutedEventArgs e)
-    {
-        CopyToClipboard();
-    }
+    /// <summary>
+    /// The current date and time in the selected time zone.
+    /// </summary>
+    private DateTimeOffset CurrentTimeInSelectedTimeZone => TimeZoneInfo.ConvertTime(DateTimeOffset.Now, _timeZone);
+
+    /// <summary>
+    /// Should the clock be a countdown?
+    /// </summary>
+    private bool IsCountdown => Settings.Default.CountdownTo > DateTimeOffset.MinValue;
+
+    /// <summary>
+    /// The current date and time in the selected time zone or countdown as a formatted string.
+    /// </summary>
+    public string CurrentTimeOrCountdownString =>
+        IsCountdown ?
+        Settings.Default.CountdownTo.Humanize(CurrentTimeInSelectedTimeZone) :
+        CurrentTimeInSelectedTimeZone.ToString(Settings.Default.Format);
+
+    [RelayCommand]
+    public void CopyToClipboard() =>
+        Clipboard.SetText(TimeTextBlock.Text);
 
-    private void MenuItemNew_OnClick(object sender, RoutedEventArgs e)
+    /// <summary>
+    /// Sets app theme to parameter's value.
+    /// </summary>
+    [RelayCommand]
+    public void SetTheme(Theme theme) => Settings.Default.Theme = theme;
+
+    /// <summary>
+    /// Sets format string in settings to parameter's string.
+    /// </summary>
+    [RelayCommand]
+    public void SetFormat(string format) => Settings.Default.Format = format;
+
+    /// <summary>
+    /// Sets time zone ID in settings to parameter's time zone ID.
+    /// </summary>
+    [RelayCommand]
+    public void SetTimeZone(TimeZoneInfo tzi) => App.SetTimeZone(tzi);
+
+    /// <summary>
+    /// Creates a new clock.
+    /// </summary>
+    [RelayCommand]
+    public void NewClock()
     {
         var result = MessageBox.Show(this,
-            $"This will make a copy of the executable and start it with new settings.\n\n" +
+            $"This will copy the executable and start it with new settings.\n\n" +
             $"Continue?",
             Title, MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.OK);
 
         if (result != MessageBoxResult.OK)
             return;
 
-        var exeFileInfo = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location);
-        var newExePath = Path.Combine(exeFileInfo.DirectoryName, Guid.NewGuid().ToString() + exeFileInfo.Name);
-        File.Copy(exeFileInfo.FullName, newExePath);
+        var exeInfo = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location);
+        var newExePath = Path.Combine(exeInfo.DirectoryName, Guid.NewGuid().ToString() + exeInfo.Name);
+        File.Copy(exeInfo.FullName, newExePath);
         Process.Start(newExePath);
     }
 
-    private void MenuItemCountdown_OnClick(object sender, RoutedEventArgs e)
+    /// <summary>
+    /// Explains how to enable countdown mode.
+    /// </summary>
+    [RelayCommand]
+    public void CountdownTo()
     {
         var result = MessageBox.Show(this,
             $"In advanced settings: change {nameof(Settings.Default.CountdownTo)}, then restart.\n" +
@@ -75,10 +108,14 @@ public partial class MainWindow : Window
             Title, MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.OK);
 
         if (result == MessageBoxResult.OK)
-            MenuItemSettings_OnClick(this, new RoutedEventArgs());
+            OpenSettings();
     }
 
-    private void MenuItemSettings_OnClick(object sender, RoutedEventArgs e)
+    /// <summary>
+    /// Opens the settings file in Notepad.
+    /// </summary>
+    [RelayCommand]
+    public void OpenSettings()
     {
         Settings.Default.Save();
 
@@ -101,14 +138,65 @@ public partial class MainWindow : Window
         }
     }
 
-    private void MenuItemCheckForUpdates_OnClick(object sender, RoutedEventArgs e)
+    /// <summary>
+    /// Checks for updates.
+    /// </summary>
+    [RelayCommand]
+    public void CheckForUpdates()
     {
         Process.Start("https://github.com/danielchalmers/DesktopClock/releases");
     }
 
-    private void MenuItemExit_OnClick(object sender, RoutedEventArgs e)
+    /// <summary>
+    /// Exits the program.
+    /// </summary>
+    [RelayCommand]
+    public void Exit() => Close();
+
+    private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
     {
-        Close();
+        switch (e.PropertyName)
+        {
+            case nameof(Settings.Default.TimeZone):
+                _timeZone = App.GetTimeZone();
+                UpdateTimeString();
+                break;
+
+            case nameof(Settings.Default.Format):
+                UpdateTimeString();
+                break;
+        }
+    }
+
+    private void SystemClockTimer_SecondChanged(object sender, EventArgs e)
+    {
+        UpdateTimeString();
+    }
+
+    private void UpdateTimeString() => OnPropertyChanged(nameof(CurrentTimeOrCountdownString));
+
+    private void Window_MouseDown(object sender, MouseButtonEventArgs e)
+    {
+        if (e.ChangedButton == MouseButton.Left)
+        {
+            DragMove();
+        }
+    }
+
+    private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+    {
+        CopyToClipboard();
+    }
+
+    private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
+    {
+        if (Keyboard.Modifiers == ModifierKeys.Control)
+        {
+            // Scale size based on scroll amount, with one notch on a default PC mouse being a change of 15%.
+            var steps = e.Delta / (double)Mouse.MouseWheelDeltaForOneLine;
+            var change = Settings.Default.Height * steps * 0.15;
+            Settings.Default.Height = (int)Math.Min(Math.Max(Settings.Default.Height + change, 16), 160);
+        }
     }
 
     private void Window_SourceInitialized(object sender, EventArgs e)
@@ -116,13 +204,13 @@ public partial class MainWindow : Window
         WindowPlacementFunctions.SetPlacement(this, Settings.Default.Placement);
     }
 
-    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+    private void Window_Closing(object sender, CancelEventArgs e)
     {
         Settings.Default.Placement = WindowPlacementFunctions.GetPlacement(this);
 
         Settings.Default.SaveIfNotModifiedExternally();
 
-        SettingsHelper.SetRunOnStartup(Settings.Default.RunOnStartup);
+        App.SetRunOnStartup(Settings.Default.RunOnStartup);
 
         Settings.Default.Dispose();
     }

+ 26 - 26
DesktopClock/OutlinedTextBlock.cs

@@ -104,81 +104,81 @@ public class OutlinedTextBlock : FrameworkElement
 
     public Brush Fill
     {
-        get { return (Brush)GetValue(FillProperty); }
-        set { SetValue(FillProperty, value); }
+        get => (Brush)GetValue(FillProperty);
+        set => SetValue(FillProperty, value);
     }
 
     public FontFamily FontFamily
     {
-        get { return (FontFamily)GetValue(FontFamilyProperty); }
-        set { SetValue(FontFamilyProperty, value); }
+        get => (FontFamily)GetValue(FontFamilyProperty);
+        set => SetValue(FontFamilyProperty, value);
     }
 
     [TypeConverter(typeof(FontSizeConverter))]
     public double FontSize
     {
-        get { return (double)GetValue(FontSizeProperty); }
-        set { SetValue(FontSizeProperty, value); }
+        get => (double)GetValue(FontSizeProperty);
+        set => SetValue(FontSizeProperty, value);
     }
 
     public FontStretch FontStretch
     {
-        get { return (FontStretch)GetValue(FontStretchProperty); }
-        set { SetValue(FontStretchProperty, value); }
+        get => (FontStretch)GetValue(FontStretchProperty);
+        set => SetValue(FontStretchProperty, value);
     }
 
     public FontStyle FontStyle
     {
-        get { return (FontStyle)GetValue(FontStyleProperty); }
-        set { SetValue(FontStyleProperty, value); }
+        get => (FontStyle)GetValue(FontStyleProperty);
+        set => SetValue(FontStyleProperty, value);
     }
 
     public FontWeight FontWeight
     {
-        get { return (FontWeight)GetValue(FontWeightProperty); }
-        set { SetValue(FontWeightProperty, value); }
+        get => (FontWeight)GetValue(FontWeightProperty);
+        set => SetValue(FontWeightProperty, value);
     }
 
     public Brush Stroke
     {
-        get { return (Brush)GetValue(StrokeProperty); }
-        set { SetValue(StrokeProperty, value); }
+        get => (Brush)GetValue(StrokeProperty);
+        set => SetValue(StrokeProperty, value);
     }
 
     public double StrokeThickness
     {
-        get { return (double)GetValue(StrokeThicknessProperty); }
-        set { SetValue(StrokeThicknessProperty, value); }
+        get => (double)GetValue(StrokeThicknessProperty);
+        set => SetValue(StrokeThicknessProperty, value);
     }
 
     public string Text
     {
-        get { return (string)GetValue(TextProperty); }
-        set { SetValue(TextProperty, value); }
+        get => (string)GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
     }
 
     public TextAlignment TextAlignment
     {
-        get { return (TextAlignment)GetValue(TextAlignmentProperty); }
-        set { SetValue(TextAlignmentProperty, value); }
+        get => (TextAlignment)GetValue(TextAlignmentProperty);
+        set => SetValue(TextAlignmentProperty, value);
     }
 
     public TextDecorationCollection TextDecorations
     {
-        get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
-        set { SetValue(TextDecorationsProperty, value); }
+        get => (TextDecorationCollection)GetValue(TextDecorationsProperty);
+        set => SetValue(TextDecorationsProperty, value);
     }
 
     public TextTrimming TextTrimming
     {
-        get { return (TextTrimming)GetValue(TextTrimmingProperty); }
-        set { SetValue(TextTrimmingProperty, value); }
+        get => (TextTrimming)GetValue(TextTrimmingProperty);
+        set => SetValue(TextTrimmingProperty, value);
     }
 
     public TextWrapping TextWrapping
     {
-        get { return (TextWrapping)GetValue(TextWrappingProperty); }
-        set { SetValue(TextWrappingProperty, value); }
+        get => (TextWrapping)GetValue(TextWrappingProperty);
+        set => SetValue(TextWrappingProperty, value);
     }
 
     public OutlinedTextBlock()

+ 3 - 2
DesktopClock/Properties/Settings.cs

@@ -11,6 +11,7 @@ public sealed class Settings : INotifyPropertyChanged, IDisposable
 {
     private readonly FileSystemWatcher _watcher;
     private DateTime _fileDate = DateTime.UtcNow;
+
     private static readonly Lazy<Settings> _default = new(() => Load() ?? new Settings());
 
     private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
@@ -30,7 +31,7 @@ public sealed class Settings : INotifyPropertyChanged, IDisposable
         {
             EnableRaisingEvents = true
         };
-        _watcher.Changed += OnFileChanged;
+        _watcher.Changed += FileChanged;
 
         // Random default theme.
         var random = new Random();
@@ -132,7 +133,7 @@ public sealed class Settings : INotifyPropertyChanged, IDisposable
         }
     }
 
-    private void OnFileChanged(object sender, FileSystemEventArgs e)
+    private void FileChanged(object sender, FileSystemEventArgs e)
     {
         try
         {

+ 0 - 43
DesktopClock/SettingsHelper.cs

@@ -1,43 +0,0 @@
-using System;
-using DesktopClock.Properties;
-using Microsoft.Win32;
-
-namespace DesktopClock;
-
-public static class SettingsHelper
-{
-    /// <summary>
-    /// Gets the time zone selected in settings, or local by default.
-    /// </summary>
-    public static TimeZoneInfo GetTimeZone() =>
-        DateTimeUtil.TryGetTimeZoneById(Settings.Default.TimeZone, out var timeZoneInfo) ? timeZoneInfo : TimeZoneInfo.Local;
-
-    /// <summary>
-    /// Selects a time zone to use.
-    /// </summary>
-    public static void SetTimeZone(TimeZoneInfo timeZone) =>
-        Settings.Default.TimeZone = timeZone.Id;
-
-    public static void SetRunOnStartup(bool runOnStartup)
-    {
-        var exePath = App.ResourceAssembly.Location;
-        var keyName = GetSha256Hash(exePath);
-        using var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true);
-
-        if (runOnStartup)
-            key?.SetValue(keyName, exePath); // Use the path as the name so we can handle multiple exes, but hash it or Windows won't like it.
-        else
-            key?.DeleteValue(keyName, false);
-    }
-
-    internal static string GetSha256Hash(string text)
-    {
-        if (string.IsNullOrEmpty(text))
-            return string.Empty;
-
-        using var sha = new System.Security.Cryptography.SHA256Managed();
-        var textData = System.Text.Encoding.UTF8.GetBytes(text);
-        var hash = sha.ComputeHash(textData);
-        return BitConverter.ToString(hash).Replace("-", string.Empty);
-    }
-}

+ 2 - 2
DesktopClock/SystemClockTimer.cs

@@ -4,7 +4,7 @@ using System.Threading;
 namespace DesktopClock;
 
 /// <summary>
-/// A timer that is synced with the system clock.
+/// A timer that syncs with the system clock.
 /// </summary>
 public sealed class SystemClockTimer : IDisposable
 {
@@ -16,7 +16,7 @@ public sealed class SystemClockTimer : IDisposable
     }
 
     /// <summary>
-    /// Occurs after the second of the system clock changes.
+    /// Occurs after the second changes on the system clock.
     /// </summary>
     public event EventHandler SecondChanged;