From 446bb5b93d10c94b9c2076adeaba9f1f82c4aa4f Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Sat, 11 Jul 2026 07:31:24 -0500 Subject: [PATCH] Fall back to the solid color when the background image can't load If a background image was set and then deleted, moved, replaced with a non-image file, or put on an unavailable share, the ImageBrush failed silently and the clock rendered with no background at all - unreadable text on the bare desktop. The empty-path solid-color fallback never applied because the path wasn't empty. Build the background through a converter that tries the image and falls back to the solid outer color whenever it can't be loaded; a valid image still renders exactly as before. --- DesktopClock/MainWindow.xaml | 46 +++---------- .../Utilities/BackgroundBrushConverter.cs | 65 +++++++++++++++++++ 2 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 DesktopClock/Utilities/BackgroundBrushConverter.cs diff --git a/DesktopClock/MainWindow.xaml b/DesktopClock/MainWindow.xaml index 9057e46..fae8410 100644 --- a/DesktopClock/MainWindow.xaml +++ b/DesktopClock/MainWindow.xaml @@ -113,42 +113,16 @@ - - - + + + + + + + + + + +/// Builds the clock's background brush from the current settings, falling back to the solid outer color when a chosen background image is missing or can't be read. +/// +/// +/// Bound values, in order: BackgroundEnabled, BackgroundImagePath, OuterColor, BackgroundOpacity, BackgroundImageStretch. +/// +public class BackgroundBrushConverter : MarkupExtension, IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + var enabled = values.Length > 0 && values[0] is bool b && b; + + // Background off means outlined text on a transparent window. + if (!enabled) + return Brushes.Transparent; + + var path = values.Length > 1 ? values[1] as string : null; + var outerColor = values.Length > 2 && values[2] is Color color ? color : Colors.Transparent; + var opacity = values.Length > 3 && values[3] is double o ? o : 1d; + var stretch = values.Length > 4 && values[4] is Stretch s ? s : Stretch.Fill; + + // Use the image when it loads; otherwise fall back to the solid color so the clock never ends up with an invisible background. + var image = TryLoadImage(path); + if (image != null) + return new ImageBrush(image) { Opacity = opacity, Stretch = stretch }; + + return new SolidColorBrush(outerColor) { Opacity = opacity }; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => + throw new NotSupportedException(); + + public override object ProvideValue(IServiceProvider serviceProvider) => this; + + private static ImageSource TryLoadImage(string path) + { + if (string.IsNullOrWhiteSpace(path)) + return null; + + try + { + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.CacheOption = BitmapCacheOption.OnLoad; // Decode now so a missing or invalid file fails here, and don't hold the file locked. + bitmap.UriSource = new Uri(path, UriKind.Absolute); + bitmap.EndInit(); + bitmap.Freeze(); + return bitmap; + } + catch + { + // Missing file, non-image content, unavailable share, or a malformed path all fall back to the solid color. + return null; + } + } +}