【问题标题】:OneWayToSource DilemmaOneWayToSource 困境
【发布时间】:2013-04-23 04:37:27
【问题描述】:

我正在使用OneWayToSource 绑定,它似乎总是将我的源属性设置为空。为什么呢?这给我带来了麻烦,因为我需要源属性中目标属性的值而不是 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 而不是 765?

编辑:

请查看MyButton 构造函数的内部。实际上,如果您使用简单的TwoWay,它将起作用。我对其进行了测试,它与在构造函数中设置的内容无关。我猜它与OneWayToSource 绑定有关。

现在解释我是如何使用TwoWay 绑定的,我确实通过调用setvalue 方法在构造函数中设置了dp 的值,但是在包装器内部或者更好地说是getter 和setter,因此我没有提供任何setter为什么我让我的TwoWay 有点像它的OneWayToSource。我这样做是为了测试它的构造函数是否出错。我认为 viewmodel 中的属性的值为 765,所以这就是我对 TwoWay 绑定的意思。我只是测试了它是否是控制构造函数。在构造函数中设置一个值就可以了。

我的意思是隐藏 setter 设置{}

【问题讨论】:

  • 你得到任何 BindingErrors 吗?

标签: wpf xaml wpf-4.0


【解决方案1】:

实际上,如果您使用简单的 TwoWay,它会起作用。

我无法复制这个。鉴于您提供的代码,如果您使用 TwoWay 绑定模式,则 viewmodels 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";
    }
}

Rachels 的回答还提供了一种替代方法来完成这项工作(覆盖属性默认元数据)。

为什么每次有人使用 OneWayToSource 绑定时都应该设置 初始化后的价值?这对我来说没有意义。

我认为这对您来说没有意义的原因是因为您的 TwoWay 绑定测试没有按照您认为的方式工作。

使用 OneWayToSource 绑定:

使用OneWayToSource 绑定会发生以下情况:

  1. 您在其构造函数中将MyButton.Content 设置为“123”。
  2. 您正在 XAML 中设置OneWayToSource 绑定。这会清除您设置的值。
  3. 绑定从属性元数据中检索默认属性值 (null),并将 ViewModel.Txt 属性设置为等于该值。

如果您在按钮加载事件中设置MyButton.Content 属性,这是在上述事件发生之后,因此您的属性设置为您希望的值。

您可以通过在 MyViewModel.Txt 属性 getter 中放置一个断点来自己验证这一点。该值将依次设置为“123”、null 和“756”。

使用双向绑定:

现在,如果您将 XAML 更改为使用 TwoWay 绑定,则会发生以下情况:

  1. 您在其构造函数中将MyButton.Content 设置为“123”。
  2. 您正在 XAML 中设置TwoWay 绑定。这会清除您设置的值。
  3. 您的控件值 (MyButton.Content) 使用 Source 更新,在本例中是您的 viewModels Txt 属性,导致您的 MyButton.Content 属性等于“123”。李>

【讨论】:

  • 我不喜欢只使用东西而不知道为什么。看来在这种情况下,我必须相信你们。正如我在自定义双向绑定示例中所说的那样,我成功地证明了它不是构造函数。但是,让我们接受这个作为答案。谢谢
【解决方案2】:

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

正如我在 my answer 中向您指出的其他问题,WPF 按以下顺序运行代码:

  • 正常 - 构造函数在这里运行
  • 数据绑定
  • 渲染
  • 已加载

所以要做的第一件事就是运行MainWindow 构造函数。这将创建按钮,该按钮调用 Button 的构造函数,并将 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 绑定,因此该属性仅在源元素发生更改时更新它,并且由于这是第一次评估绑定,因此它将源设置为此@987654335 的默认值@ 所以它们是同步的。

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

TwoWay 绑定看不到这种行为,因为 DP 从绑定中获取它的值,而不是使用默认值。

如果您要在设置绑定后设置值,例如在按钮的 Loaded 事件中,您会看到在设置按钮的 Content 时源属性正确更新

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

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

// 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 Errmmm 这可能是一个错误:) 我认为设置b.Content = new Binding(...) 会将Content 属性设置为绑定对象,而不是对其进行评估(必须进行测试才能确定)。我只是使用这种语法,因为它更容易理解正在发生的事情:)
  • 是的,你是对的 :) 当我测试这个时,我以为我已经成功了,但我错了(我的测试有缺陷)。
  • 嗨,Rache,您这样写:“它将源设置为此 DependencyProperty 的默认值,因此它们是同步的。”什么?真的吗?你从哪里得到那个的?还是您只是假设正在发生这种情况?我真的很想看看你是从哪里得到的。我对此充满了假设。你怎么知道会发生这种情况?
  • @snowyhedgehog 没有假设。您可以通过更改属性的默认值自行验证这一点,如 Rachel 发布的最后一个代码示例所示。
【解决方案3】:

MyButton 在设置 DataContext 之前由 InitializeComponent 实例化,因此在其构造函数运行时绑定无效。尝试在单击按钮时设置一个值。

【讨论】:

  • 可能不是,但我已经向您解释了为什么它不起作用。非常感谢您的反对!
  • 赞成 - 在我看来这是正确的答案。我想您需要在加载按钮后设置值,而不是在其构造函数中。
  • @snowyhedgehog:在本地测试了您的代码,我可以确认您的绑定没问题,但是在按钮的构造函数中设置值是问题所在。这对您来说可能没有意义,但这是框架的工作方式:如果您在按钮创建后设置值 (并且一旦绑定到位),则绑定工作。我通过在MyButtonLoaded 事件中设置值对此进行了测试,它工作正常。
  • 使用双向绑定和您的代码,视图模型中的值会覆盖按钮中的值。这就是我希望看到的——但这不是你要求的行为。如果希望控件中的值优先,则必须在控件加载后进行设置。
  • +1 反对票对于这个答案是不公平的。这是非常正确的。 @snowyhedgehog 即使在“Mode=TwoWay”中,您的 Binding 也不会为您提供您期望的输出。是的,vm Text 属性不会被设为 null,但它也不会得到 765
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-14
  • 2017-10-08
  • 2016-02-04
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多