I wrote some comments on your question, but I decided that it would be better to write an answer with more details. You are using a converter to bind multiple font sizes, which is a valid solution, but you may end up writing too much XAML on each binding, which may not be practical.
The following, is an example of an attached dependency property you can create, to achieve the same result. The property is a
DoubleCollection
where you can add several font sizes. You would put this code inside an empty CS file. Remember to change the name of the namespace as you wish.
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfAppTest
{
public static class AppFonts
{
#region Properties
#region FontSizes dependency property
public static readonly DependencyProperty FontSizesProperty
= DependencyProperty.RegisterAttached("FontSizes",
typeof(DoubleCollection), typeof(AppFonts),
new PropertyMetadata(null,
new PropertyChangedCallback(FontSizesPropertyChanged)));
private static void FontSizesPropertyChanged(
DependencyObject dObj, DependencyPropertyChangedEventArgs e)
{
DoubleCollection value = (DoubleCollection)e.NewValue;
if (value is null || value.Count == 0)
{
if (dObj is Control control)
control.FontSize = (double)Control.FontSizeProperty.DefaultMetadata.DefaultValue;
else if (dObj is TextBlock textBlock)
textBlock.FontSize = (double)TextBlock.FontSizeProperty.DefaultMetadata.DefaultValue;
else TextBlock.SetFontSize(dObj, (double)TextBlock.FontSizeProperty.DefaultMetadata.DefaultValue);
}
else
{
double fontSizeAverage = value.Average();
if (dObj is Control control)
control.FontSize = fontSizeAverage;
else if (dObj is TextBlock textBlock)
textBlock.FontSize = fontSizeAverage;
else
TextBlock.SetFontSize(dObj, fontSizeAverage);
}
}
public static void SetFontSizes(DependencyObject element, DoubleCollection value)
{
if (element is null)
throw new ArgumentNullException(nameof(element));
element.SetValue(FontSizesProperty, value);
}
public static DoubleCollection GetFontSizes(DependencyObject element)
{
if (element is null)
throw new ArgumentNullException(nameof(element));
return (DoubleCollection)element.GetValue(FontSizesProperty);
}
#endregion
#endregion Properties
}
}
Now, to use the newly created dependency property, you would:
Use explicit values:
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
TextWrapping="Wrap" TextAlignment="Center"
local:AppFonts.FontSizes="50, 150">
The current font size is
<Run Text="{Binding FontSize, Mode=OneWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBlock}}}"
FontWeight="Bold"/>.
</TextBlock>
Or bind to a
DoubleCollection
resource you have somewhere:
<Grid>
<Grid.Resources>
<DoubleCollection x:Key="myFontSizes">25, 50, 75</DoubleCollection>
</Grid.Resources>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
TextWrapping="Wrap" TextAlignment="Center"
local:AppFonts.FontSizes="{StaticResource myFontSizes}">
The current font size is
<Run Text="{Binding FontSize, Mode=OneWay,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBlock}}}"
FontWeight="Bold"/>.
</TextBlock>
</Grid>
The preceding examples I wrote may seem a bit long, because they include a
Run
with a binding to display a sentence showing the current
FontSize
. But to demonstrate it simply, all you need to write is the following:
<TextBlock local:AppFonts.FontSizes="10, 20, 30, 40"/>
FontSizes
property will change the value of
FontSize
property to the average of the font-size values you specify. This is an alternative way to setting
FontSize
yourself. Because
FontSize
is inheritable, you can specify several font-sizes on any
Control
and their average value should propagate to its children as usually. So, this means you can set
FontSizes
property on the
Window
itself, for example.
Although I think this may not be the best solution for solving your problem with interface zooming, this should work fine for having multiple font sizes as you need.
Well, the solution I give above uses an attached dependency property, but if you still want the
IMultiValueConverter
solution, here I give you an example of an
IMultiValueConverter
that converts any combination of numbers or strings to their average (of
double
type). Feel free to change the namespace as you wish.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
#nullable enable
namespace WpfAppTest
{
public class AverageConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object? parameter, CultureInfo? culture)
{
if (values is null)
throw new ArgumentNullException(nameof(values));
if (targetType is null)
throw new ArgumentNullException(nameof(targetType));
if (!targetType.Equals(typeof(double)))
throw new ArgumentException(
$"Conversion target type must be '{typeof(double).FullName}'.", nameof(targetType));
IEnumerable<double> ToDouble()
{
foreach (IConvertible value in values)
yield return System.Convert.ToDouble(value, culture ?? CultureInfo.InvariantCulture);
}
return ToDouble().Average();
}
public object[] ConvertBack(object value, Type[] targetTypes, object? parameter, CultureInfo? culture)
{
throw new NotSupportedException("Backward conversion is not supported.");
}
}
}
Here, is an example of using this converter to convert several values from a
DoubleCollection
resource:
<Grid>
<Grid.Resources>
<DoubleCollection x:Key="fontSizes">10, 20, 30</DoubleCollection>
</Grid.Resources>
<TextBlock>
<TextBlock.FontSize>
<MultiBinding>
<MultiBinding.Converter>
<local:AverageConverter />
</MultiBinding.Converter>
<Binding Source="{StaticResource fontSizes}" Path="[0]"/>
<Binding Source="{StaticResource fontSizes}" Path="[1]"/>
<Binding Source="{StaticResource fontSizes}" Path="[2]"/>
</MultiBinding>
</TextBlock.FontSize>
This is a simple text block example.
</TextBlock>
</Grid>