Click here to Skip to main content
15,303,147 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Due to design purpose I use the fluid Layout which is mainly made by Grid.Row definition. I want to adjust my WPF application to run smoothly on every monitor that's why I use the DPI Decorator class. And this DPI Decorator is perfectly working that means the screen UI components are perfectly resizing according to the screen DPI but the main problem is related to the text rendering. The text are became blurry when I run my WPF application in lower screen resolution.

Though I successfully solve this problem by the data binding of the Font Size of the Text Block to the x:Static Application.Current. And now If I run my application in lower screen resolution my text perfectly scaled and no blurry and rendering issue is present.

But I want to scale the Text at a specific Font Size because in Segoe UI, different Font Size have different letter looks and spacing. And in my case I use the Font Size 10 and then scale it using View Box.

And my main problem is, I have to bind the Font Size from two different sources. In the First binding I have to bind the button Font Size with Text Block Font Size because in my case the Text Block is inside it's parent container button. So, that if I declare Font Size 10 in the button then by using the data binding the Text Block automatically inheritate the property from it's parent container Button.

And the second binding is with the application state UI, as I say previously. So, that it's perfectly scale into any screen DPI and screen resolution.


The only solution is I have to do a Multi data Binding with a specific converter to satisfy those data binding condition. I also tried that but that not working properly.


Here is the code of First data binding where I bind the Text Block Font Size with x:Static Application.Current. to overcome the blurry issue of the text in different DPI -

All of my Button are made by my own control template. First I bind the source with x:Static Application.Current in my control template.

Here is the part of the code of my button control template -

<Style
                 x:Key="RoundCorner"
                 x:Name="RoundCornerButton"
                 TargetType="{x:Type Button}">
             <Setter Property="HorizontalContentAlignment" Value="Center" />
             <Setter Property="VerticalContentAlignment" Value="Center" />
             <Setter Property="Padding" Value="1" />
             <Setter Property="Foreground" Value="#bababa" />
             <Setter Property="ToolTipService.InitialShowDelay" Value="500" />
             <Setter Property="ToolTipService.ShowDuration" Value="4000" />
                
             <Setter Property="FontFamily" Value="Segoe UI"/>
             <Setter Property="FontSize">
                 <Setter.Value>
                     <Binding Source="{x:Static Application.Current}" Path="fontSize"/>
                 </Setter.Value>
             </Setter>


As you clearly see that I use the Path fontSize to bind with Application.current state.

And later I use in the Text Block. Here is the code -

<Button        
         x:Name="BtnSettings" Width="90" HorizontalAlignment="Left" Margin="200,14,0,0" Height="30" DockPanel.Dock="Left" FontSize="10"
      VerticalAlignment="Top"  UseLayoutRounding="True"  RenderOptions.ClearTypeHint="Enabled"  RenderOptions.BitmapScalingMode="NearestNeighbor"   SnapsToDevicePixels="True"        
                                   >
                     <Button.Content >
                         <Viewbox Height="15" SnapsToDevicePixels="True" StretchDirection="Both"   x:Name="myViewbox"  Stretch="Uniform"  HorizontalAlignment="Stretch"  >
    
                             <TextBlock x:Name="myTextbox"                                       
                                 FontSize="{Binding Source={x:Static Application.Current}, Path=fontSize, Mode=TwoWay}"                                      
                                        SizeChanged="myTextbox_SizeChanged" 
                                        FontFamily="Segoe UI" UseLayoutRounding="True" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor"  TextOptions.TextFormattingMode="Display"       Margin="0,-2,0,0" TextOptions.TextRenderingMode="ClearType"  RenderOptions.ClearTypeHint="Enabled"                                >                   
                                             
                     Settings
                         </TextBlock>
    
                         </Viewbox>
                     </Button.Content>
                 </Button>


And the second binding is with the Text Block Font Size with Button Font Size because I have to maintain the Font Size 10 scale -

<Button
           
             x:Name="BtnSettings" Width="90" HorizontalAlignment="Left" Margin="200,14,0,0" Height="30" DockPanel.Dock="Left" FontSize="10"
          VerticalAlignment="Top"  UseLayoutRounding="True"  RenderOptions.ClearTypeHint="Enabled"  RenderOptions.BitmapScalingMode="NearestNeighbor"   SnapsToDevicePixels="True"        
                                       >
                         <Button.Content >
                             <Viewbox Height="15" SnapsToDevicePixels="True" StretchDirection="Both"   x:Name="myViewbox"  Stretch="Uniform"  HorizontalAlignment="Stretch"  >
                                 <TextBlock x:Name="myTextbox"                                       
                                     FontSize="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=FontSize}"                                      
                                            SizeChanged="myTextbox_SizeChanged" 
                                            FontFamily="Segoe UI" UseLayoutRounding="True" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor"  TextOptions.TextFormattingMode="Display"       Margin="0,-2,0,0" TextOptions.TextRenderingMode="ClearType"  RenderOptions.ClearTypeHint="Enabled"                                >                   
                                                 
                         Settings
                             </TextBlock>
                             </Viewbox>
                         </Button.Content>
                     </Button>


