Як створити WPF UserControl з вмістом NAMED


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>

Я отримую таку помилку:

Неможливо встановити значення атрибута Name "buttonName" для елемента "Button". 'Кнопка' знаходиться в області дії елемента 'UserControl1', у якого вже було зареєстровано ім'я, коли воно було визначене в іншій області.

Якщо я видаляю кнопкуName, то вона компілюється, проте мені потрібно мати змогу назвати вміст. Як я можу цього досягти?


Це збіг обставин. Я ось-ось збирався задати це питання! У мене така ж проблема. Розділення загального шаблону інтерфейсу користувача в UserControl, але бажаючи посилатися на вміст інтерфейсу користувача за назвою.
mackenir

3
Цей хлопець знайшов рішення, яке стосується позбавлення від файлу XAML користувальницького керування та побудови інтерфейсу користувальницького управління програмно. Цей пост у блозі має більше сказати на цю тему.
mackenir

2
Чому ви не використовуєте спосіб ResourceDictionary? Визначте в ньому шаблон DataTemplate. Або використовуйте ключове слово BasedOn для успадкування контролю. Лише декілька шляхів, які я б дотримувався, перш ніж робити інтерфейс із кодовим інтерфейсом у WPF ...
Луї Котманн

Відповіді:


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
Я вважав, що це насправді найзручніше рішення, оскільки ви можете чітко встановити шаблон ControlTemplate у звичайному UserControl за допомогою дизайнера і перетворити його в стиль із пов'язаним шаблоном управління.
Олівер Вайхольд,

5
Хм, це трохи дивно, що це працює для вас, тому що я намагався застосувати такий підхід, і в моєму випадку я все-таки отримую цю сумнозвісну помилку.
greenoldman

8
@greenoldman @Badiboy Я думаю, я знаю, чому це не працювало для вас. ви, ймовірно, просто змінили існуючий код з UserControlуспадкованого ContentControl. Щоб вирішити, просто додайте новий клас (а не XAML з CS). І тоді це (сподіваємось) спрацює. якщо вам подобається, я створив невелике рішення
VS2010

1
Через багато років, і я хотів би, щоб я міг схвалити це знову :)
Дрю Ноакс

2
Я здогадуюсь HeadingContainerі MyFunkyContainerпокликаний бути MyFunkyControl?!
Мартін Шнайдер

20

Здається, це неможливо, коли використовується XAML. Спеціальні елементи керування здаються надмірними, коли у мене фактично є всі потрібні мені елементи контролю, але просто потрібно згрупувати їх разом з невеликою кількістю логіки та дозволити вміст, що називається.

Рішення на блозі JD в якості пропонує mackenir, здається, кращий компроміс. Спосіб розширення рішення 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
Якщо ви це зробите, то прив'язки з використанням імені не працюватимуть, якщо ви не встановите прив'язки в коді позаду.
Вежа

3

Іноді вам може знадобитися лише посилання на елемент із C #. Залежно від випадку використання, ви можете встановити x:Uidзамість а x:Nameта отримати доступ до елементів, викликаючи метод пошуку Uid на зразок Отримати об’єкт за його 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);
            }
        }
    }
}

а ваш віконний або контрольний код позаду, який ви контролюєте за властивостями:

 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 для властивості.


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, а також усі інші зі зламаними візуальними деревами тощо. Гіпотетично кажучи.


0

Ще одне вирішення: посилайтеся на елемент як на RelativeSource .


0

У мене була така ж проблема з використанням TabControl, коли я розміщував купу іменних елементів управління.

Моє вирішення полягало в тому, щоб використовувати шаблон керування, який містить усі мої елементи керування, які відображатимуться на сторінці вкладки. Всередині шаблону можна використовувати властивість Name, а також прив'язувати дані до властивостей названого елемента управління з інших елементів управління, принаймні всередині того самого шаблону.

В якості вмісту управління TabItem використовуйте простий елемент керування та встановіть відповідний шаблон ControlTemplate:

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

Щоб отримати доступ до тих, хто називається керуванням всередині шаблону, з коду позаду, вам потрібно буде використовувати візуальне дерево.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.