WPF: template ou UserControl avec 2 (ou plus!) ContentPresenters de présenter du contenu dans "machines à sous"

je développe application LOB, où je vais avoir besoin de plusieurs fenêtres de dialogue (et l'affichage de tout dans une fenêtre n'est pas une option/n'a pas de sens).

j'aimerais avoir un contrôle d'utilisateur pour ma fenêtre qui définirait un certain style, etc., et aurait plusieurs fentes où le contenu pourrait être inséré - par exemple, le modèle d'une fenêtre de dialogue modale aurait une fente pour le contenu, et pour les boutons (de sorte que l'utilisateur peut alors fournir un contenu et un ensemble de boutons avec lié ICommands).

j'aimerais avoir quelque chose comme ça (mais ça ne marche pas):

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>

est-ce possible? Comment puis-je dire à VS que mon contrôle expose deux placeholders de contenu pour que je puisse l'utiliser comme ça?

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

ou peut-être que je pourrais utiliser un ControlTemplate pour une fenêtre comme ici , mais encore une fois: Window n'a qu'une seule fente de contenu, donc son modèle sera en mesure d'avoir un seul présentateur, mais j'en ai besoin de deux (et si dans ce cas, il serait po peut - être possible d'aller avec un, il ya d'autres cas d'utilisation où plusieurs fentes de contenu viendrait main, il suffit de penser à un modèle pour l'utilisateur de contrôle de l'article fournirait un titre, (structuré) le contenu, le nom de l'auteur, l'image...).

Merci!

PS: Si je voulais juste avoir des boutons côte à côte side, Comment puis-je mettre plusieurs commandes (boutons) sur StackPanel? ListBox a ItemsSource, mais StackPanel n'a pas, et c'est la propriété des enfants est en lecture seule-donc cela ne fonctionne pas (dans le usercontrol):

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

EDIT: Je ne veux pas utiliser binding, car je veux assigner un DataContext (ViewModel) à une fenêtre entière (qui égale View), et puis lier à ses commandes à partir de boutons insérés dans le contrôle 'slots' - ainsi toute utilisation de binding dans la hiérarchie se briserait héritage du DC de View.

quant à l'idée d'hériter de HeaderedContentControl - oui, dans ce cas, il fonctionnerait, mais que faire si je veux trois pièces remplaçables? Comment puis-je créer mon propre "Headered Andfooteredcontentcontrol" (ou, comment mettre en œuvre HeaderedContentControl si je n'en avais pas) ?

EDIT2: OK, donc mes deux solutions ne fonctionnent pas - c'est pourquoi: Le ContentPresenter obtient son contenu du Datacontexte, mais j'ai besoin des reliures sur les éléments contenus pour faire le lien avec le Datacontexte original de windows (parent de UserControl dans l'arbre logique) - parce que de cette façon, quand j'ai intégré textbox lié à la propriété de ViewModel, il n'est pas lié, comme la chaîne de transmission a été cassée à l'intérieur de la commande !

il semble que j'aurais besoin de sauver DataContext parent, et le restaurer aux enfants de tous les conteneurs de contrôle, mais je ne reçois aucun événement que DataContext up dans l'arbre logique a changé.

Edit 3: j'ai une solution! , a supprimé mes répliques précédentes. Voir ma réponse.

25
demandé sur Community 2009-06-23 03:47:46

3 réponses

OK, ma solution était totalement inutile, voici les seuls tutoriels dont vous aurez besoin pour créer un contrôle utilisateur:

en bref:

sous-classe une classe appropriée (ou UIElement si aucune ne vous convient) - le fichier est juste simple *.cs, car nous ne définissons que le comportement, pas l'apparence de la commande.

public class EnhancedItemsControl : ItemsControl

ajouter propriété de dépendance pour vos "slots" (la propriété normale n'est pas assez bonne car elle n'a qu'un support limité pour lier). Cool truc: dans VS, écrire propdp et appuyez sur tab pour étendre l'extrait :):

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

Ajouter un attribut pour un concepteur (parce que vous créez le soi-disant contrôle sans regard), de cette façon nous disons que nous avons besoin d'avoir un ContentPresenter appelé PART_AlternativeContentPresenter dans notre modèle

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

fournir un constructeur statique qui informera le système de style WPF sur notre classe (sans lui, les styles/gabarits qui ciblent notre nouveau type ne seraient pas appliqués):

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

si vous voulez faire quelque chose avec le ContentPresenter du modèle, vous le faites en écrasant la méthode 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...
    }
}

fournit un modèle par défaut: toujours dans ProjectFolder/Themes/Generic.xaml (j'ai mon projet autonome avec tous les les contrôles WPF utilisables, qui sont ensuite référencés à partir d'autres solutions). C'est seulement l'endroit où le système cherchera des gabarits pour vos contrôles, donc mettez les gabarits par défaut pour tous les contrôles Dans un projet ici: Dans cet extrait j'ai défini un nouveau ContentPresenter qui affiche une valeur de notre propriété AlternativeContent attachée. Notez que la syntaxe - je pourrais utiliser Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" ou Content="{TemplateBinding AlternativeContent}" , mais le premier fonctionnera si vous définissez un modèle à l'intérieur de votre modèle (nécessaire pour le style par exemple 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>

voilà, vous venez de faire votre premier contrôle d'utilisateur sans regard (ajouter plus de contentpresenters et des propriétés de dépendance pour plus de 'content slots').

30
répondu Tomáš Kafka 2009-11-02 00:33:12

Hasta la victoria siempre!

je suis venu avec la solution de travail (d'abord sur internet, il me semble :))

La délicate DialogControl.XAML.cs-voir les commentaires:

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

et la commande de dialogue.xaml (pas de changement):

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

exemple d'utilisation:

<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
répondu Tomáš Kafka 2009-06-23 17:54:23

si vous utilisez un UserControl

je suppose que vous voulez vraiment:

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

cela suppose que le DataContext passé à votre contrôle possède une propriété de boutons.

et avec un tempsde contrôle

l'autre option serait un ControlTemplate et alors vous pourriez utiliser:

<ContentPresenter ContentSource="Header"/>

vous auriez besoin d'être templating un contrôle qui a en fait un "Header" pour faire cela (normalement un HeaderedContentControl).

2
répondu Alun Harford 2009-06-23 00:16:51