NAMEDコンテンツでWPF UserControlを作成する方法


100

同じ方法で常に再利用されるコマンドとロジックがアタッチされたコントロールのセットがあります。すべての一般的なコントロールとロジックを保持するユーザーコントロールを作成することにしました。

ただし、名前を付けることができるコンテンツを保持できるようにするためのコントロールも必要です。私は以下を試しました:

<UserControl.ContentTemplate>
    <DataTemplate>
        <Button>a reused button</Button>
        <ContentPresenter Content="{TemplateBinding Content}"/>
        <Button>a reused button</Button>
    </DataTemplate>
</UserControl.ContentTemplate>

ただし、ユーザーコントロール内に配置されたコンテンツには名前を付けることができないようです。たとえば、次のようにコントロールを使用するとします。

<lib:UserControl1>
     <Button Name="buttonName">content</Button>
</lib:UserControl1>

次のエラーが表示されます。

要素「ボタン」に名前属性値「ボタン名」を設定できません。「ボタン」は要素「UserControl1」のスコープの下にあり、別のスコープで定義されたときにすでに名前が登録されていました。

buttonNameを削除するとコンパイルされますが、コンテンツに名前を付ける必要があります。どうすればこれを達成できますか?


これは偶然です。私はちょうどこの質問をしようとしていました!私は同じ問題を抱えています。一般的なUIパターンをUserControlに分解するが、コンテンツUIを名前で参照したい。
マッケニール2009

3
この男は、カスタムコントロールのXAMLファイルを削除し、プログラムでカスタムコントロールのUIを構築することを含むソリューションを見つけました。このブログの投稿は、このテーマについてもっと多くのことを述べています。
マッケニル2009

2
ResourceDictionaryの方法を使用しませんか?その中にDataTemplateを定義します。または、BasedOnキーワードを使用してコントロールを継承します。WPFでコードビハインドUIを実行する前にたどるいくつかのパス...
Louis Kottmann

回答:


46

答えは、UserControlを使用しないことです。

ContentControlを拡張するクラスを作成します。

public class MyFunkyControl : ContentControl
{
    public static readonly DependencyProperty HeadingProperty =
        DependencyProperty.Register("Heading", typeof(string),
        typeof(HeadingContainer), new PropertyMetadata(HeadingChanged));

    private static void HeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((HeadingContainer) d).Heading = e.NewValue as string;
    }

    public string Heading { get; set; }
}

次に、スタイルを使用して内容を指定します

<Style TargetType="control:MyFunkyControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:MyFunkyContainer">
                <Grid>
                    <ContentControl Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

そして最後に-それを使う

<control:MyFunkyControl Heading="Some heading!">            
    <Label Name="WithAName">Some cool content</Label>
</control:MyFunkyControl>

2
デザイナーを使用して通常のUserControlでControlTemplateを具体化でき、それを関連付けられたコントロールテンプレートを持つスタイルに変換できるため、これが実際に最も快適なソリューションであることがわかりました。
Oliver Weichhold 2010

5
うーん、それはあなたのために動作するのは少し奇妙です、私がこのアプローチを適用しようとしたので、私の場合にはまだこの悪名高いエラーが発生します。
greenoldman 2011

8
@greenoldman @Badiboyうまくいかなかった理由を知っていると思います。おそらく、既存のコードUserControlを継承するように変更しただけですContentControl。解決するには、新しいクラスを追加するだけです(CSを使用したXAMLではありません)。そしてそれは(うまくいけば)うまくいくでしょう。よろしければ、私は小さなVS2010ソリューションを
itho

1
何年も後、私はこれをもう一度賛成できればいいのに:)
Drew Noakes 2017年

2
私は推測しHeadingContainerMyFunkyContainerそうなることを意図していますMyFunkyControlか?
Martin Schneider、

20

XAMLを使用する場合、これは不可能のようです。カスタムコントロールは、必要なすべてのコントロールを実際に持っている場合はやり過ぎに見えますが、それらを小さなロジックでグループ化し、名前付きコンテンツを許可する必要があるだけです。

Mackenirが示唆するJDのブログの解決策は、最も妥協点があるようです。JDのソリューションを拡張して、XAMLでコントロールを定義できるようにする方法は次のとおりです。

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        var grid = new Grid();
        var content = new ContentPresenter
                          {
                              Content = Content
                          };

        var userControl = new UserControlDefinedInXAML();
        userControl.aStackPanel.Children.Add(content);

        grid.Children.Add(userControl);
        Content = grid;           
    }

上記の例では、XAMLを使用する通常のユーザーコントロールと同様に定義されるUserControlDefinedInXAMLというユーザーコントロールを作成しました。私のUserControlDefinedInXAMLには、aStackPanelというStackPanelがあり、その中に名前付きコンテンツを表示します。


このコンテンツの親を変更するメカニズムを使用すると、データバインディングの問題が発生することがわかりました。データバインディングは正しく設定されているように見えますが、データソースからのコントロールの初期設定は適切に機能しません。問題は、コンテンツプレゼンターの直接の子ではないコントロールに限定されていると思います。
マッケニール2009

