如何创建具有NAMED内容的WPF用户控件


100

我有一组带有附加命令和逻辑的控件,它们以相同的方式不断重复使用。我决定创建一个包含所有通用控件和逻辑的用户控件。

但是,我还需要控件能够容纳可以命名的内容。我尝试了以下方法:

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

我收到以下错误:

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

如果删除buttonName,它将进行编译,但是我需要能够命名内容。我该如何实现?


这是一个巧合。我正要问这个问题!我也有同样的问题。将常见的UI模式分解为UserControl,但希望按名称引用内容UI。
Mackenir

3
这个家伙找到了一个解决方案,其中涉及摆脱他的自定义控件的XAML文件,并以编程方式构建自定义控件的UI。这篇博客文章对此话题还有更多的话要说。
mackenir

2
为什么不使用ResourceDictionary方式?在其中定义DataTemplate。或使用BasedOn关键字继承控件。在WPF中执行代码后台UI之前,我将遵循一些路径……
Louis Kottmann 2011年

Answers:


46

答案是不要使用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>

2
我发现这实际上是最舒适的解决方案,因为您可以使用设计器来充实普通用户控件中的ControlTemplate并将其转换为具有关联控件模板的样式。
奥利弗·维希霍尔德

5
嗯,它对您有用,这有点奇怪,因为我尝试应用这种方法,但就我而言,我仍然遇到这个臭名昭著的错误。
greenoldman 2011年

8
@greenoldman @Badiboy我想我知道为什么它对您不起作用。您可能只是将现有代码从更改UserControl为继承ContentControl。要解决此问题,只需添加新的类(而不是带有CS的XAML)。然后它将(希望)起作用。如果您愿意,我创建了一个小型VS2010解决方案
itsho 2013年

1
多年以后,我希望我能再次投票赞成:)
Drew Noakes

2
我猜HeadingContainer并且MyFunkyContainer注定是MyFunkyControl?!
马丁·施耐德

20

当使用XAML时,这似乎是不可能的。当我实际拥有所需的所有控件时,自定义控件似乎是一个过大的杀伤力,但只需要将它们与少量逻辑组合在一起并允许命名的内容即可。

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,我希望在其中显示我的命名内容。


我发现使用这种重新创建内容的机制时,我遇到了数据绑定问题。数据绑定似乎设置正确,但是从数据源初始填充控件无法正常工作。我认为问题仅限于不是内容演示者直接子对象的控件。
mackenir

此后没有机会进行此实验,但是到目前为止,与UserControlDefinedInXAML中定义的控件(来自上述示例)或添加到ContentPresenter的控件的数据绑定都没有任何问题。我只是通过代码进行数据绑定(不是XAML-不确定是否会有所作为)。
瑞安

看来确实有所作为。在您描述的情况下,我只是尝试将XAML用于我的数据绑定,但是它不起作用。但是,如果我在代码中设置它,它将起作用!
Ryan

3

我使用的另一种选择是仅NameLoaded事件中设置属性。

就我而言,我有一个相当复杂的控件,我不想在其背后的代码中创建,它会为特定行为寻找一个具有特定名称的可选控件,并且由于我注意到我可以在一个DataTemplate我想我也可以做到这Loaded一点。

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

2
如果这样做,则使用名称的绑定将不起作用...除非您在后面的代码中设置了绑定。
2012年


1

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

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。


0

我选择为需要获取的每个元素创建一个额外的属性:

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

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

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

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

后面的代码:

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

真正的解决方案是让Microsoft修复此问题,以及所有其他损坏的可视树等的问题。



0

将一堆命名控件放入其中时,我使用TabControl遇到了相同的问题。

我的解决方法是使用控件模板,其中包含我的所有控件,这些控件将显示在选项卡页面中。在模板内部,您可以使用Name属性,也可以将数据至少从同一模板内部的其他控件绑定到命名控件的属性。

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

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

从后面的代码访问模板内部的那些命名控件,您将需要使用可视化树。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.