Created
August 6, 2025 12:38
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| namespace NBS.Core.Windows.Data; | |
| public interface IWindowFontSizeProvider | |
| { | |
| double WindowFontSize { get; } | |
| event EventHandler? WindowFontSizeChanged; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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<Window>(); | |
| /// } | |
| /// </code> | |
| /// </example> | |
| public static T? FindTargetObject<T>(this IServiceProvider serviceProvider) where T : class | |
| => FindTargetObject(serviceProvider) as T; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"> | |
| /// <Window | |
| /// xmlns:nbs="urn:nbs-wpf-common" | |
| /// > | |
| /// <TextBlock | |
| /// FontSize="{nbs:WindowRelativeFontSize SizeMultiplier=1.5}" | |
| /// /> | |
| /// </Window> | |
| /// </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