|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionThis article discusses a technique for grouping together and executing multiple commands in sequence. I accomplished this by creating a class that implements WPF’s BackgroundI recently had a conversation with Karl Shifflett, a fellow WPF Disciple, about a problem he faced in a WPF app. Suppose you have a In that situation, if you edit the Obviously you should not validate and save an object if the most recently edited value in the UI has not yet been pushed into the object. This is the exact problem Karl had worked around, by writing code in his window that manually looked for a Introducing CommandGroupMy solution to the problem posed above is to give the <ToolBar DockPanel.Dock="Top">
<Button Content="Save">
<Button.Command>
<!--
Chain together a set of commands that will sequentially
execute in the order they appear below. The first command
ensures that the focused TextBox's Text is pushed into the
source property before the Save is executed.
-->
<local:CommandGroup>
<x:StaticExtension Member="local:FlushFocusedTextBoxBindingCommand.Instance" />
<x:StaticExtension Member="ApplicationCommands.Save" />
</local:CommandGroup>
</Button.Command>
</Button>
</ToolBar> When the Save button is clicked, first my custom How CommandGroup Works
private ObservableCollection<ICommand> _commands;
/// <summary>
/// Returns the collection of child commands. They are executed
/// in the order that they exist in this collection.
/// </summary>
public ObservableCollection<ICommand> Commands
{
get
{
if (_commands == null)
{
_commands = new ObservableCollection<ICommand>();
_commands.CollectionChanged += this.OnCommandsCollectionChanged;
}
return _commands;
}
}
void OnCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// We have a new child command so our ability to execute may have changed.
this.OnCanExecuteChanged();
if (e.NewItems != null && 0 < e.NewItems.Count)
{
foreach (ICommand cmd in e.NewItems)
cmd.CanExecuteChanged += this.OnChildCommandCanExecuteChanged;
}
if (e.OldItems != null && 0 < e.OldItems.Count)
{
foreach (ICommand cmd in e.OldItems)
cmd.CanExecuteChanged -= this.OnChildCommandCanExecuteChanged;
}
}
void OnChildCommandCanExecuteChanged(object sender, EventArgs e)
{
// Bubble up the child commands CanExecuteChanged event so that
// it will be observed by WPF.
this.OnCanExecuteChanged();
} With that infrastructure in place, the public bool CanExecute(object parameter)
{
foreach (ICommand cmd in this.Commands)
if (!cmd.CanExecute(parameter))
return false;
return true;
}
public event EventHandler CanExecuteChanged;
protected virtual void OnCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
this.CanExecuteChanged(this, EventArgs.Empty);
}
public void Execute(object parameter)
{
foreach (ICommand cmd in this.Commands)
cmd.Execute(parameter);
} As seen in the code above, the The Demo ApplicationWhen you run the demo application associated with this article, you will be see a
In the screenshot above, I added an exclamation mark to the private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
Foo f = this.DataContext as Foo;
string msg = String.Format(
"Foo values:\r\nName={0}\r\nAge={1}",
f.Name,
f.Age);
MessageBox.Show(msg);
}
Here is the command class I wrote that finds the focused public class FlushFocusedTextBoxBindingCommand : ICommand
{
#region Creation
public static readonly FlushFocusedTextBoxBindingCommand Instance;
static FlushFocusedTextBoxBindingCommand()
{
Instance = new FlushFocusedTextBoxBindingCommand();
// We need to know when any element gains keyboard focus
// so that we can raise the CanExecuteChanged event.
EventManager.RegisterClassHandler(
typeof(UIElement),
Keyboard.PreviewGotKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
}
static void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
Instance.OnCanExecuteChanged();
}
protected FlushFocusedTextBoxBindingCommand()
{
}
#endregion // Creation
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
protected virtual void OnCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
this.CanExecuteChanged(this, EventArgs.Empty);
}
public void Execute(object parameter)
{
TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
if (focusedTextBox == null)
return;
BindingExpression textBindingExpr =
focusedTextBox.GetBindingExpression(TextBox.TextProperty);
if (textBindingExpr == null)
return;
textBindingExpr.UpdateSource();
}
#endregion // ICommand Members
}
Possible ImprovementsYou could enhance Another useful thing to do is subclass The possibilities are endless! :) Revision History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||