WPF文本框:如何将绑定模式默认更改为单向?

8

最初,我有以下代码:

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

我知道可以像这样定义一个样式:

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

为了我能够编写代码,我需要:
<TextBox Text="{Binding LengthUnit, Mode=OneWay}" Style="{StaticResource readOnlyTextBox}" />

由于此文本框是只读的,因此绑定模式不能为双向。那么,是否有可能使用此样式将OneWay绑定作为我的TextBox的默认值?
编辑:我需要将绑定模式更改为OneWay,因为我的属性是只读的,而不是因为我标记了TextBox为只读。但是,如果可能的话,我仍然希望更改文本框的默认绑定模式为OneWay。
以下是根据您的建议编写的代码,但它不起作用。我错过了什么吗?
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;            
    }
}

@milliu,如果它只是ReadOnly,那么为什么你还想让它成为OneWay呢?在我看来,为单个属性创建一个单独的控件并不是一个好主意。 - Prince Ashitaka
@Prince,正如我的更新所示,OneWay 是由于我的只读属性。 - newman
3个回答

4
由于此文本框是只读的,绑定模式不能是双向的。
为什么不行?IsReadOnly将防止用户修改文本,从而修改属性。只需确保不在代码中修改Text属性即可。
您可以通过子类化TextBox来防止绑定属性更新。如果这样做,您可以重写TextBox.Text依赖属性元数据。
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 });
    }

}

由于某些原因,将BindsTwoWayByDefault更改为false对我没有效果,但您可以将DefaultUpdateSourceTrigger设置为Explicit,这意味着除非通过代码更新绑定属性,否则不会更新绑定属性,从而有效地使绑定成为单向。


@foson:非常感谢您的回答。这种方法应该是可行的,因为我实际上正在考虑创建一个派生的ReadOnlyTextBox类。至于绑定模式,您是正确的。当我从xaml中删除Mode=OneWay时,我会得到这个异常:“不能在类型“xxx”的只读属性“LengthUnit”上使用TwoWay或OneWayToSource绑定。”我没有仔细查看这条消息。问题在于我的绑定源属性只有getter。所以,它与文本框的IsReadOnly无关。再次感谢! - newman
1
@foson:我刚刚测试了你建议的代码,但它不起作用。我仍然会得到异常:“TwoWay 或 OneWayToSource 绑定无法在类型为 'xxx' 的只读属性 'LengthUnit' 上工作。” 我将在答案部分发布我的代码,以使其更易读。 - newman
@Prince:这个对你有效吗?对我没用。我也尝试使用TextProperty.AddOwner(...),但是得到了相同的错误。 - newman
@miliu: 你想将其标记为正确并开始一个新问题,还是编辑问题以包含该属性只读(get-only)的信息? - foson
@foson:就像我所说的那样,你提出的解决方案不起作用(即它不能将TextBox的默认绑定模式更改为OneWay)。这与我的属性是只读无关。 - newman

3

样式是一种将相同的自定义应用于一个或多个 UI 对象的属性集的方式,例如 Background、IsReadOnly 等,这些属性通常是依赖属性。

Mode 是 Binding 对象的一个属性,它不是 UI 对象。

您可以在任何从 FrameworkElement 或 FrameworkContentElement 派生的元素上设置样式。-- 源 (MSDN)

因此,这通常不是通过 XAML/样式完成的... 我猜你需要编写代码来完成它。虽然 XAML 允许您设置嵌套属性 Text.Mode="value",但这很容易出错(因为它假定 Text 已经被设置为绑定对象)。如果 Text 属性返回一个没有 Mode 属性的对象 - 例如 Text="a plain string",它将导致绑定异常。

如果您绝对必须这样做,那么您需要以编程方式创建绑定。例如,您可以使用命名约定查看支持属性是否具有 setter,并在没有 setter 的情况下添加 OneWay 绑定。


3

我知道这个问题真的很老,但最近我自己也遇到了这个问题,所以或许我能帮助其他人。

我想创建一个具有OneWayBinding的TextBox,绑定Text属性。 我发现这与问题中展示的不同,因为WPF通过基本上将现有元数据和覆盖元数据结合在一起进行OR运算来组合它们。 由于BindsTwoWayByDefault是其中一个标志,只要其中一个Metadata对象具有BindsTwoWayByDefault=true,则它保持为true。

唯一的解决方法是在OverrideMetadata方法中,在WPF合并过程发生后更改元数据。 然而,在该方法中,Metadata对象被标记为Sealed。

像任何好的开发者一样,我就停在了这里并重新考虑了一下…… 不,我使用了反射来“解封”metadata对象,并将BindsTwoWayByDefault重置为false。

如果有人知道更好的方法,请告诉我。

以下是我的代码:

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();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接