2010-05-11 26 views
22

Estoy intentando agregar controles a un UserControl dinámicamente (programáticamente). Obtengo una lista genérica de objetos de mi Business Layer (recuperada de la base de datos) y para cada objeto, deseo agregar una etiqueta y un TextBox al UserControl de WPF y establecer la posición y el ancho para que se vea bien, y con suerte aprovecha las capacidades de Validación de WPF. Esto es algo que sería fácil en la programación de Windows Forms, pero soy nuevo en WPF. ¿Cómo puedo hacer esto (ver comentarios para preguntas) dicen que este es mi objetivo:Agregar controles mediante programación al formulario WPF

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

Luego, en mi WPF control de usuario, que estoy tratando de crear una etiqueta y cuadro de texto para cada objeto:

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

Respuesta

21

Muy bien, la segunda vez es el encanto. Según su captura de pantalla de diseño, puedo inferir de inmediato que lo que necesita es un WrapPanel, un panel de diseño que permite que los elementos se llenen hasta que llegue a un borde, en cuyo punto los elementos restantes fluyen a la siguiente línea. Pero aún desea utilizar un ItemsControl para que pueda obtener todos los beneficios de vinculación de datos y generación dinámica. Así que para esto vamos a usar la propiedad ItemsControl.ItemsPanel, que nos permite especificar el panel en el que se colocarán los elementos. Vamos a empezar con el nuevo código subyacente:

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

No ha cambiado mucho aquí, pero he editado los campos de ejemplo para adaptarse mejor a su ejemplo. Ahora vamos a ver donde la magia ocurre- el XAML para el 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> 

En primer lugar, se dará cuenta de que el ItemTemplate ha cambiado ligeramente. La etiqueta todavía está vinculada a la propiedad de nombre, pero ahora el ancho del cuadro de texto está vinculado a la propiedad de longitud (por lo que puede tener cuadros de texto de longitud variable). Además, he agregado un "*" a cualquier campo que sea necesario, usando un BoolToVisibilityConverter simplista (que puede encontrar el código para cualquier parte, y no lo publicaré aquí).

Lo principal a notar es el uso de un WrapPanel en la propiedad ItemsPanel de nuestro ListBox. Esto le dice al ListBox que cualquier elemento que genere debe ser insertado en un diseño envuelto horizontal (esto coincide con su captura de pantalla). Lo que hace que este trabajo sea aún mejor es el enlace de alto y ancho en el panel, lo que dice es "hacer que este panel tenga el mismo tamaño que mi ventana principal". Eso significa que cuando cambio el tamaño del Window, el WrapPanel ajusta su tamaño en consecuencia, lo que da como resultado una mejor disposición de los elementos. Las dos capturas de pantalla aquí demuestran esto:

alt text http://img156.imageshack.us/img156/6849/wrappanelfields.png

alt text http://img12.imageshack.us/img12/2426/wrappanelfields2.png

+0

+1, pero creo que el OP quería Longitud y Obligatorio para restringir la entrada. –

+0

Por supuesto, pero no estoy seguro de cómo exactamente quiere usar esas restricciones, así que lo dejé sin ellas. – Charlie

+0

problema es -y probablemente confundí las cosas al usar el nombre de la variable "MyGrid" -Estoy agregando thes a un , no un DataGrid.Esto es un formulario de entrada de datos que tengo que salir en un orden específico. Puede haber 3 etiquetas/combinaciones de cuadros de texto con texboxes que tienen longitudes diferentes en una línea, y 10 en la siguiente línea. Hay diferentes versiones de esta forma, es por eso que los campos se cargan desde la base de datos, y por eso lo llamo dinámico. Por lo tanto, no lo hace encajar bien en un diseño DataGrid y es por eso que estaba tratando de crearlos dinámicamente. Diga que tengo otro campo en el objeto - LastFieldOnLine para designar la nueva línea – user210757

15

No se recomienda agregar controles como este. Lo que idealmente haría en WPF es poner un ListBox (o ItemsControl) y vincular su colección de objetos Business como la propiedad itemsControl.ItemsSource. Ahora defina DataTemplate en XAML para su tipo DataObject y estará listo, esa es la magia de WPF.

Las personas que provienen de un entorno de winforms tienden a hacer la forma que describió y que no es la correcta en WPF.

+6

+1. Hay muy pocas cosas acerca de las cuales estoy dispuesto a decir, llanamente: "Esto simplemente está mal". El análisis XML con expresiones regulares es uno. El uso de técnicas WinForms en aplicaciones WPF es otro. –

+0

Amen. --------- –

7

Me gustaría escuchar a Charlie y Jobi de las respuestas, pero en aras de responder a la pregunta directamente ... (¿Cómo añadir controles y la posición manualmente ellos.)

Utilice un control Canvas, en lugar de Grid. Los lienzos dan al control una cantidad infinita de espacio y te permiten colocarlos manualmente. Utiliza propiedades adjuntas para realizar un seguimiento de la posición. En el código, que se vería así:

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

en XAML ...

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

También puede colocarlos en relación con el derecho e inferior. Al especificar tanto una parte superior como una parte inferior, el control cambiará de tamaño verticalmente con el lienzo. Del mismo modo para Izquierda y Derecha.

Cuestiones relacionadas