Skip to content

Instantly share code, notes, and snippets.

@RichardD2
Created August 6, 2025 12:38
Show Gist options
  • Select an option

  • Save RichardD2/f35d3c6145a663dcf95768ebefd189d3 to your computer and use it in GitHub Desktop.

Select an option

Save RichardD2/f35d3c6145a663dcf95768ebefd189d3 to your computer and use it in GitHub Desktop.
WPF markup extension to specify a font size relative to the window's font size
using System;
using System.Windows;
using Microsoft.Win32;
namespace NBS.Core.Windows.Data;
/// <summary>
/// Default window font size provider.
/// </summary>
public sealed class DefaultWindowFontSizeProvider : IWindowFontSizeProvider
{
public static readonly DefaultWindowFontSizeProvider Instance = new();
private DefaultWindowFontSizeProvider()
{
SystemEvents.UserPreferenceChanged += (_, e) =>
{
if (e.Category == UserPreferenceCategory.Window)
{
WindowFontSizeChanged?.Invoke(this, e);
}
};
}
public double WindowFontSize => SystemFonts.MenuFontSize;
[field: NonSerialized]
public event EventHandler? WindowFontSizeChanged;
}
using System;
namespace NBS.Core.Windows.Data;
public interface IWindowFontSizeProvider
{
double WindowFontSize { get; }
event EventHandler? WindowFontSizeChanged;
}
using System;
using System.Windows.Markup;
namespace NBS.Core.Windows.Markup;
public static class ServiceProviderExtensions
{
/// <summary>
/// Returns the target object from the specified service provider.
/// </summary>
/// <param name="serviceProvider">
/// The <see cref="IServiceProvider"/> instance.
/// </param>
/// <returns>
/// The target object, if it is available;
/// otherwise, <see langword="null"/>.
/// </returns>
/// <example>
/// <code>
/// public override object ProvideValue(IServiceProvider serviceProvider)
/// {
/// var window = serviceProvider.FindTargetObject() As Window;
/// }
/// </code>
/// </example>
public static object? FindTargetObject(this IServiceProvider? serviceProvider)
=> ((IProvideValueTarget?)serviceProvider?.GetService(typeof(IProvideValueTarget)))?.TargetObject;
/// <summary>
/// Returns the target object from the specified service provider,
/// cast as the specified type.
/// </summary>
/// <typeparam name="T">
/// The required type of the target object.
/// This must be a reference type.
/// </typeparam>
/// <param name="serviceProvider">
/// The <see cref="IServiceProvider"/> instance.
/// </param>
/// <returns>
/// The target object, if it is available and of the correct type;
/// otherwise, <see langword="null"/>.
/// </returns>
/// <example>
/// <code>
/// public override object ProvideValue(IServiceProvider serviceProvider)
/// {
/// var window = serviceProvider.FindTargetObject&lt;Window&gt;();
/// }
/// </code>
/// </example>
public static T? FindTargetObject<T>(this IServiceProvider serviceProvider) where T : class
=> FindTargetObject(serviceProvider) as T;
}
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using NBS.Core.Windows.Markup;
namespace NBS.Core.Windows.Data;
/// <summary>
/// A markup extension used to provide a font size relative to the window font size.
/// </summary>
/// <example>
/// <code lang="XAML">
/// &lt;Window
/// xmlns:nbs="urn:nbs-wpf-common"
/// &gt;
/// &lt;TextBlock
/// FontSize="{nbs:WindowRelativeFontSize SizeMultiplier=1.5}"
/// /&gt;
/// &lt;/Window&gt;
/// </code>
/// </example>
[PublicAPI]
[MarkupExtensionReturnType(typeof(double))]
public class WindowRelativeFontSizeExtension : MarkupExtension
{
private readonly RelativeFontSizeConverter _converter;
private readonly FontSizeHelper _helper;
private readonly Binding _binding;
public WindowRelativeFontSizeExtension()
{
_converter = new();
_helper = new(DefaultWindowFontSizeProvider.Instance);
_binding = new(FontSizeHelper.FontSizeProperty.Name) { Source = _helper, Converter = _converter };
}
public WindowRelativeFontSizeExtension(double sizeMultiplier) : this()
{
SizeMultiplier = sizeMultiplier;
}
[AllowNull]
public IWindowFontSizeProvider Provider
{
get { return _helper.Provider; }
set { _helper.Provider = value ?? DefaultWindowFontSizeProvider.Instance; }
}
public double SizeMultiplier
{
get { return _converter.SizeMultiplier; }
set { _converter.SizeMultiplier = value; }
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.FindTargetObject<Setter>();
return target is null ? _binding.ProvideValue(serviceProvider) : _binding;
}
[ValueConversion(typeof(double), typeof(double))]
private sealed class RelativeFontSizeConverter : IValueConverter
{
public double SizeMultiplier { get; set; } = 1D;
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => value is double d ? d * SizeMultiplier : double.NaN;
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => value is double d ? d / SizeMultiplier : double.NaN;
}
private sealed class FontSizeHelper : DependencyObject, IDisposable
{
private IWindowFontSizeProvider _provider;
public FontSizeHelper(IWindowFontSizeProvider provider)
{
SetProvider(provider);
}
public IWindowFontSizeProvider Provider
{
get
{
return _provider;
}
set
{
if (ReferenceEquals(value, _provider)) return;
_provider.WindowFontSizeChanged -= OnWindowFontSizeChanged;
SetProvider(value);
}
}
private void SetProvider(IWindowFontSizeProvider provider)
{
_provider = provider;
SetValue(FontSizePropertyKey, _provider.WindowFontSize);
_provider.WindowFontSizeChanged += OnWindowFontSizeChanged;
}
private static readonly DependencyPropertyKey FontSizePropertyKey = DependencyProperty.RegisterReadOnly(
"FontSize", typeof(double), typeof(FontSizeHelper),
new(SystemFonts.MenuFontSize));
public static readonly DependencyProperty FontSizeProperty = FontSizePropertyKey.DependencyProperty;
private void OnWindowFontSizeChanged(object? sender, EventArgs e)
{
SetValue(FontSizePropertyKey, _provider.WindowFontSize);
}
public void Dispose()
{
_provider.WindowFontSizeChanged -= OnWindowFontSizeChanged;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment