我有一组带有附加命令和逻辑的控件,它们以相同的方式不断重用。我决定创建一个包含所有常用控件和逻辑的用户控件。
但是我还需要控件才能保存可以命名的内容。我尝试了以下方法:
<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”上设置Name属性值“buttonName”。 'Button'属于元素'UserControl1'的范围,在另一个范围内定义时,已经注册了一个名称。
如果我删除buttonName,然后编译,但我需要能够命名内容。我怎样才能做到这一点?
使用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中,我有一个名为aStackPanel的StackPanel,我希望在其中显示我的命名内容。
答案是不使用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>
有时你可能只需要引用C#中的元素。根据用例,您可以设置x:Uid
而不是x:Name
并通过调用Uid Finder方法来访问元素,例如 通过WPF中的Uid获取对象 。
我使用的另一个替代方法是在Name
事件中设置Loaded
属性。
在我的情况下,我有一个相当复杂的控件,我不想在代码隐藏中创建它,它寻找一个具有特定行为的特定名称的可选控件,因为我注意到我可以在一个DataTemplate
我想我也可以在Loaded
事件中做到这一点。
private void Button_Loaded(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
b.Name = "buttonName";
}
您可以在用户控件中使用此帮助程序设置名称:
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设置为Property。
<Popup>
<TextBox Loaded="BlahTextBox_Loaded" />
</Popup>
代码背后:
public TextBox BlahTextBox { get; set; }
private void BlahTextBox_Loaded(object sender, RoutedEventArgs e)
{
BlahTextBox = sender as TextBox;
}
真正的解决方案是微软解决这个问题,以及所有其他人用破坏的视觉树等。假设说。
另一种解决方法:将元素引用为 RelativeSource 。
我已经选择为我需要获得的每个元素创建一个额外的属性:
public FrameworkElement First
{
get
{
if (Controls.Count > 0)
{
return Controls[0];
}
return null;
}
}
这使我能够访问XAML中的子元素:
<TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/>
在将一堆命名控件放入时,使用TabControl时遇到了同样的问题。
我的解决方法是使用一个控件模板,其中包含要在选项卡页面中显示的所有控件。在模板内部,您可以使用Name属性,也可以使用数据绑定到其他控件的命名控件的属性,至少在同一模板中。
作为TabItem控件的内容,使用一个简单的Control并相应地设置ControlTemplate:
<Control Template="{StaticResource MyControlTemplate}"/>
从后面的代码访问模板中的那些命名控件需要使用可视化树。