10

这个让我很困惑,因为我认为我看了所有东西,但我一定错过了一些东西。我已经脱离了 MSDN 杂志上的传统 MVVM 模式:

http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

在学习 MVVM 时。然而,我通常会复制大部分代码,然后根据需要替换它,但今天我想从头开始构建一些东西,发现它可能比我想象的要多。当我使用资源字典但直接使用 datacontext 时,MVVM 似乎不适用于绑定。这个问题最终想找到其他开发人员建议使用他们找到的绑定。

问题的总结是这样的:为什么资源字典中的“DataTemplate”似乎不起作用,但直接的“DataContext”方法可以立即使用视图进行绑定?

是不是因为我在后面的代码中混合了代码和设置视图。或者可能是因为其他原因。如果我直接在视图的 XAML 中设置“DataContext”,那么我的属性及其实现在视图模型中设置正确,但为什么不在资源字典中?我认为这种方法的优点是你可以一次设置一堆关系。我很好奇是否需要进行其他设置才能使其正常工作。在他们使用数据模板的 MVVM 方法的主要示例中很奇怪,但似乎设置它们比我要让它们的“绑定”工作更多。

什么对我不起作用:

我尝试在主窗口 xaml 中做一些非常基本的事情,留下一些代码以使其更简单:

主窗口 XAML:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dxc="http://schemas.devexpress.com/winfx/2008/xaml/charts" x:Class="WPFTesting12_2.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <ResourceDictionary Source="Resources.xaml"/>
    </Window.Resources>
    <Grid>
        <DockPanel x:Name="dockpanel">
            <Menu DockPanel.Dock="Top" Height="30">
                <MenuItem Header="Charting">
                    <MenuItem Header="MVVMDataBound" x:Name="mnuDataBoundSeriesMVVMCharting" Click="mnuDataBoundSeriesMVVMCharting_OnClick"/>
                </MenuItem>
            </Menu>
            <TextBlock Height="5" Background="Black" DockPanel.Dock="Top" />
            <DockPanel x:Name="dockchildren" DockPanel.Dock="Bottom"/>
        </DockPanel>
    </Grid>
</Window>

后面的主窗口代码:

public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();

            this.WindowState = WindowState.Maximized;
        }


        private void mnuDataBoundSeriesMVVMCharting_OnClick(object sender, RoutedEventArgs e)
        {
            View.DataBoundMVVMChart c = new DataBoundMVVMChart();

            dockchildren.Children.Clear();
            dockchildren.Children.Add(c);
        }
    }
}

资源字典:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:vw="clr-namespace:WPFTesting12_2.View"
                    xmlns:vm="clr-namespace:WPFTesting12_2.ViewModel"
                    >
  <DataTemplate DataType="{x:Type vm:DataBoundMVVMChartViewModel}">
    <vw:DataBoundMVVMChart/>
 </DataTemplate>

<Style TargetType="MenuItem">
        <Setter Property="Background" Value="Wheat"/>
    </Style>
    <Style TargetType="Menu">
        <Setter Property="Background" Value="Wheat"/>
    </Style>

</ResourceDictionary>

查看 DataBoundMVVMChart.xaml:

<UserControl x:Class="WPFTesting12_2.View.DataBoundMVVMChart"
             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:WPFTesting12_2.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <ResourceDictionary Source="..\Resources.xaml"/>
    </UserControl.Resources>
    <Grid>
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <MenuItem Header="TesterContent"/>
            </Menu>
            <Label DockPanel.Dock="Bottom" Width="300" x:Name="label" Height="50" Foreground="Blue" FontSize="24"  Content="{Binding Path=HelloString}"/>
        </DockPanel>
    </Grid>
</UserControl>

ViewModel 绑定到上面的视图:

namespace WPFTesting12_2.ViewModel
{
    class DataBoundMVVMChartViewModel : INotifyPropertyChanged
    {
        private string _HelloString;

        public string HelloString
        {
            get { return _HelloString; }
            set 
            {
                _HelloString = value;
                RaisePropertyChanged("HelloString");
            }
        }

