WPF - 如何确保每个视图都有自己的ViewModel实例? [英] WPF - How do I ensure each view gets its own ViewModel instance?
问题描述
我对WPF和MVVM模式都很陌生,所以请光临我。我为客户开发项目,并决定在数据绑定和UI设计的声明方法方面利用WPF的优势。但是我在理解我的Views和ViewModel之间的关系时遇到了很大的问题。
我有一个UserControl(ParentUserControl)和一个子UserControl(ChildUserControl)。在这个ParentUserControl中,我有一个ContentPresenter,可以容纳ChildUserControl的多个实例。 ChildUserControl有多个组合框和文本框,显示我的模型中的信息。用户可以通过单击添加新按钮,在ParentUserControl中打开尽可能多的ChildUserControl。在我的ParentViewModel中,我存储了用户创建的每个ChildViewModel的实例,并将新的ChildUserControl添加到ParentUserControl。用户可以通过查看下一个和查看上一个按钮浏览ChildUserControl。
所有这些都很有效,除非用户进行选择或更改任何ChildUserControl中的任何控件的文本,更改都会传播到用户为所有视图创建/共享一个ViewModel的所有ChildUserControl。无论使用和不使用MVVM Light工具包,我都尝试了所有我能想到的东西(这似乎将来会节省我很多时间)。
*我的问题是,我怎样才能绝对肯定每次实例化新视图时它都会获得自己的ViewModel实例?我已经坚持了几天!!!谢谢!*
'注意:此代码不是实际产品。这只是为了演示我在实际应用程序中遇到的问题。如果需要,不要害怕用C#回答,无论如何我是首选的语言。哦,我正在尝试用Pure MVVM完成这个项目。谢谢!!!
代码:
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
高度 = 350 宽度 = 525 >
< UserControl.Resources >
< DataTemplate DataType = {x:Type local:ChildUserControlViewModel} >
< local:ChildUserControl / >
< / DataTemplate >
< /UserControl.Resources >
< UserControl。 DataContext >
< local:MainViewModel / >
< / UserControl.DataContext >
< < span class =code-leadattribute>网格 >
< StackPanel 宽度 = auto 高度 = 200 >
< ContentPresenter 内容 = {Binding CurrentView} HorizontalAlignment = 拉伸 VerticalAlignment = 拉伸 < span class =code-attribute> >
< < span class =code-leadattribute> / ContentPresenter >
< / StackPanel >
< StackPanel >
< 按钮 命令 = {Binding ChangeUserControlCommand} 内容 = 点击更改视图 / >
< / StackPanel >
< / Grid >
< / UserControl >
ChildUserControl:
< UserControl x:Class = ChildUserControl
xmlns = http://schemas.microsoft.com/winfx/2006/xaml/presentation
< span class =code-attribute> xmlns:x = http://schemas.microsoft.com/winfx/2006/xaml
xmlns:mc = http ://schemas.openxmlformats.org/markup-compatibility/2006
< span class =code-attribute> xmlns:d = http://schemas.microsoft.com/expression/blend/2008
xmlns:local = clr-namespace :MVVM_Light_Test_Application
mc:可忽略 = d
d:DesignHeight = 300 d:DesignWidth = 300 已移除 = {Binding BGColor,UpdateSourceTrigger = PropertyChanged} >
< UserControl.DataContext >
< span class =code-keyword>< local:ChildUserControlViewModel / >
< / UserControl.DataContext >
< 网格 >
< Grid.ColumnDefinitions >
< ColumnDefinition 宽度 = 149 * / >
< ColumnDefinition 宽度 = 151 * / >
< < span class =code-leadattribute> / Grid.ColumnDefinitions >
< StackPanel 高度 = 200 宽度 = auto >
< StackPanel >
< ComboBox x:名称 = cboExampleObjects 保证金 = 5 ItemsSource = {绑定客户} DisplayMemberPath = 详细信息 SelectedItem = {Binding SelectedCustomer} / >
< / StackPanel >
< StackPanel Grid.Column = 1 >
< TextBox x:名称 = txtObjectPropertyValue 文字 = {Binding SelectedCustomer.Name} 保证金 = 5 / >
< / StackPanel >
< / StackPanel >
< / Grid >
< span class =code-keyword>< / UserControl >
父母视图型号:
公共 类 MainViewModel
继承 ViewModelBase
公共 Sub 新()
_currentView = 新 ChildUserControlViewModel
ChangeUserControlCommand = 新 RelayCommand( AddressOf ChangeUserControl)
结束 Sub
私有 _currentView 作为 ViewModelBase
公共 属性 CurrentView 作为 ViewModelBase
获取
返回 _currentView
结束 获取
设置(值 As ViewModelBase)
_currentView = value
RaisePropertyChanged( CurrentView )
结束 设置
结束 属性
公共 属性 ChangeUserControlCommand As RelayCommand
公共 Sub ChangeUserControl()
CurrentView = 新 ChildUserControlViewModel
CType (CurrentView,ChildUserControlViewModel).BGColor = Nothing
End Sub
结束 类
儿童景观型号:
Imports System.Collections.ObjectModel
Public Class ChildUserControlViewModel
继承 ViewModelBase
公共 Sub New ()
_customes = 新 ObservableCollection( 客户)(新列表( 客户)({新客户使用 {.Name = TestName1,。CustomerProumber = 1 },新客户使用 {。Name = TestN ame2,。CustomerProumber = 2 }}))
结束 Sub
私有 _customers As ObservableCollection( 客户)
公共 属性客户作为 ObservableCollection( 客户)
获取
返回 _customers
结束 获取
设置(值 As ObservableCollection ( 客户))
_customers = value
RaisePropertyChanged( Customers)
End 设置
结束 属性
私人 _selectedCustomer 作为客户
公开 属性 SelectedCustomer 作为客户
获取
返回 _selectedCustomer
结束 获取
设置(值 As Customer)
如果 _selectedCustomer 没什么 OrElse String .Compare(value.Name,_selectedCustomer.Name)<> 0 然后
_selectedCustomer = value
RaisePropertyChanged( SelectedCustomer)
结束 如果
结束 设置
结束 属性
私有 _bgColor 作为 SolidColorBrush
公共 属性 BGColor As SolidColorBrush
获取
Dim random As 新随机
返回 新 SolidColorBrush(Color.FromArgb(< span class =code-digit> 50 ,Random。 Next ( 0 ,< span class =code-digit> 255 ),随机。下一步( 0 , 255 ),随机。下一步( 0 , 255 )))
结束 获取
设置(值 As SolidColorBrush)
Dim random As 新随机
_bgColor = 新 SolidColorBrush(颜色。 FromArgb( 50 ,随机。下一步( 0 , 255 ),随机。下一步( 0 , 255 ),随机。下一步( 0 , 255 )))
RaisePropertyChanged( BGColor)
结束 设置
结束 属性
结束 类
SO上的优秀绅士为我回答了我的问题。在绑定到ContentPresenter时,我没有意识到子视图为它们设置了数据上下文。一旦我删除了< usercontrol.datacontext>来自ChildUserControl的标签,一切都像梦一样。要查看我收到的答案,请单击 此处 [ ^ ]
I am quite new to WPF and the MVVM pattern so please bare with me. 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" removed="{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
A fine gentleman on SO answered my question for me. I did not realize that the child views had their data context set for them when binding to the the ContentPresenter. Once I removed the <usercontrol.datacontext> tag from the ChildUserControl, everything worked like a dream. To view the answer I received click Here [^]
这篇关于WPF - 如何确保每个视图都有自己的ViewModel实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!