【问题标题】:WPF Binding and Resource Lookup ComplexitiesWPF 绑定和资源查找复杂性
【发布时间】:2012-04-16 12:35:31
【问题描述】:

我有一个内容控件如下:

<ContentControl x:Name="grid1ContentControl" Content="{Binding MainGridViewModel}" />

MainGridViewModel 是 MainGridViewModelType 类型的属性。

我还有一个 DataTemplate 如下:

<DataTemplate DataType="{x:Type App:MainGridViewModelType}">...

我初始化(即设置)MainGridViewModel 属性并引发 NotifyPropertyChanged 事件。

我的期望是,此时应该通知框架我设置了 MainGridViewModel,并且作为 ContentControl 上的绑定的结果,将与 MainGridViewModel 属性类型(即 MainGridViewModelType)匹配的 DataTemplate 内容添加到可视化树在 ContentControl 所在的位置。

确实,我可以看到我的 RaisePropertyChanged() 方法在 MainGridViewModel 属性的设置器上运行。但是,当使用可视化树检查器检查可视化树时,ContentControl 的 ContentPresenter 不会在 MainGridViewModel 初始化后显示我的 DataTemplate 的内容。为什么?

注意,在再次设置 MainGridViewModel 以响应用户交互后,我得到了我预期的可视化树的更新。

我想出的唯一解决方法是给 DataTemplate 一个 x:Key,并显式设置 ContentControl 的 Content 值,依赖于将类型与其 DataTemplate 匹配的框架,并应用给我的:

ContentControl grid1ContentControl = VisualElementFinder.FindDescendantByName(mwin, "grid1ContentControl") as ContentControl;
grid1ContentControl.SetValue(ContentControl.ContentTemplateProperty, mwin.FindResource("MainGridViewModelKey") as DataTemplate);

我一直遇到同样的问题。我对框架在视觉对象初始化后如何对绑定属性的后续分配做出反应的理解存在差距。我采纳了 Will 的建议并开发了一个原型,如下所述。我很感激任何进一步的想法。

这是一个关于这个主题的link to my blog entry。这是direct link to the prototype project

这是为进一步讨论提供上下文的 MainWindow 代码。在原型项目中,NewTabControlViaContentControlCommand 有效,而 NewTabControlCommand 无效。

MainWindow.xaml:

    <Window x:Class="DynamicTabControlSimpleProto.View.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:vm="clr-namespace:DynamicTabControlSimpleProto.ViewModel"
        xmlns:vw="clr-namespace:DynamicTabControlSimpleProto.View"
        xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
        mc:Ignorable="d"
        Height="300"
        Width="500"
        Title="Dynamic TabControl Prototype"
        DataContext="{Binding Main, Source={StaticResource Locator}}">

    <Window.Resources>
        <DataTemplate DataType="{x:Type vm:TabControlViewModel}">
            <vw:TabControlUserControl />
        </DataTemplate>

        <DataTemplate x:Key="tabControlDataTemplate">
            <vw:TabControlUserControl />
        </DataTemplate>

    </Window.Resources>

    <Grid x:Name="LayoutRoot">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="4" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <StackPanel Name="stackPanel" Grid.Column="0">
            <Button
                Content="NewTabControl" 
                Command="{Binding NewTabControlCommand}"
                CommandParameter="{Binding Path=DataContext}" />

            <Button
                Content="NewTabControlViaContentControl" 
                Command="{Binding NewTabControlViaContentControlCommand}"
                CommandParameter="{Binding ElementName=tabControlContentControl}" />
        </StackPanel>

        <DockPanel
            Name="dockPanel"
            Grid.Column="2">
            <Border 
                BorderBrush="Blue"
                BorderThickness="5">
                <ContentControl x:Name="tabControlContentControl" DataContext="{Binding TabControlViewModel, diag:PresentationTraceSources.TraceLevel=High}" />
            </Border>
        </DockPanel>
    </Grid>
</Window>

MainWindowViewModel.cs

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Windows.Controls;
using DynamicTabControlSimpleProto.View;
using System.Windows;

namespace DynamicTabControlSimpleProto.ViewModel
{

    public class MainViewModel : ViewModelBase
    {
        public string Welcome
        {
            get
            {
                return "Welcome to MVVM Light";
            }
        }

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            NewTabControlCommand = new RelayCommand<object>(obj =>
                {
                    this.NewTabControl(obj);
                });

            NewTabControlViaContentControlCommand = 
                new RelayCommand<ContentControl>(tabControlContentControl =>
                {
                    this.NewTabControlViaContentControl(tabControlContentControl);
                });


        }

        public TabControlViewModel TabControlViewModel
        {
            get
            {
                return _tabControlViewModel;
            }
            private set
            {
                _tabControlViewModel = value;
                RaisePropertyChanged("TabControlViewModel");
            }
        }

        public RelayCommand<object> NewTabControlCommand
        {
            get;
            private set;
        }

        private TabControlViewModel _tabControlViewModel = null;

        void NewTabControl(object obj)
        {
            TabControlViewModel = new TabControlViewModel();
        }

