Как создать элемент управления 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' на кнопку элемент''. "Кнопка" - это под областью действия элемента 'Файл 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; }
в моем примере выше я создали пользовательский элемент управления с именем 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, например получить объект по его 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:
<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 to 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, чтобы исправить эту проблему, а также все остальные с разбитыми визуальные деревья и т. д. Гипотетически говоря.
У меня была такая же проблема с использованием TabControl при размещении группы именованных элементов управления.
мое решение было использовать шаблон, который содержит все мои элементы управления, которые будут показаны в закладке. Внутри шаблона можно использовать свойство Name, а также привязку данных к свойствам именованного элемента управления из других элементов управления, по крайней мере, внутри того же шаблона.
в качестве содержимого элемента управления TabItem используйте простой элемент управления и установите ControlTemplate соответственно:
<Control Template="{StaticResource MyControlTemplate}"/>
доступ к этим именованным элементам управления внутри шаблона из кода позади вам нужно будет использовать визуальное дерево.