        public DataBoundMVVMChartViewModel()
        {
            HelloString = "Hello there from the ViewModel";
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}

好的,现在绑定将在视图中失败,但颜色的资源会进来。所以我想我做错了什么,但后面的代码会立即获得属性。所以让我们继续:

什么工作:

神奇的是,如果我将这四行添加到视图中:

将此添加到顶部“UserControl”部分的声明中:

xmlns:local="clr-namespace:WPFTesting12_2.ViewModel"

然后设置对 UserControl 的 DataContext 的引用:

<UserControl.DataContext>
        <local:DataBoundMVVMChartViewModel/>
    </UserControl.DataContext>
4

2 回答 2

30

在使用 WPF 时,重要的是要意识到有两层:数据层 ( DataContext) 和 UI 层 (XAML)。

绑定用于将数据从数据层拉到视图层。

当你写

<UserControl.DataContext>
    <local:DataBoundMVVMChartViewModel/>
</UserControl.DataContext>

您告诉 WPF 它应该创建一个新实例DataBoundMVVMChartViewModel并将其用于该 UserControl 的数据层。

当你写

<Label Content="{Binding HelloString}" />

您是在告诉 Label 在其数据层 (the DataContext) 中查找名为“HelloString”的属性,并将其用于该Content属性。

如果数据层中不存在属性“HelloString”(除非您设置了DataContext与 所做的一样的设置,否则它不存在<UserControl.DataContext>),则绑定将失败并且除了输出窗口中的绑定错误之外什么都不会显示。

DataTemplate的和你的ResourceDictionary是不同的DataContext

事实上,aResourceDictionary就是它听起来的样子——WPF 可以在需要时在应用程序中使用的资源字典。但是 Dictionary 中的对象默认不是应用程序 UI 本身的一部分。相反,它们需要以某种方式被引用才能使用。

但回到你的DataTemplate

<DataTemplate DataType="{x:Type vm:DataBoundMVVMChartViewModel}">
    <vw:DataBoundMVVMChart/>
</DataTemplate>

WPF 使用 DataTemplates 来了解如何绘制特定类型的对象。在您的情况下,这DataTemplate告诉 WPF 任何时候需要绘制 type 的对象DataBoundMVVMChartViewModel,都应该使用DataBoundMVVMChart.

要将对象插入 UI,Content通常使用属性,例如

MyLabel.Content = new DataBoundMVVMChartViewModel();

或者

<ContentControl Content="{Binding SomeDataboundChartViewModelProperty}" />

实际上,我是从您在问题中链接的完全相同的文章开始学习 MVVM 的,并且在弄清楚它时也遇到了很多麻烦,这导致我写了一些关于 WPF/MVVM 的博客,专门针对像我这样的初学者:)

如果你有兴趣,我有几篇关于 WPF/MVVM 的博客文章可以帮助你更好地理解这项技术。

至于你的实际问题:

在 MVVM 中绑定的首选方法、资源文件中的数据模板还是视图本身中的 DataContext?

我更喜欢DataTemplate.Resources某处使用 a ,因为这样您的 UI 并没有专门与一个特定DataContext的 .

这在使用 MVVM 创建可重用控件时尤其重要。例如,如果您有一个CalculatorUserControl,并且您在控件本身中为其分配了 a DataContext,例如 with ,那么除了在创建控件时创建的那个之外,<UserControl.DataContext>您永远不能使用它。CalculatorUserControlDataContext

所以通常我DataContext在启动时为整个应用程序设置一次,并DataTemplates用来告诉 WPF 如何在我的应用程序中绘制不同ViewModels的内容。Models

我的整个应用程序都存在于数据层中,而 XAML 只是一个与数据层交互的用户友好界面。(如果您想查看代码示例,请查看我的简单 MVVM 示例帖子)

于 2013-05-08T18:24:59.420 回答
2

如果要使用隐式DataTemplates,则必须使用视图模型优先的方法,但是您可以在没有视图模型作为其上下文的情况下向您的视图添加视图。DockPanel如果您只是简单地添加相应的视图模型(到 aContentControlItemsControl),视图将自动从 中创建DataTemplate,视图模型作为它的DataContext.

例如

<ItemsControl Name="ic"/>

ic.Items.Add(new DataBoundMVVMChartViewModel());

我个人更喜欢这个而不是视图优先(例如添加一个视图,然后分配它自己的DataContext),因为您通常需要对视图模型的引用,视图只是次要的,并且通过与视图模型交互来间接操作。

于 2013-05-08T17:55:11.433 回答