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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2814866/
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-09-08 21:44:53  来源:igfitidea点击:

Programmatically Add Controls to WPF Form

wpfdynamiccontrols

提问by user210757

I am trying to add controls to a UserControl dynamically (programatically). I get a generic List of objects from my Business Layer (retrieved from the database), and for each object, I want to add a Label, and a TextBox to the WPF UserControl and set the Position and widths to make look nice, and hopefully take advantage of the WPF Validation capabilities. This is something that would be easy in Windows Forms programming but I'm new to WPF. How do I do this (see comments for questions) Say this is my object:

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

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

Then in my WPF UserControl, I'm trying to create a Label and TextBox for each object:

然后在我的 WPF UserControl 中,我尝试为每个对象创建一个 Label 和 TextBox:

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

采纳答案by Charlie

Alright, second time's the charm. Based on your layout screenshot, I can infer right away that what you need is a WrapPanel, a layout panel that allows items to fill up until it reaches an edge, at which point the remaining items flow onto the next line. But you still want to use an ItemsControlso you can get all the benefits of data-binding and dynamic generation. So for this we're going to use the ItemsControl.ItemsPanelproperty, which allows us to specify the panel the items will be put into. Let's start with the code-behind again:

好吧,第二次是魅力。根据您的布局屏幕截图,我可以立即推断出您需要的是一个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; }
}

Not much has changed here, but I've edited the sample fields to better match your example. Now let's look at where the magic happens- the XAML for the Window:

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

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

First, you will notice that the ItemTemplatehas changed slightly. The label is still bound to the name property, but now the textbox width is bound to the length property (so you can have textboxes of varying length). Furthermore, I've added a "*" to any fields that are required, using a simplistic BoolToVisibilityConverter(which you can find the code for anywhere, and I will not post here).

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

The main thing to notice is the use of a WrapPanelin the ItemsPanelproperty of our ListBox. This tells the ListBoxthat any items it generates need to be pushed into a horizontal wrapped layout (this matches your screenshot). What makes this work even better is the height and width binding on the panel- what this says is, "make this panel the same size as my parent window." That means that when I resize the Window, the WrapPaneladjusts its size accordingly, resulting in a better layout for the items.

要注意的主要事情是运用了WrapPanelItemsPanel我们的财产ListBox。这告诉ListBox它生成的任何项目都需要推入水平包装布局(这与您的屏幕截图相匹配)。使这项工作变得更好的是面板上的高度和宽度绑定 - 这就是说,“使这个面板与我的父窗口大小相同。” 这意味着当我调整 的大小时Window, 会WrapPanel相应地调整其大小,从而为项目提供更好的布局。

回答by Jobi Joy

It is not recommended to add controls like this. What you ideally do in WPF is to put a ListBox(or ItemsControl) and bind your Business object collection as the itemsControl.ItemsSource property. Now define DataTemplate in XAML for your DataObject type and you are good to go, That is the magic of WPF.

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

People come from a winforms background tend to do the way you described and which is not the right way in WPF.

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

回答by YotaXP

I would listen to Charlie and Jobi's answers, but for the sake of answering the question directly... (How to add controls and manually position them.)

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

Use a Canvascontrol, rather than a Grid. Canvases give the control an infinite amount of space, and allow you to position them manually. It uses attached properties to keep track of position. In code, it would look like so:

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

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

In XAML...

在 XAML...

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

You can also position them relative to the Right and Bottom edges. Specifying both a Top and Bottom will have the control resize vertically with the Canvas. Similarly for Left and Right.

您还可以相对于右边缘和下边缘定位它们。指定顶部和底部将使控件与画布垂直调整大小。左和右也类似。