简体   繁体   English

与转换器的双向数据绑定不会更新源

[英]Two-way data binding with converter doesn't update source

I've got a data binding set up with a converter to transform an awkward XML source to a display- and editing- convenient tree of internal classes. 我有一个数据绑定设置与转换器,以将一个笨拙的XML源转换为显示和编辑方便的内部类树。 Everything works great for reading from the XML source, but I'm having a devil of a time trying to get changes made to the internal classes to propagate back to the XML source. 一切都非常适合从XML源读取,但我有一段时间试图对内部类进行更改以传播回XML源。

Here's the XAML for the use site: 这是使用网站的XAML:

        <local:SampleConverter x:Key="SampleConverter" />
        <Expander Header="Sample" >
            <local:SampleControl 
                Sample="{Binding Path=XmlSource, 
                                 Converter={StaticResource SampleConverter}, 
                                 Mode=TwoWay}" />
        </Expander>

XmlSource is a CLR read-write property (not DependencyProperty) of the parent data bound object. XmlSource是父数据绑定对象的CLR读写属性(不是DependencyProperty)。 It is a .NET type generated from an XSD. 它是从XSD生成的.NET类型。

SampleConverter implements IValueConverter . SampleConverter实现了IValueConverter The Convert method is called and returns non-null data, but the ConvertBack method is never called. 调用Convert方法并返回非null数据,但从不调用ConvertBack方法。

SampleControl is a UserControl that encapsulates UI interaction with the Sample data tree. SampleControl是一个UserControl,它封装了与Sample数据树的UI交互。 It's XAML looks like this: 这是XAML看起来像这样:

<UserControl x:Class="SampleControl">
    [... other stuff ...]

    <UserControl.Content>
        <Binding Path="Sample" RelativeSource="{RelativeSource Mode=Self}" Mode="TwoWay" TargetNullValue="{StaticResource EmptySampleText}" />
    </UserControl.Content>

    <UserControl.ContentTemplateSelector>
        <local:BoxedItemTemplateSelector />
    </UserControl.ContentTemplateSelector>
</UserControl>

The Sample property is a DependencyProperty in the SampleControl code behind: Sample属性是SampleControl代码中的DependencyProperty:

public static readonly DependencyProperty SampleProperty =
    DependencyProperty.Register("Sample", typeof(SampleType), typeof(SampleControl), new PropertyMetadata(new PropertyChangedCallback(OnSampleChanged)));

public SampleType Sample
{
    get { return (SampleType)GetValue(SampleProperty); }
    set { SetValue(SampleProperty, value); }
}

private static void OnSampleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue != null)
    {
        ((INotifyPropertyChanged)e.NewValue).PropertyChanged += ((SampleControl)d).MyPropertyChanged;
    }
    else if (e.OldValue != null)
    {
        ((INotifyPropertyChanged)e.OldValue).PropertyChanged -= ((SampleControl)d).MyPropertyChanged;
    }
}

private void MyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    ;  // breakpoint here shows change notices are happening
}

The internal classes that the XmlSource is converted to implement INotifyPropertyChanged, and are sending change notifications up the tree, as indicated by a breakpoint in MyPropertyChanged above. 转换为XmlSource以实现INotifyPropertyChanged的内部类,并在树上发送更改通知,如上面的MyPropertyChanged中的断点所示。

So if the data is reporting that it has changed, why isn't WPF calling my converter's ConvertBack method? 因此,如果数据报告它已经改变,为什么WPF不调用我的转换器的ConvertBack方法?

With hints from several similar questions and almost answers here on SO, I have a working solution that preserves the binding. 通过几个类似问题的提示和SO上的几乎答案,我有一个保留绑定的工作解决方案。 You can manually force the binding to update the source in a strategically placed event, such as LostFocus: 您可以手动强制绑定在策略性放置的事件中更新源,例如LostFocus:

