翼度科技»论坛 编程开发 .net 查看内容

XAML中DataTemplate变量隐藏的解决方法

8

主题

8

帖子

24

积分

新手上路

Rank: 1

积分
24
  1. title: XAML中DataTemplate变量隐藏的解决方法
  2. date: 2023-11-13
  3. categories: 编程
  4. tags:
  5. - C#
  6. - .NET
  7. - XAML
复制代码
前言

微软的许多XAML框架,如WPF、UWP、WinUI3等,在DataTemplate下都会遇到变量隐藏(Variable shadowing)的问题。为了访问外部实例成员,经常需要写很多曲折的代码,但也没有办法。本文也无法解决这个问题,但记录了我知道的方法,以便在各种情况使用,争取将可读性的影响降到最低。
问题再现

按照需求创建了一个Page:
  1. public sealed partial class SamplePage : Page
  2. {
  3. <UserControl
  4.     x:
  5.     ...>
  6.     <TextBlock Text="{x:Bind GetOuterMember}" />
  7. </UserControl>public string OuterMember { get; set; } = "OuterMember";
  8. <UserControl
  9.     x:
  10.     ...>
  11.     <TextBlock Text="{x:Bind GetOuterMember}" />
  12. </UserControl>public SamplePage()
  13. <UserControl
  14.     x:
  15.     ...>
  16.     <TextBlock Text="{x:Bind GetOuterMember}" />
  17. </UserControl>{
  18. <UserControl
  19.     x:
  20.     ...>
  21.     <TextBlock Text="{x:Bind GetOuterMember}" />
  22. </UserControl><UserControl
  23.     x:
  24.     ...>
  25.     <TextBlock Text="{x:Bind GetOuterMember}" />
  26. </UserControl>ViewModels = new ViewModel[] { new("a"), new("b"), new("c"), };
  27. <UserControl
  28.     x:
  29.     ...>
  30.     <TextBlock Text="{x:Bind GetOuterMember}" />
  31. </UserControl><UserControl
  32.     x:
  33.     ...>
  34.     <TextBlock Text="{x:Bind GetOuterMember}" />
  35. </UserControl>InitializeComponent();
  36. <UserControl
  37.     x:
  38.     ...>
  39.     <TextBlock Text="{x:Bind GetOuterMember}" />
  40. </UserControl>}
  41. <UserControl
  42.     x:
  43.     ...>
  44.     <TextBlock Text="{x:Bind GetOuterMember}" />
  45. </UserControl>public ViewModel[] ViewModels { get; }
  46. }
  47. public class ViewModel(string text)
  48. {
  49. <UserControl
  50.     x:
  51.     ...>
  52.     <TextBlock Text="{x:Bind GetOuterMember}" />
  53. </UserControl>public string Text { get; set; } = text;
  54. }
