lepoco / lepoco/wpfui

Provide mechanism for navigation to be set up to properly respect MVVM

Open
#634 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C#
Stars
9.6k
Forks
1k
PR merge metrics
No merged PRs in 30d

Description

Is your feature request related to a problem? Please describe

The navigation is very hard (impossible?) to set up in an truly MVVM segregated way.
I'll use the templates as an example, the MainWindowViewModel directly spins up NavigationItem UI controls and references the view layer of an application when providing the type of the page to present.

in a project where youhave the application, the view layer, the viewmodel layer and the model layer all in separate projects (to help enforce the layer segregation) this is impossible to do because it would create a cyclic dependency between the viewmodel layer and the view layer. The viewmodels should not be creating UI controls or referencing UI pages.
The viewmodel should hold data which the view then presents in UI elements.

Describe the solution you'd like

The way this would be better to work would be to have viewmodel representations of these items, for example

    public partial class NavigationItemViewModel : ObservableObject, INavigationItemViewModel
    {
        [ObservableProperty]
        private string _header;

        [ObservableProperty]
        private string _tag;

        [ObservableProperty]
        private IconViewModel _icon;

        [ObservableProperty]
        private object _content;
    }
    
    public partial class MenuItemViewModel : ObservableObject, IMenuItemViewModel
    {
        [ObservableProperty]
        private string _header;

        [ObservableProperty]
        private string _tag;

        [ObservableProperty]
        private IconViewModel _icon;

        [ObservableProperty]
        private object _content;
    }

    public partial class IconViewModel : ObservableObject
    {
        [ObservableProperty]
        private SymbolRegular _icon;

        [ObservableProperty]
        private bool _filled = false;
    }

you could then instantiate something like

    NavigationItems = new ObservableCollection<INavigationItemViewModel>
    {
        new NavigationItemViewModel()
        {
            Header = "Home",
            Tag = "dashboard",
            Icon = new IconViewModel
            {
                Icon = SymbolRegular.Home24
            },
            Content = new DashboardViewModel()
        },
        new NavigationItemViewModel()
        {
            Header = "Data",
            Tag = "data",
            Icon = new IconViewModel
            {
                Icon = SymbolRegular.DataHistogram24
            },
            Content = new DataViewModel()
        }
    };

    NavigationFooter = new ObservableCollection<INavigationItemViewModel>
    {
        new NavigationItemViewModel()
        {
            Header = "Settings",
            Tag = "settings",
            Icon  = new IconViewModel
            {
                Icon = SymbolRegular.Settings24
            },
            Content = new SettingsViewModel()
        }
    };

    TrayMenuItems = new ObservableCollection<IMenuItemViewModel>
    {
        new MenuItemViewModel()
        {
            Header = "Home",
            Tag = "tray_home"
        }
    };

Or something similar to this.
View side you should then bind to an items source, and provide an item template for the bound items, or an item template selector, to present the items. Something like

    <ui:NavigationCompact
        x:Name="RootNavigation"
        Grid.Column="0"
        Footer="{Binding ViewModel.NavigationFooter, Mode=OneWay}"
        Frame="{Binding ElementName=RootFrame, Mode=OneWay}"
        Items="{Binding ViewModel.NavigationItems, Mode=OneWay}">
        <ui:NavigationCompact.ItemTemplate>
            <DataTemplate DataType="{x:Type viewModels:NavigationItemViewModel}">
                <ui:NavigationItem
                    Content="{Binding Header}"
                    Icon="{Binding Icon.Icon}"
                    IconFilled="{Binding Icon.Filled}"
                    IconSize="18"
                    PageTag="{Binding Tag}"
                    PageContent="{Binding Content}"/>
            </DataTemplate>
        </ui:NavigationCompact.ItemTemplate>
    </ui:NavigationCompact>

same with the menu items.
The actual page content, which is, at this point the viewmodel, can just be presented by the control using a content control where I can specify the template in my application resources.

<Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ui:ThemesDictionary Theme="Dark" />
                <ui:ControlsDictionary />
            </ResourceDictionary.MergedDictionaries>

            <DataTemplate DataType="{x:Type viewModels:DashboardViewModel}">
                <pages:DashboardPage />
            </DataTemplate>
        </ResourceDictionary>
    </Application.Resources>

or even within the item template of the navigation item itself if you need to specify a specific page content item template or item template selector.

INavigationService and IPageService have very view specific methods like GetFrame etc, which shouldnt really be used in the viewmodel layer either. (they're passed into the MainWindowViewModel in the template). A navigation service in the viewmodel layer should solely be able to navigate via the page tag.

Describe alternatives you've considered

No response

Additional context

Overall this would provide an MVVM-respecting approach to the navigation, and completely segregate the view layers and the viewmodel layers.

I personally do not believe that the current implementation, and subsequently the WPF UI approved templates in the VS plugin, are a good example of what to do with the MVVM design pattern.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the template's MainWindowViewModel, NavigationCompact and NavigationItem usage, along with INavigationService and IPageService. Compare their view-specific responsibilities with the proposed view-model representations and tag-based navigation. Done should be a decided, documented approach that separates view-model and view-layer dependencies without leaving navigation behavior undefined.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
desktop, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.