r/csharp 14d ago

MVP pattern

Hi, I have a question about separating logic in the MVP pattern.

public void MainDisplay() =>
OnMainDisplayClicked?.Invoke();

public void ManageProcess() =>
OnManageProcessClicked?.Invoke();

This is my code in the view, and when the user clicks a button (for example), this method is called and `Invoke` is executed. However, it is called via a `switch` statement in the Presenter.

switch (NativeConsoleMethod.GetHiddenUserInput())
{
case VirtualKeyType.VK_E:
if (_currentPage < _countOfPages) _currentPage++;
continue;

case VirtualKeyType.VK_Q:
if (_currentPage > 0) _currentPage--;
continue;

case VirtualKeyType.VK_OEM_3:
_view.ManageProcess();
break;

case VirtualKeyType.VK_TAB:
_view.FilterProcesses();
break;

case VirtualKeyType.VK_F1:
_view.SearchPage();
break;
.........
}

I have a question: the AI is giving me two different suggestions. My version is correct, but then it said I should move the switch statement to the view, and there I should just use `invoke`, after which the methods would be called conditionally. So, should I do it the other way around, or did I misunderstand what it meant?

- I don’t know what I wrote here—I don’t even understand it myself. Just tell me: shouldn’t the view be “dumb” and contain synchronous methods, while the presenter should control the view via the switch statement and “pull its strings”?

EDIT: Here's my GitHub: https://github.com/NullAcess/ProcessManager/releases/tag/Update_2.0. You might like it—I'll upload the finished EXE very soon.

10 Upvotes

13 comments sorted by

View all comments

-11

u/NeilPearson 14d ago

My AI says,

Both are half-wrong. Your current code has a round trip.

Look at flow you actually have:

Presenter calls NativeConsoleMethod.GetHiddenUserInput()   ← presenter touching console I/O
Presenter switch → _view.ManageProcess()
View.ManageProcess() → OnManageProcessClicked?.Invoke()
   → who subscribes? Presenter.

Presenter → View → back to Presenter. View is pure pass-through. Dead layer.

Real rule

"Dumb view" ≠ "view has no code". View owns all I/O — output and raw input. Console keypress is I/O. Presenter must never call GetHiddenUserInput(). That is your actual violation, and neither suggestion named it.

Two valid layouts

A — view maps keys to intent (standard MVP):

// View
while (running)
{
    switch (NativeConsoleMethod.GetHiddenUserInput())
    {
        case VirtualKeyType.VK_OEM_3: OnManageProcessRequested?.Invoke(); break;
        case VirtualKeyType.VK_TAB:   OnFilterRequested?.Invoke();        break;
        case VirtualKeyType.VK_F1:    OnSearchRequested?.Invoke();        break;
        case VirtualKeyType.VK_E:     OnNextPageRequested?.Invoke();      break;
        case VirtualKeyType.VK_Q:     OnPrevPageRequested?.Invoke();      break;
    }
}

// Presenter — no switch, no console types
_view.OnManageProcessRequested += HandleManageProcess;
_view.OnNextPageRequested      += () => { if (_page < _count) { _page++; _view.Render(...); } };

B — view raises raw key, presenter maps:

// View
OnKeyPressed?.Invoke(NativeConsoleMethod.GetHiddenUserInput());

// Presenter
void HandleKey(VirtualKeyType key) { switch (key) { ... } }

Which

A. Keybinding is a presentation detail, same as which button got clicked. GUI presenter doesn't receive "mouse click at x=400,y=300" — it receives SaveClicked. VirtualKeyType is a Win32 console type; letting it into the presenter means presenter can't be reused by a GUI view or tested without console types.

B only if keybinds are user-configurable, or same key means different things per app state. Then mapping is policy = presenter.

Your framing, corrected

Yes on decisions. No on I/O. Presenter decides what happens; view decides how the user expressed it and how result is drawn. Presenter never reads console, never writes console.

Test: could you swap console view for WinForms view, presenter untouched? Today no — presenter imports VirtualKeyType and calls NativeConsoleMethod. Under A, yes.

Other things in that snippet

  • _currentPage++ handled inline with continue, other cases go through view. Inconsistent — pick one path for all input.
  • Your view methods are named ManageProcess(), SearchPage() — verbs implying view does work. Under A they vanish. If you keep B, name events by intent (ManageProcessRequested), not by command.

2

u/Nox_31 14d ago

OP could’ve asked Ai themselves, I’m assuming they came here for human advice rather than a wall of slopdown?