我想我在WPF中发现了一个错误,或者我对动态属性和绑定一无所知。
在下面的代码中我想添加(编辑时)节点到XDocument(如果不存在)。为此,我创建了附加属性,它监视TextBox上的文本更改事件,如果用户在TextBox中键入内容,我检查XDocument中是否存在元素,如果没有,则添加它。
(有关解释p1
和p2
是XElement对象。)
如果我只使用p1
,一切正常。当我键入第一个字符事件时,火灾检查节点不存在并将其添加到XDocument(键入的字符消失,但这并不重要)当我键入下一个字符节点正确更新时。
现在,如果我使用p2
并在TextBox中键入字符,则节点正在创建(字符不会消失),当我键入下一个字符时,节点不会更新。这不正常。
我观察到的是,在xaml中,我重新排序Row 1
和Row 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();
,一切正常。它看起来像绑定故障。
主要问题是:这是一个错误还是我遗漏的东西?
贝娄是重现问题的必要代码。
要显示p1
和p2
编辑控件,请在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
}
}
答案 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" />