        public RelayCommand<ContentControl> NewTabControlViaContentControlCommand
        {
            get;
            set;
        }

        void NewTabControlViaContentControl(ContentControl tabContentControl)
        {
            TabControlViewModel = new TabControlViewModel();
            MainWindow mwin = Application.Current.MainWindow as MainWindow;
            tabContentControl.SetValue(ContentControl.ContentTemplateProperty, mwin.FindResource("tabControlDataTemplate") as DataTemplate);
        }



    }
}

【问题讨论】:

  • 为什么?可能是数百个原因之一。 MainGridViewModel 是在 ContentControl 的 DataContext 上设置的实例的属性吗?在构造函数中调用 InitializeComponent 之前,您是否正在执行所有这些操作?等等等等。您需要创建尽可能简单、可编译的原型来满足您的需求,并查看是否可以重现您的问题。然后在这里使用它(有问题的代码,请不要链接)来说明您的问题。但很可能您会自己解决问题。
  • @Will:是的,MainGridViewModel 是实例的属性,它是 ContentControl 的 DataContext。在调用 InitializeComponent 之后设置 MainGridViewModel。框架第一次没有“应用数据模板”; MainGridViewModel 的后续设置具有预期的效果。我将尝试创建一个简单的复制品;似乎我需要重新触发绑定,或者,也许添加一个新绑定以获取 ContentControl 以将类型链接的 DataTemplate 添加到其 ContentPresenter,就像直接设置 ContentControl.ContentTemplateProperty 一样。
  • 你所说的让我相信它应该有效。已经做过很多次了。我认为您的原型会澄清您的问题。
  • @Will:嗨,Will,我采纳了你的建议并制作了一个原型。它真的很简单,并且说明了不符合我预期的技术以及确实有效的技术。我在我的原始帖子中包含了指向编译的 VS2010 项目的链接,并添加了代码和简短描述,以便为讨论提供上下文。感谢您提供的任何其他见解。谢谢。
  • 酷。我明天去看看。

标签: wpf xaml data-binding mvvm


【解决方案1】:

我相信我在上面不起作用的技术中发现了错误。我通过尝试直接在 XAML 中设置 DataContext 找到它,就像我在代码中所做的那样 - 即直接设置到 ViewModel。当我尝试这个时,它也没有工作。

例如,将我的 MainWindow 的 DataContext 设置为 ViewModel 不起作用:

<Window DataContext="vm:MainViewModel" ...>

但是,将我的 Window 的 DataContext 设置为在更高级别组件的资源中声明的 ViewModel - 在我的例子中,App.xaml 允许一切正常工作:

在 App.xaml 中:

<Application.Resources>
    <vm:MainViewModel x:Key="MainViewModel" />
</Application.Resources>

然后,在 MainWindow.xaml 中:

<Window DataContext="{Binding Source={StaticResource MainViewModel}}" ...>

我认为我在上面示例中的原始代码中所做的类似于将 DataContext 直接设置为 ViewModel,因为即使我的 ContentControl 正在创建一个 Binding,我认为必要的元素必须是 ViewModel ,一种或另一种方式,需要添加为资源。只有这样才能将 ContentControl 上的 DataContext Binding 设置为资源,然后框架将提供 ViewModel 类型到 DataTemplate 可视树元素的预期映射。

以上所有信息都对 ContentControls 很有用。特别是,我最初问题中的代码解决方案可能是实现所描述内容的最佳且唯一的方法。但是,对于 ItemsControl,情况就不同了。冒着这篇文章分歧太大的风险,我将这些额外的见解包含在post on my blog.

【讨论】:

  • DataContext="vm:MainViewModel" 我认为不会编译。如果有的话,它相当于this.DataContext = typeof(MainViewModel);。您在 app.xaml 中创建一个实例,然后通过静态资源绑定到该实例是对的。我实际上经常这样做;我的高级虚拟机是依赖对象,我将它们声明为资源。然后我可以绑定它们并将它们组合在一起,就像 xaml 树中的任何其他对象一样。效果很好,恕我直言。
  • DataContext="vm:MainViewModel" 确实可以编译。其他变体,例如尝试绑定到它,则不会。当您“绑定和组合”您的高级 VM 时,您是否能够创建多个实例,或者,您是否坚持资源中的每个声明一个实例?你有一个方便的代码示例吗?我现在正在研究根据需要在运行时从资源创建新实例。这个需求的丑陋之处在于,为了维护 MVVM 分离,我现在需要一个 ViewService 来在运行时操作 View 层中的资源,而不仅仅是操作 ViewModel。
  • 您可以根据需要创建任意数量的实例。但是,我通常为每个 VM 创建一个实例。您还可以将 VS 实例化为资源,然后将其绑定到您的虚拟机。有一个非常小的例子,我做了类似的事情,但在这个答案中使用了 ConsoleService](stackoverflow.com/questions/6695599/…)。请记住,xaml 被反序列化为它所代表的对象图。所以它就像任何其他代码一样......
猜你喜欢
  • 2023-04-01
  • 2010-10-16
  • 2013-04-12
  • 2014-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多