WPF绑定和资源查找复杂性

时间:2012-04-16 12:35:32

标签: wpf xaml data-binding mvvm

我有一个内容控件如下:

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

MainGridViewModel是MainGridViewModelType类型的属性。

我还有一个DataTemplate,如下所示:

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

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

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

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

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

我提出的唯一解决方法是给DataTemplate ax:Key,并明确设置ContentControl的Content值, not 依赖于匹配类型的框架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);
        }



    }
}

1 个答案:

答案 0 :(得分: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.