午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPFのListBox、ItemTemplateで表示変更したListBoxを動的作成したいのでXAMLじゃなくてC#コードで書いてみた

ListBoxを動的追加したい、それもItemTemplateで表示を変更したListBox、さらにBindingも

f:id:gogowaten:20200317114450g:plain
ボタンクリックでListBox追加される


アプリのコードとダウンロード先
ファイル名:20200317_ListBox.zip
github.com

ListBoxの設定をXAMLで書いた部分

  <ListBox x:Name="MyListBox1" ItemsSource="{Binding}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <StackPanel>
          <Border Width="20" Height="10" Background="{Binding Brush}"/>
          <TextBlock Text="{Binding ColorCode}"/>
        </StackPanel>
      </DataTemplate>
    </ListBox.ItemTemplate>

    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel Orientation="Horizontal"/>
      </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
  </ListBox>

これにBindingするデータは、BrushがSolidColorBrushで色の表示、もう1つのColorCodeはBrushに使っている色をテキスト形式にしたもの、これを指定すると
f:id:gogowaten:20200317124512p:plain

こうなる
ListBox1つだけならこれでいい、3個くらいでも上のXAMLをコピペで書いてもいい、でもそれ以上はめんどくさいし、アプリ起動後にListBoxを追加したいこともある!これはC#で書ければできるってことで
上のXAMLC#コードで書いたのが

//ListBoxを動的作成追加
private void AddListBox()
{
    var listBox = new ListBox();

    //ListBoxのItemsSourceのBindingはソースの指定もない空のBinding
    listBox.SetBinding(ListBox.ItemsSourceProperty, new Binding());

    //listboxの要素追加方向を横にする
    var stackPanel = new FrameworkElementFactory(typeof(StackPanel));
    stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
    var itemsPanel = new ItemsPanelTemplate() { VisualTree = stackPanel };
    listBox.ItemsPanel = itemsPanel;

    //ListBoxのアイテムテンプレート作成、設定
    //ItemTemplate作成、Bindingも設定する
    //縦積みのstackPanelにBorderとTextBlock
    //StackPanel(縦積み)
    //┣Border
    //┗TextBlock
    var border = new FrameworkElementFactory(typeof(Border));
    border.SetValue(WidthProperty, 20.0);
    border.SetValue(HeightProperty, 10.0);
    border.SetBinding(BackgroundProperty, new Binding(nameof(MyData.Brush)));

    var textBlock = new FrameworkElementFactory(typeof(TextBlock));
    textBlock.SetBinding(TextBlock.TextProperty, new Binding(nameof(MyData.ColorCode)));

    var panel = new FrameworkElementFactory(typeof(StackPanel));
    //panel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);//横積み
    panel.AppendChild(border);
    panel.AppendChild(textBlock);

    var dt = new DataTemplate();
    dt.VisualTree = panel;
    listBox.ItemTemplate = dt;

    //追加(表示)
    MyStackPanel.Children.Add(listBox);


    //表示するデータ作成、設定
    listBox.DataContext = MakeMyDataList(MakeColors(5));
}

すっごい長くなった


XAMLC# を比較してみる
ListBoxのItemsSourceのBinding部分

<ListBox x:Name="MyListBox1" ItemsSource="{Binding}">

C#だと

listBox.SetBinding(ListBox.ItemsSourceProperty, new Binding());

これはほとんど変わらない、Bindingはソースとかパスとかも設定していないんだけど、これが必要、よくわからん


ItemTemplateを使ってListBoxの要素の表示を変更部分
StackPanel
┣Border
┗TextBlock
に設定している部分

<ListBox.ItemTemplate>
  <DataTemplate>
    <StackPanel>
      <Border Width="20" Height="10" Background="{Binding Brush}"/>
      <TextBlock Text="{Binding ColorCode}"/>
    </StackPanel>
  </DataTemplate>
</ListBox.ItemTemplate>


C#だと

var border = new FrameworkElementFactory(typeof(Border));
border.SetValue(WidthProperty, 20.0);
border.SetValue(HeightProperty, 10.0);
border.SetBinding(BackgroundProperty, new Binding(nameof(MyData.Brush)));

var textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetBinding(TextBlock.TextProperty, new Binding(nameof(MyData.ColorCode)));

var panel = new FrameworkElementFactory(typeof(StackPanel));
//panel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);//横積み
panel.AppendChild(border);
panel.AppendChild(textBlock);

var dt = new DataTemplate();
dt.VisualTree = panel;
listBox.ItemTemplate = dt;

ここが一番長くなるけど以前にも試したので、これであっているはず

ItemsPanelTemplateを使って、ListBoxの要素の並び方向を横にするための部分

