wpf user-controls contentpresenter

WPF: plantilla o UserControl con 2(¡o más!) ContentPresenters para presentar contenido en ''slots''



user-controls (3)

OK, mi solución fue totalmente innecesaria, aquí están los únicos tutoriales que necesitará para crear un control de usuario:

En breve:

Subclasifique alguna clase adecuada (o UIElement si ninguna le conviene): el archivo es simplemente * .cs, ya que solo estamos definiendo el comportamiento, no el aspecto del control.

public class EnhancedItemsControl : ItemsControl

Agregue propiedades de dependencia para sus ''slots'' (la propiedad normal no es lo suficientemente buena, ya que solo tiene soporte limitado para el enlace). propdp truco: en VS, escriba propdp y presione la pestaña para expandir 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*/);

Agregue un atributo para un diseñador (porque está creando el llamado control sin look), de esta manera decimos que necesitamos tener un ContentPresenter llamado PART_AlternativeContentPresenter en nuestra plantilla

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

Proporcione un constructor estático que le dirá al sistema de estilo WPF acerca de nuestra clase (sin él, no se aplicarían los estilos / plantillas que apuntan a nuestro nuevo tipo):

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

Si desea hacer algo con el ContentPresenter desde la plantilla, hágalo anulando 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... } }

Proporcione una plantilla predeterminada: siempre en ProjectFolder / Themes / Generic.xaml (tengo mi proyecto independiente con todos los controles wpf personalizados de uso universal, a los que luego se hace referencia desde otras soluciones). Este es el único lugar donde el sistema buscará plantillas para sus controles, así que ponga las plantillas predeterminadas para todos los controles en un proyecto aquí: 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, acaba de hacer su primer UserControl sin apariencia (agregue más representantes de contenido y propiedades de dependencia para más ''slots de contenido'').

Estoy desarrollando una aplicación LOB, donde necesitaré múltiples ventanas de diálogo (y mostrar todo en una ventana no es una opción / no tiene sentido).

Me gustaría tener un control de usuario para mi ventana que definiera algunos estilos, etc., y tendría varias ranuras donde se podría insertar contenido; por ejemplo, una plantilla de 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):

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>

¿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 usar un ControlTemplate para una ventana como aquí , pero nuevamente: Window tiene solo un espacio de contenido, por lo tanto, su plantilla solo podrá tener un presentador, pero necesito dos (y si en este caso podría ser es posible ir con uno, hay otros casos de uso en los que varias ranuras de contenido vendrían de la mano, solo piense en una plantilla para el artículo: el usuario de control proporcionará un título, contenido (estructurado), nombre del autor, imagen ...).

¡Gracias!

PD: si solo quisiera tener botones uno al lado del otro, ¿cómo puedo colocar varios controles (botones) en StackPanel? ListBox tiene ItemsSource, pero StackPanel no, y su propiedad Children es de solo lectura, por lo que no funciona (dentro del control de usuario):

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

EDITAR: no quiero usar el enlace, ya que quiero asignar un DataContext (ViewModel) a una ventana completa (que es igual a la vista), y luego enlazar a sus comandos desde los botones insertados en las ''ranuras'' de control, por lo que cualquier uso de el enlace en la jerarquía rompería la herencia del DC de View.

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

EDIT2: OK, entonces mis dos soluciones no funcionan. Por eso: ContentPresenter obtiene su contenido del DataContext, pero necesito los enlaces de los elementos contenidos para vincularlos a las ventanas originales (padre del UserControl en el árbol lógico) DataContext, porque De esta manera, cuando incrusté un cuadro de texto vinculado a la propiedad de ViewModel, no estará vinculado, ya que la cadena de herencia se ha roto dentro del control .

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

EDIT3: tengo una solución! , eliminé mis respuestas anteriores. Mira mi respuesta.


Hasta la victoria siempre!

He venido con una solución de trabajo (primero en internet, me parece :))

El complicado DialogControl.xaml.cs - 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):

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

Uso de la 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>


Si está utilizando un UserControl

Supongo que realmente quieres:

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

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

Y con un ControlTemplate

La otra opción sería un ControlTemplate y luego podría usar:

<ContentPresenter ContentSource="Header"/>

Necesitaría crear una plantilla para un control que realmente tenga un ''Encabezado'' para hacer esto (normalmente un HeaderedContentControl).