以编程方式将控件添加到 WPF 表单

Posted

技术标签:

【中文标题】以编程方式将控件添加到 WPF 表单【英文标题】:Programmatically Add Controls to WPF Form 【发布时间】:2011-02-18 09:10:24 【问题描述】:

我正在尝试动态(以编程方式)将控件添加到 UserControl。我从我的业务层(从数据库中检索)获得了一个通用的对象列表,对于每个对象,我想向 WPF 用户控件添加一个标签和一个文本框,并设置位置和宽度以使其看起来不错,并希望利用 WPF 验证功能。这在 Windows 窗体编程中很容易,但我是 WPF 新手。我该怎么做(请参阅 cmets 了解问题)说这是我的对象:

public class Field 
   public string Name  get; set; 
   public int Length  get; set; 
   public bool Required  get; set; 

然后在我的 WPF UserControl 中,我尝试为每个对象创建一个标签和文本框:

public void createControls() 
    List<Field> fields = businessObj.getFields();

    Label label = null;
    TextBox textbox = null;

    foreach (Field field in fields) 
        label = new Label();
        // HOW TO set text, x and y (margin), width, validation based upon object? 
        // i have tried this without luck:
        // Binding b = new Binding("Name");
        // BindingOperations.SetBinding(label, Label.ContentProperty, b);
        MyGrid.Children.Add(label);

        textbox = new TextBox();
        // ???
        MyGrid.Children.Add(textbox);
    
    // databind?
    this.DataContext = fields;

【问题讨论】:

【参考方案1】:

好的,第二次是魅力。根据您的布局屏幕截图,我可以立即推断出您需要的是WrapPanel,这是一个布局面板,允许项目填充直到它到达边缘,此时剩余的项目流到下一行。但是您仍然想使用ItemsControl,这样您就可以获得数据绑定和动态生成的所有好处。所以为此我们将使用ItemsControl.ItemsPanel 属性,它允许我们指定项目将被放入的面板。让我们再次从代码隐藏开始:

public partial class Window1 : Window

    public ObservableCollection<Field> Fields  get; set; 

    public Window1()
    
        InitializeComponent();

        Fields = new ObservableCollection<Field>();
        Fields.Add(new Field()  Name = "Username", Length = 100, Required = true );
        Fields.Add(new Field()  Name = "Password", Length = 80, Required = true );
        Fields.Add(new Field()  Name = "City", Length = 100, Required = false );
        Fields.Add(new Field()  Name = "State", Length = 40, Required = false );
        Fields.Add(new Field()  Name = "Zipcode", Length = 60, Required = false );

        FieldsListBox.ItemsSource = Fields;
    


public class Field

    public string Name  get; set; 
    public int Length  get; set; 
    public bool Required  get; set; 

这里没有太大变化,但我编辑了示例字段以更好地匹配您的示例。现在让我们看看魔法发生在哪里——Window 的 XAML:

<Window x:Class="DataBoundFields.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataBoundFields"
Title="Window1" Height="200" Width="300">
<Window.Resources>
    <local:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<Grid>
    <ListBox x:Name="FieldsListBox">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label Content="Binding Name" VerticalAlignment="Center"/>
                    <TextBox Width="Binding Length" Margin="5,0,0,0"/>
                    <Label Content="*" Visibility="Binding Required, Converter=StaticResource BoolToVisConverter"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal" 
                           Height="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type Window, Path=ActualHeight"
                           Width="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type Window, Path=ActualWidth"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Grid>

首先,您会注意到ItemTemplate 略有变化。标签仍然绑定到 name 属性,但现在文本框宽度绑定到 length 属性(因此您可以拥有不同长度的文本框)。此外,我还使用简单的BoolToVisibilityConverter 在任何必填字段中添加了“*”(您可以在任何地方找到代码,我不会在这里发布)。

主要需要注意的是WrapPanel 在我们的ListBoxItemsPanel 属性中的使用。这告诉ListBox 它生成的任何项目都需要被推送到水平包装布局中(这与您的屏幕截图相符)。使这项工作变得更好的是面板上的高度和宽度绑定——这就是说,“使这个面板与我的父窗口大小相同”。这意味着当我调整Window 的大小时,WrapPanel 会相应地调整其大小,从而为项目带来更好的布局。

【讨论】:

+1,但我认为 OP 想要 Length 和 Required 来约束输入。 当然可以,但我不确定他到底想如何使用这些约束,所以我没有使用它们。 麻烦是——我可能会因为使用变量名“MyGrid”而混淆了问题——我将它们添加到 ,而不是 DataGrid。这是我必须输出的数据输入表单以特定的顺序。可能有 3 个标签/文本框组合,其中 texboxes 在一行上有不同的长度,在下一行有 10 个。这个表单有不同的版本,这就是为什么从数据库中加载字段的原因,以及为什么我称之为动态的。所以它不适合 DataGrid 布局,这就是我试图动态创建它们的原因。假设我在对象中有另一个字段 - LastFieldOnLine 来指定新行 这就好像我需要能够使用不同版本的“表单”对象,每个字段都作为成员。 你能给我看一张你想要的截图吗?我相信无论是什么,使用 ItemsControls 都可以做得更好。【参考方案2】:

我会听查理和乔比的回答,但为了直接回答问题……(如何添加控件并手动定位它们。)

使用Canvas 控件,而不是Grid。画布为控件提供了无限的空间,并允许您手动定位它们。它使用附加属性来跟踪位置。在代码中,它看起来像这样:

var tb = new TextBox();
myCanvas.Children.Add(tb);
tb.Width = 100;
Canvas.SetLeft(tb, 50);
Canvas.SetTop(tb, 20);

在 XAML 中...

<Canvas>
  <TextBox Width="100" Canvas.Left="50" Canvas.Top="20" />
</Canvas>

您还可以相对于右边缘和下边缘定位它们。同时指定 Top 和 Bottom 将使控件随 Canvas 垂直调整大小。 Left 和 Right 也是如此。

【讨论】:

【参考方案3】:

不建议添加这样的控件。您在 WPF 中理想的做法是放置一个 ListBox(或 ItemsControl)并将您的业务对象集合绑定为 itemsControl.ItemsSource 属性。现在在 XAML 中为您的 DataObject 类型定义 DataTemplate,您就可以开始了,这就是 WPF 的魔力。

来自 winforms 背景的人倾向于按照您描述的方式进行操作,这在 WPF 中是不正确的。

【讨论】:

+1。很少有事情我愿意直截了当地说:“这是错误的。”使用正则表达式解析 XML 就是其中之一。在 WPF 应用程序中使用 WinForms 技术是另一回事。

以上是关于以编程方式将控件添加到 WPF 表单的主要内容,如果未能解决你的问题,请参考以下文章

C# WPF 如何以编程方式设置控件的位置、宽度和高度?

如何通过行和列索引以编程方式访问 WPF Grid 中的控件?

当鼠标在 UC 区域之外时,WPF 用户控件可以检测到 MouseMovement

动态创建到 WPF 表单的用户控件上的按钮单击事件

以编程方式将列和行添加到 WPF Datagrid

Flash / AS3 ...设计人员如何修改以编程方式添加的控件?