单向数据绑定困境

3

我正在使用OneWayToSource绑定,但是它似乎总是将我的源属性设置为null。为什么会这样?这给我带来了麻烦,因为我需要从目标属性中获取值到我的源属性而不是null。

这是我的代码:

MyViewModel.cs:

public class MyViewModel
{
    private string str;

    public string Txt
    {
        get { return this.str; }
        set { this.str = value; }
    }
}

MainWindow.cs:

public MainWindow()
{
    InitializeComponent();
    MyViewModel vm = new MyViewModel();
    vm.Txt = "123";
    this.DataContext = vm;
}

MainWindow.xaml:

<Window x:Class="OneWayToSourceTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:local="clr-namespace:OneWayToSourceTest">
      <Grid>
        <local:MyButton Content="{Binding Path=Txt, Mode=OneWayToSource}"/>
      </Grid>
 </Window>

MyButton.cs:

public class MyButton : Button
{
    public MyButton()
    {
        this.Content = "765";
    }
}

目标属性是MyButton.Content,源属性是MyViewModel.TxtTxt 属性应该设置为"765",但实际上它是 null。
为什么会收到 null 而不是 765?
编辑:
请查看MyButton构造函数内部。实际上,如果您使用简单的TwoWay,它就能正常工作。我测试了一下,这与在构造函数中设置内容无关。我想这可能与OneWayToSource绑定有关。
现在来解释一下我如何使用TwoWay绑定,我通过调用setvalue方法在构造函数内部设置了dp的值,但是在包装器或者更好地说是getter和setter内部,我没有提供任何setter,因此我的TwoWay看起来有点像OneWayToSource。 我这样做是为了测试是否是构造函数的问题。我发现视图模型中的属性具有值765,这就是我所说的TwoWay绑定。我只是测试是否是控件构造函数的问题。 在构造函数中设置值完全没问题。
隐藏setter是指这个 set {}

你有收到任何绑定错误吗? - Jehof
3个回答

10

Content属性只能设置为单个值,您正在使用绑定替换值“756”。

正如我在我的答案中对您的另一个问题指出的那样,WPF按照以下顺序运行代码:

  • Normal - 这里运行构造函数
  • DataBind
  • Render
  • Loaded

因此,首先运行的是MainWindow构造函数。这将创建按钮,调用按钮的构造函数,并将Button.Content设置为“765”。

然而,在Button的XAML中,您指定了不同的Content属性 - 一个绑定。因此,您的按钮对象被创建,Content属性被设置为“765”,然后Content属性被设置为{Binding Path=Txt, Mode=OneWayToSource}

这与执行以下操作相同:

var b = new MyButton();
b.Content = "756";
b.Content = new Binding(...); 

你正在替换Content属性。

(严格来说,最后一行应该是b.SetBinding(MyButton.ContentProperty,new Binding(...));以正确绑定数值,但我使用了更基本的语法来使问题更容易理解。)

循环中的下一步是数据绑定,因此评估Content属性上的绑定。

由于这是OneWayToSource绑定,所以当它被更改时,属性仅更新源元素,并且由于这是第一次评估绑定,它将源设置为此DependencyProperty的默认值,以便它们同步。

对于Button.Content依赖属性,其默认值为null,因此您的源元素被设置为null

您在TwoWay绑定中看不到此行为,因为DP从绑定中获取其值,而不是使用默认值。

如果您在设置完绑定之后,例如在按钮的Loaded事件中设置值,则会发现当您设置按钮的Content时,源属性会正确更新。

void MyButton_Loaded(object sender, EventArgs e)
{
    ((Button)sender).Content = "756";
}

最后,如果您正在尝试从自定义控件设置Content属性的默认值,则需要覆盖该属性的MetaData,如下所示:

And last of all, if you're trying to set the default value of the Content property from your custom control, you need to overwrite the MetaData for that property, like this:

// Note that this is the static constructor, not the normal one
static MyButton()
{
    ContentProperty.OverrideMetadata(typeof(MyButton), 
        new FrameworkPropertyMetadata("756"));
}

这将使默认值的 Content 属性为"756",而不是null


