2009-06-22 6 views
25

Я разрабатываю приложение LOB, где мне понадобится несколько диалоговых окон (и отображение всего в одном окне не является опцией/не делает смысл).WPF: шаблон или UserControl с 2 (или более!) ContentPresenters для представления содержимого в слотах

Я хотел бы иметь пользовательский элемент управления для моего окна, который будет определять некоторый стиль и т.д., и буду иметь несколько слотов, где содержание может быть вставленными - например, шаблон модального диалогового окна будет иметь слот для контента и кнопок (чтобы пользователь мог затем предоставить контент и набор кнопок со связанными ICommands).

Я хотел бы иметь что-то подобное (но это не работает):

UserControl XAML:

<UserControl x:Class="TkMVVMContainersSample.Services.Common.GUI.DialogControl" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" 
    > 
    <DockPanel> 
     <DockPanel 
      LastChildFill="False" 
      HorizontalAlignment="Stretch" 
      DockPanel.Dock="Bottom"> 
      <ContentPresenter ContentSource="{Binding Buttons}"/> 
     </DockPanel> 
     <Border 
      Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" 
      Padding="8" 
      > 
      <ContentPresenter ContentSource="{Binding Controls}"/> 
     </Border> 
    </DockPanel> 
</UserControl> 

ли что-то подобное возможно? Как мне сказать VS, что мой элемент управления предоставляет два заполнителя содержимого, чтобы я мог использовать его так?

<Window ... DataContext="MyViewModel"> 

    <gui:DialogControl> 
     <gui:DialogControl.Controls> 
      <!-- My dialog content - grid with textboxes etc... 
      inherits the Window's DC - DialogControl just passes it through --> 
     </gui:DialogControl.Controls> 
     <gui:DialogControl.Buttons> 
      <!-- My dialog's buttons with wiring, like 
      <Button Command="{Binding HelpCommand}">Help</Button> 
      <Button Command="{Binding CancelCommand}">Cancel</Button> 
      <Button Command="{Binding OKCommand}">OK</Button> 
      - they inherit DC from the Window, so the OKCommand binds to MyViewModel.OKCommand 
      --> 
     </gui:DialogControl.Buttons> 
    </gui:DialogControl> 

</Window> 

Или, может быть, я мог бы использовать ControlTemplate для окна like here, но потом снова: Окно имеет только один слот контента, поэтому его шаблон будет иметь возможность иметь только один ведущий, но мне нужно два (и если в в этом случае, возможно, возможно, что с ним можно пойти, есть другие варианты использования, в которых будут присутствовать несколько слотов контента, просто подумайте о шаблоне для пользователя, контролирующего статьи, который предоставит заголовок, (структурированный) контент, имя автора, изображение. ..).

Спасибо!

PS: Если бы я хотел просто иметь кнопки рядом, как я могу добавить несколько элементов управления (кнопок) в StackPanel? ListBox имеет ItemsSource, но StackPanel не имеет, и это дети свойство только для чтения - так это не работает (внутри UserControl):

<StackPanel 
    Orientation="Horizontal" 
    Children="{Binding Buttons}"/> 

EDIT: Я не хочу использовать привязку, как я захотите присвоить DataContext (ViewModel) целое окно (которое равно View), а затем привязать его к командам из кнопок, вставленных в «слоты» управления, - поэтому любое использование привязки в иерархии нарушит наследование DC View.

Что касается идеи наследования от HeaderedContentControl - да, в этом случае это сработает, но что, если я хочу три заменяемые части? Как я могу создать свой собственный «HeaderedAndFooteredContentControl» (или, как я могу реализовать HeaderedContentControl, если у меня его нет)?

EDIT2: КИ, так что мои два решения doen't работы - вот почему: ContentPresenter получает это содержимое из DataContext, но мне нужна привязки на содержатся элементы для ссылки на оригинальные окна (родитель UserControl в в логическом дереве) DataContext - потому что таким образом, когда я вставляю текстовое поле, связанное с свойством ViewModel, оно не связано, так как цепочка наследования была нарушена внутри элемента управления!

