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.