private void mycontrol_LostFocus(object sender, RoutedEventArgs e)
{
    if (mycontrol.IsModified)
    {
        var binding = mycontrol.GetBindingExpression(MyControl.SampleProperty);
        binding.UpdateSource();
    }
}

Had similar problem. 有类似的问题。 The solution was the following: 解决方案如下:

Instead of the two way binding I used one way, and a converter. 而不是双向绑定我用一种方式,和转换器。 The converter converts (encapsulates) the object (in your case, the parent object of the xmlsource property) to a viewModel, and the control binds to it. 转换器将对象(在您的情况下,xmlsource属性的父对象)转换(封装)到viewModel,并且控件绑定到它。

The viewModel works like a proxy, holds a reference to the object, manages it's properties, and of course implements INotifyPropertyChanged. viewModel的工作方式类似于代理,包含对象的引用,管理它的属性,当然还实现了INotifyPropertyChanged。 In this case you don't need to call the ConvertBack method, since the operations are performed on the proper instance through the viewModel. 在这种情况下,您不需要调用ConvertBack方法,因为操作是通过viewModel在适当的实例上执行的。

So you have a clean view, you don't need any code in the xaml.cs 所以你有一个干净的视图,你不需要xaml.cs中的任何代码

XmlSource is a CLR read-write property (not DependencyProperty) of the parent data bound object. XmlSource是父数据绑定对象的CLR读写属性(不是DependencyProperty)。 It is a .NET type generated from an XSD. 它是从XSD生成的.NET类型。

I believe this is your problem. 我相信这是你的问题。 A basic CLR property won't notify, and is thus unable to participate in two-way databinding. 基本CLR属性不会通知,因此无法参与双向数据绑定。 My guess would be your are getting one-time binding? 我的猜测是你的一次性绑定? Do you have any databinding errors in your output window? 输出窗口中是否有任何数据绑定错误?

You might try creating a wrapper that contains the XmlSource property's class, make that class implement INotifyPropertyChanged, and expose XmlSource as a notifying property (it does not need to be a dependency property). 您可以尝试创建包含XmlSource属性的类的包装,对该类执行INotifyPropertyChanged,并公开XmlSource作为通知特性(它并不需要是一个依赖属性)。

Also, regarding this comment: 另外,关于这个评论:

Are you saying that data binding only works if a new instance is assigned to the Sample property, but not if properties of the existing instance referred to by the Sample property are changed and signal their changes via INotifyPropertyChanged? 您是说数据绑定仅在将新实例分配给Sample属性时才有效,但是如果Sample属性引用的现有实例的属性发生更改并且通过INotifyPropertyChanged发出更改信号,则不会这样做吗?

Sample implementions of INotifyPropertyChanged usually have the notification occuring when the property itself changes, not when children or other properties change. INotifyPropertyChanged的示例实现通常会在属性本身更改时发生通知,而不是在子项或其他属性发生更改时。 However, you can absolutely fire the notification event on any property at any time. 但是,您可以随时在任何属性上触发通知事件。 For example, you might have a "Full Name" property that notifies whenever "First Name" or "Last Name" change. 例如,您可能具有“全名”属性,该属性会在“名字”或“姓氏”发生变化时通知。

If the above isn't helpful, I would suggest posting a bit more of your code--maybe a simplified repro of what you are trying to get working. 如果以上内容没有帮助,我建议您发布一些代码 - 可能是您正在努力工作的简化版本。

Edit: 编辑:

I think I misunderstood your question. 我想我误解了你的问题。 I think the problem is what you alluded to in your comments--it's a reference type. 我认为问题是你在评论中提到的 - 它是一种参考类型。 Might be worth trying this in your OnSampleChanged: 可能值得在你的OnSampleChanged中尝试这个:

private static void OnSampleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{
    var oldValue = Sample;
    Sample = new SampleType();
    Sample = oldValue;
}

I think this will force the binding system to recognize that something changed on the target side. 我认为这将迫使绑定系统识别出目标端发生了某些变化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM