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

102

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

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

<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». «Кнопка» находится в области действия элемента «UserControl1», имя которого уже было зарегистрировано, когда оно было определено в другой области.

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

Райан
источник
Это совпадение. Я как раз собирался задать этот вопрос! У меня та же проблема. Вынесение общего шаблона пользовательского интерфейса в UserControl, но при этом желательно ссылаться на пользовательский интерфейс содержимого по имени.
mackenir
3
Этот парень нашел решение, включающее избавление от файла XAML своего настраиваемого элемента управления и программное создание пользовательского интерфейса настраиваемого элемента управления. В этом сообщении блога есть больше сказать по этому поводу.
mackenir
2
Почему вы не используете способ ResourceDictionary? Определите в нем DataTemplate. Или используйте ключевое слово BasedOn для наследования элемента управления. Просто некоторые пути, по которым я бы пошел, прежде чем
создавать

Ответы:

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>
Sybrand
источник
2
Я нашел это на самом деле наиболее удобным решением, поскольку вы можете конкретизировать ControlTemplate в обычном UserControl с помощью конструктора и преобразовать его в стиль со связанным шаблоном элемента управления.
Оливер Вайххолд
5
Хм, немного странно, что это работает для вас, потому что я пытался применить этот подход, и в моем случае я все еще получаю эту печально известную ошибку.
greenoldman
8
@greenoldman @Badiboy Думаю, я знаю, почему у тебя это не сработало. вероятно, вы просто изменили существующий код с UserControlна наследование ContentControl. Чтобы решить эту проблему, просто добавьте новый класс ( не XAML с CS). И тогда это (надеюсь) сработает. если хотите, я создал небольшое решение
VS2010
1
Много лет спустя, и я хотел бы снова проголосовать за это :)
Дрю Ноукс
2
Думаю, так HeadingContainerи MyFunkyContainerдолжно быть MyFunkyControl?!
Мартин Шнайдер
20

Похоже, это невозможно при использовании 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;           
    }

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

Райан
источник
Я обнаружил, что у меня возникают проблемы с привязкой данных при использовании этого механизма повторного родительства контента. Привязка данных настроена правильно, но первоначальное заполнение элементов управления из источника данных не работает должным образом. Я думаю, что проблема ограничена элементами управления, которые не являются прямым потомком ведущего.
mackenir
С тех пор у меня не было возможности поэкспериментировать с этим, но у меня не было проблем с привязкой данных ни к элементам управления, определенным в 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
Если вы сделаете это, то привязки с использованием имени не будут работать ... если вы не установите привязки в коде позади.
Tower
3

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

Дэни
источник
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);
            }
        }
    }
}

и ваш Window или Control Code Behind задают вам контроль по свойству:

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

Али Юсефи
источник
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;
}

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

15ee8f99-57ff-4f92-890c-b56153
источник
0

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

Voccoeisuoi
источник
0

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

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

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

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

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

Дэвид Велна
источник