Pregunta

I working on a project for a client and decided to utilize the benefits of WPF in terms of data binding and the declarative approach to UI design. But I'm having a huge issue understanding the relationship between my Views and ViewModels.

I have a UserControl (ParentUserControl), and a child UserControl (ChildUserControl). Within this ParentUserControl I have a ContentPresenter that can hold multiple instances of ChildUserControl. The ChildUserControl has multiple comboboxes and textboxes displaying information from my Model. The user can open as many ChildUserControls within the ParentUserControl as they wish by clicking an 'Add New' Button. In my ParentViewModel, I'm storing the instances of each ChildViewModel that is created with the user adds a new ChildUserControl to the ParentUserControl. The user can navigate through the ChildUserControls through 'View Next' and 'View Previous' Buttons.

All of this works great, except if a user makes a selection or changes the text of any control in any ChildUserControl, the change propagates throughout all the ChildUserControls the user has created / sharing one ViewModel for all views. I've tried everything that I can think of, with and without using the MVVM Light tool kit (which seems like it's going to save me a ton of time in the future).

My question is how can I be absolutely positively sure that each time a new View is instantiated that it gets its own instance of it's ViewModel? I've been stuck on this for days!!! Thanks!

'NOTE: This code is not of an actual product. It's to simply demonstrate the issue I'm having with a real application. Don't be afraid to answer in C# if need be, my preferred language anyway. Oh, and I'm trying to complete this project with Pure MVVM in mind. Thank you!!!

Code:

ParentUserControl:

<UserControl x:Class="ParentUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MVVM_Light_Test_Application"
    Height="350" Width="525">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type local:ChildUserControlViewModel}">
            <local:ChildUserControl/>
        </DataTemplate>
    </UserControl.Resources>

    <UserControl.DataContext>
        <local:MainViewModel/>
    </UserControl.DataContext>
    <Grid>
        <StackPanel Width="auto" Height="200">
            <ContentPresenter Content="{Binding CurrentView}"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
            </ContentPresenter>            
        </StackPanel>
        <StackPanel>
            <Button Command="{Binding ChangeUserControlCommand}" Content="Click To Change View"/>
        </StackPanel>
    </Grid>
</UserControl>

ChildUserControl:

<UserControl x:Class="ChildUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MVVM_Light_Test_Application"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300" Background="{Binding BGColor, UpdateSourceTrigger=PropertyChanged}">
    <UserControl.DataContext>
        <local:ChildUserControlViewModel/>
    </UserControl.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="149*"/>
            <ColumnDefinition Width="151*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Height="200" Width="auto">
            <StackPanel>
                <ComboBox x:Name="cboExampleObjects" Margin="5" ItemsSource="{Binding Customers}" DisplayMemberPath="Details" SelectedItem="{Binding SelectedCustomer}"/>
            </StackPanel>
            <StackPanel Grid.Column="1">
                <TextBox x:Name="txtObjectPropertyValue" Text="{Binding SelectedCustomer.Name}" Margin="5"/>
            </StackPanel>   
        </StackPanel>
    </Grid>
</UserControl>

Parent View Model:

Public Class MainViewModel
    Inherits ViewModelBase

    Public Sub New()
        _currentView = New ChildUserControlViewModel
        ChangeUserControlCommand = New RelayCommand(AddressOf ChangeUserControl)
    End Sub

    Private _currentView As ViewModelBase
    Public Property CurrentView As ViewModelBase
        Get
            Return _currentView
        End Get
        Set(value As ViewModelBase)
            _currentView = value
            RaisePropertyChanged("CurrentView")
        End Set
    End Property

    Public Property ChangeUserControlCommand As RelayCommand


    Public Sub ChangeUserControl()
        CurrentView = New ChildUserControlViewModel
        CType(CurrentView, ChildUserControlViewModel).BGColor = Nothing
    End Sub
End Class

Child View Model:

Imports System.Collections.ObjectModel

Public Class ChildUserControlViewModel
    Inherits ViewModelBase

    Public Sub New()
        _customes = New ObservableCollection(Of Customer)(New List(Of Customer)({New Customer With {.Name = "TestName1", .CustomerNumber = 1}, New Customer With {.Name = "TestName2", .CustomerNumber = 2}}))
    End Sub

    Private _customers As ObservableCollection(Of Customer)
    Public Property Customers As ObservableCollection(Of Customer)
        Get
            Return _customers
        End Get
        Set(value As ObservableCollection(Of Customer))
            _customers = value
            RaisePropertyChanged("Customers")
        End Set
    End Property

    Private _selectedCustomer As Customer
    Public Property SelectedCustomer As Customer
        Get
            Return _selectedCustomer
        End Get
        Set(value As Customer)
            If _selectedCustomer Is Nothing OrElse String.Compare(value.Name, _selectedCustomer.Name) <> 0 Then
                _selectedCustomer = value
                RaisePropertyChanged("SelectedCustomer")
            End If
        End Set
    End Property

    Private _bgColor As SolidColorBrush
    Public Property BGColor As SolidColorBrush
        Get
            Dim random As New Random
            Return New SolidColorBrush(Color.FromArgb(50, Random.Next(0, 255), Random.Next(0, 255), Random.Next(0, 255)))
        End Get
        Set(value As SolidColorBrush)
            Dim random As New Random
            _bgColor = New SolidColorBrush(Color.FromArgb(50, random.Next(0, 255), random.Next(0, 255), random.Next(0, 255)))
            RaisePropertyChanged("BGColor")
        End Set
    End Property
End Class
¿Fue útil?

Solución

I see several issues:

  1. You set ChildUserControl.DataContext in XAML, but instances of ChildUserControl already get data context from binding in the ContentPresenter. You can safely remove it.

  2. ContentPresenter can contain only one child control. When you create a new view model and set it to CurrentView, the old view model is forgotten, the old ChildUserControl is removed, then a new ChildUserControl is created which represents the new view model.

How do you navigate between child views? I don't see the relevant code.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top