WPF - DataTemplate 绑定到静态成员

发布于 2024-08-10 05:48:40 字数 1176 浏览 4 评论 0原文

我有一个带有名为 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,则设置器永远不会被设置。

什么给?

I have a class with a boolean static property called CanSeePhotos and this should control the visibility of pictures in my DataTemplate.
For debugging purposes I am binding "CanSeePhotos" to a text block in the DataTemplate.

What I would like to do is:

  1. InitializeComponent()
  2. Set CanSeePhotos based on the logged-in user
  3. Load data and show it appropriately

My problem is that if I set CanSeePhotos = true after InitializeComponent(), the data is still shown with CanSeePhotos as false (if I do it before it works ok). Why is that? How can I fix it so that I can set the value at any point before loading the data?

Here's how I am binding to the static variable in my DataTemplate:

<TextBlock Text="{Binding Source={x:Static DAL:LoggedInUser.CanSeePhotos}, Mode=OneWay}"/>

And here's the LoggedInUser class:

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

EDIT:
If I bind the visibility of a control straight to the static property it will show/collapse according to the value of the property:

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

But I need to use a DataTrigger like so:

<DataTrigger Binding="{Binding Source={x:Static DAL:LoggedInUser.CanSeePhotos}}" Value="true">
   <Setter TargetName="icon" Property="Source" Value="{Binding Photo}"/>
</DataTrigger>

In the case above the setter never gets set if the property is true.

What gives?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

半城柳色半声笛 2024-08-17 05:48:40

这里有三个注意事项:

注意事项 1:属性没有更改通知

某些数据绑定可能会在 InitializeComponent() 调用期间进行评估,而其他数据绑定则稍后进行评估。您请求能够在 InitializeComponent() 返回后设置 CanSeePhotos。如果没有任何更改通知,则在 InitializeComponent() 期间评估的任何绑定都将具有原始值并且不会更新。之后评估的任何绑定(例如在 DataBind 优先级)都将具有新值。为了使这项工作在所有情况下都有效,您需要某种更改通知。

使用以“{ get; set; }”声明的 NET Framework 属性将不起作用,因为该属性没有机制可以在其值发生更改时通知任何人。实际上有两种非常偷偷摸摸的方法可以从标准 NET Framework 属性(MarshalByRefObject 和 IL 重写)获取通知,但它们对于您的情况来说太复杂了。

注意事项 2:属性是静态的

NET Framework 有多种属性更改通知机制(DependencyProperty、INotifyPropertyChanged 等),但没有任何内置机制支持静态属性的更改通知。因此,如果不创建用于发出更改信号的新机制(例如,您可以有一个包装该属性的对象),则不能为此使用静态属性。

注意事项 3:DataTriggers 共享单个 Binding

设置 Visibility 时,您每次都会构建一个新的 Binding,因此它会获取 LoggedInUser.CanSeePhotos 的最新值。

创建 DataTrigger 时,WPF 会在加载触发器时构造单个 Binding 并将其用于每个对象。此 Binding 是在加载包含 DataTrigger 的资源字典时构造的,这可能是在应用程序启动时,因此它将始终获取 CanSeePhotos 的默认值。这是因为 Source= 将实际对象分配到绑定中(其计算不会延迟)。因此,每个 Binding 都是使用 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;

There are three considerations here:

Consideration 1: The property has no change notification

Some data bindings may be evaluated during the InitializeComponent() call, and others are evaluated later. You are requesting the ability to set CanSeePhotos after InitializeComponent() has already returned. If don't have any change notification, any binding evaluated during InitializeComponent() will have the original value and won't be update. Any binding evaluated afterwards (such as at DataBind priority) will have the new value. To make this work in all cases you need some kind of change notification.

Using a NET Framework property declared with "{ get; set; }" won't work because the property has no mechanism to notify anyone if its value is changed. There are actually two very sneaky ways to get notification from a standard NET Framework property (MarshalByRefObject and IL rewriting) but they are much too complex for your situation.

Consideration 2: The property is static

NET Framework has several property change notification mechanisms (DependencyProperty, INotifyPropertyChanged, etc) but none of the built-in mechanisms support change notification on static properties. So you can't use a static property for this without creating a new mechanism for signaling changes (for example, you could have an object that wraps the property).

Consideration 3: DataTriggers share a single Binding

When setting Visibility you are constructing a new Binding each time, so it gets the latest value of LoggedInUser.CanSeePhotos.

When creating the DataTrigger, WPF constructs a single Binding when the trigger is loaded and uses it for every object. This Binding is constructed when the resource dictionary containing the DataTrigger is loaded, which is probably at app startup, so it will always get the default value for CanSeePhotos. This is because Source= assigns an actual object into the binding (its computation is not deferred). So every Binding is getting constructed with either Source=true or Source=false.

Recommended Solution

Use a DependencyObject with a DependencyProperty and reference it from a static property, like this:

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());

}

This class will always have one instance and that instance will be available as LoggedInUser.Instance. So it is somewhat like a static class. The difference is, LoggedInUser.Instance has a DependencyProperty, so when you modify the property it can notify any interested parties. WPF's Binding will register for this notification, so your UI will be updated.

The code above would be used like this in XAML:

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

In your code-behind if you need to access CanSeePhotos it would be, for example:

LoggedInUser.Instance.CanSeePhotos = true;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文