C# 如何使用 NAMED 内容创建 WPF UserControl

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/751325/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 23:46:02  来源:igfitidea点击:

How to create a WPF UserControl with NAMED content

c#wpfxamluser-controlscontrols

提问by Ryan

I have a set of controls with attached commands and logic that are constantly reused in the same way. I decided to create a user control that holds all the common controls and logic.

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

However I also need the control to be able to hold content that can be named. I tried the following:

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

<UserControl.ContentTemplate>
    <DataTemplate>
        <Button>a reused button</Button>
        <ContentPresenter Content="{TemplateBinding Content}"/>
        <Button>a reused button</Button>
    </DataTemplate>
</UserControl.ContentTemplate>

However it seems any content placed inside the user control cannot be named. For example if I use the control in the following way:

但是,似乎无法命名放置在用户控件内的任何内容。例如,如果我按以下方式使用控件:

<lib:UserControl1>
     <Button Name="buttonName">content</Button>
</lib:UserControl1>

I receive the following error:

我收到以下错误:

Cannot set Name attribute value 'buttonName' on element 'Button'. 'Button' is under the scope of element 'UserControl1', which already had a name registered when it was defined in another scope.

无法在元素“Button”上设置 Name 属性值“buttonName”。'Button' 位于元素 'UserControl1' 的范围内,该元素在另一个范围中定义时已经注册了一个名称。

If I remove the buttonName, then it compiles, however I need to be able to name the content. How can I achieve this?

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

采纳答案by Ryan

It seems this is not possible when XAML is used. Custom controls seem to be a overkill when I actually have all the controls I need, but just need to group them together with a small bit of logic and allow named content.

当使用 XAML 时,这似乎是不可能的。当我实际上拥有我需要的所有控件时,自定义控件似乎是一种矫枉过正,但只需要用一点逻辑将它们组合在一起并允许命名内容。

The solution on JD's blogas mackenir suggests, seems to have the best compromise. A way to extend JD's solution to allow controls to still be defined in XAML could be as follows:

正如 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;           
    }

In my example above I have created a user control called UserControlDefinedInXAML which is define like any normal user controls using XAML. In my UserControlDefinedInXAML I have a StackPanel called aStackPanel within which I want my named content to appear.

在我上面的示例中,我创建了一个名为 UserControlDefinedInXAML 的用户控件,它的定义与使用 XAML 的任何普通用户控件一样。在我的 UserControlDefinedInXAML 中,我有一个名为 aStackPanel 的 StackPanel,我希望在其中显示我的命名内容。

回答by Sybrand

The answer is to not use a UserControl to do it.

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

Create a class that extends ContentControl

创建一个扩展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; }
}

then use a style to specify the contents

然后使用样式来指定内容

<Style TargetType="control:MyFunkyControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:MyFunkyContainer">
                <Grid>
                    <ContentControl Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

and finally - use it

最后 - 使用它

<control:MyFunkyControl Heading="Some heading!">            
    <Label Name="WithAName">Some cool content</Label>
</control:MyFunkyControl>

回答by Rachel

Another alternative I've used is to just set the Nameproperty in the Loadedevent.

我使用的另一个替代方法是NameLoaded事件中设置属性。

In my case, I had a rather complex control which I didn't want to create in the code-behind, and it looked for an optional control with a specific name for certain behavior, and since I noticed I could set the name in a DataTemplateI figured I could do it in the Loadedevent too.

就我而言,我有一个相当复杂的控件,我不想在代码隐藏中创建它,它为某些行为寻找具有特定名称的可选控件,并且因为我注意到我可以在DataTemplate我想我也可以在Loaded活动中做到这一点。

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

回答by aliceraunsbaek

I've chosen to create an extra property for each element I need to get:

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

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

This enables me to access the child elements in XAML:

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

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

回答by Dani

Sometimes you might just need to reference the element from C#. Depending on the use case, you can then set an x:Uidinstead of an x:Nameand access the elements by calling a Uid finder method like Get object by its Uid in WPF.

有时您可能只需要从 C# 引用元素。根据用例,您可以设置 anx:Uid而不是 anx:Name并通过调用 Uid finder 方法(如WPF 中的 Uid 获取对象)来访问元素。

回答by Ali Yousefi

You can use this helper for set name inside the user control:

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

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);
            }
        }
    }
}

and your Window or Control Code Behind set you control by Property:

和您的窗口或控制代码隐藏设置您通过属性控制:

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

    }

    public Button BtnOK { get; set; }
}

your window xaml:

你的窗口 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 get your control name and your Class name for set Control to Property.

UserControlNameHelper 获取您的控件名称和类名称,以便将控件设置为属性。

回答by 15ee8f99-57ff-4f92-890c-b56153

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

Code behind:

后面的代码:

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

The real solution would be for Microsoft to fix this issue, as well as all the others with broken visual trees etc. Hypothetically speaking.

真正的解决方案是让微软解决这个问题,以及所有其他视觉树损坏等问题。假设地说。

回答by voccoeisuoi

Yet another workaround: reference the element as RelativeSource.

另一种解决方法:将元素引用为RelativeSource

回答by David Wellna

I had the same problem using a TabControl when placing a bunch of named controls into.

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

My workaround was to use a control template which contains all my controls to be shown in a tab page. Inside the template you can use the Name property and also data bind to properties of the named control from other controls at least inside the same template.

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

As Content of the TabItem Control, use a simple Control and set the ControlTemplate accordingly:

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

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

Accessing those named control inside the template from code behind you would need to use the visual tree.

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