2009-06-22 14 views
25

Estoy desarrollando aplicación LOB, donde necesitaré varias ventanas de diálogo (y mostrar todo en una ventana no es una opción/no hace sentido).WPF: template o UserControl con 2 (o más) ContentPresenters para presentar contenido en 'slots'

Me gustaría tener un control de usuario de mi ventana que definiría algo más de estilo, etc., y no tendría varios ranuras donde el contenido se podría insertar - por ejemplo, la plantilla de una ventana de diálogo modal tendría una ranura para contenido y para botones (para que el usuario pueda proporcionar un contenido y un conjunto de botones con ICommands enlazados).

me gustaría tener algo como esto (pero esto no funciona): xaml

control de usuario:

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

es algo como esto posible? ¿Cómo debo decirle a VS que mi control expone dos marcadores de posición de contenido para que pueda usarlo así?

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

O tal vez podría utilizar un ControlTemplate para una ventana like here, pero por otra parte: La ventana tiene sólo una ranura de contenido, por lo que su plantilla podrá tener sólo un presentador, pero necesito dos (y si en En este caso, podría ser posible ir con uno, hay otros casos de uso donde varios espacios de contenido vendrían a la mano, solo piense en una plantilla para el artículo: el usuario del control proporcionaría un título, contenido (estructurado), nombre del autor, imagen. ..).

¡Gracias!

PD: Si quisiera tener solo botones uno al lado del otro, ¿cómo puedo poner varios controles (botones) en StackPanel? ListBox tiene ItemsSource, pero no tiene StackPanel, y es propiedad de la Infancia es de sólo lectura - por lo que este no funciona (en el interior del control de usuario):

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

EDIT: No quiero usar la unión, como lo quiere asignar un DataContext (ViewModel) a una ventana completa (que es igual a View), y luego vincularlo a sus comandos desde botones insertados en "slots" de control, por lo que cualquier uso de enlace en la jerarquía rompería la herencia de DC de View.

En cuanto a la idea de heredar de HeaderedContentControl - sí, en este caso funcionaría, pero ¿y si quiero tres partes reemplazables? ¿Cómo puedo crear mi propio "HeaderedAndFooteredContentControl" (o, ¿cómo implementaría HeaderedContentControl si no tuviera uno)?

Edit2: OK, así que mis dos soluciones doen't trabajo - esta es la razón: El ContentPresenter recibe su contenido desde el DataContext, pero necesito los enlaces en elementos contenidos para vincular a las ventanas originales (matriz de control de usuario en el árbol lógico) DataContext - porque de esta manera, cuando incrusto textbox vinculado a la propiedad de ViewModel, no está enlazado, como , la cadena de herencia se ha roto dentro del control!

Parece que necesitaría guardar el DataContext del padre y restaurarlo a los hijos de todos los contenedores del control, pero no me da ningún evento de que el DataContext en el árbol lógico haya cambiado.

EDIT3: ¡Tengo una solución!, borré mis servidores anteriores. Ver mi respuesta.

Respuesta

29

bien, mi solución era totalmente innecesaria, aquí son los únicos tutoriales que necesitará para la creación de cualquier control de usuario:

En resumen:

Subclase una clase adecuada (o UIElement si no le conviene): el archivo es simplemente * .cs, ya que solo definimos el comportamiento, no el aspecto del control.

public class EnhancedItemsControl : ItemsControl 

Añadir propiedad de dependencia de sus ranuras '' (propiedad normal no es lo suficientemente bueno ya que sólo tiene un soporte limitado para la unión).buen truco: en VS, escribir propdp y presiona el tabulador para ampliar el fragmento :):

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*/); 

añadir un atributo para un diseñador (debido a que está creando el llamado control lookless), de esta manera podemos decir que necesitamos tener un ContentPresenter llamada PART_AlternativeContentPresenter en nuestra plantilla

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

Proporcionar un constructor estático que le dirá al sistema de estilo WPF sobre nuestra clase (sin ella, los estilos/templates que se dirigen a nuestro nuevo tipo no se aplica):

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

Si usted quiere hacer algo con el ContentPresenter de la plantilla, que hacerlo reemplazando el método 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... 
    } 
} 

Proporcionar una plantilla por defecto: siempre en ProjectFolder/Temas/Generic.xaml (tengo mi proyecto independiente con todos los controles wpf universalmente utilizables, que luego se referencia desde otras soluciones). Este es el único lugar donde el sistema buscará plantillas para sus controles, por lo tanto, coloque las plantillas predeterminadas para todos los controles en un proyecto: En este fragmento, definí un nuevo ContentPresenter que muestra un valor de nuestra propiedad adjunta AlternativeContent. Tenga en cuenta la sintaxis: podría usar Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" o Content="{TemplateBinding AlternativeContent}", pero la primera funcionará si define una plantilla dentro de su plantilla (necesaria para el estilo, por ejemplo, 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> 

Voila, que acaba de hacer su primer control de usuario lookless (añadir más contentpresenters y propiedades de dependencia 'para más ranuras de contenido').

+4

¡Gracias a Dios que alguien explicó esto de una manera simple y fácil de entender! Es difícil encontrar recursos sobre esto, o es solo una falla de wpf-beginners: p – Shion

2

Si está utilizando un control de usuario

Te estoy adivinando realmente quieren:

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

Esto supone que el DataContext pasado a su control tiene una propiedad Buttons.

Y con un ControlTemplate

La otra opción sería un ControlTemplate y entonces es posible utilizar:

<ContentPresenter ContentSource="Header"/> 

que tendría que ser templating un control que tiene en realidad una 'cabecera' de haz esto (normalmente un HeaderedContentControl).

+0

Gracias - No quiero usar encuadernación, ya que quiero asignar un DataContext (ViewModel) a una ventana completa (que es igual a Ver), y luego unirme a sus comandos desde botones insertados en 'ranuras' de control, por lo que cualquier uso de enlace en la jerarquía rompería la herencia de DC de View. En cuanto a la otra opción - sí, en este caso deriva de HeaderedContentControl funcionaría, pero ¿y si quiero tres partes? ¿Cómo puedo crear mi propio "HeaderedAndFooteredContentControl" (o, ¿cómo implementaría HeaderedContentControl si no tuviera uno)? –

4

Hasta la victoria siempre!

he llegado con la solución (por primera vez en internet, me parece :))

Los DialogControl.xaml.cs difíciles de trabajo - ver comentarios:

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

Y el DialogControl. xaml (sin cambios): uso

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

muestra:

<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

No haga esto, use mi segunda respuesta http://stackoverflow.com/questions/1029955/wpf-template-or-usercontrol-with-2-or-more-contentpresenters-to-present-conte/1658916# 1658916 en su lugar! –

Cuestiones relacionadas