I am currently writing a GUI for a C# application that adheres to the following software requirements (and am struggling with the conceptual design):
- Have a GUI with some choices of different operations to run.
- Each possible selection choice on the GUI should have a few sub-selections that the user can pick from.
- Each of the selections should correspond with a certain object to be utilized.
- Each of the sub-selections should correspond with a certain method to run (belonging to the selected object).
In one sentence, I am looking to associate the choices/options with different parts of executable code.
I really want to figure out a way to separate these “choices/selections” from the application, but with my current level experience, the only thing that I know how to do is make a big switch statement and hard-code the selections/sub-selection operations based on the hard-coded strings. There has got to be another way of associating the selections with the operations but I am failing to understand.
Let’s say that I did decide to use XML and coded all of the possible selections, all that would allow me to do is populate them on the GUI. I would still have to hard-code the values in the application to make the decisions about what to do when they are selected right? I just don’t see any other way.
In case anyone is curious, I am using WPF to build this application.
I’d probably have an object model design that looked something like this :
public interface ISelectionModel
{
string DisplayName { get; }
List<string> SubSelections { get; }
void RunSubSelection(string sub);
}
public SelectionBaseModel : ISelectionModel
{
private string _displayName;
private List<string> _subSelections;
public string DisplayName { get { return _displayName; }}
public List<string> SubSelections { get { return _subSelections; }}
}
public SelectionAModel : ISelectionModel
{
public SelectionAModel()
{
_displayName = "Selection A";
_subSelections = new List<string>() { "Sub 1", "Sub 2", "Sub 3" });
}
public void RunSubSelection(string sel)
{
switch(sel)
{
case "Sub 1":
Method1();
break;
case "Sub 2":
Method2();
break;
case "Sub 3":
Method3();
break;
}
}
private void Method1() { .. }
private void Method2() { .. }
private void Method3() { .. }
}
There is some hardcoding, but that’s unavoidable I think since you need to write your code methods. It would be split out though, so only methods relevant to SelectionA should be in the SelectionA model, or shared methods for all Selections could go in the Base model.
For display purposes, you’d have a list or collection of ISelectionModel
objects bound to whatever control you choose to display the collection, and each item would display using another control with an ItemsSource for displaying the sub menu items. When clicking a sub item to run it, you would simply call something like SelectedSelection.Run(SelectedSelection.SelectedSub)
from your ICommand
.
Each child object could be self-contained, and could be initialized from an XML file if you wanted. You could make the list of sub strings dynamic too, for example :
- “Selection A1” of type
SelectionAModel
with{ "Sub 1", "Sub 2" }
- “Selection A2” of type
SelectionAModel
with{ "Sub 2", "Sub 3" }
- “Selection A3” of type
SelectionAModel
with{ "Sub 1", "Sub 2", "Sub 3" }
- “Selection B” of type
SelectionBModel
with{ "Sub 4", "Sub 5" }
4
One way to do this:
- Selections in the UI are an
ItemsControl
with an appropriateItemTemplate
(e.g.ToggleButton
), bound to a collection of your objects (or view models representing them). - Subselections in the UI are also an
ItemsControl
, bound to a collection representing methods of the selected object (this time theItemTemplate
could beButton
). - The methods are retrieved using reflection.
The core of this design is the view model for the whole control, which could look like this:
public class SelectionsViewModel : INotifyPropertyChanged
{
private SelectionViewModel selection;
public SelectionsViewModel(IEnumerable<object> selections)
{
Selections = selections.Select(s => new SelectionViewModel(this, s));
}
public IEnumerable<SelectionViewModel> Selections { get; }
public SelectionViewModel Selection
{
get { return selection; }
set
{
selection = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Subselections));
}
}
public IEnumerable<SubselectionViewModel> Subselections =>
CreateSubselections(Selection?.Selection);
private static IEnumerable<SubselectionViewModel> CreateSubselections(object selection)
{
if (selection == null)
return Enumerable.Empty<SubselectionViewModel>();
return from m in selection.GetType().GetMethods(Public | Instance | DeclaredOnly)
// methods with no parameters, except property getters
where !m.GetParameters().Any() && !m.Name.StartsWith("get_")
select new SubselectionViewModel(m.Name, () => m.Invoke(selection, null));
}
// INPC implementation omitted
}
This sounds to me to be exactly what the ICommand
interface and command binding are for. You want to execute a command, with a certain parameter, when a certain UI action takes place. That is precisely what ICommand
is designed to do.
Just bind the sub-selection’s Command
to a corresponding ICommand
property in your view model, and it’s CommandParameter
to the top level selection. (You may need a little bit of code to retrieve the actual object you want, but likely you can find a way to properly bind that object as a parameter.)
3