XAML 中的花括号究竟是什么意思?

What do curly braces in XAML actually mean?

我是一名资深的 C# 程序员,但我对 WPF 和 XAML 完全陌生。我可以找到很多教程说“这是如何实现这个特定的事情”而不是“这是为什么你这样做来实现这个特定的事情”。我真的很难理解 XAML.

中各种语法的含义

在这种情况下,属性中的大括号 究竟是什么意思? 它们在代码方面被翻译成什么?我如何推理它们?他们如何被解释?为什么似乎有多种语法(Binding="{Binding someProperty}" vs Binding="{Binding path=someProperty}")?

我肯定遗漏了一些明显的东西,但我确实花了几天时间阅读教程、观看教程,甚至在极其枯燥和难以理解的过程中奋力拼搏 Microsoft documentation 但我仍然无法理解似乎明白了。


让我试着说明我卡在哪里。

例如,假设我得到了这个:

int result = SomeUnknownFunction(42, 79);

我不必知道 SomeUnknownFunction 做什么来推断这里发生的事情。我可以说,好的,第一部分定义了一个 int 类型的新变量,第二部分运行一些具有两个输入的函数,等号将该函数的结果分配给变量。

现在假设我得到了这个:

<TextBox Text="{Binding Name}">

我可以说,好的,当 XAML 解析器到达这个位时,它吐出的 C# 代码会创建类型 TextBox 的对象实例。 class 有(或继承)一个名为 Text 的 属性,我们将其设置为...呃...大括号内的一些神奇语法...

这就是我卡住的地方。我认为其中的某些部分被称为“标记扩展”(这是最令人难以置信的通用和无意义的名称),但我什至无法弄清楚它是指“花括号和里面的所有东西”还是仅仅是“绑定”部分”。有时大括号内有等号,有时没有。有什么不同? Binding 这个词是……什么东西?它是一个函数吗,我们传入 Name,它 returns 一个新的 Binding 对象,它以某种方式分配给 Binding 属性?

这是 XAML 文件语法的一部分,您可以在此处阅读所有详细信息

Markup Extensions

XAML defines a markup extension programming entity that enables an escape from the normal XAML processor handling of string attribute values or object elements, and defers the processing to a backing class. The character that identifies a markup extension to a XAML processor when using attribute syntax is the opening curly brace ({), followed by any character other than a closing curly brace (}). The first string following the opening curly brace must reference the class that provides the particular extension behavior, where the reference may omit the substring "Extension" if that substring is part of the true class name. Thereafter, a single space may appear, and then each succeeding character is used as input by the extension implementation, up until the closing curly brace is encountered.

这里有更多信息

Overview of markup extensions for XAML

还有 @Charlieface

提供的另一件商品 link

Basic Markup Extension Syntax

A markup extension can be implemented to provide values for properties in an attribute usage, properties in a property element usage, or both.

When used to provide an attribute value, the syntax that distinguishes a markup extension sequence to a XAML processor is the presence of the opening and closing curly braces ({ and }). The type of markup extension is then identified by the string token immediately following the opening curly brace.

When used in property element syntax, a markup extension is visually the same as any other element used to provide a property element value: a XAML element declaration that references the markup extension class as an element, enclosed within angle brackets (<>).

我想这解释了大括号的句法含义:

"Markup extensions are a XAML technique for obtaining a value that's not a primitive or a specific XAML type. For attribute usage, markup extensions use the known character sequence of an opening curly brace { to enter the markup extension scope, and a closing curly brace } to exit." (Overview of markup extensions for XAML)


"I think some part of this is called a "markup extension" (which is the most incredibly generic and meaningless name possible), but I can't even figure out if that means "the curly braces and everything inside" or just "the Binding part" "

试图在保持简单的同时进行解释:

XAML 是一种基于某些语义规则的标记语言。由于非常先进的处理器,XAML 有一个动态集 tags/objects。使用声明的命名空间,XAML 处理器能够将 XAML 元素映射到实际的 C# 对象。 XAML 允许使用像 Trigger 这样的特殊对象来实现一些简单的逻辑。但是当涉及到复杂的逻辑和动态值时,这种语言,或者一般的标记语言,是非常有限的。

标记语言是静态的,因为它们需要静态值。例如,在 C# 中,您可以基于在运行时动态计算的变量(充当数据的占位符)实现算法。在标记中,所有值都是在您编写标记时静态提供的。
这个想法是使用标记扩展来启用动态值计算。

例如,您无法单独使用标记来实现像 WPF 绑定引擎这样的复杂动态逻辑。数据绑定是一个允许将动态数据值“注入”到标记中的概念(简单来说)。数据绑定是利用标记扩展的概念实现的。

扩展 XAML 标记功能的 WPF 方法是引入标记扩展的概念。从这个角度来看,“Markup Extension”这个名字非常好。其他标记语言试图通过向处理器添加更多功能(扩展语言语法和语义)或通过动态 modifying/overwriting 实际标记来实现类似的功能。相比之下,标记扩展更强大,因为它们真正将标记扩展到处理器的限制之外。这些规则现在由 .NET 编译器而非标记处理器决定。

要实现标记扩展,对象必须扩展抽象 class MarkupExtension。实现通常有一个构造函数,可以提供额外的 public 属性。 MarkupExtension 公开了一个抽象方法 MarkupExtension.ProvideValue,该方法由 XAML 处理器调用,以获得实现者提供的动态计算值。
一个著名的扩展是 Binding class。 Binding 扩展 MarkupExtension 并公开其他属性,例如 SourceConverterPath

