如何使用NAMED内容创buildWPF UserControl

我有一组控件,附带的命令和逻辑不断以相同的方式重用。 我决定创build一个拥有所有通用控件和逻辑的用户控件。

不过,我也需要控制能够保存可以命名的内容。 我尝试了以下内容:

<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> 

我收到以下错误:

无法在元素“button”上设置名称属性值“buttonName”。 “button”在元素“UserControl1”的范围内,当在另一个范围中定义时,它已经有了一个名字注册。

如果我删除buttonName,然后编译,但是我需要能够命名的内容。 我怎样才能做到这一点?

答案是不要使用UserControl来做到这一点。

创build一个扩展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的博客上的解决scheme,作为马克尼尔build议,似乎有最好的妥协。 扩展JD解决scheme以允许在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; } 

在我上面的例子中,我创build了一个名为UserControlDefinedInXAML的用户控件,它的定义与使用XAML的任何普通用户控件一样。 在我的UserControlDefinedInXAML中,我有一个名为aStackPanel的StackPanel,我想让我的命名内容出现。

我使用的另一种方法是在Loaded事件中设置Name属性。

在我的情况下,我有一个相当复杂的控制,我不想在代码隐藏中创build,它寻找一个具有特定名称的可选控件的特定行为,因为我注意到我可以设置名称在一个DataTemplate我想我也可以在Loaded事件中做到这一点。

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

有时你可能只需要引用C#中的元素。 根据用例,您可以设置一个x:Uid而不是x:Name并通过在WPF中调用Uid查找程序方法(如获取对象的Uid)来访问元素。

我select为每个我需要得到的元素创build一个额外的属性:

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

这使我能够访问XAML中的子元素:

 <TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/> 

您可以在用户控件中使用此帮助器来设置名称:

 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获取您的控件名称和您的类名称设置控制属性。

 <Popup> <TextBox Loaded="BlahTextBox_Loaded" /> </Popup> 

代码后面:

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

真正的解决办法是微软解决这个问题,以及所有其他与视觉树木破碎等。假设来说。

又一个解决方法:引用元素作为RelativeSource

放置一堆命名控件时,使用TabControl时遇到同样的问题。

我的解决方法是使用包含我的所有控件的控件模板在标签页中显示。 在模板内部,您可以使用Name属性,也可以将数据绑定到至less在同一模板内的其他控件的指定控件的属性。

作为TabItem控件的内容,使用一个简单的Control并相应地设置ControlTemplate:

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

在模板后面的代码中访问这些命名的控件将需要使用可视化树。