Кажется, что мне нужно сохранить DataContext родителя и восстановить его для дочерних элементов всех контейнеров управления, но я не получаю никакого события, которое DataContext в логическом дереве изменилось.

EDIT3: У меня есть решение!, удалил мои предыдущие аверы. См. Мой ответ.

ответ

29

OK, мое решение было совершенно не нужно, вот только уроки вы когда-нибудь понадобится для создания какого-либо контроля пользователя:

Вкратце:

Подкласс представляет собой подходящий класс (или UIElement, если он вам не подходит) - файл просто * .cs, поскольку мы определяем поведение, а не внешний вид элемента управления.

public class EnhancedItemsControl : ItemsControl 

Добавить свойство зависимостей для «слотов» (нормальное свойство не достаточно хорошо, поскольку он имеет лишь ограниченную поддержку для связывания).Холодный прием: в VS, написать propdp и нажмите вкладку, чтобы развернуть фрагмент :):

public object AlternativeContent 
{ 
    get { return (object)GetValue(AlternativeContentProperty); } 
    set { SetValue(AlternativeContentProperty, value); } 
} 

// Using a DependencyProperty as the backing store for AlternativeContent. This enables animation, styling, binding, etc... 
public static readonly DependencyProperty AlternativeContentProperty = 
    DependencyProperty.Register("AlternativeContent" /*name of property*/, typeof(object) /*type of property*/, typeof(EnhancedItemsControl) /*type of 'owner' - our control's class*/, new UIPropertyMetadata(null) /*default value for property*/); 

Добавить атрибут для дизайнера (потому что вы создаете так называемый контроль lookless), таким образом, мы говорим, что нам нужно иметь ContentPresenter под названием PART_AlternativeContentPresenter в нашем шаблоне

[TemplatePart(Name = "PART_AlternativeContentPresenter", Type = typeof(ContentPresenter))] 
public class EnhancedItemsControl : ItemsControl 

Обеспечить статический конструктор, который сообщит системе моделирования WPF о нашем классе (без него, стилей/шаблонов, которые нацелены на наш новый тип не будет применяться):

static EnhancedItemsControl() 
{ 
    DefaultStyleKeyProperty.OverrideMetadata(
     typeof(EnhancedItemsControl), 
     new FrameworkPropertyMetadata(typeof(EnhancedItemsControl))); 
} 

Если вы хотите сделать что-то с ContentPresenter из шаблона, вы делаете это переопределение OnApplyTemplate метод:

//remember that this may be called multiple times if user switches themes/templates! 
public override void OnApplyTemplate() 
{ 
    base.OnApplyTemplate(); //always do this 

    //Obtain the content presenter: 
    contentPresenter = base.GetTemplateChild("PART_AlternativeContentPresenter") as ContentPresenter; 
    if (contentPresenter != null) 
    { 
     // now we know that we are lucky - designer didn't forget to put a ContentPresenter called PART_AlternativeContentPresenter into the template 
     // do stuff here... 
    } 
} 

Обеспечить шаблон по умолчанию: всегда в ProjectFolder/Темы/Generic.xaml (у меня есть мой автономный проект со всеми настраиваемыми универсальными средствами управления wpf, которые затем ссылаются на другие решения). Это только место, где система будет искать шаблоны для ваших элементов управления, поэтому поместите шаблоны по умолчанию для всех элементов управления в проекте здесь: В этом фрагменте я определил новый ContentPresenter, который отображает значение нашего AlternativeContent прикрепленного свойства. Обратите внимание на синтаксис - я мог бы использовать либо Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}", либо Content="{TemplateBinding AlternativeContent}", но первый будет работать, если вы определяете шаблон внутри вашего шаблона (необходимый для стилизации, например, ItemPresenters).

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:WPFControls="clr-namespace:MyApp.WPFControls" 
    > 

    <!--EnhancedItemsControl--> 
    <Style TargetType="{x:Type WPFControls:EnhancedItemsControl}"> 
     <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate TargetType="{x:Type WPFControls:EnhancedItemsControl}"> 
        <ContentPresenter 
         Name="PART_AlternativeContentPresenter" 
         Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" 
         DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" 
         /> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 

