2010-05-11 11 views
22

Próbuję dodać formanty do UserControl dynamicznie (programowo). Otrzymuję ogólną listę obiektów z mojej warstwy biznesowej (pobraną z bazy danych), a dla każdego obiektu chcę dodać etykietę i pole tekstowe do kontrolki użytkownika WPF oraz ustawić położenie i szerokości, aby wyglądały ładnie i mam nadzieję, że skorzystaj z możliwości sprawdzania poprawności WPF. To jest coś, co byłoby łatwe w programowaniu Windows Forms, ale jestem nowy w WPF. Jak mogę zrobić (patrz komentarze do pytań), że to jest mój obiekt:Programowo dodawać formanty do formularza WPF

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

Wtedy w moim WPF UserControl, Próbuję utworzyć etykietę i TextBox dla każdego obiektu:

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

Odpowiedz

21

Dobra, po raz drugi urok. W oparciu o zrzut ekranu układu mogę od razu wywnioskować, że potrzebny jest WrapPanel, panel układu, który pozwala elementom wypełnić się, aż osiągnie krawędź, w którym to momencie pozostałe elementy przepływają do następnej linii. Ale nadal chcesz korzystać z ItemsControl, aby uzyskać wszystkie korzyści związane z wiązaniem danych i generowaniem dynamicznym. W tym celu użyjemy właściwości ItemsControl.ItemsPanel, która pozwala nam określić panel, w którym zostaną umieszczone elementy. Zacznijmy opóźnieniem kodu ponownie:

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

Niewiele zmieniło się tu, ale już edycja pól próbnych, aby lepiej dopasować swój przykład. Teraz przyjrzyjmy się, gdzie magia happens- XAML dla 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> 

Po pierwsze, można zauważyć, że ItemTemplate zmieniła się nieznacznie. Etykieta jest nadal powiązana z właściwością name, ale teraz szerokość textbox jest powiązana z właściwością length (więc możesz mieć pola tekstowe o różnej długości). Co więcej, dodałem "*" do wszystkich wymaganych pól, używając uproszczonego BoolToVisibilityConverter (którego kod można znaleźć w dowolnym miejscu i nie będę tu publikować).

Najważniejszą rzeczą, którą należy zauważyć, jest użycie WrapPanel w obiekcie ItemsPanel naszego . To mówi ListBox, że wszelkie elementy, które generuje, muszą być wciśnięte w poziomy układ zawinięty (to pasuje do zrzutu ekranu). Co sprawia, że ​​praca ta jest jeszcze lepsza, to wiążąca się wysokość i szerokość na panelu - jak to się mówi, "ustaw ten panel na taki sam rozmiar jak moje okno nadrzędne". Oznacza to, że gdy zmieniam rozmiar Window, WrapPanel dostosowuje odpowiednio swój rozmiar, co daje lepszy układ elementów. Te dwa screenshoty tutaj wykazać to:

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

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

+0

+1, ale myślę, że OP chciał Długość i Wymagane, aby ograniczyć wejście. –

+0

Oczywiście, ale nie jestem pewien, jak dokładnie on chce wykorzystać te ograniczenia, więc po prostu zostawiłem to bez nich. – Charlie

+0

Kłopot jest - i prawdopodobnie pomieszałem sprawy, używając nazwy zmiennej "MyGrid" - dodam te do , a nie do DataGrid. Jest to formularz wprowadzania danych, który muszę podać w określonej kolejności.Może to być 3 etykieta/combminations textbox z texboksami o różnych długościach w jednym wierszu i 10 w następnym wierszu.Istnieją różne wersje tego formularza, dlatego pola są ładowane z bazy danych i dlaczego nazywam to dynamic.So to nie dobrze pasuje do układu DataGrid i dlatego próbowałem ich dynamicznie tworzyć. Jeśli mam inne pole w obiekcie - LastFieldOnLine do wyznaczenia nowej linii – user210757

15

Nie zaleca się dodawania elementów sterujących w ten sposób. W programie WPF najlepiej jest umieścić ListBox (lub ItemsControl) i powiązać kolekcję obiektów biznesowych jako właściwość itemsControl.ItemsSource. Teraz zdefiniuj DataTemplate w XAML dla swojego typu DataObject i jesteś gotowy do pracy, to jest magia WPF.

Ludzie pochodzą z tła WinForm, zwykle robią to, co opisałeś, a który nie jest właściwy w WPF.

+6

+1. Jest bardzo mało rzeczy, o których jestem gotów powiedzieć bez ogródek: "To po prostu źle". Parsowanie XML z wyrażeniem regularnym to jeden. Korzystanie z technik WinForm w aplikacjach WPF to kolejna. –

+0

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

7

chciałbym słuchać Charlie i Jobi-tych odpowiedzi, ale ze względu na odpowiedź na pytanie bezpośrednio ... (Jak dodać kontroli i ręcznie pozycję im.)

Użyj kontrolki Canvas, a nie Grid. Płótnie dają kontrolę nieskończoną ilość miejsca i pozwalają ustawić je ręcznie. Używa on dołączonych właściwości do śledzenia pozycji. W kodzie, to będzie wyglądać tak:

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

w XAML ...

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

Można również umieścić je w stosunku do prawej i dolnej krawędzi. Określenie górnej i dolnej części spowoduje, że kontrolka zmieni rozmiar pionowo w obszarze Canvas. Podobnie dla lewego i prawego.

Powiązane problemy