与XElement绑定的WPF动态属性无法正常工作

时间:2018-04-18 11:33:27

标签: c# wpf xaml linq-to-xml

我想我在WPF中发现了一个错误,或者我对动态属性和绑定一无所知。

在下面的代码中我想添加(编辑时)节点到XDocument(如果不存在)。为此,我创建了附加属性,它监视TextBox上的文本更改事件,如果用户在TextBox中键入内容,我检查XDocument中是否存在元素,如果没有,则添加它。

(有关解释p1p2是XElement对象。)

如果我只使用p1,一切正常。当我键入第一个字符事件时,火灾检查节点不存在并将其添加到XDocument(键入的字符消失,但这并不重要)当我键入下一个字符节点正确更新时。

现在,如果我使用p2并在TextBox中键入字符,则节点正在创建(字符不会消失),当我键入下一个字符时,节点不会更新。这不正常。

我观察到的是,在xaml中,我重新排序Row 1Row 2 Row 2高于Row 1

<!--ROW P2-->
<TextBlock Grid.Column="0" Grid.Row="1" Text="p2:" />
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Element[p2].Value, UpdateSourceTrigger=PropertyChanged}"
        local:TextChangedAction.AddNodeIfNotExists="p2" />
<!--ROW P1-->
<TextBlock Grid.Column="0" Grid.Row="0" Text="p1:" />
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Element[p1].Value, UpdateSourceTrigger=PropertyChanged}"
        local:TextChangedAction.AddNodeIfNotExists="p1" />
然后

工作p2而不工作p1。它看起来只是集合中的第一个元素才能正确更新绑定。

此外,如果我编辑(在非重新排序的版本中)p2。它添加节点但不更新节点值。现在,如果我编辑p1,则添加节点并更新值。然后我再次编辑p2值正在更新。我认为这是有效的,因为更新p1时会触发负责更新绑定的事件。

最后,如果在附加属性方法TextBox_TextChanged取消注释textBox.GetBindingExpression(TextBox.TextProperty).UpdateTarget();,一切正常。它看起来像绑定故障。

主要问题是:这是一个错误还是我遗漏的东西?

贝娄是重现问题的必要代码。

要显示p1p2编辑控件,请在TreeView中选择节点。

    <Window x:Class="WpfApp13.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp13" mc:Ignorable="d"
        xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:xlinq="clr-namespace:System.Xml.Linq;assembly=System.Xml.Linq"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ObjectDataProvider x:Key="xmlData" ObjectType="{x:Type xlinq:XElement}" MethodName="Parse">
            <ObjectDataProvider.MethodParameters>
                <system:String xml:space="preserve">  
<![CDATA[  
<root>
    <header Name="Headre 1">
        <p0>text</p0>
    </header>
</root> 
]]>                  </system:String>

                <xlinq:LoadOptions>PreserveWhitespace</xlinq:LoadOptions>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <TreeView x:Name="tv" Grid.Column="0" ItemsSource="{Binding Source={StaticResource xmlData}, Path=Elements}">
            <TreeView.Resources>
                <DataTemplate DataType="header">
                    <TextBlock Text="{Binding Attribute[Name].Value}" />
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>
        <TextBox Grid.Column="1" Text="{Binding Source={StaticResource xmlData}, Path=Xml, Mode=OneWay}" />
        <ContentControl Grid.Column="2" DataContext="{Binding ElementName=tv, Path=SelectedItem}" Content="{Binding}">
            <ContentControl.Resources>
                <DataTemplate DataType="header">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="auto" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto" />
                            <RowDefinition Height="auto" />
                        </Grid.RowDefinitions>
                        <!--ROW P1-->
                        <TextBlock Grid.Column="0" Grid.Row="0" Text="p1:" />
                        <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Element[p1].Value, UpdateSourceTrigger=PropertyChanged}"
                                local:TextChangedAction.AddNodeIfNotExists="p1" />
                        <!--ROW P2-->
                        <TextBlock Grid.Column="0" Grid.Row="1" Text="p2:" />
                        <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Element[p2].Value, UpdateSourceTrigger=PropertyChanged}"
                                local:TextChangedAction.AddNodeIfNotExists="p2" />
                    </Grid>
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Xml.Linq;

namespace WpfApp13
{
    public static class TextChangedAction
    {

        #region AddNodeIfNotExists

        public static string GetAddNodeIfNotExists(DependencyObject obj)
        {
            return (string)obj.GetValue(AddNodeIfNotExistsProperty);
        }

        public static void SetAddNodeIfNotExists(DependencyObject obj, string value)
        {
            obj.SetValue(AddNodeIfNotExistsProperty, value);
        }

        public static readonly DependencyProperty AddNodeIfNotExistsProperty =
            DependencyProperty.RegisterAttached("AddNodeIfNotExists", typeof(string), typeof(TextChangedAction), new PropertyMetadata(null, AddNodeIfNotExistsPropertyChanged));

        private static void AddNodeIfNotExistsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is TextBox textBox)
            {
                textBox.TextChanged += TextBox_TextChanged;
            }
        }
        private static void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            var textBox = (TextBox)sender;
            var parentNode = (XElement)textBox.DataContext;
            var nodeName = GetAddNodeIfNotExists(textBox);

            if (parentNode.Element(nodeName) == null)
            {
                parentNode.Add(new XElement(nodeName));
                //uncoment a line bellow and everything works fine
                //textBox.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
            }
        }

        #endregion
    }
}

1 个答案:

答案 0 :(得分:1)

所以,我认为它更像是一个边缘案例而不是一个bug。 实际上,您一方面有一个绑定,从TextBox到XElement,另一方面是TextChanged事件,从同一个TextBox触发并创建相同的XElement。

我不认为我们可以判断应该发生的顺序,或者它是否会发生。

例如,尝试在文本框中添加Mode=TwoWay&#39;绑定。如果您向p2添加文字,则会发现问题仍然存在,但只要您向p1添加文字,p2就会按预期开始工作。

我认为最简单的解决方案是在特定情况下忘记绑定,只需使用代码隐藏事件。

在MainWindow.xaml.cs(或其名称)中添加此代码:

private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    TextBox tb = sender as TextBox;
    XElement parentNode = tb.DataContext as XElement;

    string nodeName = tb.Tag as string;

    XElement node = parentNode.Element(nodeName);
    if (node == null)
    {
        node = new XElement(nodeName);
        parentNode.Add(node);
    }

    node.Value = tb.Text
}

修改文本框,将Tag属性设置为相应的XElement名称:

<!--ROW P1-->
<TextBlock Grid.Column="0" Grid.Row="0" Text="p1:" />
<TextBox Grid.Column="1" Grid.Row="0" Tag="p1" TextChanged="TextBox_TextChanged"/>
<!--ROW P2-->
<TextBlock Grid.Column="0" Grid.Row="1" Text="p2:" />
<TextBox Grid.Column="1" Grid.Row="1" Tag="p2" TextChanged="TextBox_TextChanged" />