复制代码
  1. <Page
  2. <UserControl
  3.     x:
  4.     ...>
  5.     <TextBlock Text="{x:Bind GetOuterMember}" />
  6. </UserControl>x:
  7. <UserControl
  8.     x:
  9.     ...>
  10.     <TextBlock Text="{x:Bind GetOuterMember}" />
  11. </UserControl>...>
  12. <UserControl
  13.     x:
  14.     ...>
  15.     <TextBlock Text="{x:Bind GetOuterMember}" />
  16. </UserControl><ItemsRepeater ItemsSource="{x:Bind ViewModels}">
  17. <UserControl
  18.     x:
  19.     ...>
  20.     <TextBlock Text="{x:Bind GetOuterMember}" />
  21. </UserControl><UserControl
  22.     x:
  23.     ...>
  24.     <TextBlock Text="{x:Bind GetOuterMember}" />
  25. </UserControl><ItemsRepeater.ItemTemplate>
  26. <UserControl
  27.     x:
  28.     ...>
  29.     <TextBlock Text="{x:Bind GetOuterMember}" />
  30. </UserControl><UserControl
  31.     x:
  32.     ...>
  33.     <TextBlock Text="{x:Bind GetOuterMember}" />
  34. </UserControl><UserControl
  35.     x:
  36.     ...>
  37.     <TextBlock Text="{x:Bind GetOuterMember}" />
  38. </UserControl><DataTemplate x:DataType="local:ViewModel">
  39. <UserControl
  40.     x:
  41.     ...>
  42.     <TextBlock Text="{x:Bind GetOuterMember}" />
  43. </UserControl><UserControl
  44.     x:
  45.     ...>
  46.     <TextBlock Text="{x:Bind GetOuterMember}" />
  47. </UserControl><UserControl
  48.     x:
  49.     ...>
  50.     <TextBlock Text="{x:Bind GetOuterMember}" />
  51. </UserControl><UserControl
  52.     x:
  53.     ...>
  54.     <TextBlock Text="{x:Bind GetOuterMember}" />
  55. </UserControl><TextBlock Text="{x:Bind Text}" />
  56. <UserControl
  57.     x:
  58.     ...>
  59.     <TextBlock Text="{x:Bind GetOuterMember}" />
  60. </UserControl><UserControl
  61.     x:
  62.     ...>
  63.     <TextBlock Text="{x:Bind GetOuterMember}" />
  64. </UserControl><UserControl
  65.     x:
  66.     ...>
  67.     <TextBlock Text="{x:Bind GetOuterMember}" />
  68. </UserControl></DataTemplate>
  69. <UserControl
  70.     x:
  71.     ...>
  72.     <TextBlock Text="{x:Bind GetOuterMember}" />
  73. </UserControl><UserControl
  74.     x:
  75.     ...>
  76.     <TextBlock Text="{x:Bind GetOuterMember}" />
  77. </UserControl></ItemsRepeater.ItemTemplate>
  78. <UserControl
  79.     x:
  80.     ...>
  81.     <TextBlock Text="{x:Bind GetOuterMember}" />
  82. </UserControl></ItemsRepeater>
  83. </Page>
复制代码
大部分情况下,写到这种程度就能完成任务了。但有时候需要把外部的成员(如OuterMember)传给DataTemplate内的控件(如此处的TextBlock),那么如何实现呢?
首先可以发现,在DataTemplate内并非只能使用ViewModel类的成员,而还能访问以下这些东西:

  • static成员
  • StaticResource
  • 事件处理方法
据此我们就可以利用这些来实现跨域(scope)访问类实例成员。一共有三种思路:
解决方案

Static转实例

这是很常用的方法,就连官方库都可以看到这样的实现,例如Application.Current。
那我们可以仿照这样写:
  1. public sealed partial class SamplePage : Page
  2. {
  3. <UserControl
  4.     x:
  5.     ...>
  6.     <TextBlock Text="{x:Bind GetOuterMember}" />
  7. </UserControl>public static SamplePage Current { get; private set; }
  8. <UserControl
  9.     x:
  10.     ...>
  11.     <TextBlock Text="{x:Bind GetOuterMember}" />
  12. </UserControl>public SamplePage()
  13. <UserControl
  14.     x:
  15.     ...>
  16.     <TextBlock Text="{x:Bind GetOuterMember}" />
  17. </UserControl>{
  18. <UserControl
  19.     x:
  20.     ...>
  21.     <TextBlock Text="{x:Bind GetOuterMember}" />
  22. </UserControl><UserControl
  23.     x:
  24.     ...>
  25.     <TextBlock Text="{x:Bind GetOuterMember}" />
  26. </UserControl>Current = this;
  27. <UserControl
  28.     x:
  29.     ...>
  30.     <TextBlock Text="{x:Bind GetOuterMember}" />
  31. </UserControl><UserControl
  32.     x:
  33.     ...>
  34.     <TextBlock Text="{x:Bind GetOuterMember}" />
  35. </UserControl>...
  36. <UserControl
  37.     x:
  38.     ...>
  39.     <TextBlock Text="{x:Bind GetOuterMember}" />
  40. </UserControl>}
  41. <UserControl
  42.     x:
  43.     ...>
  44.     <TextBlock Text="{x:Bind GetOuterMember}" />
  45. </UserControl>...
  46. }
