简体   繁体   中英

WPF Binding and Resource Lookup Complexities

I have a content control as follows:

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

MainGridViewModel is a property of type MainGridViewModelType.

I also have a DataTemplate as follows:

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

I initialize (ie, set) the MainGridViewModel property and raise the NotifyPropertyChanged event.

It is my expectation that at this moment, the framework should be notified that I set MainGridViewModel and, as a result of the Binding on ContentControl, add the DataTemplate contents matching the MainGridViewModel property type (ie, MainGridViewModelType) to the visual tree at the location where the ContentControl is located.

Indeed, I can see my RaisePropertyChanged() method run on the MainGridViewModel property's setter. However, when inspecting the visual tree using the visual tree inspector, the ContentControl's ContentPresenter does not show the contents of my DataTemplate following the initialization of MainGridViewModel. Why?

Note, after setting MainGridViewModel again in response to a user interaction, I get the update to the visual tree that I expected.

The only workaround I have come up with is to give the DataTemplate ax:Key, and set the ContentControl's Content value explicitly, and not rely on the framework matching the type to its DataTemplate, and applying it for me:

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

I keep running into the same problem. There is a gap in my understanding of how the framework is expected to react to the subsequent assignment of a bound property after the visual has been initialized. I have taken Will's suggestion and developed a prototype, as described below. I appreciate any further thoughts.

Here is a link to my blog entry on the subject. Here is a direct link to the prototype project .

Here is the MainWindow code to provide context for further discussion. In the prototype project, NewTabControlViaContentControlCommand works, while NewTabControlCommand does not.

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



    }
}

I believe that I found the error in the technique that was not working above. I found it by trying to set the DataContext directly in XAML the same way I was doing in code - namely, directly to a ViewModel. When I tried this, it too didn't work.

For example, setting the DataContext of my MainWindow to a ViewModel does not work:

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

However, setting the DataContext of my Window to a ViewModel declared in the resources of a higher level component - in my case, App.xaml, allows everything to work fine:

In App.xaml:

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

Then, in MainWindow.xaml:

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

I think what I was doing in my original code in the example above is similar to setting the the DataContext directly to the ViewModel, because, even though my ContentControl was creating a Binding, I think the necessary element must be that the ViewModel, one way or another, needs to be added as a Resource. Only then can the DataContext Binding on the ContentControl be set to the resource and the framework will then provide the expected mapping of the ViewModel type to the DataTemplate visual tree elements.

All of the above information is useful for ContentControls. In particular, the code solution in my original question may be the best and only way to achieve what is described. However, the story is different for an ItemsControl. At the risk of this post diverging too far, I included these additional insights in a post on my blog.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM