YehHyunLee
YehHyunLee

Reputation: 93

How should I do any work to resolve the problem for overriding metadata of Width and Height properties originated from FrameworkElement?

There is a greatly difficult (at least just for me) problem about WPF. From the first on, I've wanted to create my own dependency object class that adjusts its FontSize property automatically through Width, Height, and also HorizontalProportion and VerticalProportion that the last two properties are customized by the class, however, on my view I seemed to get my code execute successfully and maybe cause slight errors or none but my custom class didn't work as expected anyway.

The following code reveals the current problem:

public class DynamicTextBlock : TextBlock {
        public static readonly DependencyProperty HorizontalProportionProperty
            = DependencyProperty.Register(
                nameof(HorizontalPropotion), typeof(double?), typeof(DynamicTextBlock),
                new FrameworkPropertyMetadata(
                    null, // default value
                    null, // property changed callback
                    CoerceHorizontalAndVerticalProportion // coerce property callback
                    )
                );
        public static readonly DependencyProperty VerticalPropoertionProperty
            = DependencyProperty.Register(
                nameof(VerticalProportion), typeof(double?), typeof(DynamicTextBlock),
                new FrameworkPropertyMetadata(
                    null, // default value
                    null, // property changed callback
                    CoerceHorizontalAndVerticalProportion // coerce property callback
                    )
                );

        // If a value passed in is not null, that is forced to modify its value
        // greater than or being equal to 0.0001, less than or equaled to 100,
        // but not coerced if it is null.
        private static object CoerceHorizontalAndVerticalProportion(DependencyObject element, object value) {
            double? horizontalOrVerticalProportion = (double?)value;
            if (horizontalOrVerticalProportion.HasValue) {
                return Math.Max(0.0001, Math.Min(100, horizontalOrVerticalProportion.Value));
            }
            else return null;
        }

        static DynamicTextBlock() {
            // Overriding Its Default Style Key
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicTextBlock), new FrameworkPropertyMetadata(typeof(DynamicTextBlock)));

            // Property Changed Callback Methods
            void newWidthPropertyChangedCallback (DependencyObject element, DependencyPropertyChangedEventArgs e) { ... }
            void newHeigthPropertyChangedCallback(DependencyObject element, DependencyPropertyChangedEventArgs e) { ... }
            void newFontSizePropertyChangedCallback(DependencyObject element, DependencyPropertyChangedEventArgs e) { ... }

            // Overriding Width Property Metadata
            TextBlock.WidthProperty.OverrideMetadata(
                typeof(DynamicTextBlock),
                new FrameworkPropertyMetadata(
                    TextBlock.WidthProperty.DefaultMetadata.DefaultValue,
                    newWidthPropertyChangedCallback,
                    TextBlock.WidthProperty.DefaultMetadata.CoerceValueCallback
                    )
                );

            // Overriding Height Property Metadata
            TextBlock.HeightProperty.OverrideMetadata(
                typeof(DynamicTextBlock),
                new FrameworkPropertyMetadata(
                    TextBlock.HeightProperty.DefaultMetadata.DefaultValue,
                    newHeigthPropertyChangedCallback,
                    TextBlock.HeightProperty.DefaultMetadata.CoerceValueCallback
                    )
                );

            // Overriding FontSize Property Metadata
            // But this behavior is not important in this case
            // so you could ignore it below.
            TextBlock.FontSizeProperty.OverrideMetadata(
                typeof(DynamicTextBlock),
                new FrameworkPropertyMetadata(
                    TextBlock.FontSizeProperty.DefaultMetadata.DefaultValue,
                    newFontSizePropertyChangedCallback,
                    TextBlock.FontSizeProperty.DefaultMetadata.CoerceValueCallback
                    )
                );
        }

        public DynamicTextBlock() : base() { }

        // To relatively explain something that the two properties' type is defined as nullable double,
        // if HorizontalProportion receives null,
        // to justify the DynamicTextBlock object's Width cannot affect the FontSize property of those.
        public double? HorizontalPropotion {
            get => (double?)this.GetValue(HorizontalProportionProperty);
            set => this.SetValue(HorizontalProportionProperty, value);
        }
        public double? VerticalProportion {
            get => (double?)this.GetValue(VerticalPropoertionProperty);
            set => this.SetValue(VerticalPropoertionProperty, value);
        }
    }

So... what I'd like to ask is how to adapt the code to behave as my expectation.


Additional Comments Well, I'm thinking I need to tell you more information that 'my expectation' for the class' behavior is to adjust FontSize of it itself automatically from those Width and Height property, as I mentioned at the first paragraph. Instead, my class with that code lines couldn't operate to set its FontSize but the property was just fixed as an input.

I have truly wanted to take how to design someone's unique custom classes or controls. Thus I used this approach that you saw precedently to manipulate an instance's FontSize fluidly regardless of whether that is a child of a ViewBox.

Upvotes: 0

Views: 151

Answers (1)

grek40
grek40

Reputation: 13458

TL;DR

Basically, overriding a WPF property value without breaking existing bindings can be done via SetCurrentValue method:

textblockInstance.SetCurrentValue(TextBlock.FontSizeProperty, newFontSize);

So, here is an example how you could apply this to create an attached property, that allows scaling of text size based on the textblock height, as long as the scaling happens within some constraints... you may be able to adopt the code in order to create your actually desired scaling logic.

Class that contains the attached property FontProportion and the associated calculations

public static class Utility
{
    public static double? GetFontProportion(TextBlock obj)
    {
        return (double?)obj.GetValue(FontProportionProperty);
    }

    public static void SetFontProportion(TextBlock obj, double? value)
    {
        obj.SetValue(FontProportionProperty, value);
    }

    public static readonly DependencyProperty FontProportionProperty =
        DependencyProperty.RegisterAttached("FontProportion", typeof(double?), typeof(Utility),
            new FrameworkPropertyMetadata(null, new PropertyChangedCallback(FontProportionChanged)));

    private static void FontProportionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var self = (TextBlock)d;

        if (e.NewValue != null)
        {
            // The textblock will never shrink below its content size, so if the content completely fills the textblock,
            // it won't actually change its size and therefore the change event will not trigger
            // Maybe observing some parent elements size would be a better idea...
            if ((double)e.NewValue >= 1.0 || (double)e.NewValue <= 0.0)
            {
                throw new ArgumentException("Scaling only works with values greater than 0.0 and less than 1.0");
            }
            self.SizeChanged -= TextBlock_SizeChanged;
            self.SizeChanged += TextBlock_SizeChanged;
        }
        else
        {
            self.SizeChanged -= TextBlock_SizeChanged;
        }
    }

    private static void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var self = (TextBlock)sender;

        var proportion = GetFontProportion(self);

        double newFontSize = e.NewSize.Height * proportion.Value / self.FontFamily.LineSpacing;

        // Set font size without breaking existing bindings or whatever
        self.SetCurrentValue(TextBlock.FontSizeProperty, newFontSize);
    }
}

WPF that uses the property to scale some text:

<TextBlock local:Utility.FontProportion="0.9">
    Height auto scaling only
</TextBlock>

Upvotes: 1

Related Questions