4

I wanted to create a readonly TextBox that has a default OneWay Binding on its Text property. Like in a very similar question my first try was this:

public partial class SelectableTextBlock : TextBox
{
    static SelectableTextBlock ()
    {
        TextBox.TextProperty.OverrideMetadata(typeof(ReadOnlyTextBox), 
            new FrameworkPropertyMetadata() { BindsTwoWayByDefault = false, Journal = true, DefaultUpdateSourceTrigger = UpdateSourceTrigger.Explicit }); 
    }
    public SelectableTextBlock()
    {
        InitializeComponent();
    }
}

I discovered that this is does not work because WPF combines the existing metadata and the overriding metadata by basically ORing all flags together. Since BindsTwoWayByDefault is one of those flags, as long as one of the Metadata objects has BindsTwoWayByDefault=true is stays true.

The only way around that is to change the Metadata after the WPF merging process takes places in OverrideMetadata. However the Metadata object is marked as Sealed in the method.

As any good developer would I stopped here and reconsidered... Naaa, I used reflection to "unseal" the metadata object and set the BindsTwoWayByDefault back to true.

Please tell me that I am stupid and did not see the obvious and correct way of doing this in WPF.

Here my code:

public partial class SelectableTextBlock : TextBox
{
    static SelectableTextBlock()
    {
        var defaultMetadata = (FrameworkPropertyMetadata)TextProperty.GetMetadata(typeof(TextBox));

        var newMetadata = new FrameworkPropertyMetadata(
            defaultMetadata.DefaultValue,
            FrameworkPropertyMetadataOptions.Journal,
            defaultMetadata.PropertyChangedCallback,
            defaultMetadata.CoerceValueCallback,
            defaultMetadata.IsAnimationProhibited,
            defaultMetadata.DefaultUpdateSourceTrigger);

        TextProperty.OverrideMetadata(typeof(SelectableTextBlock), newMetadata);

        //Workaround for a bug in WPF were the Metadata is merged wrongly and BindsTwoWayByDefault is always true
        var sealedProperty = typeof(PropertyMetadata).GetProperty("Sealed", BindingFlags.Instance | BindingFlags.NonPublic);
        sealedProperty.SetValue(newMetadata, false);
        newMetadata.BindsTwoWayByDefault = false;
        sealedProperty.SetValue(newMetadata, true);
    }

    public SelectableTextBlock()
    {
        InitializeComponent();
    }
}
Bluuu
  • 482
  • 4
  • 12
  • 1
    is it really the goal to do it by code instruction? I'd better defined a style for your particulat case and applied it type-wise through all your form content if it fits your requirements – Yaugen Vlasau Dec 13 '16 at 09:29
  • I am not sure I completely understand what you mean. But setting the default binding mode for a DependencyProperty can only be done in code and not in a Style. – Bluuu Dec 13 '16 at 09:44
  • 2
    What he probably meant was to ignore the default value and set it every time to OneWay when you need it to avoid the headache – nkoniishvt Dec 13 '16 at 09:45
  • 1
    What i'll normally do, create new control derived from textbox , in generic file, add textbox inside border and bind the needed properties to TextBox using TemplateBinding, finally bind TextProperty with Binding and TemplatedParent, so you can set mode. with this we can have whole control over our control. – WPFUser Dec 13 '16 at 10:00
  • 1
    @nkoniishvt Oh, yeah that is the obvious way. But were is the fun in that? – Bluuu Dec 13 '16 at 10:17
  • @WPFUser Also a good point. But these are all just work arounds. Should this not be possible to do in WPF without hacks and reflection? – Bluuu Dec 13 '16 at 10:18

1 Answers1

1

So after trying to find a better solution for almost a year, it seems like my workaround with the reflection is still the best solution.

Bluuu
  • 482
  • 4
  • 12