复制代码
此时在XAML中就可以:
  1. ...
  2. <DataTemplate x:DataType="local:ViewModel">
  3. <UserControl
  4.     x:
  5.     ...>
  6.     <TextBlock Text="{x:Bind GetOuterMember}" />
  7. </UserControl><TextBlock Text="{x:Bind local:SamplePage.Current.OuterMember}" />
  8. </DataTemplate>
  9. ...
复制代码
这样写的优点是简洁明了,缺点是只能单例使用
StaticResource

这种方法也有人使用:
  1. public class Box
  2. {
  3. <UserControl
  4.     x:
  5.     ...>
  6.     <TextBlock Text="{x:Bind GetOuterMember}" />
  7. </UserControl>public object Content { get; set; }
  8. }
  9. public sealed partial class SamplePage : Page
  10. {
  11. <UserControl
  12.     x:
  13.     ...>
  14.     <TextBlock Text="{x:Bind GetOuterMember}" />
  15. </UserControl>public SamplePage()
  16. <UserControl
  17.     x:
  18.     ...>
  19.     <TextBlock Text="{x:Bind GetOuterMember}" />
  20. </UserControl>{
  21. <UserControl
  22.     x:
  23.     ...>
  24.     <TextBlock Text="{x:Bind GetOuterMember}" />
  25. </UserControl><UserControl
  26.     x:
  27.     ...>
  28.     <TextBlock Text="{x:Bind GetOuterMember}" />
  29. </UserControl>...
  30. <UserControl
  31.     x:
  32.     ...>
  33.     <TextBlock Text="{x:Bind GetOuterMember}" />
  34. </UserControl><UserControl
  35.     x:
  36.     ...>
  37.     <TextBlock Text="{x:Bind GetOuterMember}" />
  38. </UserControl>InitializeComponent();
  39. <UserControl
  40.     x:
  41.     ...>
  42.     <TextBlock Text="{x:Bind GetOuterMember}" />
  43. </UserControl><UserControl
  44.     x:
  45.     ...>
  46.     <TextBlock Text="{x:Bind GetOuterMember}" />
  47. </UserControl>((Box)Resources["Box"]).Content = OuterMember;
  48. <UserControl
  49.     x:
  50.     ...>
  51.     <TextBlock Text="{x:Bind GetOuterMember}" />
  52. </UserControl>}
  53. <UserControl
  54.     x:
  55.     ...>
  56.     <TextBlock Text="{x:Bind GetOuterMember}" />
  57. </UserControl>...
  58. }
复制代码
  1. ...
  2. <Page.Resources>
  3. <UserControl
  4.     x:
  5.     ...>
  6.     <TextBlock Text="{x:Bind GetOuterMember}" />
  7. </UserControl><local:Box x:Key="Box" />
  8. <UserControl
  9.     x:
  10.     ...>
  11.     <TextBlock Text="{x:Bind GetOuterMember}" />
  12. </UserControl><local:UnboxConverter x:Key="UnboxConverter" />
  13. </Page.Resources>
  14. <ItemsRepeater ItemsSource="{x:Bind ViewModels}">
  15. <UserControl
  16.     x:
  17.     ...>
  18.     <TextBlock Text="{x:Bind GetOuterMember}" />
  19. </UserControl><ItemsRepeater.ItemTemplate>
  20. <UserControl
  21.     x:
  22.     ...>
  23.     <TextBlock Text="{x:Bind GetOuterMember}" />
  24. </UserControl><UserControl
  25.     x:
  26.     ...>
  27.     <TextBlock Text="{x:Bind GetOuterMember}" />
  28. </UserControl><DataTemplate x:DataType="local:ViewModel">
  29. <UserControl
  30.     x:
  31.     ...>
  32.     <TextBlock Text="{x:Bind GetOuterMember}" />
  33. </UserControl><UserControl
  34.     x:
  35.     ...>
  36.     <TextBlock Text="{x:Bind GetOuterMember}" />
  37. </UserControl><UserControl
  38.     x:
  39.     ...>
  40.     <TextBlock Text="{x:Bind GetOuterMember}" />
  41. </UserControl><TextBlock Text="{Binding Converter={StaticResource UnboxConverter}, ConverterParameter={StaticResource Box}}" />
  42. <UserControl
  43.     x:
  44.     ...>
  45.     <TextBlock Text="{x:Bind GetOuterMember}" />
  46. </UserControl><UserControl
  47.     x:
  48.     ...>
  49.     <TextBlock Text="{x:Bind GetOuterMember}" />
  50. </UserControl></DataTemplate>
  51. <UserControl
  52.     x:
  53.     ...>
  54.     <TextBlock Text="{x:Bind GetOuterMember}" />
  55. </UserControl></ItemsRepeater.ItemTemplate>
  56. </ItemsRepeater>
  57. ...
