ContentPresenter丢失了DataContext

时间:2009-01-26 17:07:07

标签: silverlight silverlight-2.0

我想创建一个“FlipPanel”,它提供同一对象的两个不同视图。这是我正在采取的方法。

这是主页面,它由ItemsControl组成,其ItemTemplate是一个FlipPanel。 FlipPanel公开了两个属性,这两个属性定义了用于Front和Back的DataTemplate。

<UserControl.Resources>

    <ControlTemplate x:Key="MyFlipTemplate">
        <StackPanel>
            <Button Content="Flip" x:Name="PART_FlipButton"/>
            <ContentPresenter Content="{TemplateBinding Content}" x:Name="PART_FlipContent"/>
        </StackPanel>
    </ControlTemplate>

    <DataTemplate x:Key="Front">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Front"/>
            <TextBlock Text="{Binding Name}"/>
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="Back">
        <StackPanel>
            <TextBlock Text="Back"/>
            <TextBlock Text="{Binding Description}"/>
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>

<StackPanel>
    <ItemsControl x:Name="_items">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel></StackPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <SLTest:FlipPanel Template="{StaticResource MyFlipTemplate}" FrontDataTemplate="{StaticResource Front}" BackDataTemplate="{StaticResource Back}" Side="Front"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</StackPanel>

主页面的Code Behind非常简单,因为它只是将ItemsControl的DataContext设置为测试数据列表。

using System.Collections.Generic;
using System.Windows.Controls;

namespace SLTest
{
    public partial class NewPage : UserControl
    {
        public NewPage()
        {
            InitializeComponent();

            _items.ItemsSource = Items;
        }

        public IList Items 
        {
            get
            {
                return new List
                           {
                               new NewClass { Name = "Name 1", Description = "Description 1"},
                               new NewClass { Name = "Name 2", Description = "Description 2"},
                               new NewClass { Name = "Name 3", Description = "Description 3"},
                               new NewClass { Name = "Name 4", Description = "Description 4"}
                           };
            }
        }
    }

    public class NewClass
    {
        public string Name;
        public string Description;
    }
}

FlipPanel代码也相对简单,因为它试图根据Side DependencyProperty更改DataTemplate。问题似乎是ContentPresenter的DataContext在某些时候丢失了。在代码中,我有两条注释,表明ContentPresenter的DataContext的有效性。

using System;
using System.Windows;
using System.Windows.Controls;

namespace SLTest
{
    [TemplatePart(Name = FlipPanel.ButtonPart, Type = typeof(Button))]
    [TemplatePart(Name = FlipPanel.ContentPart, Type = typeof(ContentPresenter))]
    public partial class FlipPanel : ContentControl
    {
        private const string ButtonPart = "PART_FlipButton";
        private const string ContentPart = "PART_FlipContent";

        public enum FlipSide
        {
            Front,
            Back
        }

        private FlipSide _flipSide;

        public static readonly DependencyProperty SideProperty = DependencyProperty.RegisterAttached("FlipSide", typeof(FlipSide), typeof(FlipPanel), new PropertyMetadata(FlipSide.Front, FlipSidePropertyChanged));
        public static readonly DependencyProperty FrontDataTemplateProperty = DependencyProperty.Register("FrontDataTemplate", typeof (DataTemplate), typeof (FlipPanel), null);
        public static readonly DependencyProperty BackDataTemplateProperty = DependencyProperty.Register("BackDataTemplate", typeof(DataTemplate), typeof(FlipPanel), null);

        private Button _flipButton;
        private ContentPresenter _content;

        public FlipPanel()
        {
            InitializeComponent();
        }

        public DataTemplate FrontDataTemplate
        {
            get
            {
                return (DataTemplate) GetValue(FrontDataTemplateProperty);
            }
            set
            {
                SetValue(FrontDataTemplateProperty, value);
            }
        }

        public DataTemplate BackDataTemplate
        {
            get
            {
                return (DataTemplate)GetValue(BackDataTemplateProperty);
            }
            set
            {
                SetValue(BackDataTemplateProperty, value);
            }
        }

        private static void FlipSidePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var flipSide = (FlipSide)e.NewValue;
            var flipPanel = d as FlipPanel;

            flipPanel._content.ContentTemplate = flipSide == FlipSide.Front ? flipPanel.FrontDataTemplate : flipPanel.BackDataTemplate;
        }

