【问题标题】:How to create a WPF UserControl with NAMED content如何使用命名内容创建 WPF 用户控件
【发布时间】:2010-10-19 13:49:25
【问题描述】:

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

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

<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。
  • 这个人发现了一个solution,涉及删除他的自定义控件的 XAML 文件,并以编程方式构建自定义控件的 UI。这个blog post 在这个问题上有更多要说的。
  • 为什么不用ResourceDictionary的方式呢?在其中定义 DataTemplate。或者使用 BasedOn 关键字来继承控件。在 WPF 中执行代码隐藏 UI 之前,我会遵循一些路径...

标签: c# wpf xaml user-controls controls


【解决方案1】:

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

然后使用样式指定内容

<Style TargetType="control:MyFunkyControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:MyFunkyControl">
                <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>

【讨论】:

  • 我发现这实际上是最舒适的解决方案,因为您可以使用设计器在普通 UserControl 中充实 ControlTemplate 并将其转换为具有关联控件模板的样式。
  • 嗯,它对你有用有点奇怪,因为我尝试应用这种方法,但就我而言,我仍然遇到这个臭名昭著的错误。
  • @greenoldman @Badiboy 我想我知道为什么它不适合你。您可能只是将现有代码从UserControl 更改为继承ContentControl。要解决此问题,只需添加新类(not XAML with CS)。然后它会(希望)工作。如果你喜欢,我创建了一个小的VS2010 solution
  • 多年后,我希望我能再次投票:)
  • 我猜HeadingContainerMyFunkyContainer 应该是MyFunkyControl?!
【解决方案2】:

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

JD's blog 上的解决方案正如 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,我希望我的命名内容出现在其中。

【讨论】:

  • 我发现在使用这种重新设置内容的机制时会遇到数据绑定问题。数据绑定似乎设置正确,但从数据源初始填充控件时无法正常工作。我认为问题仅限于不是内容演示者的直接子控件。
  • 从那以后没有机会对此进行试验,但是到目前为止,我没有遇到任何数据绑定到 UserControlDefinedInXAML 中定义的控件(来自上面的示例)或添加到 ContentPresenter 的控件的问题。我一直只通过代码进行数据绑定(不是 XAML - 不确定这是否会有所不同)。
  • 看来它确实有所作为。在您描述的情况下,我刚刚尝试将 XAML 用于我的数据绑定,但它不起作用。但如果我在代码中设置它,它确实有效!
  • 非常感谢!经过 1 天的研究,终于有帮助了!
【解决方案3】:

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

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

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

【讨论】:

  • 如果您这样做,那么使用该名称的绑定将不起作用...除非您在后面的代码中设置绑定。
【解决方案4】:

有时您可能只需要引用 C# 中的元素。根据用例,您可以设置x:Uid 而不是x:Name,并通过调用像Get object by its Uid in WPF 这样的Uid finder 方法来访问元素。

【讨论】:

    【解决方案5】:

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

    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。

    【讨论】:

      【解决方案6】:

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

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

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

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

      【讨论】:

        【解决方案7】:
        <Popup>
            <TextBox Loaded="BlahTextBox_Loaded" />
        </Popup>
        

        后面的代码:

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

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

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

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

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

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

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

            【讨论】:

              【解决方案10】:

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

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

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

              我要指出的是,上面的 XAML 没有命名它派生的控件,并且模板中的所有内容都使用相对查找来绑定其属性;甚至是定制的。

              在 C# 方面,我使用 Xaml 中的控件模板的 Base64 编码版本和指令来调整控件的开发/发布版本。理论是我在开发空间中的控件不会遇到这个主题所涉及的问题,但会给我一种测试/开发它们的方法。发布的 DLL 版本似乎运行良好,并且所构建的控件确实具有很好的设计时支持,就像它们在调试/开发方面所做的那样。

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

              在创建要在主应用程序中使用的“发布”控件 DLL 之前要记住的关键事项是使用最新和最好的控件模板版本更新您的 base64 编码字符串。这是因为 Release 版本与原始 Xaml 完全分离,完全依赖于编码版本。

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

              重要提示:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-02-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多