+1 我删除了我的回答,因为你的更好,因为它解释了为什么绑定按照它所做的方式工作。我也不知道你可以像这样设置绑定 b.Content = new Binding(...),而且认为你必须使用 SetBinding(..) 方法。谢谢你的提示。 - Benjamin Gale
@Benjamin 嗯,那可能是个错误 :) 我认为设置 b.Content = new Binding(...) 会将 Content 属性设置为绑定对象,而不是对其进行评估(必须测试以确保)。我只是使用了这种语法,因为它使理解正在发生的事情更容易 :) - Rachel
是的,你说得对 :) 当我测试它时,我认为它已经可以工作了,但我错了(我的测试有缺陷)。 - Benjamin Gale
嗨Rache,你写了这个:“它将源设置为此DependencyProperty的默认值,以便它们保持同步。”什么?真的吗?你从哪里得到这个信息的?还是你只是假设发生了这种情况?我真的很想知道你是从哪里得到这个信息的。我对这个问题充满了假设。你是怎么知道发生了这种情况的? - snowy hedgehog
@snowyhedgehog 没有任何假设。您可以通过更改属性的默认值来验证此操作,如Rachel发布的最后一个代码示例所示。 - Benjamin Gale
显示剩余3条评论

4

在设置DataContext之前,MyButton被InitializeComponent实例化,因此当它的构造函数运行时绑定尚未生效。尝试在按钮点击时设置一个值。


1
可能不是这样,但我已经向您解释了为什么它无法工作。非常感谢您的负评!! - johndsamuels
2
点赞 - 在我看来,这似乎是正确的答案。我想你需要在按钮加载后设置值,而不是在构造函数中设置。 - Dan Puzey
2
@snowyhedgehog:在本地测试了您的代码后,我可以确认您的绑定是正确的,但在按钮构造函数中设置值是问题所在。可能对您来说没有意义,但这是框架的工作方式:如果您在创建按钮之后(并且一旦绑定就位)设置值,则绑定将起作用。我通过在MyButtonLoaded 事件中设置值进行了测试,结果正常。 - Dan Puzey
2
通过双向绑定和您的代码,视图模型中的值将覆盖按钮中的值。这是我期望看到的 - 但这不是您要求的行为。如果您希望控件中的值优先,请在加载控件后设置它。 - Dan Puzey
3
这个回答被-1负分评价是不公平的,因为它非常正确。@snowyhedgehog,即使在“Mode=TwoWay”模式下,你的绑定也无法给出你期望的输出。是的,vm Text属性不会变成null,但它也不会得到765。 - Viv
显示剩余19条评论

2
实际上,如果你使用简单的TwoWay绑定,它就会起作用。根据你提供的代码,如果使用TwoWay绑定模式,视图模型的Txt属性将等于"123",即在MainWindows构造函数中赋予它的值。我无法复制这个问题。我测试过了,它与在构造函数中设置内容无关。正如Rachel所指出的,XAML绑定会清除本地值。绑定发生在按钮构造函数代码运行之后。然后绑定从依赖属性元数据中检索其默认值。这很容易通过在更合适的时间(如Loaded事件中)建立绑定后设置值来解决。这个简单的修改将给你想要的结果:
public class MyButton : Button
{
    public MyButton()
    {
        this.Loaded += MyButton_Loaded;
    }

    void MyButton_Loaded(object sender, RoutedEventArgs e)
    {
        this.Content = "765";
    }
}

Rachel的回答提供了另一种使此方法生效的替代方法(覆盖属性的默认元数据)。

为什么每次有人使用OneWayToSource绑定后都要设置初始值?我觉得这没有意义。

我认为你觉得这没意义是因为你的TwoWay绑定测试并不像你想象的那样工作。

使用OneWayToSource绑定:

使用OneWayToSource绑定时,将会发生以下操作:

  1. 在构造函数中将MyButton.Content设置为"123"。
  2. 在XAML中设置了一个OneWayToSource绑定,这将清除你设置的值。
  3. 绑定检索属性元数据的默认属性值(null),并将ViewModel.Txt属性设置为该值。

如果你在按钮加载事件中设置MyButton.Content属性,则在上述事件发生后,该属性将被设置为你想要的值。

你可以通过在MyViewModel.Txt属性的getter中设置断点来验证这一点。值将按顺序设置为"123"、null和"756"。

使用TwoWay绑定:

现在,如果你将XAML更改为使用TwoWay绑定,则将会发生以下操作:

  1. 在构造函数中将MyButton.Content设置为"123"。
  2. 在XAML中设置了一个TwoWay绑定,这将清除你设置的值。
  3. 使用更新控件的值(MyButton.Content),在这种情况下是你的viewModels的Txt属性,结果是MyButton.Content属性等于"123"。

我不喜欢不知道为什么就使用东西。在这种情况下,似乎我只能相信你们。尽管如此,正如我所说的,通过我的自定义双向绑定示例,我成功地证明了这不是构造函数的问题。然而,让我们接受这个答案。谢谢。 - snowy hedgehog

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