WPF:具有 2 个(或更多!)ContentPresenters 的模板或 UserControl 以在“插槽"中呈现内容 [英] WPF: template or UserControl with 2 (or more!) ContentPresenters to present content in 'slots'

查看:32
本文介绍了WPF:具有 2 个(或更多!)ContentPresenters 的模板或 UserControl 以在“插槽"中呈现内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发 LOB 应用程序,我将需要多个对话框窗口(并且在一个窗口中显示所有内容不是一种选择/毫无意义).

I am developing LOB application, where I will need multiple dialog windows (and displaying everything in one window is not an option/makes no sense).

我希望我的窗口有一个用户控件,它可以定义一些样式等,并且有几个可以插入内容的插槽 - 例如,模态对话框窗口的模板将有一个用于内容和按钮的插槽(以便用户可以提供一个内容和一组带有绑定 ICommand 的按钮).

I would like to have a user control for my window that would define some styling, etc., and would have several slots where content could be inserted - for example, a modal dialog window's template would have a slot for content, and for buttons (so that user can then provide a content and set of buttons with bound ICommands).

我想要这样的东西(但这不起作用):

I would like to have something like this (but this doesn't work):

用户控件 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 我的控件公开了两个内容占位符,以便我可以像这样使用它?

Is something like this possible? How should I tell VS that my control exposes two content placeholders so that I can use it like this?

<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 就像这里,但话又说回来:窗口只有一个内容槽,因此它的模板将只能有一个演示者,但我需要两个(如果在这种情况下它可能可以使用一个,还有其他使用多个内容插槽的用例,只需考虑文章模板 - 控件的用户将提供标题、(结构化)内容、作者姓名、图像...).

Or maybe I could use a ControlTemplate for a window like here, but then again: Window has only one content slot, therefore its template will be able to have only one presenter, but I need two (and if in this case it would po maybe possible to go with one, there are other use cases where several content slots would come hand, just think about a template for article - control's user would supply a title, (structured) content, author name, image...).

谢谢!

PS:如果我只想并排放置按钮,如何将多个控件(按钮)放到 StackPanel 上?ListBox 有 ItemsSource,但 StackPanel 没有,它的 Children 属性是只读的 - 所以这不起作用(在用户控件内):

PS: If I wanted to just have buttons side by side, how can I put multiple controls (buttons) to StackPanel? ListBox has ItemsSource, but StackPanel has not, and it's Children property is read-only - so this doesn't work (inside the usercontrol):

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

我不想使用绑定,因为我想将 DataContext (ViewModel) 分配给整个窗口(等于 View),然后从插入控件插槽"的按钮绑定到它的命令 - 所以在层次结构中使用任何绑定都会破坏视图 DC 的继承.

I don't want to use binding, as I want to assign a DataContext (ViewModel) to a whole window (which equals View), and then bind to it's commands from buttons inserted into control 'slots' - so any use of binding in the hierarchy would break inheritance of View's DC.

至于从 HeaderedContentControl 继承的想法 - 是的,在这种情况下它会起作用,但是如果我想要三个可替换的部分怎么办?我如何制作自己的HeaderedAndFooteredContentControl"(或者,如果我没有,我将如何实现 HeaderedContentControl)?

As for the idea of inheriting from HeaderedContentControl - yes, in this case it would work, but what if I want three replacable parts? How do I make my own "HeaderedAndFooteredContentControl" (or, how would I implement HeaderedContentControl if I didn't have one)?

好的,所以我的两个解决方案不起作用 - 这就是原因:ContentPresenter 从 DataContext 获取它的内容,但我需要包含元素的绑定以链接到原始窗口(逻辑树中的 UserControl 的父级)DataContext - 因为这样,当我嵌入绑定到 ViewModel 属性的文本框时,它没有绑定,因为控件内部的继承链被破坏了

OK, so my two solutions doen't work - this is why: The ContentPresenter gets it's content from the DataContext, but I need the bindings on contained elements to link to original windows' (UserControl's parent in logical tree) DataContext - because this way, when I embed textbox bound to ViewModel's property, it is not bound, as the inheritance chain has been broken inside the control!

似乎我需要保存父级的 DataContext,并将其恢复到所有控件容器的子级,但我没有收到逻辑树中的 DataContext 已更改的任何事件.

It seems that I would need to save parent's DataContext, and restore it to the children of all control's containers, but I don't get any event that DataContext up in the logical tree has changed.

我有一个解决方案!,删除了我以前的回答.查看我的回复.

I have a solution!, deleted my previous aswers. See my response.

推荐答案

好吧,我的解决方案完全没有必要,这里是创建任何用户控件所需的唯一教程:

OK, my solution was totally unnecessary, here are the only tutorials you will ever need for creating any user control:

简而言之:

子类化一些合适的类(或 UIElement,如果不适合你) - 文件只是普通的 *.cs,因为我们只定义行为,而不是控件的外观.

Subclass some suitable class (or UIElement if none suits you) - the file is just plain *.cs, as we are only defining the behaviour, not the looks of the control.

public class EnhancedItemsControl : ItemsControl

为您的槽"添加依赖属性(普通属性不够好,因为它对绑定的支持有限).很酷的技巧:在 VS 中,编写 propdp 并按 Tab 键展开代码段 :):

Add dependency property for your 'slots' (normal property is not good enough as it has only limited support for binding). Cool trick: in VS, write propdp and press tab to expand the snippet :):

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

Add an attribute for a designer (because you are creating so-called lookless control), this way we say that we need to have a ContentPresenter called PART_AlternativeContentPresenter in our template

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

提供一个静态构造函数,它将告诉 WPF 样式系统我们的类(没有它,将不会应用针对我们的新类型的样式/模板):

Provide a static constructor that will tell to WPF styling system about our class (without it, the styles/templates that target our new type would not be applied):

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

如果你想对模板中的 ContentPresenter 做一些事情,你可以通过覆盖 OnApplyTemplate 方法来实现:

If you want to do something with the ContentPresenter from the template, you do it by overriding the OnApplyTemplate method:

//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/Themes/Generic.xaml 中(我的独立项目包含所有自定义通用 wpf 控件,然后从其他解决方案中引用).这是系统为您的控件寻找模板的唯一地方,因此将项目中所有控件的默认模板放在这里:在这个片段中,我定义了一个新的 ContentPresenter,它显示了我们的 AlternativeContent 附加属性的值.注意语法 - 我可以使用Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}"Content="{TemplateBinding AlternativeContent}",但如果您在模板中定义模板(例如 ItemPresenters 的样式所必需的),则前者将起作用.

Provide a default template: always in ProjectFolder/Themes/Generic.xaml (I have my standalone project with all custom universally usable wpf controls, which is then referenced from other solutions). This is only place where system will look for templates for your controls, so put default templates for all controls in a project here: In this snippet I defined a new ContentPresenter that displays a value of our AlternativeContent attached property. Note the syntax - I could use either Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" or Content="{TemplateBinding AlternativeContent}", but the former will work if you define a template inside your template (necessary for styling for example 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>

瞧,您刚刚制作了第一个无外观的 UserControl(为更多内容槽"添加更多内容展示器和依赖属性).

Voila, you just made your first lookless UserControl (add more contentpresenters and dependency properties for more 'content slots').

这篇关于WPF:具有 2 个(或更多!)ContentPresenters 的模板或 UserControl 以在“插槽"中呈现内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