views:

1061

answers:

1

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?

+4  A: 

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;
Ray Burns
Hi Ray, I understand Dependency Properties but didn't want to use them because CanSeehotos will never be changed after the data is retrieve the 1st time. What throws me off is that binding the visibility of an object to the static property in the DataTemplate works fine, but it does not work in the DataTemplate's DataTrigger. Thanks for your answer. I will try it.
Gustavo Cavalcanti
Thanks for clarifying your question. I added a new "Problem 3" to my answer to clarify why DataTrigger doesn't work, plus some explanation in "Problem 1" to explain why you need change notification in your scenario. Hope this helps.
Ray Burns
+1, however you don't really need a DependencyProperty here, IMHO implementing INotifyPropertyChanged is better
Thomas Levesque
Although I personally prefer DependencyProperty in this situation, INotifyPropertyChanged would be about the same amount of effort. I usually stay away from INotifyPropertyChanged because once you've added the interface you must implement notifications for *every* property, or carefully document which properies don't notify. Also, I know of no way to mix INotifyPropertyChanged and DependencyProperty. That said, in this situation I don't think it would make much difference.
Ray Burns
It's mostly a matter of preference to choose between DependencyProperty and INotifyPropertyChanged... For me, DependencyProperty has a major drawback : you need to inherit DependencyObject. That can be a big issue with complex class hierarchies. Kent Boogaart has an excellent article about the subject: http://kentb.blogspot.com/2009/03/view-models-pocos-versus.html
Thomas Levesque
Many many thanks Ray!!! You rock!
Gustavo Cavalcanti
Thanks Ray for this great post!!!!
LnDCobra