Using ICommand as a message pattern
This solution focuses on
Separation of Concerns and the Single Responsibility Principle
It allows you to skip the RelayCommand Pattern in MVVM.
If you are using XAML you can refer to the namespace that has the single command class. Like this:
xmlns:cmd="clr-namespace:MyProject"
Then a global or local style can be defined as shown here. This makes all buttons use just one command passing in the text of the button as parameter. Most buttons use Text as context but the Tag could be used too.
<Style BasedOn="{StaticResource XDButton}" TargetType="{x:Type Button}">
<Setter Property="Command" Value="{StaticResource ResourceKey=cmd}"/>
<Setter Property="CommandParameter" Value="{Binding Content, RelativeSource={RelativeSource Self}}"/>
</Style>
You can create just one command for entire project like this, notice the 'routing' is based on the button text. 'Favor naming conventions over configuration'
public class Commands : ICommand
{
private bool canExecute = true;
public bool CanExecute(object parameter)
{
return canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
NotifyCanExecute(false);
var information = parameter.ToString();
try
{
if (information == "Show Passed") Events.ShowAllPassedTests(this, new EventArgs());
if (information == "Show Failed") Events.ShowAllFailedTests(this, new EventArgs());
if (information == "Sort By elapsed Time") Events.SortByElapsedTime(this, new EventArgs());
if (information == "Sort By Run Data") Events.SortByRunData(this, new EventArgs());
if (information == "Sort By Title") Events.SortByTitle(this, new EventArgs());
if (information == "Generate HTML Report") Events.GenerateHTMLReport(this, new EventArgs());
}
catch (NullReferenceException nre) {
Trace.WriteLine("Test Runner Commands 320- An attempt to fire an event failed due to no subscribers");
}
NotifyCanExecute(true);
}
private void NotifyCanExecute(bool p)
{
canExecute = p;
if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs());
}
}
Create a single Events aggregation class like this:
public class Events
{
public static EventHandler ShowAllPassedTests;
public static EventHandler ShowAllFailedTests;
public static EventHandler ClearAllFilters;
public static EventHandler SortByElapsedTime;
public static EventHandler SortByRunData;
public static EventHandler SortByTitle;
public static EventHandler GenerateHTMLReport;
public static EventHandler<CheckBox> ColumnViewChanged;
}
You can create a decoupled Navigator user control with buttons in it like this. When the button is clicked it just calls the Command class passing in the Button context.
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style BasedOn="{StaticResource XDButton}" TargetType="{x:Type Button}">
<Setter Property="Command" Value="{StaticResource ResourceKey=cmd}"/>
<Setter Property="CommandParameter" Value="{Binding Content, RelativeSource={RelativeSource Self}}"/>
</Style>
</StackPanel.Resources>
<Button x:Name="XBTNShowPassed" >Show Passed</Button>
<Button x:Name="XBTNShowFailed" >Show Failed</Button>
<Button x:Name="XBTNShowAll" >Show All</Button>
<Button x:Name="XBTNSortByElapsedTime" >Sort by Elapsed Time</Button>
<Button x:Name="XBTNSortByRunData" >Sort By Run Data</Button>
<Button x:Name="XBTNSortByTitle" >Sort By Title</Button>
<Button x:Name="XBTNGenerateHTMLReport" >Generate HTML Report</Button>
</StackPanel>
Finally the receiving ViewModel or other class looks like this:
Events.ColumnViewChanged += OnColumnViewChanged;
Events.SortByTitle += OnSortByTitle;
Events.SortByRunData += OnSortByRunData;
Events.SortByElapsedTime += OnSortByElapsedTime;
Events.GenerateHTMLReport += OnGenerateHTMLReport;
Events.ShowAllFailedTests += OnShowAllFailedTests;
Events.ShowAllPassedTests += OnShowAllPassedTests;
}
private void OnShowAllPassedTests(object sender, EventArgs e)
{
FilterCVS(tr => tr.DidTestPass);
}
private void OnShowAllFailedTests(object sender, EventArgs e)
{
FilterCVS(tr => tr.DidTestFail);
}
Don't forget to implement Dispose
When code hooks up to an EventHandler it becomes ineligible for Garbage Collection. To fix this, implement the Dispose pattern and disconnect the eventhandlers... e.g
Events.OnColumnViewChanged -= OnColumnViewChanged;