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.
3 Answers 3
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" }
-
Is this kind of like MVVM (which I just started reading about)?Snoop– Snoop2016年07月08日 12:24:16 +00:00Commented Jul 8, 2016 at 12:24
-
This object model design could be used with the MVVM pattern, yes. The pattern is only for separation of UI and data, and there are many ways to work with it. If you're interested, I have a couple of beginner WPF/MVVM articles on my blog site, such as this simple MVVM exampleRachel– Rachel2016年07月08日 13:49:18 +00:00Commented Jul 8, 2016 at 13:49
-
Thank you, will definitely check it out later on. I did a quick tutorial I found on code-project for MVVM, but I got kind of confused on how I would work it into my application which is already using an observable pattern with reactive extensions to collect data through some events generated in my libraries.Snoop– Snoop2016年07月08日 14:43:18 +00:00Commented Jul 8, 2016 at 14:43
-
I should have initially stated that in my question earlier on, but it's too late now. That is a separate problem and will probably have to be a separate question.Snoop– Snoop2016年07月08日 14:44:55 +00:00Commented Jul 8, 2016 at 14:44
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.)
-
Would you recommend making the view-model a separate assembly?Snoop– Snoop2016年07月08日 12:29:09 +00:00Commented Jul 8, 2016 at 12:29
-
You could @StevieV, but I see little value in it. Even though the code for the VM is loosely coupled to the view, it's still very semantically coupled to the view. The VM is really just a kind of controller. Do you keep your MVC controllers in a separate lib?RubberDuck– RubberDuck2016年07月08日 13:19:42 +00:00Commented Jul 8, 2016 at 13:19
-
I don't know what MVC is actually, so I cannot answer that.Snoop– Snoop2016年07月08日 13:21:01 +00:00Commented Jul 8, 2016 at 13:21