</ResourceDictionary> 

Вуаля, вы только что сделали свой первый lookless UserControl (добавить contentpresenters и свойства зависимостей для более «слотов контента»).

+4

Слава богу, кто-то объяснил это простым и понятным способом! Трудно найти ресурсы об этом, или это просто ошибка wpf-beginners: p – Shion

2

Если вы используете UserControl

Я предполагаю, что вы на самом деле хотите:

<ContentPresenter Content="{Binding Buttons}"/> 

Это предполагает, что DataContext передается элемент управления имеет свойство кнопок.

И с ControlTemplate

Другим вариантом был бы ControlTemplate, а затем вы можете использовать:

<ContentPresenter ContentSource="Header"/> 

Вы должны были бы быть шаблонированием элемента управления, который на самом деле имеет «заголовок» для сделайте это (обычно это HeaderedContentControl).

+0

Спасибо - я не хочу использовать привязку, поскольку я хочу назначить DataContext (ViewModel) для целого окна (которое равно View), а затем привязать его к командам из кнопок, вставленных в «слоты» управления, - так любое использование привязки в иерархии нарушит наследование DC View. Что касается другого варианта - да, в этом случае работа с HeaderedContentControl будет работать, но что, если я хочу три части? Как создать собственный «HeaderedAndFooteredContentControl» (или как я могу реализовать HeaderedContentControl, если у меня его нет)? –

4

Hasta la victoria siempre!

Я пришел с рабочим раствором (первым в Интернете, мне кажется :))

хитрого DialogControl.xaml.cs - см комментариев:

public partial class DialogControl : UserControl 
{ 
    public DialogControl() 
    { 
     InitializeComponent(); 

     //The Logical tree detour: 
     // - we want grandchildren to inherit DC from this (grandchildren.DC = this.DC), 
     // but the children should have different DC (children.DC = this), 
     // so that children can bind on this.Properties, but grandchildren bind on this.DataContext 
     this.InnerWrapper.DataContext = this; 
     this.DataContextChanged += DialogControl_DataContextChanged; 
     // need to reinitialize, because otherwise we will get static collection with all buttons from all calls 
     this.Buttons = new ObservableCollection<FrameworkElement>(); 
    } 


    void DialogControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     /* //Heading is ours, we want it to inherit this, so no detour 
     if ((this.GetValue(HeadingProperty)) != null) 
      this.HeadingContainer.DataContext = e.NewValue; 
     */ 

     //pass it on to children of containers: detours 
     if ((this.GetValue(ControlProperty)) != null) 
      ((FrameworkElement)this.GetValue(ControlProperty)).DataContext = e.NewValue; 

     if ((this.GetValue(ButtonProperty)) != null) 
     { 
      foreach (var control in ((ObservableCollection<FrameworkElement>) this.GetValue(ButtonProperty))) 
      { 
       control.DataContext = e.NewValue; 
      } 
     } 
    } 

    public FrameworkElement Control 
    { 
     get { return (FrameworkElement)this.GetValue(ControlProperty); } 
     set { this.SetValue(ControlProperty, value); } 
    } 

    public ObservableCollection<FrameworkElement> Buttons 
    { 
     get { return (ObservableCollection<FrameworkElement>)this.GetValue(ButtonProperty); } 
     set { this.SetValue(ButtonProperty, value); } 
    } 

    public string Heading 
    { 
     get { return (string)this.GetValue(HeadingProperty); } 
     set { this.SetValue(HeadingProperty, value); } 
    } 

    public static readonly DependencyProperty ControlProperty = 
      DependencyProperty.Register("Control", typeof(FrameworkElement), typeof(DialogControl)); 
    public static readonly DependencyProperty ButtonProperty = 
      DependencyProperty.Register(
       "Buttons", 
       typeof(ObservableCollection<FrameworkElement>), 
       typeof(DialogControl), 
       //we need to initialize this for the designer to work correctly! 
       new PropertyMetadata(new ObservableCollection<FrameworkElement>())); 
    public static readonly DependencyProperty HeadingProperty = 
      DependencyProperty.Register("Heading", typeof(string), typeof(DialogControl)); 
} 

