Home > database >  Is this proper use of MVVM when trying to create a new window based on a item in a ListView?
Is this proper use of MVVM when trying to create a new window based on a item in a ListView?

Time:02-17

So I'm currently playing around with WPF and MVVM and I've been trying to find a way to select an item in a list and display it in a new window. And I came up with a solution that I personally like but I'm not sure if it follows a valid MVVM architecture.

So I have my MainWindow.xaml

<Window x:Class="Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Views" 
        xmlns:viewmodel="clr-namespace:Views.MVVM.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        Background="#252525">

    <Window.DataContext>
        <viewmodel:MainViewModel/>
    </Window.DataContext>

    <StackPanel Orientation="Horizontal">
        <ListView ItemsSource="{Binding NetworkObjects}"
                  Style="{StaticResource ListStyle}"
                  Name="MainList"/>
    </StackPanel>
</Window>

Which uses this style that binds each item in the collection and also applies a MouseBinding which allows me to LeftDoubleClick the item to invoke a command. I pass the the entire object as a command parameter because that's the DataContext. And this is needed for the new Window.

<Style TargetType="ListView" x:Key="ListStyle">
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="BorderThickness" Value="0"/>

    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="ListViewItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListViewItem">
                            <ContentPresenter />
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>

    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <DockPanel Margin="2">
                    <DockPanel.InputBindings>
                        <MouseBinding Gesture="LeftDoubleClick" 
                                      Command="{Binding DisplayItemCommand}"
                                      CommandParameter="{Binding Path=.}"/>
                    </DockPanel.InputBindings>
                    <DockPanel.Style>
                        <Style TargetType="DockPanel">
                            <Style.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter Property="Background" Value="#303030"/>
                                </Trigger>
                                <Trigger Property="IsMouseOver" Value="False">
                                    <Setter Property="Background" Value="Transparent"/>
                                </Trigger>
                                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                               AncestorType={x:Type ListBoxItem}}, 
                                                               Path=IsSelected}" Value="True">
                                    <Setter Property="Background" Value="MediumSpringGreen"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </DockPanel.Style>
                    <TextBlock Text="{Binding NetworkModel.Address}" Foreground="Black"/>
                </DockPanel>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

The MainViewModel does nothing but generate some dummy data in the constructor. This is where I start doubting whether or not this is valid MVVM, because I'm generating objects that are not based on a Model but rather an entire ViewModel which in theory makes sense, but I'm not sure.

public ObservableCollection<NetworkObjectViewModel> NetworkObjects { get; set; }
public MainViewModel()
{
    NetworkObjects = new ObservableCollection<NetworkObjectViewModel>();
    for (int i = 0; i < 10; i  )
    {
        NetworkObjects.Add(new NetworkObjectViewModel() { NetworkModel = new NetworkModel { Address = $"Address {i}", Port = i } });
    }
}

And the NetworkObjectViewModel contains a RelayCommand and NetworkModel.

public class NetworkObjectViewModel
{
    public NetworkModel NetworkModel { get; set; }
    public RelayCommand DisplayItemCommand { get; set; }

    public NetworkObjectViewModel()
    {
        DisplayItemCommand = new RelayCommand(o =>
        {
            WindowService.ShowWindow(o);
        });
    }
}

The WindowService is simple, it just creates a new GenericWindow and sets it DataContext so that it can make a decision and display the correct UserControl based on the DataContext

internal class WindowService
{
    public static void ShowWindow(object DataCtx)
    {
        var t  = new GenericWindow();
        t.DataContext = DataCtx;
        t.Show();
    }
}

And here's the GenericWindow.xaml

    <Window.Resources>
        <DataTemplate DataType="{x:Type vms:NetworkObjectViewModel}">
            <v:TestView/>
        </DataTemplate>
    
        <DataTemplate DataType="{x:Type vms:NetworkObjectViewModel2}">
            <v:TestView2/>
        </DataTemplate>
    </Window.Resources>
    <ContentPresenter Content="{Binding .}" />

And the TestView.xaml which is nothing but a simple UserControl that looks like this

<Grid>
    <TextBox Text="{Binding NetworkModel.Address, UpdateSourceTrigger=PropertyChanged}"
               Width="100"
               Height="25"
               IsEnabled="True"/>
</Grid>

CodePudding user response:

I have a suggestion here that might make some sense: Move DisplayItemCommand to MainViewModel.And Binding in Xaml should be adjusted as:

 <MouseBinding Gesture="LeftDoubleClick" 
               Command="{Binding DataContext.DisplayItemCommand ,RelativeSource={RelativeSource AncestorType={x:Type Window},Mode=FindAncestor}}"
               CommandParameter="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ListViewItem},Mode=FindAncestor}}"/>

CodePudding user response:

This is where I start doubting whether or not this is valid MVVM, because I'm generating objects that are not based on a Model but rather an entire ViewModel which in theory makes sense, but I'm not sure.

This makes perfect sense.

The model in MVVM typically refers to types that you don't want to bind to directly, such as for example data transfer objects (DTOs), domain objects that contain business logic or even services or repositories.

Creating and exposing a collection of "child" view models to bind to from a "parent" view model class is a perfectly fine and a common approach.

  • Related