Beyond the Dogma: A Pragmatic Hybrid MVVM Architecture in WPF**

beyond-the-dogma:-a-pragmatic-hybrid-mvvm-architecture-in-wpf**

MVVM is the de facto standard for building WPF applications. It promises a beautiful world of decoupled layers, testability, and clean architecture. The ideal is great.

But when you’re in the trenches, shipping features, you start running into the painful parts, don’t you?

  • The dogma of “data-binding for everything” becomes a bottleneck. How do you cleanly show a dialog or trigger a complex animation?
  • You can’t seem to control the timing of View updates from your ViewModel without jumping through hoops.
  • Your ViewModels get bloated with silly “trigger properties” like IsTimeToUpdateTheViewTrigger that exist only to signal the UI.
  • For performance-critical apps, like a CAD viewer, the “chattiness” of data-binding can kill your frame rate.

To solve these real-world problems, I’d like to share a hybrid, pragmatic approach that respects the spirit of MVVM while introducing a “ViewService” to handle the messy parts.

The Solution: A “ViewService” as an Interpreter

The core of this architecture is an interpreter that sits between the ViewModel and the View. Let’s call it a ViewService.

  • The ViewModel remains ignorant of the concrete View (MainWindow.xaml, etc.).
  • Instead, it only knows about an abstract interface, like IMyViewService.
  • The concrete ViewService class takes on the responsibility of translating the ViewModel’s commands into actual View manipulations.

This setup allows us to call View-specific logic safely, without sacrificing the testability of our ViewModel—the biggest win of MVVM.

A Real-World Example: IMainContentViewService

Here is a ViewService interface I used in a real CAD-style application.

public interface IMainContentViewService
{
    // Exposes essential data from the View to the ViewModel
    IEnumerable SelectedVertices { get; }

    // Exposes behaviors (as commands) for the ViewModel to invoke
    RelayCommand FitCommand { get; }
    RelayCommand SelectAllCommand { get; }
    RelayCommand ShowSettingDialogCommand { get; }
    RelayCommand ShowGridCommand { get; }
    // ...and many more
}

Key Decision: Exposing ICommand Properties

You’ll notice it exposes ICommand properties (RelayCommand) instead of methods like void Fit(). This is a deliberate choice to empower the UI layer. It allows us to wire up different UI components entirely in XAML.

For example, a menu item in a parent ShellView can now directly bind to a command that controls our MainContentView:


The ViewService Implementation

The implementation of this service is beautifully simple. Its only job is to delegate.

public class MainContentViewService : ViewService, IMainContentViewService
{
    public MainContentViewService(MainContentView view) : base(view) { }

    // SelectedVertices just gets the data from an inner control in the View
    public IEnumerable SelectedVertices => this.View.ProfileDataTableView.SelectedItemsAsProfilePointsData;

    private RelayCommand _fitCommand;
    public RelayCommand FitCommand
    {
        get
        {
            if (this._fitCommand == null)
            {
                // The 'CanExecute' logic is simple because it can directly access the View's state
                bool canExecute(object obj)
                {
                    return this.View.ProfileDataTableView.Items.Count > 0;
                }

                // The 'Execute' logic just calls a method on a specific part of the View
                void execute(object obj)
                {
                    this.View.ProfileDataGraphView.InternalZoomViewer.Fit();
                }

                this._fitCommand = new RelayCommand(canExecute, execute);
            }
            return this._fitCommand;
        }
    }
    // ...other command implementations
}

Note how the CanExecute logic for FitCommand directly checks this.View.ProfileDataTableView.Items.Count. This is far more efficient than trying to sync that state back to the ViewModel.

Our 4 Rules for This Hybrid MVVM

To keep our codebase consistent and avoid confusion about “where do I put this logic?”, we established four simple rules.

1. For State Synchronization → Use Data Binding

When a ViewModel property and a View property should always be in sync, use traditional data binding. This is the heart of MVVM.
(e.g., Text property of a TextBox, ItemsSource of a ListBox)

2. For Behavior Invocation → Use a ViewService

When the ViewModel needs to tell the View to perform a one-time action, use a method or command on the ViewService.
(e.g., FitToScreen(), ShowDialog())

3. For View-Internal Logic → Use Code-Behind

It’s okay to write code in the code-behind (.xaml.cs) for logic that is purely visual and has no impact on the ViewModel.
(e.g., handling the close button, decorative animations on mouse-over)

4. For Info Flowing to the VM → Keep It on a “Need-to-Know” Basis

Only pass information from the View to the ViewModel if the ViewModel truly needs it for its business logic. The ViewModel should not care about visual state (e.g., whether a panel is expanded or not).

Final Thoughts

MVVM is a powerful pattern, but being a dogmatic purist can lead to overly complex and inefficient code.

This hybrid ViewService approach is a pragmatic compromise. It maintains the core benefits of MVVM—like testability and separation of concerns—while providing a clean, simple escape hatch for the realities of complex UI development.

I hope this gives you a useful tool for your next WPF project!

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
own-your-ai:-learn-how-to-fine-tune-gemma-3-270m-and-run-it-on-device

Own your AI: Learn how to fine-tune Gemma 3 270M and run it on-device

Next Post
medical-device-q&a

Medical Device Q&A

Related Posts