我正在尝试使用WPF和MVVM协议(protocol)构建应用程序。该应用程序具有一个窗口和多个 View (UserControls)。其中一个 View 还具有 subview ,以显示不同的数据。
我的问题
我不明白如何将数据绑定(bind)到 subview 。我试图了解如何绑定(bind)数据,但是只能将数据向下绑定(bind)一层。
代码
这是一些代码。我希望它可以使我的问题更容易理解。
App.xaml.cs
public partial class App : Application
{
private Engine _engine;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
_engine = new Engine("test");
var viewModel = new MainWindowViewModel(_engine);
EventHandler handler = null;
handler = delegate
{
viewModel.RequestClose -= handler;
window.Close();
};
viewModel.RequestClose += handler;
window.DataContext = viewModel;
window.Show();
}
}
在这里,我创建了引擎对象,并将其传递给要进一步绑定(bind) View 层次结构的MainWindowViewModel。
MainWindow.xaml
<Window
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:"
xmlns:ViewModels="clr-namespace:ViewModels"
xmlns:View="clr-namespace:Views"
x:Class="MainWindow"
Title="title" Height="800" Width="1200"
WindowStartupLocation="CenterScreen" Icon="Resources/Images/logo.png"
>
<DockPanel Margin="0" Background="#FF4F4F4F" LastChildFill="True">
<Menu DockPanel.Dock="Top" Height="20">
<MenuItem Header="File">
<MenuItem Header="Exit"/>
</MenuItem>
</Menu>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="200" MaxWidth="200"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<View:TabView Grid.Column="0"/>
<View:WorkspaceView Grid.Column="1"/>
</Grid>
</DockPanel>
WorkspaceView.xaml
<UserControl x:Class="Views.WorkspaceView"
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:Views"
xmlns:ViewModels="clr-namespace:ViewModels"
mc:Ignorable="d"
d:DesignHeight="750"
d:DesignWidth="1000"
d:DataContext="{d:DesignInstance ViewModels:WorkspaceViewModel}"
>
<Grid>
<Label x:Name="label" Height="750" VerticalAlignment="Top" FontSize="60" Foreground="White" Content="{Binding Engine.BarCode}"/>
<Grid Margin="40">
<Grid.Background>
<ImageBrush ImageSource="/;component/Resources/Images/logo.png" Stretch="Uniform"/>
</Grid.Background>
<ContentPresenter Content="{Binding CurrentView}"/>
</Grid>
<DockPanel>
<!--ContentPresenter Content="{Binding CurrentView}"/-->
</DockPanel>
</Grid>
在这里,我试图绑定(bind)
{Binding Engine.BarCode}
,它可以工作并为我提供带有正确数据的字符串。但是<ContentPresenter Content="{Binding CurrentView}"/>
不会显示我在ViewModel中为workspaceView设置的当前 View 。WorkspaceViewModel.cs
public WorkspaceViewModel()
{
_currentView = new InjectorView();
}
public UserControl CurrentView
{
get { return _currentView; }
}
_currentView = new InjectorView();
仅用于测试,并且currentView应该根据在另一个 View 中按下的按钮而更改。* WorkspaceView.xaml.cs
public WorkspaceView()
{
InitializeComponent();
this.DataContext = new WorkspaceViewModel();
}
InjectorView.xaml
<UserControl x:Class="Views.InjectorView"
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:Views"
xmlns:ViewModels="clr-namespace:ViewModels"
mc:Ignorable="d"
d:DesignHeight="750"
d:DesignWidth="1000"
d:DataContext="{d:DesignInstance ViewModels:InjectorViewModel}"
>
<Grid Background="#FFAEAEAE">
<Label x:Name="label1" Content="{Binding Engine.BarCode}"/>
</Grid>
</UserControl>
但是,如果我从WorkspaceView.xaml中删除
d:DataContext="{d:DesignInstance ViewModels:WorkspaceViewModel}"
并将this.DataContext = new WorkspaceViewModel();
添加到xaml代码的C#文件中,它将显示当前 View (InjectorView)。现在唯一的问题是,当我尝试在InjectorView {Binding Engine.BarCode}
中绑定(bind)某些数据时,它将不会显示与以前相同的字符串(我想它不再是该对象的相同实例了?)我想念什么?我将MVVM和wpf解释完全错误吗?
(由于该产品,我不得不删除一些代码(例如 namespace ))
最佳答案
您肯定误会了不少。
首先,_currentView = new InjectorView();
永远不会出现在ViewModel中。 ViewModels不应该包含任何视觉类(您自己的View类和UI类)的任何引用。您应该实例化该 View 的ViewModel。
接下来,如果CurrentView
始终是InjectorView
的实例(这意味着它不能是其他东西),那么您可以简单地执行以下操作:
<View:InjectorView>
<View:InjectorView.DataContext>
<ViewModel:InjectorViewModel />
<View:InjectorView.DataContext>
</View:InjectorView>
这与您在MainWindow.xaml
中为WorkspaceView
完成的操作非常相似。现在到下一个大问题。
d:DataContext="{d:DesignInstance ViewModels:InjectorViewModel}"
用作设计时的DataContext
。这意味着,通常没有此功能,Visual Studios Designer不会渲染数据绑定(bind)的东西,因为在设计时没有DataContext
。该行告诉设计者您要创建InjectorViewModel
实例来模拟此行为-但这纯粹是为设计者设计的。当您的应用程序运行时,该d:DataContext
行的绝对对您的应用程序没有的影响。目前,您的
InjectorView
没有ViewModel
(没有DataContext
)。如果您遵循我在此答案前面部分中的建议,那么您现在将拥有一个DataContext
。编辑(根据OP的评论)
上面的方法称为 View First Approach 。使用这种方法,您可以在XAML中定义一个View实例。然后,使用
DataContext
附加相应的ViewModel。对于您的情况,应该使用 ViewModel First Approach 。您定义在ViewModel中使用哪些组件。
WorkspaceViewModel:
private ViewModelBase _myCurrentView;
public ViewModelBase MyCurrentView
{
get { return _currentView; }
set
{
if (value != _myCurrentView)
{
_myCurrentView = value;
RaisePropertyChanged(); // You need to implement INotifyPropertyChanged interface
}
}
}
工作区 View :<ContentControl Content="{Binding MyCurrentView}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type ViewModels:InjectorViewModel}">
<local:InjectorView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:MySecondViewModel}">
<local:MySecondView />
</DataTemplate>
.....
....
</ContentControl.Resources>
</ContentControl>
使用此方法,当您需要加载InjectorView
时,只需实例化其ViewModel InjectorViewModel
,并将其分配给MyCurrentView
属性。在
WorkspaceView
View 中,您将使用ContentControl
托管此 subview 。此ContentControl
需要绑定(bind)到MyCurrentView
类型的ViewModelBase
属性。 DataTemplate
会告诉WPF,如果内容为InjectorViewModel
类型,则为我实例化InjectorView
对象,因为InjectorViewModel
只是非可视数据对象-可渲染的等效项是InjectorView
。您需要为每个期望的ViewModel类创建一个DataTemplate
。使用这种方法时要注意两件事(首先是ViewModel)。首先,所有可以动态加载的ViewModel必须是
ViewModelBase
的子类。 ViewModelBase
可以是抽象类或接口(interface),您可以随意更改为所需的名称。最重要的是,它必须是所有可能的ViewModel的通用类/接口(interface)。其次,您无需在 View 中实例化另一个ViewModel。您也不需要设置
DataContext
。使用DataTemplate
将自动设置 View 的DataContext
。编辑2
有很多方法可以将数据从主ViewModel传递到子ViewModel。一种方法是让存储库(数据库)保存数据。
如果您不想拥有存储库,则可以使用单例作为存储库进行仿真,也可以让主ViewModel保留所有数据,让所有其他ViewModel保留主ViewModel实例的引用。但老实说,通过View-First Approach实例化子ViewModel时,这很难实现,因为ViewModel是通过View实例化的,这不会调用传入主ViewModel的构造函数。
解决此问题的另一种方法是使用静态内部单例实例创建单例ViewModel。这使ViewModels可以访问彼此的数据。要使ViewModels单例,您需要更改“ View 优先”方法定义DataContext的方式。
例如,使用我之前为View-First
InjectorView
提供的示例:<View:InjectorView>
<View:InjectorView.DataContext>
<Binding Source="{x:Static ViewModel:InjectorViewModel.Instance}" />
<View:InjectorView.DataContext>
</View:InjectorView>
您无法从View实例化ViewModel,因为必须为单例实现创建构造函数private
。相反,您将提供对静态实例的绑定(bind)。总的来说,我将只使用一个singleton类来充当存储库。对我来说,实现起来似乎很容易。
关于c# - 在WPF(MVVM)中将数据绑定(bind)到 subview ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38652918/