それ以来、これを試す機会はありませんでしたが、UserControlDefinedInXAMLで定義されたコントロール(上記の例から)またはContentPresenterに追加されたコントロールへのデータバインドに問題はありませんでした。私はコードだけでデータバインドを行ってきました(XAMLではありません-それが違いを生むかどうかはわかりません)。
ライアン

それは違いを生むようです。私はあなたが説明した場合に私のデータバインディングにXAMLを使用してみましたが、機能しません。しかし、コードで設定した場合、機能します。
ライアン、

3

私が使用した別の方法Nameは、Loadedイベントでプロパティを設定することです。

私の場合、コードビハインドで作成したくないかなり複雑なコントロールがあり、特定の動作に対して特定の名前を持つオプションのコントロールを探しました。そして、名前をイベントでもDataTemplateできると思ったLoaded

private void Button_Loaded(object sender, RoutedEventArgs e)
{
    Button b = sender as Button;
    b.Name = "buttonName";
}

2
これを行うと、コードビハインドでバインディングを設定しない限り、名前を使用したバインディングは機能しません。
タワーの

3

場合によっては、C#から要素を参照する必要があるだけかもしれません。ユースケースに応じて、のx:Uid代わりにを設定し、Getオブジェクトのx:NameようなUidファインダーメソッドをWPFのUidで呼び出すことにより、要素にアクセスできます。


1

このヘルパーをユーザーコントロール内のセット名に使用できます。

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
namespace UI.Helpers
{
    public class UserControlNameHelper
    {
        public static string GetName(DependencyObject d)
        {
            return (string)d.GetValue(UserControlNameHelper.NameProperty);
        }

        public static void SetName(DependencyObject d, string val)
        {
            d.SetValue(UserControlNameHelper.NameProperty, val);
        }

        public static readonly DependencyProperty NameProperty =
            DependencyProperty.RegisterAttached("Name",
                typeof(string),
                typeof(UserControlNameHelper),
                new FrameworkPropertyMetadata("",
                    FrameworkPropertyMetadataOptions.None,
                    (d, e) =>
                    {
                        if (!string.IsNullOrEmpty((string)e.NewValue))
                        {
                            string[] names = e.NewValue.ToString().Split(new char[] { ',' });

                            if (d is FrameworkElement)
                            {
                                ((FrameworkElement)d).Name = names[0];
                                Type t = Type.GetType(names[1]);
                                if (t == null)
                                    return;
                                var parent = FindVisualParent(d, t);
                                if (parent == null)
                                    return;
                                var p = parent.GetType().GetProperty(names[0], BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
                                p.SetValue(parent, d, null);
                            }
                        }
                    }));

        public static DependencyObject FindVisualParent(DependencyObject child, Type t)
        {
            // get parent item
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);

            // we’ve reached the end of the tree
            if (parentObject == null)
            {
                var p = ((FrameworkElement)child).Parent;
                if (p == null)
                    return null;
                parentObject = p;
            }

            // check if the parent matches the type we’re looking for
            DependencyObject parent = parentObject.GetType() == t ? parentObject : null;
            if (parent != null)
            {
                return parent;
            }
            else
            {
                // use recursion to proceed with next level
                return FindVisualParent(parentObject, t);
            }
        }
    }
}

そして、ウィンドウまたはコントロールコードビハインドは、プロパティによって制御します。

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

    }

    public Button BtnOK { get; set; }
}

あなたのウィンドウxaml:

    <Window x:Class="user_Control_Name.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:test="clr-namespace:user_Control_Name"
            xmlns:helper="clr-namespace:UI.Helpers" x:Name="mainWindow"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <test:TestUserControl>
                <Button helper:UserControlNameHelper.Name="BtnOK,user_Control_Name.MainWindow"/>
            </test:TestUserControl>
            <TextBlock Text="{Binding ElementName=mainWindow,Path=BtnOK.Name}"/>
        </Grid>
    </Window>

UserControlNameHelperは、コントロールにプロパティを設定するためのコントロール名とクラス名を取得します。


0

取得する必要がある要素ごとに追加のプロパティを作成することを選択しました。

    public FrameworkElement First
    {
        get
        {
            if (Controls.Count > 0)
            {
                return Controls[0];
            }
            return null;
        }
    }

これにより、XAMLの子要素にアクセスできます。

<TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/>

0
<Popup>
    <TextBox Loaded="BlahTextBox_Loaded" />
</Popup>

コードビハインド:

public TextBox BlahTextBox { get; set; }
private void BlahTextBox_Loaded(object sender, RoutedEventArgs e)
{
    BlahTextBox = sender as TextBox;
}

本当の解決策は、マイクロソフトがこの問題を修正することであり、視覚ツリーが壊れている他のすべての問題も同様です。



0

名前付きコントロールの束を配置するときに、TabControlを使用して同じ問題が発生しました。

私の回避策は、タブページに表示されるすべてのコントロールを含むコントロールテンプレートを使用することでした。テンプレート内では、Nameプロパティを使用でき、少なくとも同じテンプレート内の他のコントロールから、名前付きコントロールのプロパティにデータバインドすることもできます。

TabItemコントロールのコンテンツとして、単純なコントロールを使用し、それに応じてControlTemplateを設定します。

<Control Template="{StaticResource MyControlTemplate}"/>

テンプレート内の名前付きコントロールに背後のコードからアクセスするには、ビジュアルツリーを使用する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.