Now the goal is to use a Multi data binding to satisfy those both data binding condition -

As much as I tried this is the code -

<Button
        
         x:Name="BtnSettings" Width="90" HorizontalAlignment="Left" Margin="200,14,0,0" Height="30" DockPanel.Dock="Left" FontSize="10"
      VerticalAlignment="Top"  UseLayoutRounding="True"  RenderOptions.ClearTypeHint="Enabled"  RenderOptions.BitmapScalingMode="NearestNeighbor"   SnapsToDevicePixels="True"        
                                   >
                     <Button.Content >
                         <Viewbox Height="15" SnapsToDevicePixels="True" StretchDirection="Both"   x:Name="myViewbox"  Stretch="Uniform"  HorizontalAlignment="Stretch"  >
                             <TextBlock x:Name="myTextbox"                                       
                                                                        
                                        SizeChanged="myTextbox_SizeChanged" 
                                        FontFamily="Segoe UI" UseLayoutRounding="True" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor"  TextOptions.TextFormattingMode="Display"  Margin="0,-2,0,0" TextOptions.TextRenderingMode="ClearType"  RenderOptions.ClearTypeHint="Enabled" >
                                           
                                         <TextBlock.FontSize>
    
                                     <MultiBinding Converter="{StaticResource AverageConverter}">
                                         <Binding Source="{x:Static Application.Current}" Path="fontSize" Mode="TwoWay" />
                                         <Binding Path="FontSize"  RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}" />
                                     </MultiBinding>
    
                                 </TextBlock.FontSize>
                                             
                     Settings
                         </TextBlock>
                         </Viewbox>
                     </Button.Content>
                 </Button>


Here is the code of that AverageConverter -

C#
namespace WpfApp1
 {
     class AverageConverter : IMultiValueConverter
     {
    
         #region IMultiValueConverter Members
         public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
         {
             int total = 0;
             int number = 0;
             foreach (object o in values)
             {
                 int i;
                 bool parsed = int.TryParse(o.ToString(), out i);
                 if (parsed)
                 {
                     total += i;
                     number++;
                 }
             }
             if (number == 0) return 0;
             return (total / number).ToString();
         }
    
         public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
         {
             throw new NotImplementedException();
         }
    
         #endregion
    
     }
 }


What I have tried:

The main goal is to use that two data binding in a single multi binding. I know there some code issue in my converter.
Posted
Updated 1-Aug-21 8:39am
Comments
Adérito Silva 31-Jul-21 14:42pm
   
The ViewBox uses render transforms internally, which transforms content after it has passed layout calculations. That's probably why text becomes blurry. In your case, using a layout transform might give you better results.
Adérito Silva 31-Jul-21 14:50pm
   
In your converter, you are converting from string to int, but you should expect double, because you are binding your values to FontSize property, and also return double because FontSize property where you are using the converter expects a double.

The correct way would be to check if the value is of type double, throwing an exception if it isn't, or returning your calculated values if it is. Parsing strings and returning strings is not necessary there.
Adérito Silva 31-Jul-21 14:55pm
   
In your converter, you would simply do the following:

// This requires System.Linq namespace.
return values.Cast<double>().Average();
Member 15061773 31-Jul-21 17:11pm
   
Thank you sir for your reply. Your code is slightly working But I get the error "Specified cast is not working".
Adérito Silva 31-Jul-21 17:16pm
   
What is the type of fontSize property on your Application.Current? It should be double.
Member 15061773 1-Aug-21 8:05am
   
the fontsize is just like other number. I both tested with int and double but the error "Specified cast is not working" is same.
Member 15061773 2-Aug-21 10:56am
   
Thank you sir for your reply. But TextFormattingMode does not solve my problem. I hope we have to do something in data binding. If you know about multi binding then please help me to solve this problem.

XAML
... FontSize="{Binding Path=TextSize}" ...


C#
public double TextSize {get { return xxxx ? aaa : bbb; } }
   
Comments
Member 15061773 22-Jul-21 11:41am
   
