0

I'm attempting to extend a Label control to support stroking the text ( sort of like this questions answer, but I want to extend a Label to do it rather than write a brand new ( ish ) control ).

I've had a measure of success but I hit a wall in which I can't find how the Label renders text. If I could figure out how that happens ( and override it ) then I could add a few properties to my extended Label class ( a brush and a double ) and be all set.

This is what I have so far :

public class StrokedLabel : Label {
    public static readonly DependencyProperty
        StrokeProperty = DependencyProperty.Register(
            "Stroke",
            typeof( Brush ),
            typeof( StrokedLabel ),
            new PropertyMetadata(
                Brushes.Red,
                ( S, E ) => ( S as StrokedLabel ).InvalidateVisual( ) ) ),
        StrokeWidthProperty = DependencyProperty.Register(
            "StrokeWidth",
            typeof( double ),
            typeof( StrokedLabel ),
            new PropertyMetadata(
                2.0D,
                ( S, E ) => ( S as StrokedLabel ).InvalidateVisual( ) ) );

    /// <summary>
    /// Get or Set Stroke Brush.
    /// </summary>
    public Brush Stroke {
        get { return this.GetValue( StrokeProperty ) as Brush; }
        set { this.SetValue( StrokeProperty, value ); }
    }

    /// <summary>
    /// Get or Set Stroke Width
    /// </summary>
    public double StrokeWidth {
        get { return ( double )this.GetValue( StrokeWidthProperty ); }
        set { this.SetValue( StrokeWidthProperty, value ); }
    }

    protected override void OnRender( DrawingContext drawingContext ) {
        if ( !( this.Content is string ) )
            base.OnRender( drawingContext );
        else {
            drawingContext.DrawGeometry(
                this.Foreground,
                new Pen(
                    this.Stroke,
                    this.StrokeWidth ),
                new FormattedText(
                    this.Content.ToString( ),
                    CultureInfo.CurrentUICulture,
                    this.FlowDirection,
                    new Typeface(
                        this.FontFamily,
                        this.FontStyle,
                        this.FontWeight,
                        this.FontStretch ),
                    this.FontSize,
                    this.Foreground ).BuildGeometry( new Point( 0.0D, 0.0D ) ) );
        }
    }
}

I've tried tracing the chain of inheritance and I've made it as far as UIElement which defines the OnRender method, but I can't find where in the chain of inheritance that method is actually used ( or if it's even involved ).

I feel like the text is rendered within the ContentControl ( from which the Label directly inherits ) but I can't find where in that control the text is rendered. Is it rendered in the Content Control, or is it rendered in what the Content Control inherits ( the Control class )?

In which class ( and through which method(s) ) is a string rendered as text?

Community
  • 1
  • 1
Will
  • 3,413
  • 7
  • 50
  • 107
  • It's been ages and i can't test it right now but: I think it was done via the content control functionality which tries to resolve a content template via its default template selector ([can be overridden](https://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplateselector%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396)) . If the selector finds a `string` it returns a template containing a `TextBlock`. I think you can style said `TextBlock` via `Application.Resources` which apply across template boundaries. – H.B. Mar 12 '16 at 01:41
  • Wait, it's an `AccessText`, obviously, as you want the underlining of the access key given by the "_" in front of said key in the string. – H.B. Mar 12 '16 at 01:47

1 Answers1

0

As i'm a bit inquisitive i looked this up again properly. Here's what happens in the static ContentPresenter constructor:

// Default template for strings when hosted in ContentPresener with RecognizesAccessKey=true
template = new DataTemplate();
text = CreateAccessTextFactory();
text.SetValue(AccessText.TextProperty, new TemplateBindingExtension(ContentProperty));
template.VisualTree = text;
template.Seal();
s_AccessTextTemplate = template;
// Default template selector
s_DefaultTemplateSelector = new DefaultSelector();

It creates an AccessText template that is stored in a private field with an internal property accessor (along with a handful of other default templates) and the default template selector.

When a template is resolved ChooseTemplate is invoked which contains this:

...
// if that failed, try the default TemplateSelector
if (template == null)
{
    template = DefaultTemplateSelector.SelectTemplate(content, this);
}

In the DefaultSelector's SelectTemplate method there is the following call if no template is found:

...
if ((s = item as string) != null)
    template = ((ContentPresenter)container).SelectTemplateForString(s);

SelectTemplateForString:

...
if (this.RecognizesAccessKey && s.IndexOf(AccessText.AccessKeyMarker) > -1)
{
    template = (String.IsNullOrEmpty(format)) ?
        AccessTextContentTemplate : FormattingAccessTextContentTemplate;
}

So here either the static template which was created in the constructor is chosen or one that is created dynamically using the ContentStringFormat value.

How you change that is up to you, though personally i am not a fan messing with default functionality (especially if it impacts keyboard accessibility).

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • But a label doesn't inherit a content presenter. A label inherits a `ContentControl`. Did you mean `ContentControl` or `ContentPresenter`? – Will Mar 12 '16 at 02:48
  • @Will: Can't quite trace it back and don't have the tools at hand currently but i think the default template of `ContentControl` uses a `ContentPresenter` for it's content related properties. That is true for almost all controls, like `TabViews`, which uses one for the selected tab's content. (For the tabs themselves and other list based content an `ItemsPresenter` is used.) – H.B. Mar 13 '16 at 01:46
  • 1
    @Will: If you want to dig into what is generated you can use something like [Snoop](https://snoopwpf.codeplex.com/). You can also do that manually using the `VisualTreeHelper` which i have used before to write a poor man's version of Snoop. – H.B. Mar 13 '16 at 01:48
  • Thanks. I'll take a look at it. I was using dnSpy but oh. my. god. the only thing that I learned from that is I don't know jack $#!t... – Will Mar 13 '16 at 01:58
  • @Will: Well, WPF is rather huge and complex but also a masterpiece of a framework in my opinion. – H.B. Mar 13 '16 at 02:08
  • Uhg... Snoop reveals that text is rendered from within a TextBlock... and the TextBlock OnRender override is sealed... FFFFF... That means that I probably will have to writer my own ( or use the one that already exists... or just alter the existing TextBlock code... ). – Will Mar 13 '16 at 02:14
  • @Will: Well, doing low level rending related thinks is hardly ever pretty, i usually avoid that at all costs. – H.B. Mar 13 '16 at 02:42