首页
/
工具
/
在XAML中绑定值类型的危险
在XAML中绑定值类型的危险
摘要
这篇博客文章讨论了XAML中的一个技术陷阱,其中绑定到值类型会导致辅助工具的导航失败,原因在于值拷贝语义。文章建议使用运行时类和辅助库如wil来避免这个问题。
<p>一位同事在使用他们的XAML程序时遇到了问题。他们正在使用一个<code>FlipView</code>控件绑定到一个集合,但当用户尝试使用辅助技术工具导航<code>FlipView</code>时,出现了导航失败的情况。</p>
<p>过了一段时间,他们带着谜团的解决方案回来了。</p>
<p>团队注意到他们的数据模型只由字符串和其他值类型组成,因此他们决定将数据模型声明为<code>struct</code>而不是完整的<code>runtimeclass</code>,从而避免了大量样板代码编写。</p>
<p>如果定义为<code>runtimeclass</code>:</p>
<pre>// MyComponent.idl
runtimeclass MyPageContent
{
String Title { get; };
String Description { get; };
String LinkUri { get; };
Boolean IsNew{ get; };
}
// MyPageContent.h
namespace winrt::MyComponent
{
struct MyPageContent : implements<MyPageContent>
{
MyPageContent(hstring const& title,
hstring const& description,
hstring const& link,
bool isNew) :
m_title(title),
m_description(description),
m_link(link),
m_isNew(isNew) {}
hstring Title() const { return m_title; }
hstring Description() const { return m_description; }
hstring Link() const { return m_link; }
bool IsNew() const { return m_isNew; }
private:
hstring m_title;
hstring m_description;
Windows::Foundation::Uri m_link;
bool m_isNew;
};
}
// Consumer.cpp
m_pages.Append(winrt::make<MyPageContent>(
title, description, link, isNew));
</pre>
<p>但如果定义为<code>struct</code>,则大部分代码就不需要了:</p>
<pre>// MyComponent.idl
<span style="border: solid 1px currentcolor;">struct</span> MyPageContent
{
String Title;
String Description;
String Link;
Boolean IsNew;
}
// <span style="text-decoration: line-through;">MyPageContent.h</span> 不需要
// Consumer.cpp
m_pages.Append(MyPageContent(title, description, link, isNew));
</pre>
<p><a href="https://en.wikipedia.org/wiki/Miller_Lite#Advertising">口感好,更清爽</a>。</p>
<p>现在,值类型之所以是值类型,在于它们是按值拷贝,而不是按引用拷贝。这意味着当XAML调用<code>GetAt(n)</code>从<code>m_pages</code>获取第<var>n</var>项时,它得到的是<code>MyPageContent</code>的一个<i>副本</i>,并绑定到该副本。</p>
<p>而这就是问题的根源。</p>
<p>当代码需要在辅助技术工具的请求下导航到特定项时,它传递要导航到的<code>MyPageContent</code>,但那只是另一个副本,因为值类型总是按副本传递。XAML说,“我没有那个家伙”,并导致导航失败。(XAML没有意识到它有一个<i>看起来完全像</i>那个家伙的家伙。不过这不重要,因为它不是同一个家伙。)</p>
<p>这个巧妙的快捷方式结果成了问题所在。</p>
<p>现在,虽然实现C++/WinRT运行时类确实需要大量代码编写,但有辅助工具可以减少所需代码量。在Windows Implementation Library (wil)中,<code>cppwinrt_authoring.h</code>头文件包含用于简化事件和属性实现的类。它利用了CRTP<a title="利用C++/WinRT CRTP:属性和事件声明" href="https://devblogs.microsoft.com/oldnewthing/20230317-00/?p=107946">,方式与我之前讨论的类似</a>。</p>
<pre>// MyPageContent.h
namespace winrt::MyComponent
{
struct MyPageContent : implements<MyPageContent>
{
MyPageContent(hstring const& title,
hstring const& description,
hstring const& link,
bool isNew) :
m_title(title),
m_description(description),
m_link(link),
m_isNew(isNew) {}
<span style="border: solid 1px currentcolor; border-bottom: none;">wil::single_threaded_property<hstring> Title; </span>
<span style="border: 1px currentcolor; border-style: none solid;">wil::single_threaded_property<hstring> Description;</span>
<span style="border: 1px currentcolor; border-style: none solid;">wil::single_threaded_property<hstring> Link; </span>
<span style="border: solid 1px currentcolor; border-top: none;">wil::single_threaded_property<bool> IsNew; </span>
};
}
</pre>
<p>我们可以使用<code>single_<wbr />threaded_<wbr />property</code>,因为属性仅在构造时写入,因此并发读取不会引起问题。</p>
<p>文章<a href="https://devblogs.microsoft.com/oldnewthing/20260902-00/?p=112668">在XAML中绑定值类型的危险</a>首次出现在<a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看缓存全文
缓存时间:
2026/09/03 11:43
# 在XAML中绑定值类型的陷阱 - Old New Thing专栏
来源:https://devblogs.microsoft.com/oldnewthing/20260902-00?p=112668
一位同事在他们的XAML程序中遇到了问题。他们正在使用`FlipView`控件绑定到一个集合,但当用户尝试使用辅助技术工具导航`FlipView`时,出现了导航失败的情况。一段时间后,他们带着这个谜题的解决方案回来了。
该团队注意到他们的数据模型只由字符串和其他值类型组成,因此他们决定将数据模型声明为`struct`(结构体)而非完整的`runtimeclass`(运行时类),从而避免了大量样板式代码编写。如果定义为`runtimeclass`:
```
// MyComponent.idl
runtimeclass MyPageContent
{
String Title { get; };
String Description { get; };
String LinkUri { get; };
Boolean IsNew{ get; };
}
// MyPageContent.h
namespace winrt::MyComponent
{
struct MyPageContent : implements<MyPageContent, IPageContent>
{
MyPageContent(hstring const& title, hstring const& description, hstring const& link, bool isNew)
: m_title(title), m_description(description), m_link(link), m_isNew(isNew) {}
hstring Title() const { return m_title; }
hstring Description() const { return m_description; }
hstring Link() const { return m_link; }
bool IsNew() const { return m_isNew; }
private:
hstring m_title;
hstring m_description;
Windows::Foundation::Uri m_link;
bool m_isNew;
};
}
// Consumer.cpp
m_pages.Append(winrt::make<MyPageContent>(title, description, link, isNew));
```
但如果将其定义为`struct`,则大部分代码都变得不必要了:
```
// MyComponent.idl
struct MyPageContent
{
String Title;
String Description;
String Link;
Boolean IsNew;
}
// MyPageContent.h 不再需要
// Consumer.cpp
m_pages.Append(MyPageContent{title, description, link, isNew});
```
这可谓“口感更佳,负担更轻”(Miller Lite经典广告词)。现在,使值类型成为值类型的关键在于它们是按值复制的,而非按引用复制。这意味着当XAML调用`m_pages`上的`GetAt(n)`来获取第n个项目时,它获得的是`MyPageContent`的*副本*,并绑定到该副本。而这就是问题的根源。当代码需要根据辅助技术工具的请求导航到特定项目时,它传递的是要导航到的`MyPageContent`,但由于值类型总是按副本传递,这仅仅是另一个副本。XAML表示:“我没有那个家伙”,导航因此失败。(XAML没有意识到它拥有一个*看起来与那个家伙完全相同*的家伙。但这并不重要,因为它并非同一个家伙。)
这个巧妙的捷径最终被证明是问题所在。虽然实现C++/WinRT运行时类确实需要大量代码编写,但也有工具可以减少所需的代码量。在Windows实现库(WIL)中,`cppwinrt_authoring.h`头文件包含用于简化事件和属性实现的类。它利用CRTP(奇异递归模板模式)的方式与我之前讨论(https://devblogs.microsoft.com/oldnewthing/20230317-00/?p=107946)的相同。
```
// MyPageContent.h
namespace winrt::MyComponent
{
struct MyPageContent : implements<MyPageContent, IPageContent>
{
MyPageContent(hstring const& title, hstring const& description, hstring const& link, bool isNew)
: m_title(title), m_description(description), m_link(link), m_isNew(isNew) {}
wil::single_threaded_property<hstring> Title{m_title};
wil::single_threaded_property<hstring> Description{m_description};
wil::single_threaded_property<Windows::Foundation::Uri> Link{m_link};
wil::single_threaded_property<bool> IsNew{m_isNew};
};
}
```
我们可以使用`single_threaded_property`,因为这些属性仅在构造时写入,因此并发读取不会引起问题。
### 分类
### 主题
## 作者 Raymond Chen
Raymond参与Windows的演进已有30余年。2003年,他创办了一个名为"The Old New Thing"的网站,其受欢迎程度远远超出他最疯狂的想象,这一发展至今仍让他感到不安。该网站催生了一本书,书名也恰巧叫做《The Old New Thing》(Addison Wesley出版社,2007年)。他偶尔会出现在Windows开发文档的Twitter账号上,讲述一些并无实用信息的故事。
相似文章
arXiv cs.LG
本文介绍了语言模型中的'明晰的失败',即模型在隐藏状态中拥有正确信息但未能使用,并表明线性探针可以通过引导干预检测和修复此类失败。
Hacker News Top
一位开发者描述了与盲人客户合作如何发现了Microsoft的Power Automate、SharePoint和Teams中隐藏的可访问性差距,强调了平台层面的问题可能比实现错误更严重。
Lobsters Hottest
一位开发者吐槽.NET自定义属性在二进制元数据层面的糟糕设计,解释了它们的存储方式以及为何会引发问题。
arXiv cs.CL
This paper introduces M2BIND, a benchmark to evaluate whether vision-language models maintain stable visual-linguistic associations across languages. It finds that binding is not language-invariant, with cross-family and cross-script settings causing significant performance collapse and weaker internal causal binding.
The Old New Thing (Raymond Chen)
Raymond Chen continues his series on creating a fake agile wrapper in COM, explaining how to move non-marshalable objects by forwarding references into the force_marshal wrapper and discussing C++/WinRT deduction guides.