WPF:(!或更多)模板或用户控件与2 ContentPresenters呈现内容在“槽”(WPF:

2019-06-23 22:05发布

我开发LOB应用程序,在那里我将需要多个对话窗口(并在一个窗口中显示的一切是不是一种选择/是没有意义的)。

我想对我的窗口的用户控件,将定义一些造型等,将有多个插槽,其中的内容可以被插入-例如,一个模式对话框的模板需要有对内容的插槽,以及按钮(使得用户然后可以提供按钮的具有结合的个ICommand内容和集)。

我想有这样的事情(但不工作):

用户控件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一个窗口喜欢这里 ,但话又说回来:窗口只有一个内容时段,因此它的模板就可以只有一个主持人,但我需要两个(如果在这种情况下,将PO也许可能去一个,还有其他使用情况下,一些内容槽会来的手,只是觉得有关的文章模板 - 控件的用户将提供一个标题,(结构)的内容,作者姓名,图像...)。

谢谢!

PS:如果我想只是通过侧键身边,我怎么可以把多个控件(按钮)的StackPanel? 列表框的ItemsSource有,但StackPanel中已经没有了,它的Children属性是只读的 - 所以,这并不工作(在用户控件内):

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

编辑:我不想使用绑定,因为我想分配一个DataContext(视图模型)的整个窗口(等于查看),然后结合到它从插入控制按钮“插槽”命令 - 因此,任何使用层次结构中的结合将打破查看DC的继承。

至于从HeaderedContentControl继承的想法 - 是的,在这种情况下,它会工作,但如果我想哪三个部分替代的? 如何让我的自己的“HeaderedAndFooteredContentControl”(或者,我将如何实现HeaderedContentControl如果我没有一个)?

EDIT2:OK,所以我的两个解决方案doen't工作-这就是为什么:将ContentPresenter得到它的从DataContext的内容,但我需要包含的元素链接到原始窗口(用户控件的逻辑树父)的DataContext绑定-因为这样一来,当我嵌入势必视图模型的财产文本框,它不绑定,作为继承链已经控制内部碎了

看来,我需要拯救父母的DataContext,并将其恢复到所有控制的容器的孩子,但我没有得到的datacontext了在逻辑树被更改的任何事件。

EDIT3:我有一个解决办法! ,删除了我以前的aswers。 看到我的反应。

Answer 1:

OK,我的解决方案是完全不必要的,这里是你永远都需要创建任何用户控制的唯一教程:

  • http://www.interact-sw.co.uk/iangblog/2007/02/14/wpfdefaulttemplate
  • http://www.codeproject.com/Articles/37326/Lookless-Controls-Themes.aspx
  • http://www.codeproject.com/Articles/35444/Defining-the-Default-Style-for-a-Lookless-Control.aspx
  • http://blog.pixelingene.com/?p=58

简而言之:

子类一些合适的类(或的UIElement如果没有适合你) - 该文件只是普通的*的.cs,因为我们只定义行为,控制的不是长相。

public class EnhancedItemsControl : ItemsControl

添加依赖属性为您的“槽”(普通财产是不够好,因为它具有约束力的只有有限的支持)。 酷招:在VS,写propdp然后按Tab键展开片段:):

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

添加属性对于一个设计师(因为您要建立所谓的无外观控制),这样,我们说,我们需要有一个在我们的模板被称为PART_AlternativeContentPresenter ContentPresenter

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

瞧,你只是做你的第一个无外观的用户控件(添加更多contentpresenters和依赖属性更“内容槽”)。



Answer 2:

直到胜利永远!

我来与工作液(在互联网上第一,在我看来:))

最棘手的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>


Answer 3:

如果您使用的是用户控件

我猜你真正想要的:

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

这是假定传递给控制DataContext的有一个按钮属性。

并用的ControlTemplate

另一种选择将是一个控件模板,然后你可以使用:

<ContentPresenter ContentSource="Header"/>

你将需要模板化实际上有一个“头”来做到这一点(通常是HeaderedContentControl)的控制。



文章来源: WPF: template or UserControl with 2 (or more!) ContentPresenters to present content in 'slots'