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

C# 如何使用命名内容创建WPF UserControl,c#,wpf,xaml,user-controls,controls,C#,Wpf,Xaml,User Controls,Controls,我有一组带有附加命令和逻辑的控件,它们以相同的方式不断重复使用。我决定创建一个包含所有常用控件和逻辑的用户控件 但是,我还需要控件能够保存可以命名的内容。我尝试了以下方法: 重复使用的按钮 重复使用的按钮 但是,似乎无法命名放置在用户控件内的任何内容。例如,如果我以以下方式使用控件: 内容 我收到以下错误: 无法设置名称属性值“buttonName” 在元素“按钮”上“按钮”是 在要素范围内 “UserControl1”,它已具有 在中定义时注册的名称 另一个范围 如果我删除button

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

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


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


内容
我收到以下错误:

无法设置名称属性值“buttonName” 在元素“按钮”上“按钮”是 在要素范围内 “UserControl1”,它已具有 在中定义时注册的名称 另一个范围


如果我删除buttonName,它就会编译,但是我需要能够命名内容。如何实现这一点?

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

麦肯尼建议的解决方案似乎是最好的折衷方案。扩展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;           
    }

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

答案是不要使用UserControl来执行此操作

创建一个扩展ContentControl的类

public class MyFunkyControl : ContentControl
{
    public static readonly DependencyProperty HeadingProperty =
        DependencyProperty.Register("Heading", typeof(string),
        typeof(MyFunkyControl), new PropertyMetadata(HeadingChanged));

    private static void HeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((MyFunkyControl) d).Heading = e.NewValue as string;
    }

    public string Heading { get; set; }
}
然后使用样式指定内容


最后,使用它


一些很酷的内容

我使用的另一种选择是在
加载的
事件中设置
名称
属性

在我的例子中,我有一个相当复杂的控件,我不想在代码隐藏中创建它,它为某些行为寻找一个具有特定名称的可选控件,因为我注意到我可以在
DataTemplate
中设置名称,我想我也可以在
Loaded
事件中进行设置

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

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

    public FrameworkElement First
    {
        get
        {
            if (Controls.Count > 0)
            {
                return Controls[0];
            }
            return null;
        }
    }
这使我能够访问XAML中的子元素:

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


有时您可能只需要引用C#中的元素。根据用例的不同,您可以设置
x:Uid
而不是
x:Name
,并通过调用Uid finder方法访问元素,如。

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

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>

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

还有另一个解决方法:将元素引用为相对资源

在将一组命名控件放置到中时,使用TabControl时遇到了相同的问题

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

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

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


从代码隐藏中访问模板中的命名控件需要使用可视化树。

我遇到了这个问题,找到了一个解决方法,可以使用Xaml设计自定义控件。它仍然有一点黑客,但它解决了我所有的问题,没有任何明显的妥协

基本上,您可以按照通常使用xaml的方式执行所有操作,但也可以在控制模板本身上包含一些头声明,并对要加载到代码构造函数中的模板进行Base64编码。在这个Xaml摘录中没有显示,但是我的完整Xaml使用的名称空间实际上是针对一个XamlTemplates而不是控件名称空间。这是故意的,因为“Release”构建将开发调试引用从我的生产控件命名空间中移出。下面将详细介绍

<ControlTemplate TargetType="{x:Type TabControl}" 
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="templateRoot" 
          ClipToBounds="True" 
          SnapsToDevicePixels="True" 
          Background="Transparent"
          KeyboardNavigation.TabNavigation="Local">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="ColumnDefinition0"/>
            <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
            <RowDefinition x:Name="RowDefinition1" Height="*"/>
        </Grid.RowDefinitions>
        <TabPanel x:Name="HeaderPanel"
                  Panel.ZIndex="1"                          
                  Margin="{Binding MarginHeaderPanel, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  IsItemsHost="True"                          
                  KeyboardNavigation.TabIndex="2"/>
        <Border x:Name="blankregion" Panel.ZIndex="1" Margin="0" Padding="0" 
                Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}">
            <ContentPresenter x:Name="blankpresenter"                                      
                              KeyboardNavigation.TabIndex="1"    
                              Content="{Binding TabBlankSpaceContent, RelativeSource={RelativeSource AncestorType=TabControl}}"                                          
                              ContentSource="TabBlankSpaceContent" 
                              SnapsToDevicePixels="True"/>
        </Border>

        <Grid x:Name="ContentPanel">
            <Border 
                BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=TabControl}}"
                BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType=TabControl}}"                       
                Background="{Binding SelectedItem.Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.DirectionalNavigation="Contained" 
                KeyboardNavigation.TabNavigation="Local"                                           
                CornerRadius="{Binding BorderRadius, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.TabIndex="3">
                <ContentControl x:Name="PART_SelectedContentHost" 
                                ContentTemplate="{Binding SelectedContentTemplate, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                Content="{Binding SelectedContent, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                ContentStringFormat="{Binding SelectedContentStringFormat, RelativeSource={RelativeSource AncestorType=TabControl}}" 
                                Margin="{Binding Padding, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                SnapsToDevicePixels="{Binding SnapsToDevicePixels, RelativeSource={RelativeSource AncestorType=TabControl}}"/>
            </Border>

        </Grid>
    </Grid>
    <ControlTemplate.Triggers>
        <!--Triggers were removed for clarity-->
    </ControlTemplate.Triggers>
</ControlTemplate>
在创建要在主应用程序中使用的“release”控件DLL之前,要记住的关键一点是,使用其控件模板的最新和最好版本更新base64编码字符串。这是因为发布版本与原始Xaml完全分离,并且完全依赖于编码版本

上面的控件和其他类似控件可以在上找到。这是我正在制作的一个库,旨在“解锁”许多我想要设置的样式,而标准控件不会公开这些样式。并添加了一些不存在的功能。例如,上面的TabControl有一个额外的内容属性,用于利用选项卡标题的“未使用”区域

重要提示:
  • 使用此方法会丢失基本样式,但如果自定义控件的样式使用
    BasedOn=“{StaticResource{x:Type TabControl}}}”
    机制,则可以恢复基本样式
  • 我需要找时间研究这是否会导致任何值得注意的内存泄漏,以及我是否可以采取任何措施来应对它们,如果有人对此有任何想法,请在评论中告诉我

