Как создать пользовательский элемент управления WPF с содержимым NAMED

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

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

       

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

    

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

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

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

Ответ заключается в том, чтобы не использовать UserControl для этого.

Создайте class, расширяющий 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; } } 

затем используйте стиль для указания содержимого

  

и, наконец, использовать его

    

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

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

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

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

Иногда вам может понадобиться ссылка на элемент из C #. В зависимости от x:Uid использования вы можете установить x:Uid вместо x:Name и получить доступ к элементам, вызвав метод поиска Uid, например Get, его 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); } } } } 

и ваше окно или контрольный код За установленным вами контролем по свойству:

  public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } public Button BtnOK { get; set; } } 

ваше окно xaml:

          

UserControlNameHelper получает ваше управляющее имя и ваше имя classа для установки Control на Property.

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

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

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

  
    меню    

Код позади:

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

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

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

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

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

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

  

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

  • Почему элементы управления не хотят удаляться?
  • Давайте будем гением компьютера.