前言
微软的许多XAML框架,如WPF、UWP、WinUI 3等,在DataTemplate
下都会遇到变量隐藏(Variable shadowing)的问题。为了访问外部实例成员,经常需要写很多曲折的代码,但也没有办法。本文也无法解决这个问题,但记录了我知道的方法,以便在各种情况使用,争取将可读性的影响降到最低。
问题再现
按照需求创建了一个Page
:
public sealed partial class SamplePage : Page { public string OuterMember { get; set; } = "OuterMember"; public SamplePage() { ViewModels = new ViewModel[] { new("a"), new("b"), new("c"), }; InitializeComponent(); } public ViewModel[] ViewModels { get; } } public class ViewModel(string text) { public string Text { get; set; } = text; }
<Page x:Class="SampleApp.SamplePage" ...> <ItemsRepeater ItemsSource="{x:Bind ViewModels}"> <ItemsRepeater.ItemTemplate> <DataTemplate x:DataType="local:ViewModel"> <TextBlock Text="{x:Bind Text}" /> </DataTemplate> </ItemsRepeater.ItemTemplate> </ItemsRepeater> </Page>
大部分情况下,写到这种程度就能完成任务了。但有时候需要把外部的成员(如OuterMember
)传给DataTemplate
内的控件(如此处的TextBlock
),那么如何实现呢?
首先可以发现,在DataTemplate内并非只能使用ViewModel类的成员,而还能访问以下这些东西:
-
static成员
-
StaticResource
-
事件处理方法
据此我们就可以利用这些来实现跨域(scope)访问类实例成员。一共有三种思路:
解决方案
Static转实例
这是很常用的方法,就连官方库都可以看到这样的实现,例如Application.Current
。
那我们可以仿照这样写:
public sealed partial class SamplePage : Page { public static SamplePage Current { get; private set; } public SamplePage() { Current = this; ... } ... }
此时在XAML中就可以:
... <DataTemplate x:DataType="local:ViewModel"> <TextBlock Text="{x:Bind local:SamplePage.Current.OuterMember}" /> </DataTemplate> ...
这样写的优点是简洁明了,缺点是只能单例使用
StaticResource
这种方法也有人使用:
public class Box { public object Content { get; set; } } public sealed partial class SamplePage : Page { public SamplePage() { ... InitializeComponent(); ((Box)Resources["Box"]).Content = OuterMember; } ... }
... <Page.Resources> <local:Box x:Key="Box" /> <local:UnboxConverter x:Key="UnboxConverter" /> </Page.Resources> <ItemsRepeater ItemsSource="{x:Bind ViewModels}"> <ItemsRepeater.ItemTemplate> <DataTemplate x:DataType="local:ViewModel"> <TextBlock Text="{x:Bind Converter={StaticResource UnboxConverter}, ConverterParameter={StaticResource Box}}" /> </DataTemplate> </ItemsRepeater.ItemTemplate> </ItemsRepeater> ...
Box
是用来装箱的,防止使用值类型时复制赋值导致前后不是同一个对象。
这种方法优点是十分灵活,处理方法写在Converter
里,传递参数写在Box
里,可以随意扩展,几乎没有限制。
缺点也很明显,写了许多不明所以的代码,逻辑曲折难懂,而且外部OuterMember
变化后难以通知到内部。
事件处理
这种方法可以很方便地获取需要的参数,但可能需要多写一个子控件:
public sealed partial class SampleControl : UserControl { public event Func<SamplePage>? ThisRequested; public string? GetOuterMember => ThisRequested?.Invoke().OuterMember; public SampleControl() { InitializeComponent(); } }
<UserControl x:Class="SampleApp.SampleControl" ...> <TextBlock Text="{x:Bind GetOuterMember}" /> </UserControl>
原页面只需:
public sealed partial class SamplePage : Page { private SamplePage MyControlOnThisRequested() => this; ... }
... <DataTemplate x:DataType="local:ViewModel"> <local:SampleControl ThisRequested="MyControlOnThisRequested"/> </DataTemplate> ...
这种方法十分优雅,也很灵活,缺点是要单独写一个子控件。但如果由于DataTemplate
内容太长,本来就打算分开写控件,那这个缺点就不存在了。
总之三个方法各有利弊,大家可以根据需要选择最合适的。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· 10亿数据,如何做迁移?
· 推荐几款开源且免费的 .NET MAUI 组件库
· c# 半导体/led行业 晶圆片WaferMap实现 map图实现入门篇
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!