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'sCanExecute
- event
Click
is linked to the command'sExecute
(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 applicati开发者_JAVA百科on 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.
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 theCopyTextCommand.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.
Use a Timer
to constantly poll the CanExecute
property. Raise the PropertyChanged
event when the property changes.
I' do the polling on the Application.Idle event, as long as your can execute logic is simple, there shouldn't be any problem.
here is an extract of my current 'CommandManager' implementation;
public CommandManager()
{
Commands = new List<ICommand>();
Binders = new List<ICommandBinder>
{
new ControlBinder(),
new MenuItemCommandBinder()
};
Application.Idle += UpdateCommandState;
}
private void UpdateCommandState(object sender, EventArgs e)
{
Commands.Do(c => c.Enabled);
}
(Do() is just an extension method that does a foreach, like linq Select() but taking Action instead of Func)
I've blogged about this some time ago, feel free to check: http://codewithpassion.blogspot.com/2010/11/icommand-and-commandmanager-for-windows.html
hope it helps
精彩评论