<ListBox.ItemsPanel>
  <ItemsPanelTemplate>
    <StackPanel Orientation="Horizontal"/>
  </ItemsPanelTemplate>
</ListBox.ItemsPanel>

C#だと

//listboxの要素追加方向を横にする
var stackPanel = new FrameworkElementFactory(typeof(StackPanel));
stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
var itemsPanel = new ItemsPanelTemplate() { VisualTree = stackPanel };
listBox.ItemsPanel = itemsPanel;

これはあっているかわからん


コード全部
XAML

<Window x:Class="_20200317_ListBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20200317_ListBox"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="480">
  <Grid Margin="10">
    <StackPanel x:Name="MyStackPanel">

      <StackPanel.Resources>
        <Style TargetType="ListBox">
          <Setter Property="Margin" Value="0,2"/>
        </Style>
      </StackPanel.Resources>

      <Button x:Name="Button1" Content="ListBox追加" Click="Button1_Click"/>

      <ListBox x:Name="MyListBox1" ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
          <DataTemplate>
            <StackPanel>
              <Border Width="20" Height="10" Background="{Binding Brush}"/>
              <TextBlock Text="{Binding ColorCode}"/>
            </StackPanel>
          </DataTemplate>
        </ListBox.ItemTemplate>

        <ListBox.ItemsPanel>
          <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
      </ListBox>

    </StackPanel>
  </Grid>
</Window>

C#

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Collections.ObjectModel;
//ItemTemplateで表示を変更したListBoxを動的作成したい

namespace _20200317_ListBox
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //XAMLで用意してあるListBoxにデータ追加
            MyListBox1.DataContext = MakeMyDataList(MakeColors(5));
        }

        
        //ListBoxを動的作成追加
        private void AddListBox()
        {
            var listBox = new ListBox();

            //ListBoxのItemsSourceのBindingはソースの指定もない空のBinding
            listBox.SetBinding(ListBox.ItemsSourceProperty, new Binding());

            //listboxの要素追加方向を横にする
            var stackPanel = new FrameworkElementFactory(typeof(StackPanel));
            stackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            var itemsPanel = new ItemsPanelTemplate() { VisualTree = stackPanel };
            listBox.ItemsPanel = itemsPanel;

            //ListBoxのアイテムテンプレート作成、設定
            //ItemTemplate作成、Bindingも設定する
            //縦積みのstackPanelにBorderとTextBlock
            //StackPanel(縦積み)
            //┣Border
            //┗TextBlock
            var border = new FrameworkElementFactory(typeof(Border));
            border.SetValue(WidthProperty, 20.0);
            border.SetValue(HeightProperty, 10.0);
            border.SetBinding(BackgroundProperty, new Binding(nameof(MyData.Brush)));

            var textBlock = new FrameworkElementFactory(typeof(TextBlock));
            textBlock.SetBinding(TextBlock.TextProperty, new Binding(nameof(MyData.ColorCode)));

            var panel = new FrameworkElementFactory(typeof(StackPanel));
            //panel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);//横積み
            panel.AppendChild(border);
            panel.AppendChild(textBlock);

            var dt = new DataTemplate();
            dt.VisualTree = panel;
            listBox.ItemTemplate = dt;

            //追加(表示)
            MyStackPanel.Children.Add(listBox);


            //表示するデータ作成、設定
            listBox.DataContext = MakeMyDataList(MakeColors(5));
        }



        private List<Color> MakeColors(int count)
        {
            var vs = new byte[count * 3];
            var r = new Random();
            r.NextBytes(vs);
            var colors = new List<Color>();
            for (int i = 0; i < vs.Length; i += 3)
            {
                colors.Add(Color.FromRgb(vs[i], vs[i + 1], vs[i + 2]));
            }
            return colors;
        }
        private ObservableCollection<MyData> MakeMyDataList(List<Color> colors)
        {
            var list = new ObservableCollection<MyData>();
            for (int i = 0; i < colors.Count; i++)
            {
                list.Add(new MyData(colors[i]));
            }
            return list;
        }

        private void Button1_Click(object sender, RoutedEventArgs e)
        {
            AddListBox();
        }
    }


    //表示するデータ用
    public class MyData
    {
        //Bindingで使う項目はpublicでgetが必要
        public SolidColorBrush Brush { get; set; }
        public string ColorCode { get; set; }

        public MyData(Color color)
        {
            Brush = new SolidColorBrush(color);
            ColorCode = color.ToString();
        }
    }

}



関連記事
一年前も同じことしてたけど、ほとんど忘れてた
gogowaten.hatenablog.com

gogowaten.hatenablog.com