1

我有一个内容控件如下:

<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 ax:Key,并显式设置 ContentControl 的 Content 值,而不是依赖将类型与其 DataTemplate 匹配的框架,并为我应用它:

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

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

这是我关于该主题的博客条目的链接。这是原型项目的直接链接

这是 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);
        }



    }
}
4

1 回答 1

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 正在创建一个绑定,我认为必要的元素必须是 ViewModel,一种方式或其他,需要添加为资源。只有这样才能将 ContentControl 上的 DataContext Binding 设置为资源,然后框架将提供 ViewModel 类型到 DataTemplate 可视树元素的预期映射。

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

于 2012-04-18T15:06:53.153 回答