        public override void OnApplyTemplate()
        {
            _flipButton = GetTemplateChild(ButtonPart) as Button;
            _flipButton.Click += OnFlipClicked;

            _content = GetTemplateChild(ContentPart) as ContentPresenter;

            // _content.DataContext is valid right here...

            _content.ContentTemplate = Side == FlipSide.Front ? FrontDataTemplate : BackDataTemplate;

            base.OnApplyTemplate();
        }

        private void OnFlipClicked(object sender, RoutedEventArgs e)
        {
            // _content.DataContext is now NULL!!!!

            Side = (Side == FlipSide.Front) ? FlipSide.Back : FlipSide.Front;
        }

        public FlipSide Side
        {
            get
            {
                return (FlipSide) GetValue(SideProperty);
            }
            set
            {
                SetValue(SideProperty, value);
            }
        }
    }
}

有什么想法吗?

我不确定这是否是解决我的要求的正确方法,如果有更好的方法我会欢迎任何进一步的建议。

由于

1 个答案:

答案 0 :(得分:2)

克里斯,

我不知道你是否还在寻找一种方法来做到这一点。我知道有很多类似问题的指南,但不完全是你想要的。从我对你想要的东西的理解,你想要一个具有两个面(真正的x面)的控件,用户可以按下按钮并使面板“翻转”并显示不同的数据。但是,您希望显示的数据足够通用,以便此翻盖面板可以在其他位置使用,只是具有不同的实现,而不是完全不同的代码。我能正确理解您的需求吗?如果没有,请澄清我误入歧途的地方,我可以为你找到更好的答案。话虽如此,这就是我所做的(谷歌代码演示项目在底部):

  1. 我创建了一个包含我的FlipPanel的控件库(因为这就是我的工作方式;所以我可以在其他项目中使用控件。)
  2. 我在控件库中设置控件的样式,以包含场景中所需的上述属性。
  3. 我创建了一个Silverlight 2.0应用程序来创建控件的实例。
  4. 我为绑定创建了一个具有一些属性的基本对象,以便我可以展示控件的潜力。
  5. 以下是在Silverlight 2.0页面中使用的可能定义:

        <Grid x:Name="LayoutRoot" Background="White">
      <controls:FlipPanel x:Name="TestingFlipPanel" Side="Front" >
       <controls:FlipPanel.BackDataTemplate>
        <DataTemplate>
         <StackPanel Orientation="Horizontal">
          <TextBlock Text="Back: "/>
          <TextBlock Text="{Binding BackText}" />
         </StackPanel>
        </DataTemplate>
       </controls:FlipPanel.BackDataTemplate>
       <controls:FlipPanel.FrontDataTemplate>
        <DataTemplate>
         <StackPanel Orientation="Horizontal">
          <TextBlock Text="Front: "/>
          <TextBlock Text="{Binding FrontText}" />
         </StackPanel>
        </DataTemplate>
       </controls:FlipPanel.FrontDataTemplate>
      </controls:FlipPanel>
        </Grid>
    

    替代方法是在用户控件(页面级别,甚至应用级别)中定义数据模板,如下所示:

                                                               

    这样你就知道我的绑定对象在这里是什么样的定义(是的,它是VB ...抱歉!):

    Public Class BindingObject
     Private _FrontText As String
     Private _BackText As String
    
     Public Sub New(ByVal frontText As String, ByVal backText As String)
      MyBase.New()
      _FrontText = frontText
      _BackText = backText
     End Sub
    
     Public Property FrontText() As String
      Get
       Return _FrontText
      End Get
      Set(ByVal value As String)
       _FrontText = value
      End Set
     End Property
     Public Property BackText() As String
      Get
       Return _BackText
      End Get
      Set(ByVal value As String)
       _BackText = value
      End Set
     End Property
    
    End Class
    

    在我的代码背后,这里是我的页面的定义和翻转面板的数据上下文设置:

    Partial Public Class Page
     Inherits UserControl
     Dim _BindingObject As New BindingObject("This is the front", "This is the back")
     Public Sub New()
      InitializeComponent()
     End Sub
    
     Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
      TestingFlipPanel.DataContext = _BindingObject
     End Sub
    End Class
    

    所以这一切都摆在你面前,我们希望控件会显示一个按钮(在控件样式中)和一个文本块(实际上是两个),上面写着“Front:This is the front”,当按下按钮它“翻转”显示“后退:这是后面”。

    尽管如此,这是我用于控件的风格:

        <Style TargetType="controls:FlipPanel">
      <Setter Property="Template">
       <Setter.Value>
        <ControlTemplate TargetType="controls:FlipPanel">
         <Grid x:Name="LayoutRoot">
          <Grid>
           <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
           </Grid.RowDefinitions>
           <Button x:Name="FlipButton" Content="Flip" Grid.Row="0" />
           <ContentPresenter Grid.Row="1" x:Name="FrontContentPresenter" Content="{TemplateBinding DataContext}" ContentTemplate="{TemplateBinding FrontDataTemplate}" />
           <ContentPresenter Grid.Row="1" x:Name="BackContentPresenter"  Content="{TemplateBinding DataContext}" ContentTemplate="{TemplateBinding BackDataTemplate}" />
          </Grid>
    
         </Grid>
        </ControlTemplate>
       </Setter.Value>
      </Setter>
     </Style>
    

    最后,控件的定义(注意 - 很长):

    <TemplatePart(Name:=FlipPanel.LayoutRoot_ElementName, Type:=GetType(FrameworkElement))> _
    <TemplatePart(Name:=FlipPanel.FrontContentPresenter_ElementName, Type:=GetType(FrameworkElement))> _
    <TemplatePart(Name:=FlipPanel.BackContentPresenter_ElementName, Type:=GetType(FrameworkElement))> _
    <TemplatePart(Name:=FlipPanel.FlipButton_ElementName, Type:=GetType(FrameworkElement))> _
    Public Class FlipPanel
     Inherits Control
    
     Public Const LayoutRoot_ElementName As String = "LayoutRoot"
     Public Const FlipButton_ElementName As String = "FlipButton"
     Public Const FrontContentPresenter_ElementName As String = "FrontContentPresenter"
     Public Const BackContentPresenter_ElementName As String = "BackContentPresenter"
    
     Public Enum Sides
      Front
      Back
     End Enum
    
     Private _LayoutRoot As FrameworkElement = Nothing
     Private _FlipButton As FrameworkElement = Nothing
     Private _FrontContentPresenter As FrameworkElement = Nothing
     Private _BackContentPresenter As FrameworkElement = Nothing
    
     Private _ControlUpdating As Boolean = False
    
     Public Sub New()
      MyBase.New()
      MyBase.DefaultStyleKey = GetType(FlipPanel)
     End Sub
    
     Public Overrides Sub OnApplyTemplate()
      MyBase.OnApplyTemplate()
      UpdateControl()
     End Sub
    
     Private Sub UpdateControl()
      If _ControlUpdating Then Exit Sub
      _ControlUpdating = True
    
      If _LayoutRoot Is Nothing Then _LayoutRoot = TryCast(GetTemplateChild(LayoutRoot_ElementName), FrameworkElement)
      If _LayoutRoot IsNot Nothing Then
       Dim element As Grid = TryCast(_LayoutRoot, Grid)
       If element IsNot Nothing Then
        ' Update LayoutGrid here.  
       End If
      End If
      If _FlipButton Is Nothing Then _FlipButton = TryCast(GetTemplateChild(FlipButton_ElementName), FrameworkElement)
      If _FlipButton IsNot Nothing Then
       Dim element As Button = TryCast(_FlipButton, Button)
       If element IsNot Nothing Then
        ' Update Button
        RemoveHandler element.Click, AddressOf _FlipButton_Click
        AddHandler element.Click, AddressOf _FlipButton_Click
       End If
      End If
      If _FrontContentPresenter Is Nothing Then _FrontContentPresenter = TryCast(GetTemplateChild(FrontContentPresenter_ElementName), FrameworkElement)
      If _FrontContentPresenter IsNot Nothing Then
       Dim element As ContentPresenter = TryCast(_FrontContentPresenter, ContentPresenter)
       If element IsNot Nothing Then
        ' Update FrontContentPresenter here.
        If Side = Sides.Front Then
         element.Visibility = Windows.Visibility.Visible
        Else
         element.Visibility = Windows.Visibility.Collapsed
        End If
       End If
      End If
      If _BackContentPresenter Is Nothing Then _BackContentPresenter = TryCast(GetTemplateChild(BackContentPresenter_ElementName), FrameworkElement)
      If _BackContentPresenter IsNot Nothing Then
       Dim element As ContentPresenter = TryCast(_BackContentPresenter, ContentPresenter)
       If element IsNot Nothing Then
        ' Update BackContentPresenter here.  
        If Side = Sides.Front Then
         element.Visibility = Windows.Visibility.Collapsed
        Else
         element.Visibility = Windows.Visibility.Visible
        End If
       End If
      End If
    
    
      _ControlUpdating = False
     End Sub
    
     Private Sub _FlipButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
      Select Case Side
       Case Sides.Front
        Side = Sides.Back
       Case Sides.Back
        Side = Sides.Front
       Case Else
        Throw New ArgumentOutOfRangeException("Side")
      End Select
      UpdateControl()
     End Sub
    
     Private Sub FlipPanel_LayoutUpdated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.LayoutUpdated
      UpdateControl()
     End Sub
    
    #Region " FrontDataTemplateProperty Dependency Property "
    
    #Region " FrontDataTemplate Property "
    
     Public Property FrontDataTemplate() As DataTemplate
      Get
       Return DirectCast(GetValue(FrontDataTemplateProperty), DataTemplate)
      End Get
      Set(ByVal value As DataTemplate)
       SetValue(FrontDataTemplateProperty, value)
      End Set
     End Property
    
    #End Region
    
    #Region " FrontDataTemplate Dependency Property "
    
     Public Shared ReadOnly FrontDataTemplateProperty As DependencyProperty = DependencyProperty.Register("FrontDataTemplate", GetType(DataTemplate), GetType(FlipPanel), New PropertyMetadata(Nothing, AddressOf OnFrontDataTemplatePropertyChanged))
    
    #End Region
    
    #Region " FrontDataTemplate Property Changed CallBack "
    
     Private Shared Sub OnFrontDataTemplatePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      If e.OldValue Is Nothing AndAlso e.NewValue Is Nothing Then Exit Sub
      If e.OldValue IsNot Nothing AndAlso e.OldValue.Equals(e.NewValue) Then Exit Sub
    
      Dim source As FlipPanel = TryCast(d, FlipPanel)
      If source Is Nothing Then Throw New ArgumentException("source is not an instance of FlipPanel!")
    
      ' Provide any other validation here.
    
      ' Apply any other changes here.
    
     End Sub
    
    #End Region
    
    #End Region
    
    #Region " BackDataTemplateProperty Dependency Property "
    
    #Region " BackDataTemplate Property "
    
     Public Property BackDataTemplate() As DataTemplate
      Get
       Return DirectCast(GetValue(BackDataTemplateProperty), DataTemplate)
      End Get
      Set(ByVal value As DataTemplate)
       SetValue(BackDataTemplateProperty, value)
      End Set
     End Property
    
    #End Region
    
    #Region " BackDataTemplate Dependency Property "
    
     Public Shared ReadOnly BackDataTemplateProperty As DependencyProperty = DependencyProperty.Register("BackDataTemplate", GetType(DataTemplate), GetType(FlipPanel), New PropertyMetadata(Nothing, AddressOf OnBackDataTemplatePropertyChanged))
    
    #End Region
    
    #Region " BackDataTemplate Property Changed CallBack "
    
     Private Shared Sub OnBackDataTemplatePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      If e.OldValue Is Nothing AndAlso e.NewValue Is Nothing Then Exit Sub
      If e.OldValue IsNot Nothing AndAlso e.OldValue.Equals(e.NewValue) Then Exit Sub
    
      Dim source As FlipPanel = TryCast(d, FlipPanel)
      If source Is Nothing Then Throw New ArgumentException("source is not an instance of FlipPanel!")
    
      ' Provide any other validation here.
    
      ' Apply any other changes here.
    
     End Sub
    
    #End Region
    
    #End Region
    
    #Region " SideProperty Dependency Property "
    
    #Region " Side Property "
    
     Public Property Side() As Sides
      Get
       Return DirectCast(GetValue(SideProperty), Sides)
      End Get
      Set(ByVal value As Sides)
       SetValue(SideProperty, value)
      End Set
     End Property
    
    #End Region
    
    #Region " Side Dependency Property "
    
     Public Shared ReadOnly SideProperty As DependencyProperty = DependencyProperty.Register("Side", GetType(Sides), GetType(FlipPanel), New PropertyMetadata(Sides.Front, AddressOf OnSidePropertyChanged))
    
    #End Region
    
    #Region " Side Property Changed CallBack "
    
     Private Shared Sub OnSidePropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
      If e.OldValue Is Nothing AndAlso e.NewValue Is Nothing Then Exit Sub
      If e.OldValue IsNot Nothing AndAlso e.OldValue.Equals(e.NewValue) Then Exit Sub
    
      Dim source As FlipPanel = TryCast(d, FlipPanel)
      If source Is Nothing Then Throw New ArgumentException("source is not an instance of FlipPanel!")
    
      ' Provide any other validation here.
    
      ' Apply any other changes here.
    
     End Sub
    
    #End Region
    
    #End Region
    
    End Class
    

    现在,你一直在等待,代码!

    享受!

    Google Code - http://code.google.com/p/stackoverflow-answers-by-scott/

    Google Code - Source Code (Zip)

    谢谢!