И в DialogControl. не XAML (без изменений): использование

<UserControl x:Class="TkMVVMContainersSample.Views.Common.DialogControl" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" 
    > 
    <DockPanel x:Name="InnerWrapper"> 
     <DockPanel 
      LastChildFill="False" 
      HorizontalAlignment="Stretch" 
      DockPanel.Dock="Bottom"> 
      <ItemsControl 
       x:Name="ButtonsContainer" 
       ItemsSource="{Binding Buttons}" 
       DockPanel.Dock="Right" 
       > 
       <ItemsControl.ItemTemplate> 
        <DataTemplate> 
         <Border Padding="8"> 
          <ContentPresenter Content="{TemplateBinding Content}" /> 
         </Border> 
        </DataTemplate> 
       </ItemsControl.ItemTemplate> 
       <ItemsControl.ItemsPanel> 
        <ItemsPanelTemplate> 
         <StackPanel Orientation="Horizontal" Margin="8"> 
         </StackPanel> 
        </ItemsPanelTemplate> 
       </ItemsControl.ItemsPanel> 
      </ItemsControl> 
     </DockPanel> 
     <Border 
      Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" 
      Padding="8,0,8,8" 
      > 
      <StackPanel> 
       <Label 
        x:Name="HeadingContainer" 
        Content="{Binding Heading}" 
        FontSize="20" 
        Margin="0,0,0,8" /> 
       <ContentPresenter 
        x:Name="ControlContainer" 
        Content="{Binding Control}"     
        /> 
      </StackPanel> 
     </Border> 
    </DockPanel> 
</UserControl> 

Пример:

<Window x:Class="TkMVVMContainersSample.Services.TaskEditDialog.ItemEditView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Common="clr-namespace:TkMVVMContainersSample.Views.Common" 
    Title="ItemEditView" 
    > 
    <Common:DialogControl> 
     <Common:DialogControl.Heading> 
      Edit item 
     </Common:DialogControl.Heading> 
     <Common:DialogControl.Control> 
      <!-- Concrete dialog's content goes here --> 
      <Grid> 
       <Grid.RowDefinitions> 
        <RowDefinition Height="Auto" /> 
        <RowDefinition Height="Auto" /> 
       </Grid.RowDefinitions> 
       <Grid.ColumnDefinitions> 
        <ColumnDefinition Width="Auto" /> 
        <ColumnDefinition Width="*" /> 
       </Grid.ColumnDefinitions> 

       <Label Grid.Row="0" Grid.Column="0">Name</Label> 
       <TextBox Grid.Row="0" Grid.Column="1" MinWidth="160" TabIndex="1" Text="{Binding Name}"></TextBox> 
       <Label Grid.Row="1" Grid.Column="0">Phone</Label> 
       <TextBox Grid.Row="1" Grid.Column="1" MinWidth="160" TabIndex="2" Text="{Binding Phone}"></TextBox> 
      </Grid> 
     </Common:DialogControl.Control> 
     <Common:DialogControl.Buttons> 
      <!-- Concrete dialog's buttons go here --> 
      <Button Width="80" TabIndex="100" IsDefault="True" Command="{Binding OKCommand}">OK</Button> 
      <Button Width="80" TabIndex="101" IsCancel="True" Command="{Binding CancelCommand}">Cancel</Button> 
     </Common:DialogControl.Buttons> 
    </Common:DialogControl> 

</Window> 
+4

Пожалуйста, не делайте этого, используйте мой второй ответ http://stackoverflow.com/questions/1029955/wpf-template-or-usercontrol-with-2-or-more-contentpresenters-to-present-conte/1658916# 1658916 вместо этого! –