虽然为 Source 制作适配器相对清晰,但不幸的是,第二个问题的核心('不将每个字符串都包装在 miniobject 中')是 .Net 和 WPF 中内置的冲突。
首先,WPF 确实为您提供了许多注册“修改数据”回调的方法,但没有提供注册可提供值的回调的方法。我的意思是,“设置”阶段只能扩展,不可拦截,而“获取”阶段 - 什么都没有。 WPF 将简单地保留并返回它曾经缓存过的任何数据。
第二件事是,在 .Net 中,string 是...不可变的。
现在,如果您直接将字符串作为无路径绑定或作为数据上下文提供给任何控件,那么您将搞砸。问题是,WPF 实际上只传递绑定的实际值,而没有“它来自哪里”的信息。底层控件将被简单地赋予字符串实例,并且由于字符串无法自行更改,因此无法正常修改它。您甚至不会收到有关此类尝试的通知,就像使用只读属性一样。更重要的是 - 如果您设法拦截这样的修改尝试,并且如果您生成了正确的新字符串,则 WPF 将永远不会再次要求您提供新值。要更新 UI,您必须手动强制 WPF 重新询问您,例如更改原始绑定,使其指向其他位置(指向新值)或设置数据上下文(指向新实例)。它可以通过一些 VisualTree 扫描来实现,因为每个“更改”回调都会为您提供 DependencyObject(控件!),因此您可以向上/向下扫描并篡改它们的属性。记住该选项 - 我'待会儿会提到这个。
所以,一切都归结为这样一个事实,即要获得一个正常的 2 路绑定,您不必有一个路径,您“只”必须有一个可变的底层数据对象。如果您有不可变的 - 那么您必须使用绑定到包含不可变值的可变属性..
话虽如此,如果你想修改字符串,你只需要将字符串包装起来。
另一个问题是,如何做到这一点。有很多方法可以做到这一点。当然,您可以像 Joe 和 Davio 建议的那样简单地包装它们(Joe 注意:那里也需要 INotify),或者您可以尝试使用附加属性和/或行为和/或转换器执行一些 XAML 技巧来做到这一点你。这是完全可行的,例如my other post - 我已经展示了如何“注入一个虚拟属性”,从其他地方完全提取数据(一个绑定+转换器即时执行包装,第二个绑定从附加包装)。这样您就可以在字符串上创建一个“Contents”属性,并且该属性可以简单地返回字符串本身,并且它完全可以双向绑定,没有例外。
但是..它不会以两种方式工作。
在绑定/行为/转换器链的根部某处,会有不可变字符串。一旦您的智能自动包装绑定链通过“修改后”回调触发,您将收到一对旧/新值的通知。您将能够将值重新映射到新旧字符串。如果您完美地实现了一切,WPF 将简单地使用新值。如果您在某个地方绊倒,那么您将不得不人为地将新值推回 UI(请参阅我要求您记住的选项)。所以,没关系。没有包装器,旧值可见,它是可变的,你有新值,UI 显示新值。存储呢?
与此同时,您获得了一个旧/新值对。如果你分析它们,你会得到旧的/新的字符串。但是如何更新旧的不可变字符串?做不到。即使自动换行有效,即使 UI 有效,即使编辑似乎有效,您现在正面临着真正的任务:您的 onmodified 回调已被调用,您必须实际更新该不可变字符串片段。
首先,您需要您的来源。是静态的吗?呸。多么幸运!所以肯定是实例化的。在 on-modified 回调中,我们只得到一个 old+new 字符串。如何获取 Source 实例?选项:
- 扫描 VisualTree 并在数据上下文中搜索它并使用找到的任何内容..
- 添加更多附加属性和绑定以将虚拟“源”属性绑定到每个字符串并从新值中读取该属性
可行,但有异味,但没有其他选择。
等等,还有更多:不仅需要旧/新值和 Source 实例!您还需要 ROW INDEX。哦!如何从绑定数据中获取?再次,选项:
- 扫描 VisualTree 并搜索它 (blaargh)...
- 添加更多附加属性和绑定以将虚拟“RowIndex”属性绑定到每个 (blaaergh)...
目前,虽然我看到所有这些似乎都可以实现并且实际上可能工作正常,但我真的认为将每个字符串包装在一个小
public class LocalItem // + INotifyPropertyChanged
{
public int Index { get; }
public Source Source { get; }
public string Content
{
get { Source...}
set { Source... }
}
}
将变得更易读、更优雅,而且实现起来更短。而且更不容易出错,因为更多细节将是明确的,而不是一些 WPF 的绑定+附加魔法..