WPF 调试 AvalonEdit 绑定到 Document 属性
我一整天都坐着试图找出为什么绑定到 AvalonEdits Document 属性不起作用。 AvalonEdit 是一个高级 WPF 文本编辑器 - SharpDevelop 项目的一部分。(它将在 SharpDevelop v4 Mirador 中使用)。
因此,当我设置一个简单的项目 - 一个 TextEditor(这是库中的 AvalonEdits 真实名称)并创建一个具有一个属性 - Document 的简单类,并且它返回一个带有一些静态文本的虚拟对象时,绑定工作正常。
然而,在现实生活中的解决方案中,我将 SomeEditor 对象的集合绑定到 TabControl。 TabControl 有 SomeEditor 的 DataTemplate 和 TextEditor 对象。
<TabControl Grid.Column="1" x:Name="tabControlFiles" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type m:SomeEditor}">
<a:TextEditor
Document="{Binding Path=Document, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource NoopConverter}, IsAsync=True}"
x:Name="avalonEdit"></a:TextEditor>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style BasedOn="{StaticResource TabItemStyle}" TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"></Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
这是行不通的。到目前为止我所调查的内容:
- TextEditor 的 DataContext 设置为 SomeEditor TextEditors 的正确实例
- Document 属性设置为 SomeEditor.Document 属性之外的其他实例
- 当我将断点设置为附加到它显示的绑定的无操作转换器时, 我找到了 Document 的正确值(使用了转换器!)
我还通过 VisualTree 进行了挖掘,以获取对 TextEditor 的引用并调用了 GetBindingExpression(TextEditor.DocumentProperty),但这并没有返回任何内容
WPF 生成以下信息:
System.Windows.Data 信息:10:无法使用绑定检索值,并且不存在有效的后备值;使用默认值代替。 BindingExpression:路径=文档; DataItem='SomeEditor' (HashCode=26280264);目标元素是“TextEditor”(名称=“avalonEdit”);目标属性是“Document”(类型“TextDocument”)
绑定到的 SomeEditor 实例在绑定发生之前已经创建并缓存了 Document 的副本。 getter 永远不会被调用。
任何人都可以告诉我可能出了什么问题吗?为什么没有设置 BindingExpression ?为什么属性获取器永远不会被调用?
//编辑:新测试和新结果
我已经阅读了更多内容并在后面的代码中设置了绑定。当我这样做时,它起作用了。 为什么在 XAML 中设置此功能不起作用,而在代码中执行相同的操作却可以?
//edit2:将对象添加到用作更高级别数据源的可观察集合后立即调用该代码也会失败。(在 xaml 绑定应该触发之后不久)。这让我认为这是时间问题。任何人都可以告诉一些有关它的事情吗?
//edit3:绑定代码:
private List<T> GetObjectOfTypeInVisualTree<T>(DependencyObject dpob) where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(dpob);
List<T> returnlist = new List<T>();
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dpob, i);
T childAsT = child as T;
if (childAsT != null)
{
returnlist.Add(childAsT);
}
List<T> lst = GetObjectOfTypeInVisualTree<T>(child);
if (lst != null)
{
returnlist.AddRange(lst);
}
}
if (returnlist.Count > 0)
{
return returnlist;
}
return null;
}
private void RebindMenuItem_Click(object sender, RoutedEventArgs e)
{
foreach (XHTMLStudioPrototypeFileEditor ed in CurrentProject.OpenedFiles)
{
List<ContentPresenter> cps = GetObjectOfTypeInVisualTree<ContentPresenter>(tabControlFiles);
if (cps != null)
{
foreach (ContentPresenter cp in cps)
{
foreach (DataTemplate dt in tabControlFiles.Resources.Values)
{
try
{
object o = dt.FindName("avalonEdit", cp);
TextEditor ted = (TextEditor)o;
bool isDataBound = BindingOperations.IsDataBound(ted, TextEditor.DocumentProperty);
if (!isDataBound)
{
BindingOperations.SetBinding(ted, TextEditor.DocumentProperty, new Binding("Document"));
}
Console.WriteLine(isDataBound);
}
catch (Exception)
{
}
}
}
}
}
}
all day long I am sitting and trying to find out why binding to AvalonEdits Document property isn't working. AvalonEdit is an advanced WPF text editor - part of the SharpDevelop project.(it's going to be used in SharpDevelop v4 Mirador).
So when I set up a simple project - one TextEditor (that's the AvalonEdits real name in the library) and made a simple class that has one property - Document and it returns a dummy object with some static text the binding is working perfectly.
However in real life solution I'm binding a collection of SomeEditor objects to TabControl.
TabControl has DataTemplate for SomeEditor and there's the TextEditor object.
<TabControl Grid.Column="1" x:Name="tabControlFiles" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type m:SomeEditor}">
<a:TextEditor
Document="{Binding Path=Document, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource NoopConverter}, IsAsync=True}"
x:Name="avalonEdit"></a:TextEditor>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style BasedOn="{StaticResource TabItemStyle}" TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"></Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
This doesn't work. What I've investigated so far:
- DataContext of TextEditor is set to the proper instance of SomeEditor
- TextEditors Document property is set to some other instance than SomeEditor.Document property
- when I set breakpoint to no-op converter that is attached to that binding it shows me the correct value for Document (the converter is used!)
I also dug through the VisualTree to obtain reference to TextEditor and called GetBindingExpression(TextEditor.DocumentProperty) and this did return nothing
WPF produces the following information:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Document; DataItem='SomeEditor' (HashCode=26280264); target element is 'TextEditor' (Name='avalonEdit'); target property is 'Document' (type 'TextDocument')
SomeEditor instance that is bound to already has a created and cached copy of Document before the binding occurs. The getter is never called.
Anyone can tell me what might be wrong? Why BindingExpression isn't set ? Why property getter is never called?
//edit: new tests and new results
I've read some more and set the binding in code behind. When I do that it works.
How come setting this in XAML doesn't work and doing the same thing in code does?
//edit2: The code also fails when called immediately after adding the object to the observable collection that is used as higher level DataSource.(that's not long after the xaml binding should fire). That makes me think this is timing issue. Anyone can tell something about it ?
//edit3: The binding code:
private List<T> GetObjectOfTypeInVisualTree<T>(DependencyObject dpob) where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(dpob);
List<T> returnlist = new List<T>();
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dpob, i);
T childAsT = child as T;
if (childAsT != null)
{
returnlist.Add(childAsT);
}
List<T> lst = GetObjectOfTypeInVisualTree<T>(child);
if (lst != null)
{
returnlist.AddRange(lst);
}
}
if (returnlist.Count > 0)
{
return returnlist;
}
return null;
}
private void RebindMenuItem_Click(object sender, RoutedEventArgs e)
{
foreach (XHTMLStudioPrototypeFileEditor ed in CurrentProject.OpenedFiles)
{
List<ContentPresenter> cps = GetObjectOfTypeInVisualTree<ContentPresenter>(tabControlFiles);
if (cps != null)
{
foreach (ContentPresenter cp in cps)
{
foreach (DataTemplate dt in tabControlFiles.Resources.Values)
{
try
{
object o = dt.FindName("avalonEdit", cp);
TextEditor ted = (TextEditor)o;
bool isDataBound = BindingOperations.IsDataBound(ted, TextEditor.DocumentProperty);
if (!isDataBound)
{
BindingOperations.SetBinding(ted, TextEditor.DocumentProperty, new Binding("Document"));
}
Console.WriteLine(isDataBound);
}
catch (Exception)
{
}
}
}
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这里还有六件事需要尝试:
仔细搜索您的应用程序,找到直接分配给 TextEditor 的 Document 属性的任何位置。看起来有些代码在某个地方正在执行
avalonEdit.Document = ...
,这会覆盖绑定。我会在您的整个应用程序中搜索匹配大小写的全字字符串“Document”和“DocumentProperty”,并考虑每次出现的情况,看看它是否可以设置此属性。在 TextEditor.OnDocumentChanged 中设置断点,以查看文档是否正确绑定,然后稍后再更改回来。检查禁用“仅我的代码”并显示外部代码的调用堆栈。
尝试在 NoopConverter.Convert、SomeEditor.get_Document 和 TextEditor.OnDocumentChanged 中设置断点以找出精确的操作顺序。另请注意何时显示绑定错误消息。
暂时修改 TextEditor 的构造函数,以存储对公共静态 List 字段中每个实例的引用,以便您可以确定曾经创建过哪些 TextEditor,然后编写代码来查看它们,显示它们的
GetHashCode()
和它们的 <代码>BindingOperations.GetBindingExpression(editor, DocumentProperty) 结果。完成后请确保取出公共静态字段!从构造 Binding 的 XAML 中取出“Path=”,以便它更好地匹配 C# 版本。 (我曾经遇到过一个问题,由于传递给 PropertyConverter 的 ITypeDescriptorContext,XAML 解释的路径与 Binding 构造函数不同。)与您发布的 C# 代码完全相同的是
Document="{Binding Document}"
。创建一个自定义跟踪侦听器并在其中设置断点以获取产生绑定错误时的调用堆栈,搜索堆栈帧以找到涉及的对象并为其提供调试器对象 ID(右键单击,创建对象 ID),然后调查财产的实际价值以确保它们符合预期。
享受!
Here are six more things to try:
Search your carefully application for any place at all where you directly assign to the Document property of a TextEditor. It looks like some code, somewhere is doing an
avalonEdit.Document = ...
which would overwrite the binding. I would search your entire app for the match-case whole-word strings "Document" and "DocumentProperty" and give each occurence a moment's thought to see if it could be setting this property.Set a breakpoint in TextEditor.OnDocumentChanged to see if the document is being properly bound and then changed back later. Check call stacks with "Just My Code" disabled and showing external code.
Try setting breakpoints in the NoopConverter.Convert, SomeEditor.get_Document, and TextEditor.OnDocumentChanged to figure out the precise sequence of operations. Also note when the Binding error message is shown.
Temporarily modify TextEditor's constructor to store a reference to every instance in a public static List field so you can determine which TextEditors have ever been created, then write code that looks through them displaying their
GetHashCode()
and theirBindingOperations.GetBindingExpression(editor, DocumentProperty)
results. Make sure you take out the public static field when you're done!Take the "Path=" out of your XAML that constructs the Binding so it will better match the C# version. (I once had a problem where the XAML interpreted the path different than the Binding constructor because of the ITypeDescriptorContext passed to PropertyConverter.) The exact equivalent to the C# code you posted is
Document="{Binding Document}"
.Create a custom trace listener and set a breakpoint in it to get the call stack when the binding error is produced, search up the stack frames to find the objects involved and give them debugger object ids (right-click, Make Object ID), then investigate the actual values of properties to make sure they are as expected.
Enjoy!
只是一个观察:我遇到了同样的问题并查看了 AvalonEdit 源代码;问题似乎是 TextEditor 构造函数覆盖了 Document 属性(实例化一个新的 TextDocument);如果你注释掉这个,绑定就会起作用;但是,如果您没有绑定,则需要进行进一步的修改。我会尝试与作者讨论这个问题,也许会建议一个补丁。
Just an observation: I had the same problem and looked through the AvalonEdit source; it seems the problem is that the TextEditor constructor overwrites the Document property (instantiates a new TextDocument); if you comment this out, the bindings work; however, if you don't have a binding, you'd need to make further modifications. I'll try to discuss this with the authors and maybe suggest a patch.