Programmatically Add Controls to WPF Form

73,251

Solution 1

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 ItemsControl so you can get all the benefits of data-binding and dynamic generation. So for this we're going to use the ItemsControl.ItemsPanel property, which allows us to specify the panel the items will be put into. Let's start with the code-behind again:

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:

<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 ItemTemplate has 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).

The main thing to notice is the use of a WrapPanel in the ItemsPanel property of our ListBox. This tells the ListBox that 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 WrapPanel adjusts its size accordingly, resulting in a better layout for the items.

Solution 2

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.

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

Solution 3

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 Canvas control, 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:

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

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

Share:
73,251

Related videos on Youtube

user210757
Author by

user210757

Updated on July 09, 2022

Comments

  • user210757
    user210757 almost 2 years

    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:

    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:

    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;
    }
    
  • 500 - Internal Server Error
    500 - Internal Server Error about 14 years
    +1, but I think the OP wanted Length and Required to constrain the input.
  • Robert Rossney
    Robert Rossney about 14 years
    +1. There are very few things about which I'm willing to say, flatly, "This is just wrong." Parsing XML with regular expression is one. Using WinForms techniques in WPF applications is another.
  • Charlie
    Charlie about 14 years
    Of course, but I'm not sure how exactly he wants to use those constraints so I just left it without them.
  • user210757
    user210757 about 14 years
    trouble is-and I probably confused matters by using the variable name "MyGrid"-I am adding thes to a <Grid />,not a DataGrid.This is a data entry form I have to out in a specific order.There might be 3 label/textbox combminations with texboxes having varying lengths on one line,and 10 on the next line.There are different versions of this form, is why the fields are loaded up from the database,and why I call it dynamic.So it doesn't fit well into a DataGrid layout & this is why I was trying to create them dynamically.Say I have another field in the object - LastFieldOnLine to designate new line
  • user210757
    user210757 about 14 years
    it is almost as if I need to be able to use different versions of a "Form" object, that has each of the fields as members.
  • Charlie
    Charlie about 14 years
    Can you show me a screenshot of how you want it to look? I am sure that whatever it is, it can be done better with ItemsControls.
  • Charlie
    Charlie about 14 years
    Aha. Well that is certainly a strange layout. But with WPF- all things are possible! I'll update my example code shortly to demonstrate.
  • user210757
    user210757 about 14 years
    marking as answer. I need to do some more reading on WPF. thanks!