web-dev-qa-db-ja.com

プログラムでコントロールをWPFフォームに追加する

コントロールを動的に(プログラム的に)UserControlに追加しようとしています。ビジネスレイヤーからオブジェクトの汎用リスト(データベースから取得)を取得し、各オブジェクトについて、ラベルとTextBoxをWPF UserControlに追加し、見た目が良くなるように位置と幅を設定します。 WPF検証機能を利用します。これはWindows Formsプログラミングでは簡単なことですが、WPFは初めてです。どうすればいいですか(質問についてはコメントを参照)これが私のオブジェクトだとしましょう:

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

次に、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;
}
26
user210757

さて、二度目は魅力です。レイアウトのスクリーンショットに基づいて、必要なのはWrapPanelであるとすぐに推測できます。これは、アイテムがEdgeに到達するまでいっぱいになるレイアウトパネルで、残りのアイテムは次の行に流れます。ただし、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がわずかに変更されていることに気付くでしょう。ラベルはまだnameプロパティにバインドされていますが、テキストボックスの幅はlengthプロパティにバインドされています(したがって、さまざまな長さのテキストボックスを作成できます)。さらに、単純なBoolToVisibilityConverterを使用して、必要なフィールドに「*」を追加しました(どこでもコードを見つけることができます。ここでは投稿しません)。

主な注意点は、WrapPanelItemsPanelプロパティでListBoxを使用していることです。これにより、ListBoxに、生成されるすべてのアイテムを水平方向のラップされたレイアウトにプッシュする必要があることが通知されます(これはスクリーンショットと一致します)。これをさらに良くするのは、パネル上の高さと幅のバインディングです。これは、「このパネルを親ウィンドウと同じサイズにする」ということです。これは、Windowのサイズを変更すると、それに応じてWrapPanelがサイズを調整するため、アイテムのレイアウトが改善されることを意味します。

24
Charlie

このようなコントロールを追加することはお勧めしません。 WPFで理想的に行うことは、ListBox(またはItemsControl)を配置し、ビジネスオブジェクトコレクションをitemsControl.ItemsSourceプロパティとしてバインドすることです。次に、DataObject型のXAMLでDataTemplateを定義します。これで準備完了です。これがWPFの魔法です。

Winformsのバックグラウンドを持つ人々は、あなたが説明した方法を実行する傾向があり、これはWPFでは正しい方法ではありません。

17
Jobi Joy

チャーリーとジョビの答えを聞きますが、質問に直接答えるために...(コントロールを追加して手動で配置する方法)

Canvasではなく、Gridコントロールを使用します。キャンバスはコントロールに無限のスペースを与え、手動で配置できます。添付プロパティを使用して位置を追跡します。コードでは、次のようになります。

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

XAMLで...

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

また、右端と下端に相対的に配置することもできます。 TopとBottomの両方を指定すると、Canvasでコントロールのサイズが垂直に変更されます。左と右についても同様です。

8
YotaXP