Как создать пользовательский элемент управления WPF с содержимым NAMED
У меня есть набор элементов управления с прикрепленными командами и логикой, которые постоянно используются повторно. Я решил создать пользовательский элемент управления, который содержит все общие элементы управления и логику.
Однако мне также нужен элемент управления для хранения содержимого, которое можно назвать. Я попробовал следующее:
<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'. 'Button' - под элементом 'UserControl1', который уже имя, зарегистрированное, когда оно было определено в другой области.
Если я удалю buttonName, тогда он компилируется, однако мне нужно иметь возможность указывать содержимое. Как я могу достичь этого?
Ответы
Ответ 1
Кажется, это невозможно, если используется XAML. Пользовательские элементы управления кажутся чрезмерными, когда у меня есть все элементы управления, в которых я нуждаюсь, но просто нужно сгруппировать их вместе с небольшим количеством логических элементов и разрешить именованный контент.
Решение на JD blog, как предполагает макенир, похоже, имеет лучший компромисс. Способ расширения 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, внутри которого я хочу, чтобы отображался именованный контент.
Ответ 2
Ответ заключается в том, чтобы не использовать 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>
Ответ 3
Другая альтернатива, которую я использовал, - это просто установить свойство Name
в событии Loaded
.
В моем случае у меня был довольно сложный элемент управления, который я не хотел создавать в коде, и он искал дополнительный элемент управления с определенным именем для определенного поведения, и поскольку я заметил, что могу установить name в DataTemplate
Я решил, что могу сделать это и в событии Loaded
.
private void Button_Loaded(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
b.Name = "buttonName";
}
Ответ 4
Иногда вам может понадобиться ссылка на элемент из С#. В зависимости от варианта использования вы можете установить x:Uid
вместо x:Name
и получить доступ к элементам, вызвав метод поиска Uid, например Получить объект по его Uid в WPF.
Ответ 5
Я решил создать дополнительное свойство для каждого элемента, который мне нужно получить:
public FrameworkElement First
{
get
{
if (Controls.Count > 0)
{
return Controls[0];
}
return null;
}
}
Это позволяет мне получить доступ к дочерним элементам в XAML:
<TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/>
Ответ 6
Вы можете использовать этот помощник для заданного имени внутри пользовательского элемента управления:
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 получает ваше управляющее имя и ваше имя класса для установки "Контроль над свойством".
Ответ 7
<Popup>
<TextBox Loaded="BlahTextBox_Loaded" />
</Popup>
Код позади:
public TextBox BlahTextBox { get; set; }
private void BlahTextBox_Loaded(object sender, RoutedEventArgs e)
{
BlahTextBox = sender as TextBox;
}
Реальное решение было бы для Microsoft решить эту проблему, а также все остальные с разбитыми визуальными деревьями и т.д. Гипотетически.
Ответ 8
Еще одно обходное решение: ссылайтесь на элемент как RelativeSource.
Ответ 9
У меня была та же проблема с помощью TabControl, когда вы добавляли кучу именованных элементов управления.
Мое обходное решение заключалось в использовании шаблона управления, который содержит все мои элементы управления, которые будут отображаться на закладке. Внутри шаблона вы можете использовать свойство Name, а также привязать данные к свойствам именованного элемента управления из других элементов управления, по крайней мере, внутри одного и того же шаблона.
В качестве содержимого элемента управления TabItem используйте простой элемент управления и установите ControlTemplate соответственно:
<Control Template="{StaticResource MyControlTemplate}"/>
Доступ к указанному элементу управления внутри шаблона из кода позади вам нужно будет использовать визуальное дерево.