Question

I am currently writing a GUI for a C# application that adheres to the following software requirements (and am struggling with the conceptual design):

  1. Have a GUI with some choices of different operations to run.
  2. Each possible selection choice on the GUI should have a few sub-selections that the user can pick from.
  3. Each of the selections should correspond with a certain object to be utilized.
  4. 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.

Était-ce utile?

La solution

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" }

Autres conseils

One way to do this:

  • Selections in the UI are an ItemsControl with an appropriate ItemTemplate (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 the ItemTemplate could be Button).
  • 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.)

Licencié sous: CC-BY-SA avec attribution
scroll top