XAML 处理器期望的语法是

{<markup_extension_class_name> constructor_value, <markup_extension_property>=property_value}

喜欢<TextBlock Text="{Binding Username, ElementName=MyControl}" />:

  • 处理器正在处理标记 <TextBlock

  • 处理器找到 TextBlock 元素上的属性 Text 和分配的值(表达式)。

  • 值的大括号告诉处理器左右大括号之间的内容是一个对象 延伸 MarkupExtension.

  • XAML 引擎将创建此对象的实例,在本例中为 Binding class,使用 构造函数参数 Username 并初始化它的 属性 ElementName 的值为 "MyControl".

  • 然后 XAML 引擎将使用构造的实例调用 MarkupExtension.ProvideValue 获取动态计算值的方法。在这种情况下,XAML 引擎将创建一个 TextBlock 实例,并将 Binding 标记扩展的动态计算结果分配给 TextBlock.Text 属性

XAML 处理器的一个特殊规则是,如果扩展的 class 名称带有 “Extension” 后缀。即使实际标记省略了 "Extension" 后缀,处理器仍然能够识别 class。例如,如果 class 被命名为 BindingExtension,您可以简化名称并在标记中写入 Binding

通过实施 Binding 标记扩展,相当简单的标记代码扩展到高度复杂的 WPF 框架中,以将绑定引擎“连接”到 XAML 语言。

遗憾的是,除了 Microsoft 官方文档之外,我不能推荐好的来源。除了您在问题中提供的 link 或其他答案建议的 link 之外,您还可以阅读 MarkupExtension.ProvideValue property and the MarkupExtension class.
上的有用评论 我还建议实现您自己的非常简单的标记扩展,或者查看您可以在网上找到的现有实现以了解想法。标记扩展使用起来非常简单。

他们说在互联网上获得正确答案的最好方法是 post 一个不正确的答案,所以让我们试一试。

我将以此为例:

<TextBox x:Name="MyTextBox" Text="Some default text :)"/>
<Label Content="{Binding Text, ElementName=MyTextBox}"/>

这将 Label class 的 Content 属性 绑定到 TextBox Text 属性 class,给我们这个结果:


这是如何工作的

当XAML 编译器运行时,<Label ... /> 将被翻译成在运行时生成Label class 实例的代码。我们可以从 XAML 设置该实例的属性。通常,当您使用 Attribute syntax 设置 属性 时,双引号内的字符串正是 属性 将被设置的内容。例如:

<Label Content="Binding Text, ElementName=MyTextBox"/>

由于我们没有使用大括号,该字符串将按字面意思作为值分配给 Content 属性:

这是我们目前学到的:

如果我们添加花括号,现在 XAML 编译器会以不同的方式对待它。花括号中第一个 space 之前的单词现在被视为 标记扩展 的名称。标记扩展 classes 继承自 MarkupExtension class.

在这种情况下,花括号中文本的第一部分是BindingBy convention,标记扩展 classes 通常以“Extension”结尾,如 BindingExtension1。 XAML 很聪明,在花括号中输入标记扩展的名称时,您可以通过省略 class 名称的 Extension 部分来节省一些输入并减少一些冗长。

现在我们知道大括号和大括号内字符串的第一部分是什么意思了,但其余部分呢?为什么有时等号有时没有?

在文本 Binding 之后有一个 space,字符串的其余部分被视为标记扩展的逗号分隔输入。这在实践中如何运作?好吧,如果第一个输入没有等号,它们将作为字符串传递到标记扩展 class' 构造函数中。2

在我们的示例中,Binding class 将使用参数值 Text 创建。如果您查看 source for the Binding class,您会发现有两个构造函数。采用字符串的方法将 Path 属性 设置为该字符串的值:

        /// <summary>
        /// Default constructor.
        /// </summary>
        public Binding() {}
 
        /// <summary>
        /// Convenience constructor.  Sets most fields to default values.
        /// </summary>
        /// <param name="path">source path </param>
        public Binding(string path)
        {
            if (path != null)
            {
                if (System.Windows.Threading.Dispatcher.CurrentDispatcher == null)
                    throw new InvalidOperationException();  // This is actually never called since CurrentDispatcher will throw if null.
 
                Path = new PropertyPath(path, (object[])null);
            }
        }

在使用您提供的任何构造函数参数实例化 class 之后,它将剩余的等号分隔输入视为键值对。键是标记扩展中 public 属性 的名称,值是 public 属性 设置的内容。

如果您再次查看 Binding class' source,您会看到有一个名为 ElementName 的 public 属性。在我们的示例中,ElementName 被设置为 MyTextBox.

的文字字符串值


总结

花括号告诉 XAML 编译器将这些花括号的内容视为继承自 MarkupExtension 的 class 的名称,后跟 space ,后跟构造函数参数(如果有),后跟表示 public 属性名称及其值的键值对。

属性 的值将通过调用扩展 MarkupExtension.

的 class 上的 ProvideValue 方法来计算

注释

1. Binding class 是这个规则的一个例外,字面意思就是 Binding 没有“扩展名”部分。

2. 我不确定如果没有匹配的构造函数会发生什么。


免责声明:我完全不知道我在说什么。如有不妥请指正!