views:

37

answers:

4

Let's assume I'm implementing a Winforms UI where all commands adhere to the following pattern:

interface ICommand
{
    bool CanExecute { get; }
    void Execute();
}

Buttons or menu items that trigger such a command should have the following set-up:

  • property Enabled is bound to the command's CanExecute
  • event Click is linked to the command's Execute (through an intermediate event handler due to the differing method signatures)

The trouble with CanExecute is, implementing INotifyPropertyChanged for it won't work here, as this property cannot be directly modified, but depends on other factors in the program which needn't be related to the command. And one shouldn't have to trigger the command's PropertyChanged event in completely unrelated parts of the program.

How do you let the data binding manager know when CanExecute has changed?

Here's a (purly fictional) example of my problem:

bool CanExecute
{
    get
    {
        return User.LoggedInForAtLeastNMinutes(5);
        // how would you trigger data-binding updates for CanExecute? 
    }
}

Ideally, I'd like to have the UI constantly checking CanExecute (as if it were a volatile field), but AFAIK this is not how Winforms data binding works. Does anyone have a solution for this problem?


Note: I am aware of WPF, btw. The background of my question is that I'm going to gradually improve an existing Winforms application in the general direction of WPF. But actually using WPF and thus getting rid of the problem I've asked about is not feasible right now.

+1  A: 

Use a Timer to constantly poll the CanExecute property. Raise the PropertyChanged event when the property changes.

Amnon
Are there no alternatives? Manually creating a second data binding mechanism that runs in addition to the standard one seems like the worst case (big effort, possibly repetition of functionality existent somewhere in the framework). But this is what I'll do when no other solutions turn up. +1
stakx
I'd be really reluctant to poll - there's almost always some action you can use to drive the property changed event
Tim Robinson
If you want to support arbitrary CanExecute properties which are transparent to the entity changing the state, you have to poll.
Amnon
+2  A: 

I would implement INotifyPropertyChanged regardless (or add a CanExecuteChanged event, which has the same effect). I would try hard for objects to know when to raise the property changed event at the right time, rather than polling.

For instance, in your fictional example, you could have a UserLoggedIn event. In response to that, you could set a 5-minute timer; when that timer elapses, you raise the property changed event.

If you go for the polling approach then you face two dangers:

  • Polling too often, where your application consumes CPU checking for events that can't possibly happen yet (for instance, polling every 10 seconds to see if 5 minutes are up)
  • Not polling often enough, where controls bound to CanExecute properties lag the rest of the UI (for instance, a delay between making a text selection and the CopyTextCommand.CanExecute property updating)

A hybrid approach is that taken by Microsoft Foundation Classes in C++, which was to make this check any time the application's message loop was idle. This is a reasonable approach when you know that only user interface interaction that can affect your CanExecute property.

Tim Robinson
I've accepted this answer because -- although I would much prefer a more "decoupled" solution such as constant polling -- having to figure out how often to poll seems somewhat random to me; additionally, I've decided that with a UI pattern such as MVP, it's probably OK to let the presenter/controller do all this hard work of figuring out when a dependent property is affected, as long as that logic stays there and doesn't ever spread outside the presenter.
stakx
A: 

I found a different, workable solution for this problem, which I'm going to outline here with some example code. It may look complicated, but much of the code shown is infrastructure code that needs only be written once.


1) First define a new attribute that will be used to specify a dependency between properties:

class DependentOnAttribute : Attribute
{
    public DependentOnAttribute(string propertyName) { ... }
    public string PropertyName { get { ... } }
}

2) Second, apply the attribute to a dependent property, e.g. as follows:

class Person : INotifyPropertyChanged
{
    public DateTime DateOfBirth { get { ... }; set { ... } }

    [DependentOn("DateOfBirth")]
    public bool IsDateOfBirthValid { get { ... } }

    public event PropertyChangedEventHandler PropertyChanged;
}

3) Third, define an extension method on PropertyChangedEventHandler that will be used to trigger the event, recursively resolve dependencies, and trigger the event for all of these, too:

public static void Notify(this PropertyChangedEventHandler propertyChangedEvent,
                          object obj, string propertyName)
{
    // make sure someone is subscribed to the event:
    if (propertyChangedEvent == null) return;

    // raise the PropertyChanged event for the specified property:
    propertyChangedEvent(obj, new PropertyChangedEventArgs(propertyName));

    // find all other properties that are dependent on the specified property:
    var namesOfDependentProperties = from property in obj.GetType().GetProperties()
                                     where ...  // (abbreviated for legibility)
                                     select property.Name;

    // trigger the event for all dependent properties (recursion!):
    foreach (var nameOfDependentProperty in namesOfDependentProperties)
    {
        propertyChangedEvent.Notify(obj, nameOfDependentProperty);
    }
}

Now, the PropertyChanged event must be triggered through this extension method, e.g. for DateOfBirth:

public DateTime DateOfBirth
{
     get { ... }
     set
     {
         ...;
         PropertyChanged.Notify(this, "DateOfBirth");
     }
 }

This will cause an invocation of PropertyChanged for all dependent properties, too.

All this requires is a bit of general infrastructure (namely the new attribute class and the extension method), applying the DependentOn attribute on dependent attributes, and invoking the PropertyChanged event via the Notify extension method.

(It remains to be seen how this solution would have to be modified to also work for inter-object or sub-object dependencies. I assume that the required changes would be minimal.)

stakx
A: 

I don't know why I didn't think of this earler... The following example demonstrates a variation on the other answer I've posted, but that stays closer to the BCL:

class Person : INotifyPropertyChanged
{
    public DateTime DateOfBirth
    {
        get { ... }
        set { ...; OnPropertyChanged("DateOfBirth"); }
    } 

    public bool IsDateOfBirthValid
    {
        get { ... }
    }

    public Person()
    {
        this.PropertyChanged += DateOfBirth_PropertyChanged;
    }

    void DateOfBirth_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (this == sender && "DateOfBirth".Equals(e.PropertyName))
        {
            OnPropertyChanged("IsDateOfBirthValid");
        }
    }
}
stakx