8

Initially, I have the following code:

<TextBox Text="{Binding LengthUnit, Mode=OneWay}" IsReadOnly="True" Background="{x:Static SystemColors.ControlBrush}" />

I know I can define a style like this:

<Style TargetType="{x:Type TextBox}" x:Key="readOnlyTextBox">
    <Setter Property="Background" Value="{x:Static SystemColors.ControlBrush}"></Setter>
    <Setter Property="IsReadOnly" Value="True"></Setter>
</Style>

So that I can write:

<TextBox Text="{Binding LengthUnit, Mode=OneWay}" Style="{StaticResource readOnlyTextBox}" />

Because this textbox is readonly, the binding mode cannot be twoway. So, is it possible to make the OneWay binding as the default for my TextBox with this style?

EDIT: I need to change the binding mode to OneWay, because my property is get-only, not because I marked the TextBox readonly. However, I still want to change the default binding mode of the textbox to OneWay if possible.


this is the code I have following your suggestion, but it doesn't work. Did I miss anything?

public class ReadOnlyTextBox : TextBox
{
    static ReadOnlyTextBox()
    {
        TextBox.TextProperty.OverrideMetadata(typeof(ReadOnlyTextBox), 
            new FrameworkPropertyMetadata() { BindsTwoWayByDefault = false, Journal = true, DefaultUpdateSourceTrigger = UpdateSourceTrigger.Explicit }); 
    }
    public ReadOnlyTextBox()
    {
        base.Background = SystemColors.ControlBrush;
        base.IsReadOnly = true;            
    }
}
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
newman
  • 6,841
  • 21
  • 79
  • 126
  • @milliu, if it is going to be ReadOnly then, why should you want it to be OneWay? imho Creating a separate control for a single property is not a healthy idea. – Prince Ashitaka Mar 24 '11 at 05:01
  • @Prince, as my update indicated, OneWay is due to my get-only property. – newman Mar 25 '11 at 01:49

3 Answers3

4

Because this textbox is readonly, the binding mode cannot be twoway.

Why not? IsReadOnly will prevent the user from modifying the Text and thereby modifying the property. Just make sure not to modify the Text property in code.

You can prevent the bound property from updating if you subclass TextBox. If you do so, you can override the TextBox.Text Dependency Property metadata.

public class TextBoxEx : TextBox
{
    public TextBoxEx() : base() { }

    static TextBoxEx()
    {
        TextBox.TextProperty.OverrideMetadata(typeof(TextBoxEx), 
            new FrameworkPropertyMetadata() { BindsTwoWayByDefault = false, Journal = true,
                DefaultUpdateSourceTrigger = System.Windows.Data.UpdateSourceTrigger.Explicit });
    }

}

For some reasion changing BindsTwoWayByDefault to false doesn't work for me, but you can set DefaultUpdateSourceTrigger to Explicit which means that the bound property won't be updated unless done so by code, effectively making the binding OneWay.

foson
  • 10,037
  • 2
  • 35
  • 53
  • @foson: Thank you very much for the answer. This approach should be okay because I was actually thinking about creating a derived ReadOnlyTextBox class anyway. As for binding mode, you are right. When I remove Mode=OneWay from xaml, I get this exception: "A TwoWay or OneWayToSource binding cannot work on the read-only property 'LengthUnit' of type 'xxx'." I didn't look at this message more closely. The problem is that my binding soure property has a getter only. So, it's not related to IsReadOnly of the textbox. Thanks again! – newman Mar 24 '11 at 04:47
  • 1
    @foson: I just tested your suggested code, but it doesn't work. I still get the exception: "A TwoWay or OneWayToSource binding cannot work on the read-only property 'LengthUnit' of type 'xxx'.". I'll post my code in answer section to make it more readable. – newman Mar 24 '11 at 04:56
  • @Prince: Does it work for you? It doesn't do for me. I also tried to use TextProperty.AddOwner(...), but got same error. – newman Mar 24 '11 at 14:42
  • @miliu: do you want to mark this correct and start a new question, or edit the question to include that the property is get-only? – foson Mar 24 '11 at 15:09
  • @foson: As I said, your suggested solution doesn't work (i.e. it does not change the TextBox's default binding mode to OneWay). This is independent of the fact that my property is get-only. – newman Mar 24 '11 at 18:11
3

I know this question is really old, but I recently encountered this problem myself, so maybe I can help somebody else as well.

I wanted to create a TextBox that have a OneWayBinding on its Text property. I discovered that this is not working as shown in the question because WPF combines the existing metadata and the overriding metadata together by basically ORing the 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 false.

If anybody knows a better way to do that please let me know.

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();
    }
}
Community
  • 1
  • 1
Bluuu
  • 482
  • 4
  • 12
3

Styles are a way to apply the same set of customizations to one or more properties for UI objects e.g. Background, IsReadOnly etc which are typically dependency properties.

Mode is a property of the Binding object, which is not a UI object.

You can set a style on any element that derives from FrameworkElement or FrameworkContentElement. -- Source (MSDN)

So this is not typically done via XAML/Styling.. my guess is you'd have to write code for it. Although XAML allows you to set nested properties Text.Mode="value", it is error prone (because it assumes that Text has been already set to a binding object). It will result in a binding exception if Text property returns an object that doesn't have a Mode property on it - e.g. if Text="a plain string".

If you absolutely must have this, then you'd need to create your binding programatically. You could use a naming convention for example to see if the backing property has a setter and add a OneWay binding if it doesn't.

Gishu
  • 134,492
  • 47
  • 225
  • 308