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

30

我正在尝试以编程方式动态地向UserControl添加控件。 我从业务层获取了一个对象的通用列表(从数据库检索),并且对于每个对象,我想要向WPF UserControl中添加一个Label和一个TextBox,并设置位置和宽度以使其看起来漂亮,并希望利用WPF的验证功能。 在Windows Forms编程中,这很容易实现,但我是WPF的新手。 请问如何实现这一点?(请参见注释中的问题)假设以下是我的对象:

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

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

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;
}
3个回答

28

好的,第二次尝试。根据您的布局截图,我可以立刻推断出您需要的是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略有变化。标签仍然绑定到名称属性,但现在文本框宽度绑定到长度属性(这样您就可以拥有不同长度的文本框)。此外,我使用了简单的BoolToVisibilityConverter(您可以在任何地方找到代码,我不会在此发布)将“*”添加到任何必填字段。

最重要的是,在我们的ListBoxItemsPanel属性中使用了WrapPanel。这告诉ListBox生成的任何项都需要被推入水平换行布局(与您的屏幕截图相匹配)。更好的是,面板上的高度和宽度绑定-这意味着,“使此面板与我的父窗口大小相同”。这意味着当我调整Window大小时,WrapPanel会相应地调整其大小,从而为项提供更好的布局。


问题是,我可能通过使用变量名“MyGrid”混淆了问题-我正在将它们添加到<Grid />而不是DataGrid中。这是一个数据输入表单,我必须按特定顺序输出。一行可能有3个标签/文本框组合,文本框的长度各不相同,下一行可能有10个标签/文本框组合。由于存在不同版本的此表单,因此从数据库加载字段,并且这就是为什么我称其为动态的原因。因此,它不适合DataGrid布局,这就是我尝试动态创建它们的原因。假设对象中还有另一个字段-LastFieldOnLine,以指定新行。 - user210757
我似乎需要能够使用不同版本的“表单”对象,其中每个字段都是成员。 - user210757
你能给我展示一下你想要的样子的截图吗?我相信无论是什么,都可以通过ItemsControls做得更好。 - Charlie
啊哈。那确实是一个奇怪的布局。但是在WPF中,一切皆有可能!我很快会更新我的示例代码来演示。 - Charlie
标记为答案。我需要在WPF方面进行更多的阅读。谢谢! - user210757
显示剩余2条评论

18

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

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


8
我很少会断言某些事情“完全错误”,但有两件事我可以明确说是错的。一件是使用正则表达式解析XML,另一件是在WPF应用程序中使用WinForms技术。 - Robert Rossney

13
我会听取Charlie和Jobi的回答,但为了直接回答问题...(如何添加控件并手动定位。)
使用Canvas控件而不是Grid控件。Canvas给控件提供了无限的空间,并允许您手动定位它们。它使用附加属性来跟踪位置。在代码中,它看起来像这样:
var tb = new TextBox();
myCanvas.Children.Add(tb);
tb.Width = 100;
Canvas.SetLeft(tb, 50);
Canvas.SetTop(tb, 20);

XAML是一种用于定义用户界面和组合应用程序服务的语言。在WPF、Silverlight以及Windows Phone应用程序中,它被广泛使用。XAML被设计为可读性强且易于理解,它使得开发者可以使用声明性方式来定义用户界面。

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

你也可以相对于右边和底部定位它们。如果同时指定顶部和底部,控件将随着画布垂直调整大小。左侧和右侧同理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接