Reputation: 93
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
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