Question

I have WinForms GUI application, which can work in two "states" with different functionality, but these states share GUI controls. I am dividing these states by using enum almost in every method, like:

    private void picBox_MouseDown(object sender, MouseEventArgs e)
    {
        if (picBox.Image == null)
            return;

        xDown = e.X;
        yDown = e.Y;

        if (appState == AppState.Annotating)
        {
            PointF ptImg = new PointF((xDown - absImgPos.X) * scale,
                (yDown - absImgPos.Y) * scale);
            annotateEngine.AddPoint(e.Location, ptImg);
        }
        else if (appState == AppState.Cropping)
        {
            picBox.Invalidate();
        }
    }

I want to make something like IAppState and than implement it for my states AnnotatingState::IAppState and CroppingState::IAppState, with some methods which should affect GUI:

interface IAppState
{
    void OnLoadImages();
    void OnSave();
    void OnPathChanged();
    void OnMouseDown();
    void OnMouseUp();
}

Can you suggest some suitable pattern?

Était-ce utile?

La solution

One can use MVP (Model View Presenter) here. This decouples the view from the view's logic and is typically used to make views exchangable, but there is no reason why it cannot also be used to exchange the Presenter object (in your case one Presenter for each mode). That makes the presenter essentially a strategy object. This will look like this:

public class MyForm : Form, IView
{
    private IPresenter presenter = null;

    public MyForm(AppState appState)
    {
        // For the sake of demonstration, presenters are created here,
        // in the real application they could be injected or switched
        // at run time:
        if (appState == AppState.Annotating)
            presenter = new AnnotatingPresenter(this);
        else
            presenter = new CroppingPresenter(this);
    }
}

The interface IView must provide all access to the form's elements / controls the presenter objects need to implement their functionality. The event implementation could look like this:

 private void picBox_MouseDown(object sender, MouseEventArgs e)
 {
     presenter.OnPicBoxMouseDown(sender,e);
 }

The related method in the annotation presenter then looks like this:

void OnPicBoxMouseDown(object sender, MouseEventArgs e)
{
     if (myview.PicBox.Image == null)
        return; 

      var xDown = e.X;
      var yDown = e.Y;
      PointF ptImg = new PointF((xDown - absImgPos.X) * scale,
            (yDown - absImgPos.Y) * scale);
      myView.AnnotateEngine.AddPoint(e.Location, ptImg);
}

Here, myView is the reference to the IView passed through the constructor, and AnnotateEngine as well as PicBox are properties which need to be made available in the IView and return the members annotateEngine or picBox respectively.

Of course, here are some design decisions you still can make differently. For example, you could leave the common code for both states still in the picBox_MouseDow of the view, or find a place in the presenter layer where you can reuse it from both Presenters (could be a tool class or a common base class). Or you can encapsulate all WinForms access by IView in a way so the Presenter layer does not need any reference to WinForms any more. This depends on your overall design goals, pick your choice.

Autres conseils

I would create separate form classes for the states. Replace the part that creates your MainForm instance with a switch statement that creates either an AnnotatingForm or a CroppingForm, depending on the desired application state.

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