Как создать WPF UserControl с именованным содержимым

у меня есть набор элементов управления с прикрепленными командами и логикой, которые постоянно используются таким же образом. Я решил создать пользовательский элемент управления, который содержит все общие элементы управления и логику.

однако мне также нужен элемент управления, чтобы иметь возможность удерживать контент, который может быть назван. Я попробовал следующее:

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

Я получаю следующую ошибку:

не удается установить значение атрибута Name 'buttonName' на кнопку элемент''. 'Button' is в рамках элемента 'UserControl1', который уже имел имя, зарегистрированное при определении в другой области.

Если я удаляю имя кнопки, то он компилируется, однако мне нужно иметь возможность назвать содержимое. Как я могу достичь этого?

9 ответов


ответ заключается в том, чтобы не использовать 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>

Кажется, это невозможно, когда используется XAML. Пользовательские элементы управления кажутся излишними, когда у меня есть все необходимые элементы управления, но просто нужно сгруппировать их вместе с небольшим количеством логики и разрешить именованный контент.

на блог 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;           
    }

в моем примере выше I создали пользовательский элемент управления UserControlDefinedInXAML, который определяется как любой обычный пользовательский элемент управления с помощью XAML. В моем UserControlDefinedInXAML у меня есть StackPanel под названием aStackPanel, в котором я хочу, чтобы мой именованный контент появлялся.


другая альтернатива, которую я использовал, - просто установить Name собственность в Loaded событие.

в моем случае у меня был довольно сложный элемент управления, который я не хотел создавать в коде, и он искал необязательный элемент управления с определенным именем для определенного поведения, и поскольку я заметил, что могу установить имя в DataTemplate Я подумал, что смогу сделать это в Loaded событие тоже.

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

иногда вам может потребоваться просто ссылаться на элемент из C#. В зависимости от варианта использования вы можете установить x:Uid вместо x:Name и получить доступ к элементам, вызвав метод uid finder, как получить объект по его Uid в WPF.


вы можете использовать этот помощник для имени внутри пользовательского элемента управления:

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

и Ваше окно или код управления за set You control by Property:

 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 получите имя элемента управления и имя класса для set Control в Property.


Я решил создать дополнительное свойство для каждого элемента, который мне нужно получить:

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

Это позволяет мне получить доступ к дочерним элементам в XAML:

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

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

код:

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

реальное решение будет для Microsoft, чтобы исправить эту проблему, а также все остальные с разбитыми визуальные деревья и т. д. Гипотетически.


еще один обходной путь: ссылка на элемент RelativeSource.


У меня была такая же проблема с использованием TabControl при размещении группы именованных элементов управления.

мой обходной путь должен был использовать шаблон элемента управления, который содержит все мои элементы управления, которые будут показаны на вкладке. Внутри шаблона можно использовать свойство Name, а также привязку данных к свойствам именованного элемента управления из других элементов управления, по крайней мере, внутри того же шаблона.

в качестве содержимого элемента управления TabItem используйте простой элемент управления и установите ControlTemplate соответственно:

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

доступ к именованному элементу управления внутри шаблона из кода позади вас должен будет использовать визуальное дерево.