ContentPresenter теряет DataContext - PullRequest
2 голосов
/ 26 января 2009

Я хочу создать «FlipPanel», которая обеспечивает два разных вида одного и того же объекта. Вот подход, который я использую.

Это главная страница, которая состоит из ItemsControl, ItemTemplate которого является FlipPanel. FlipPanel предоставляет два свойства, которые определяют шаблон данных, используемый для передней и задней частей.

<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>

Код, лежащий в основе главной страницы, очень прост, поскольку он просто устанавливает DataContext ItemsControl в список тестовых данных.

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 также относительно прост, поскольку он пытается изменить шаблон DataTemplate на основе свойства Side DependencyProperty. Проблема заключается в том, что DataContext ContentPresenter в какой-то момент теряется. В коде у меня есть два комментария, которые указывают на действительность DataContext для ContentPresenter.

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 Ответ

2 голосов
/ 09 ноября 2009

Крис,

Я не знаю, если вы все еще ищете способ сделать это. Я знаю, что есть много руководств по подобным проблемам, но не совсем то, что вы ищете. Исходя из моего понимания того, что вы хотите, вам нужен элемент управления с двумя гранями (действительно x гранями), где пользователь может нажать кнопку и заставить панель «перевернуться» и отобразить разные данные. Однако вы хотите, чтобы эти данные были достаточно общими, чтобы эту панель можно было использовать в других местах с другой реализацией, а не с совершенно другим кодом. Я правильно понимаю ваши потребности? Если нет, пожалуйста, уточните, где я сбился с пути, и, возможно, я смогу получить лучший ответ для вас. Учитывая сказанное, вот что я сделал (демонстрационный проект Google Code внизу):

  1. Я создал библиотеку элементов управления, в которой хранится моя FlipPanel (потому что именно так я и поступаю; чтобы в дальнейшем я мог использовать элементы управления в других проектах.)
  2. Я создал элемент управления в библиотеке элементов управления, чтобы он содержал описанные выше свойства, необходимые для вашего сценария.
  3. Я создал приложение Silverlight 2.0 для создания экземпляра элемента управления.
  4. Я создал базовый объект для привязки, который имеет несколько свойств, чтобы я мог продемонстрировать потенциал элемента управления.

Вот возможное определение для использования на странице 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: Это передняя часть" кнопка нажата, она «перевернута» для отображения «Назад: Это задняя часть».

С учетом всего вышесказанного, вот стиль, который я использовал для управления:

    <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 - Исходный код (Zip)

Спасибо!

...