WPF - DataTemplate绑定到静态成员

4
我有一个类,其中包含一个名为CanSeePhotos的布尔静态属性,这应该控制我的DataTemplate中图片的可见性。出于调试目的,我将“CanSeePhotos”绑定到DataTemplate中的文本块上。
我想做的是:
1. InitializeComponent() 2. 根据已登录用户设置CanSeePhotos 3. 加载数据并适当显示
我的问题是,如果我在InitializeComponent()之后设置CanSeePhotos = true,则数据仍会以CanSeePhotos为false显示(如果我在之前设置则可以正常工作)。为什么会这样?如何修复它,以便在加载数据之前的任何时候都可以设置值?
以下是我如何在DataTemplate中绑定静态变量的方式:
<TextBlock Text="{Binding Source={x:Static DAL:LoggedInUser.CanSeePhotos}, Mode=OneWay}"/>

以下是 LoggedInUser 类的代码:

public class LoggedInUser
{
    public static bool CanSeePhotos { get; set; }
}

编辑: 如果我直接将控件的可见性绑定到静态属性,它将根据该属性的值显示/折叠:

Visibility="{Binding Source={x:Static DAL:LoggedInUser.CanSeePhotos}, Converter={StaticResource BooleanToVisibilityConverter}}"

但我需要像这样使用DataTrigger:
<DataTrigger Binding="{Binding Source={x:Static DAL:LoggedInUser.CanSeePhotos}}" Value="true">
   <Setter TargetName="icon" Property="Source" Value="{Binding Photo}"/>
</DataTrigger>

在上述情况下,如果属性为true,则setter永远不会被设置。
怎么回事?
1个回答

7
这里有三点需要考虑:
考虑1:属性没有更改通知
某些数据绑定可能在InitializeComponent()调用期间进行评估,而其他数据绑定则稍后进行评估。您正在请求能够在InitializeComponent()返回后设置CanSeePhotos的能力。如果没有任何更改通知,则在InitializeComponent()期间评估的任何绑定都将具有原始值,并且不会更新。以后评估的任何绑定(例如在DataBind优先级处)将具有新值。要使所有情况下都起作用,您需要某种更改通知。
使用"{ get; set; }"声明的NET Framework属性将无法工作,因为该属性没有机制通知任何人其值是否已更改。实际上,有两种非常狡猾的方法可以从标准NET Framework属性获得通知(MarshalByRefObject和IL重写),但它们对于您的情况来说过于复杂。
考虑2:属性是静态的
NET Framework有几个属性更改通知机制(DependencyProperty、INotifyPropertyChanged等),但没有一个内置机制支持静态属性的更改通知。因此,您不能在此处使用静态属性,而无需创建用于信号更改的新机制(例如,您可以使用包装该属性的对象)。
考虑3:DataTriggers共享单个绑定
当设置Visibility时,每次都会构造一个新的绑定,因此它会获取LoggedInUser.CanSeePhotos的最新值。
当创建DataTrigger时,WPF在加载触发器时构造单个绑定,并将其用于每个对象。该绑定在包含DataTrigger的资源字典加载时构造,这可能是应用程序启动时,因此它始终会获取CanSeePhotos的默认值。这是因为Source=将实际对象分配给绑定(其计算不是延迟的)。因此,每个绑定都使用Source=true或Source=false进行构造。
推荐解决方案
使用DependencyObject和DependencyProperty,并从静态属性引用它,如下所示:
public class LoggedInUser : DependencyObject
{
   // Singleton pattern (Expose a single shared instance, prevent creating additional instances)
   public static readonly LoggedInUser Instance = new LoggedInUser();
   private LoggedInUser() { }

   // Create a DependencyProperty 'CanSeePhotos'
   public bool CanSeePhotos { get { return (bool)GetValue(CanSeePhotosProperty); } set { SetValue(CanSeePhotosProperty, value); } }
   public static readonly DependencyProperty CanSeePhotosProperty = DependencyProperty.Register("CanSeePhotos", typeof(bool), typeof(LoggedInUser), new UIPropertyMetadata());

}

这个类始终只有一个实例,并且该实例将作为LoggedInUser.Instance可用。因此它有点像静态类。区别在于,LoggedInUser.Instance具有DependencyProperty,因此当您修改属性时,它可以通知任何感兴趣的方。WPF的Binding将注册此通知,因此您的UI将更新。

在XAML中使用上面的代码如下:

Visibility="{Binding CanSeePhotos, Source={x:Static LoggedInUser.Instance}, Converter=...

如果您需要在代码后端访问CanSeePhotos,可以这样做:

LoggedInUser.Instance.CanSeePhotos = true;

嗨Ray,我了解依赖属性,但不想使用它们,因为CanSeehotos在第一次检索数据后将永远不会更改。让我困惑的是,将对象的可见性绑定到DataTemplate中的静态属性可以正常工作,但在DataTemplate的DataTrigger中却无法正常工作。谢谢你的回答。我会尝试的。 - Gus Cavalcanti
感谢您澄清问题。我在我的答案中添加了一个新的“问题3”,以阐明为什么DataTrigger不起作用,还在“问题1”中解释了为什么您需要在您的场景中进行更改通知。希望这可以帮助到您。 - Ray Burns
+1,然而在这里你实际上不需要一个DependencyProperty,我个人认为实现INotifyPropertyChanged更好。 - Thomas Levesque
虽然我个人更喜欢在这种情况下使用DependencyProperty,但INotifyPropertyChanged的工作量大致相同。我通常避免使用INotifyPropertyChanged,因为一旦你添加了接口,就必须为每个属性实现通知,或者仔细记录哪些属性不会通知。此外,我不知道如何混合使用INotifyPropertyChanged和DependencyProperty。话虽如此,在这种情况下,我认为这不会有太大的区别。 - Ray Burns
在选择 DependencyProperty 和 INotifyPropertyChanged 之间,大多数情况下是根据个人偏好来决定的... 对我来说,DependencyProperty 有一个主要缺点:你需要继承 DependencyObject。这在处理复杂类层次结构时可能会成为一个大问题。Kent Boogaart 在这个主题上有一篇很好的文章: http://kentb.blogspot.com/2009/03/view-models-pocos-versus.html - Thomas Levesque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接