Elegant way to prevent circular events in MVC?

后端 未结 2 1293
悲哀的现实
悲哀的现实 2021-01-02 19:30

The question, in brief:

In MVC, how do you distinguish between a checkbox click (or a selectbox or listbox change) from a human meaning \"Controller

相关标签:
2条回答
  • 2021-01-02 20:03

    This is a tough one. If I understand correctly, the problem results because you've exposed a click handler on your model, and the model's click event is caught by the controller. The controller updates the view, which in turn toggles the same event.

    From my point of view, I would consider it inappropriate for the Controller to attach itself to the Edge's Click event, because it exposes too much detail about how the Edge is implemented and used. Controller does not care about how the Edge is used or any other implementation details.

    In fact, canonical MVC style doesn't require the Controller to hook onto any Model events at all, generally because the Model's state is not mutated by the View or any other Controllers. Its not necessary for the Model to notify the Controller that it's been changed.

    To fix your problem, you should define View's interface to have a single method, such as ToggleEdge:

    
    public interface GraphView
    {
        event Action ToggleEdge;
    }
    

    Its tempting to want to create two methods, EdgeClicked and CheckboxClicked, but insisting on two independent methods like that violates the principle of encapsulation. It exposes too many implementation details to your Controller or anyone else who wants to hook onto those events. Remember, the Controller only cares that the View's state changed, it doesn't care how it changed.

    When you implement the View interface onto your user interface, you should take care to ensure that the ToggleEdge event is invoked from one location. You can do this by hooking onto the Edge.Clicked event in your View and using it to toggle your checkbox; this makes your checkbox responsible for raising the Toggle vent up to the controller:

    
    public class UI : UserControl, GraphView
    {
        public event Action ToggleEdge;
    
        void OnToggleEdge(Edge edge)
        {
            if (ToggleEdge != null)
                ToggleEdge(edge);
        }
    
        protected void Edge_Clicked(object sender, EventArgs e)
        {
            CheckBox chkbox = FindCheckBoxThatCorrespondsToEdge((Edge)sender);
            chkbox.Checked = !chkbox.Checked;    
        }
    
        protected void chkEdge_CheckChanged(object sender, EventArgs e)
        {
            Edge edge = FindEdgeThatCorrespondsToCheckbox((CheckBox)sender);
            OnToggleEdge(edge);
        }
    }
    

    You can make an argument that the View knows too much about its implementation now: its aware that edges and checkboxes are fundamentally connected. Maybe this is another hack, but it can probably be dismissed as "UI logic" need to keep the View's display syncronized.

    0 讨论(0)
  • 2021-01-02 20:17

    Just to recap the MVC model. Views should generally update themselves. Here's how it works: a controller changes the state of the model, the model sends updates to its views, the views pull in new state from the model and update themselves. While controllers and views are generally bundled (i.e. drilling down on data in a graphic representation) they should never interact directly, only through the model. This in general of course.

    So the JS functions that update your views are not actually controllers, which is an important distinction. They should be considered part of your view. This might not be helpful to the problem at hand but I thought it merited pointing out.

    You can also not delete your model, I assume you mean you're deleting someting from your model, since no views or controllers can actually exist (or be in a functional state) if they're not backed by a model.

    Not being a JS code jockey and not having used gmaps I don't really see where the problem is. Does changing the state of a checkbox(checked property) fire the onClick() event? It really shouldn't IMHO but perhaps they implemented it that way, otherwise you could just attach your controller to the onClick() and add some logic to the checkbox (or, this being JS, in a function somewhere) to change the checkbox state. If that's not possible, option 1 and 2 are definitely your best bet.

    addition: user interacting with a view

    So what happens when a user wants to interact with a view? Frequently a widget will include both a view and the controller. A checkbox has a view (you can see if it's checked or not) and also a controller (you can click it). When you click the checkbox, in principle the following should happen:

    • checkbox controller receives the event
    • checkbox controller changes the state for the value this checkbox represents in the model
    • model updates listeners (including the checkbox)
    • checkbox updates its look to reflect that that value has changed

    The first step, how the controller receives the event is somewhat language dependent, but in OOP languages it's likely a listener object attached to user interface events on this particular widget which either is the controller or notifies the controller of the user interaction.

    0 讨论(0)
提交回复
热议问题