复制代码
Box是用来装箱的,防止使用值类型时复制赋值导致前后不是同一个对象。
这种方法优点是十分灵活,处理方法写在Converter里,传递参数写在Box里,可以随意扩展,几乎没有限制。
缺点也很明显,写了许多不明所以的代码,逻辑曲折难懂,Binding效率也较差。
事件处理

这种方法可以很方便地获取需要的参数,但可能需要多写一个子控件:
  1. public sealed partial class SampleControl : UserControl
  2. {
  3. <UserControl
  4.     x:
  5.     ...>
  6.     <TextBlock Text="{x:Bind GetOuterMember}" />
  7. </UserControl>public event Func<SamplePage>? ThisRequested;
  8. <UserControl
  9.     x:
  10.     ...>
  11.     <TextBlock Text="{x:Bind GetOuterMember}" />
  12. </UserControl>public string? GetOuterMember => ThisRequested?.Invoke().OuterMember;
  13. <UserControl
  14.     x:
  15.     ...>
  16.     <TextBlock Text="{x:Bind GetOuterMember}" />
  17. </UserControl>public SampleControl()
  18. <UserControl
  19.     x:
  20.     ...>
  21.     <TextBlock Text="{x:Bind GetOuterMember}" />
  22. </UserControl>{
  23. <UserControl
  24.     x:
  25.     ...>
  26.     <TextBlock Text="{x:Bind GetOuterMember}" />
  27. </UserControl><UserControl
  28.     x:
  29.     ...>
  30.     <TextBlock Text="{x:Bind GetOuterMember}" />
  31. </UserControl>InitializeComponent();
  32. <UserControl
  33.     x:
  34.     ...>
  35.     <TextBlock Text="{x:Bind GetOuterMember}" />
  36. </UserControl>}
  37. }
复制代码
  1. <UserControl
  2.     x:
  3.     ...>
  4.     <TextBlock Text="{x:Bind GetOuterMember}" />
  5. </UserControl>
复制代码
原页面只需:
  1. public sealed partial class SamplePage : Page{<UserControl
  2.     x:
  3.     ...>
  4.     <TextBlock Text="{x:Bind GetOuterMember}" />
  5. </UserControl>private SamplePage MyControlOnThisRequested() => this;<UserControl
  6.     x:
  7.     ...>
  8.     <TextBlock Text="{x:Bind GetOuterMember}" />
  9. </UserControl>...}
复制代码
  1. ...
  2. <DataTemplate x:DataType="local:ViewModel">
  3. <UserControl
  4.     x:
  5.     ...>
  6.     <TextBlock Text="{x:Bind GetOuterMember}" />
  7. </UserControl><TextBlock Text="{x:Bind local:SamplePage.Current.OuterMember}" />
  8. </DataTemplate>
  9. ...
复制代码
这种方法十分优雅,也很灵活,缺点是要单独写一个子控件。但如果由于DataTemplate内容太长,本来就打算分开写控件,那这个缺点就不存在了。
总之三个方法各有利弊,大家可以根据需要选择最合适的。

来源:https://www.cnblogs.com/pokersang/Undeclared/17964182
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具