Please kindly give the full source code example. Because Path=TextSize is not working.
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.

C#
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;
            // Use the average of the values on the collection. If no font size is
            // specified on the collection, use the target's default FontSize value.
            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:
XML
<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:
XML
<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:
XML
<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.
C#
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:
XML
<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>
   
v2
Comments
Member 15061773 1-Aug-21 15:29pm
   
Thank you for your help but your code is not working. As soon as I lower the screen resolution the text became blurry. Previously I solve this by binding to {x:Static Application.Current} to the font size. Your double collection is not solve my problem. Please give me a solution to add this line in the multi binding. <binding source="{x:Static Application.Current}" path="fontSize" mode="TwoWay">
Adérito Silva 1-Aug-21 16:11pm
   
My answer has the code for the converter. You can use the converter I wrote to bind to whatever you want. In the binding example, I used a DoubleCollection just for convenience, but you can bind to Application.Current as well.

Just replace my code where it has:
<binding source="{StaticResource fontSizes}" path="[0]">
<binding source="{StaticResource fontSizes}" path="[1]">
<binding source="{StaticResource fontSizes}" path="[2]">

With your own bindings, like:
<binding source="{x:Static Application.Current}" path="fontSize" mode="OneWay">
<binding ...any="" other="" binding="" you="" want...="">
Member 15061773 1-Aug-21 16:25pm
   
Thank you sir for your guide. But again I face the error in the converter. I get some error like this unable to cast object of type 'ms.internal.namedobject' to type 'system.iconvertible.
Adérito Silva 1-Aug-21 16:49pm
   
Probably, there must be something wrong with your binding. The converter expects to receive an IConvertible, which includes any number type, but it is getting a NamedObject. That means your bindings are getting the wrong value. It would be better to first try simple bindings in your XAML code, so you can see if they are working. If yes, try each of your bindings elsewhere, like on the Text of a TextBlock to see what values the bindings are getting or so. The converter is working fine on my end.
Member 15061773 2-Aug-21 2:57am
   
Thank you sir. If I run those binding separately then that bindings are perfectly working. That means there no error in binding but only error comes in the multi binding. You already know what I want, that means I want font size =10 and the font size should be clearly visible in every screen resolution.

I suggest you to give me your binding suggestion regarding the Font size, because you already solve the problem of the converter.
Adérito Silva 2-Aug-21 15:45pm
   
Your bindings are working fine on my end. For example, I set the Application.Current.FontSize to 100 and the button FontSize to 10. My TextBlock FontSize is 55, which is the average of 10+100. The text gets blurry, because of the text options you are using. Set TextOptions.TextFormattingMode="Ideal" or remove text options all together. That works for me.

If you are still getting the same error on the converter, check your bindings again. For example, you are using camel case on the binding to 'Application.Current.fontSize'. Is it intended, or may it be a simple misspell? Because, that happens to me if I misspell it, as well.
Member 15061773 2-Aug-21 15:55pm
   
Can you please post the code what you tried? it helps me a lot.
Adérito Silva 2-Aug-21 16:01pm
   
This is the code I'm using, which is working fine and the text doesn't get blurry:

        <Button x:Name="BtnSettings" Width="90" Height="30" Margin="200,14,0,0" Padding="6,3"
                HorizontalAlignment="Left" VerticalAlignment="Top"
                UseLayoutRounding="True" SnapsToDevicePixels="True"       
                FontSize="10">
            <Button.Content >
                <Viewbox x:Name="myViewbox"
                         SnapsToDevicePixels="True" 
                         Stretch="Uniform" StretchDirection="Both">
                    <TextBlock x:Name="myTextbox" Margin="0,-2,0,0"
                               FontFamily="Segoe UI">
                        <TextBlock.FontSize>
                            <MultiBinding>
                                <MultiBinding.Converter>
                                    <local:AverageConverter/>
                                </MultiBinding.Converter>
                                <Binding Source="{x:Static Application.Current}" Path="FontSize" Mode="OneWay"/>
                                <Binding Path="FontSize" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}"/>
                            </MultiBinding>
                        </TextBlock.FontSize>
                        FontSize =
                        <Run Text="{Binding FontSize, Mode=OneWay, 
                             RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBlock}}}"
                             FontWeight="Bold"/>;
                    </TextBlock>
                </Viewbox>
            </Button.Content>
        </Button>
Adérito Silva 2-Aug-21 16:02pm
   
And this is my App class, where FontSize is declared:

    public partial class App : Application
    {

        public double FontSize => 30.0;

    }

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900