web-dev-qa-db-ja.com

WPF:「スロット」にコンテンツを表示するための2つ(またはそれ以上!)のContentPresentersを含むテンプレートまたはUserControl

複数のダイアログウィンドウが必要なLOBアプリケーションを開発しています(1つのウィンドウにすべてを表示することはオプションではありません/意味がありません)。

いくつかのスタイリングなどを定義するウィンドウのユーザーコントロールが必要で、コンテンツを挿入できるいくつかのスロットがあります-forたとえば、モーダルダイアログウィンドウのテンプレートには、コンテンツ用とボタン用のスロットがあります(これにより、ユーザーは、バインドされたICommandを使用してコンテンツとボタンのセットを提供できます)。

私はこのようなものが欲しいです(しかしこれは機能しません):

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>

このようなことは可能ですか?このように使用できるように、コントロールが2つのコンテンツプレースホルダーを公開していることを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を使用することもできます ここのように しかし、もう一度:ウィンドウにはコンテンツスロットが1つしかないため、テンプレートにはプレゼンターを1つしか含めることができませんが、2つ必要です(この場合、1つで行くことが可能かもしれませんが、いくつかのコンテンツスロットが手に入る他のユースケースがあります。記事のテンプレートについて考えてください。コントロールのユーザーは、タイトル、(構造化された)コンテンツ、作成者名、画像を提供します。 ...)。

ありがとうございました!

PS:ボタンを並べて表示したい場合、StackPanelに複数のコントロール(ボタン)を配置するにはどうすればよいですか? ListBoxにはItemsSourceがありますが、StackPanelにはありません。また、Childrenプロパティは読み取り専用です。したがって、これは機能しません(usercontrol内)。

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

編集:DataContext(ViewModel)をウィンドウ全体(Viewに等しい)に割り当ててから、コントロールの「スロット」に挿入されたボタンからそのコマンドにバインドしたいので、バインディングを使用したくありません-したがって、階層内のバインディングは、ViewのDCの継承を壊します。

HeaderedContentControlから継承するという考えについては-はい、この場合は機能しますが、3つの交換可能なパーツが必要な場合はどうなりますか?独自の「HeaderedAndFooteredContentControl」を作成するにはどうすればよいですか(または、HeaderedContentControlがない場合はどのように実装しますか)

EDIT2:OK、2つのソリューションが機能しない-これが理由です:ContentPresenterはDataContextからコンテンツを取得しますが、バインディングが必要です元のウィンドウにリンクする要素が含まれています '(論理ツリー内のUserControlの親)DataContext-このように、ViewModelのプロパティにバインドされたテキストボックスを埋め込むと、継承チェーンが内部で壊れているため、バインドされません。制御

親のDataContextを保存し、それをすべてのコントロールのコンテナーの子に復元する必要があるようですが、論理ツリーでDataContextが変更されたというイベントは発生しません。

EDIT3:解決策があります!、以前のアワーを削除しました。私の応答を参照してください。

25
Tomáš Kafka

OK、私の解決策は完全に不要でした。ユーザーコントロールを作成するために必要なチュートリアルは次のとおりです。

要するに:

いくつかの適切なクラス(または、適切でない場合はUIElement)をサブクラス化します-コントロールの外観ではなく動作のみを定義しているため、ファイルは単なる* .csです。

public class EnhancedItemsControl : ItemsControl

'slots'の依存関係プロパティを追加します(通常のプロパティは、バインドのサポートが制限されているため、十分ではありません)。クールなトリック: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/Themes/Generic.xamlにあります(私はスタンドアロンプ​​ロジェクトに、普遍的に使用できるすべてのカスタムwpfコントロールがあり、他のソリューションから参照されます)。これは、システムがコントロールのテンプレートを検索する場所であるため、プロジェクト内のすべてのコントロールのデフォルトテンプレートをここに配置します。このスニペットでは、AlternativeContent添付プロパティの値を表示する新しいContentPresenterを定義しました。構文に注意してください-どちらかを使用できます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>

出来上がり、あなたは最初の見栄えの悪いUserControlを作成しました(より多くの「コンテンツスロット」にコンテンツプレゼンターと依存関係プロパティを追加します)。

32
Tomáš Kafka

Hasta la victoria siempre!

私は実用的な解決策を持ってきました(最初にインターネット上で、それは私には思えます:))

トリッキーな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>
4
Tomáš Kafka

serControlを使用している場合

私はあなたが実際に欲しいと思います:

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

これは、コントロールに渡されるDataContextにButtonsプロパティがあることを前提としています。

そしてControlTemplateを使用

他のオプションはControlTemplateであり、次を使用できます。

<ContentPresenter ContentSource="Header"/>

これを行うには、実際に「ヘッダー」を持つコントロール(通常はHeaderedContentControl)をテンプレート化する必要があります。

2
Alun Harford