这是巧合。我正要问这个问题!我也有同样的问题。考虑商品
<ControlTemplate TargetType="{x:Type TabControl}" 
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="templateRoot" 
          ClipToBounds="True" 
          SnapsToDevicePixels="True" 
          Background="Transparent"
          KeyboardNavigation.TabNavigation="Local">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="ColumnDefinition0"/>
            <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
            <RowDefinition x:Name="RowDefinition1" Height="*"/>
        </Grid.RowDefinitions>
        <TabPanel x:Name="HeaderPanel"
                  Panel.ZIndex="1"                          
                  Margin="{Binding MarginHeaderPanel, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  IsItemsHost="True"                          
                  KeyboardNavigation.TabIndex="2"/>
        <Border x:Name="blankregion" Panel.ZIndex="1" Margin="0" Padding="0" 
                Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}">
            <ContentPresenter x:Name="blankpresenter"                                      
                              KeyboardNavigation.TabIndex="1"    
                              Content="{Binding TabBlankSpaceContent, RelativeSource={RelativeSource AncestorType=TabControl}}"                                          
                              ContentSource="TabBlankSpaceContent" 
                              SnapsToDevicePixels="True"/>
        </Border>

        <Grid x:Name="ContentPanel">
            <Border 
                BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=TabControl}}"
                BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType=TabControl}}"                       
                Background="{Binding SelectedItem.Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.DirectionalNavigation="Contained" 
                KeyboardNavigation.TabNavigation="Local"                                           
                CornerRadius="{Binding BorderRadius, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.TabIndex="3">
                <ContentControl x:Name="PART_SelectedContentHost" 
                                ContentTemplate="{Binding SelectedContentTemplate, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                Content="{Binding SelectedContent, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                ContentStringFormat="{Binding SelectedContentStringFormat, RelativeSource={RelativeSource AncestorType=TabControl}}" 
                                Margin="{Binding Padding, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                SnapsToDevicePixels="{Binding SnapsToDevicePixels, RelativeSource={RelativeSource AncestorType=TabControl}}"/>
            </Border>

        </Grid>
    </Grid>
    <ControlTemplate.Triggers>
        <!--Triggers were removed for clarity-->
    </ControlTemplate.Triggers>
</ControlTemplate>
#if DEBUG
namespace AgileBIM.Controls
{
    public class AgileTabControl : AgileBIM.XamlTemplates.AgileTabControlDesigner { }
}

namespace AgileBIM.XamlTemplates
#else
namespace AgileBIM.Controls
#endif
{
#if DEBUG    
    public partial class AgileTabControlDesigner : TabControl
#else
    public class AgileTabControl : TabControl
#endif
    {

        

#if DEBUG
        private static Type ThisControl = typeof(AgileTabControlDesigner);
#else
        private static Type ThisControl = typeof(AgileTabControl);
        private string Template64 = "Base64 encoded template removed for clarity"
#endif


#if DEBUG
        public AgileTabControlDesigner() { InitializeComponent(); }
#else
        public AgileTabControl()
        {
            string decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Template64));
            System.IO.StringReader sr = new System.IO.StringReader(decoded);
            System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr);
            ControlTemplate ct = (ControlTemplate)System.Windows.Markup.XamlReader.Load(xr);

            DefaultStyleKey = ThisControl;
            Template = ct;
        }
#endif

        public Thickness MarginHeaderPanel 
        {
            get { return (Thickness)GetValue(MarginHeaderPanelProperty); } 
            set { SetValue(MarginHeaderPanelProperty, value); } 
        }
        public static readonly DependencyProperty MarginHeaderPanelProperty =
            DependencyProperty.Register("MarginHeaderPanel", typeof(Thickness), ThisControl, new PropertyMetadata(new Thickness(0)));

        public CornerRadius BorderRadius 
        { 
            get { return (CornerRadius)GetValue(BorderRadiusProperty); } 
            set { SetValue(BorderRadiusProperty, value); }
        }
        public static readonly DependencyProperty BorderRadiusProperty =
            DependencyProperty.Register("BorderRadius", typeof(CornerRadius), ThisControl, new PropertyMetadata(new CornerRadius(0)));

        public object TabBlankSpaceContent 
        { 
            get { return (object)GetValue(TabBlankSpaceContentProperty); } 
            set { SetValue(TabBlankSpaceContentProperty, value); } 
        }
        public static readonly DependencyProperty TabBlankSpaceContentProperty =
            DependencyProperty.Register("TabBlankSpaceContent", typeof(object), ThisControl, new PropertyMetadata());
    }
}