午後わてんのブログ

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

WPFのListBoxでいろいろ、Binding、見た目の変更、横リスト

ListBoxでのデータバインディング見た目の変更とか
 
横表示は
ListBox.ItemsPanel
	ItemsPanelTemplate
		StackPanel Orientation="Horizontal"
 
リストに表示される要素の見た目の変更は
ListBox.ItemTemplate
	DataTemplate
		StackPanel
			Border
			TextBlock
		/StackPanel
など
 
今回のアプリのダウンロード先
イメージ 1
Color(色)とString(色名)のリストをListBoxにBinding
ボタン1,2でリストの先頭の要素を変更、Bindingしてるのでリストの表示も変更される
一番上のListBoxは普通の縦並びで、二番目は横並びに変更したもの
この2つは同じデータをBindingしている
一番下は横並びでColorsクラスの色一覧表示したもの
 
 
 
デザイン画面

f:id:gogowaten:20191213153213p:plain

 
<Window x:Class="_20190304_ListBoxのDataTemplate.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:_20190304_ListBoxのDataTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="540">
  <Window.Resources>
    <local:MyColorConverter x:Key="colorConverter"/>
  </Window.Resources>
  <Grid Margin="4,20">
    <StackPanel>
      <Button Name="MyButton1" Content="button1"/>
      <Button Name="MyButton2" Content="button2"/>
      <ListBox Name="MyListBox1" ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
          <DataTemplate>
            <StackPanel Orientation="Horizontal">
              <Border Background="{Binding Color, Converter={StaticResource colorConverter}}"
                      Width="20" Height="10" HorizontalAlignment="Left"/>
              <!--<TextBlock Text="{Binding Name}"/>-->
              <CheckBox Content="{Binding Name}"/>
              <Ellipse Fill="{Binding Color, Converter={StaticResource colorConverter}}"
                       Width="10" Height="10" Margin="2"/>
            </StackPanel>
          </DataTemplate>
        </ListBox.ItemTemplate>
      </ListBox>

      <ListBox Name="MyListBoxHorizontal" ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
          <DataTemplate>
            <StackPanel>
              <Border Background="{Binding Color, Converter={StaticResource colorConverter}}" Width="20" Height="10"/>
              <TextBlock Text="{Binding Name}"/>
            </StackPanel>
          </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.ItemsPanel>
          <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
      </ListBox>
      <ListBox Name="MyListBoxWPFColors" ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
          <DataTemplate>
            <StackPanel>
              <Border Background="{Binding Color, Converter={StaticResource colorConverter}}" Width="20" Height="10"/>
              <TextBlock Text="{Binding Name}"/>
            </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.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Collections.ObjectModel;//ObservableCollection用
using System.Globalization;//ValueConverterで使用


//WPF の ListBox に対する リストデータ の バインディング と 表示名 の 制御 - galife
//https://garafu.blogspot.com/2014/09/wpf-listbox.html
//WPF4.5入門 その56「コレクションのバインディング」 - かずきのBlog @hatena
//https://blog.okazuki.jp/entry/2014/10/29/220236
//第5回 WPFの「データ・バインディング」を理解する(2/3):連載:WPF入門 - @IT
//https://www.atmarkit.co.jp/ait/articles/1010/08/news123_2.html
namespace _20190304_ListBoxのDataTemplate
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {

        ObservableCollection<MyColorData> myData;//Bindingのソースになるデータ

        public MainWindow()
        {
            InitializeComponent();

            //データリスト作成
            //データを入れるリストには変更通知してくれるObservableCollectionを使うのがラク
            myData = new ObservableCollection<MyColorData>();
            myData.Add(new MyColorData { Color = Colors.BlanchedAlmond, Name = "BlanchedAlmond" });
            myData.Add(new MyColorData { Color = Colors.Orange, Name = "Orange" });
            myData.Add(new MyColorData { Color = Colors.Olive, Name = nameof(Colors.Olive) });
            myData.Add(new MyColorData { Color = Color.FromRgb(0xEB, 0x79, 0x88), Name = "紅梅" });
            myData.Add(new MyColorData { Color = Color.FromRgb(242, 216, 223), Name = "桜色" });

            //アプリ自体にデータをBinding、参照の追加、関連付け
            this.DataContext = myData;

            //ボタンクリック時の動作
            //MyButton1.Click += (s, e) => { myData[0].Color = Colors.Tomato; myData[0].Name = "Tomato"; };
            //↑だとBindingが外れてしまう
            //ボタンクリックでデータの変更
            MyButton1.Click += (s, e) => { myData[0] = new MyColorData { Color = Colors.Tomato, Name = "Tomato" }; };
            //↑ラムダ式↓今までの
            MyButton2.Click += MyButton2_Click;


            //Colorsクラスの色一覧表示、MyListBoxWPFColors用
            //TypeクラスのGetPropertyメソッドを使ってColorsクラスの色取得
            System.Reflection.PropertyInfo[] infos = typeof(Colors).GetProperties(
                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

            var wpfColors = new ObservableCollection<MyColorData>();//Bindingのソースになるデータ
            foreach (var item in infos)
            {
                wpfColors.Add(new MyColorData { Color = (Color)item.GetValue(null), Name = item.Name });
            }
            //関連付けはアプリ自体じゃなくて対象のListBoxにした
            MyListBoxWPFColors.DataContext = wpfColors;
        }

        private void MyButton2_Click(object sender, RoutedEventArgs e)
        {
            myData[0] = new MyColorData { Color = Colors.MediumSpringGreen, Name = nameof(Colors.MediumSpringGreen) };
            //↓だとBindingが外れてしまう
            //myData[0].Color = Colors.MediumSpringGreen;
            //myData[0].Name = nameof(Colors.MediumSpringGreen);
        }
    }

    public class MyColorData
    {
        public Color Color { get; set; }
        public string Name { get; set; }
    }


    //ColorをSolidColorBrushに変換
    public class MyColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Color c = (Color)value;
            return new SolidColorBrush(c);
        }

        //逆変換のこっちは未使用
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

}

f:id:gogowaten:20191213153317p:plain

 
BindingのソースとターゲットSource、Target
ソース 元、基本になるほう
ターゲット ソースを元に動きを変えるほう
今回のソースは色の情報リストがソースで、ListBoxがターゲットになる
 
 
イメージ 3
Color(色)とString(色名)を持つMyColorDataクラスを用意しておいて
 
 

f:id:gogowaten:20191213153348p:plain

適当な5色分のデータからMyColorDataを作成してリストに追加(44~48行目)
使うリストは変更通知をしてくれるObservableCollectionを使う
 
できたデータリストはアプリ自体(this)のDataContextに指定する(51行目)
これはBindingというか、参照の追加のような、関連付けみたいな感じで
アプリ自体にするとデザイン画面にある要素のどれからでも参照できるようになる
 
    Grid1
        ┗StackPanel1
            ┣Button1
            ┗Button2
ってあったときに
Grid1.DataContext = MyData; どれからでも参照できる
StackPanel1.DataContext = MyData; Grid1以外は参照できる
Button1.DataContext = MyData; Button1だけ参照できる
多分こんな感じで、thisは一番上
 
 
XAMLでListBoxにBinding

f:id:gogowaten:20191213153401p:plain

16行目のItemsSource="{Binding}"
これでBindingになるみたい、名前がItemsっていう複数形になっているくらいだから、リスト形式のデータの一つ一つを自動で処理してくれるみたい
 
要素1つ1つをどんなふうに表示するのかの指定が17~24行目
ListBoxのItemTemplateのDataTemplate
この中に自分で好きなように要素を配置して表示できるみたい
    StackPanel
        ┣Borderで色表示
        ┗TextBlockで色名表示
 
21行目、String形式の色の名前をTextBlockのTextプロパティにBinding
Text="{Binding Name}"
アプリ自体に関連付けしたのは
this.DataContext = myData
だったから色の名前のフルパスはmyData.NameとかmyData[0].Nameになると思うけど
Nameだけでいいみたい
 
 
Bindingの値を変換
20行目は色の表示で、Border要素のBackgroundの色で表現している
BackgroundにはBrushを指定する、けどBindingするのはColorなので
ColorをBrushに変換するConverterを作成
 
C#に戻ってIValueConverterを継承(追加)したクラスMyColorConverterを作成

f:id:gogowaten:20191213153413p:plain

99~103行目
Convertの引数のValueにデータ元(ソース)のColorが入ってくるので
それをもとにSolidColorBrushを作成して返しているだけ
このConverterをXAMLで使うには
XAMLに戻って
イメージ 7
WindowのResourcesに登録
 
イメージ 8
local:って打つと入力候補にでてくる
あとはx:Keyで適当な名前(ColorConverter)をつけて登録完了
これでXAMLのどこからでもこの名前で使うことができるようになったので
ListBoxのBindingに戻って
 

f:id:gogowaten:20191213153433p:plain

Converter={StaticResource 名前}
これでBindingしたColorはSolidColorBrushに変換されて
BorderのBackgroundに指定される
 
 
イメージ 10
赤枠がそれ
色表示のBorderの横位置の不揃いが気になったら
 
イメージ 11
BorderのHorizontalAlignmentにLeftを指定すれば
 
イメージ 12
左寄せで揃う、いいねえ
 
 
イメージ 13
StackPanelの積み上げ方向を水平にすれば
 
イメージ 14
Borderの横にTextBlockが積み上げられて
色の横に色名表示になる
 
イメージ 15
TextBlockのかわりにCheckBoxとEllipseと追加で
 
イメージ 16
いろいろできるねえ、面白い
 
 
 
リストの横表示
イメージ 17
何も指定しないと縦のリストになる
 
横表示リストのMyListBoxHorizontal
イメージ 18
40~44行目
ItemTemplateのItemsPanelのItemsPanelTemplate
これにOrientationにHorizontalを指定したStackPanelを指定すれば
横表示のリストになる
 
ソースの変更でターゲットも変化

f:id:gogowaten:20191213153459p:plain

ソースのリストの先頭要素(MyData[0])を
Button1クリックでTomatoに
Button2クリックでMediumSpringGreenに変更
この変更は要素の中のColorやNameを変更してもターゲット側に無視されるので
要素自体を変更する必要があった
 
イメージ 19
Button1,2でListBoxの表示も変化する
 
変更通知機能があるObservableCollection
ソースになるデータはこれに入れているけど、変更通知されるのは要素自体が入れ替わるとか追加削除のときだけで、要素の中が変化したときはリストが変化したわけじゃないから変更通知は出ないってことかしらねえ
 
 
Button2でランダム色を追加
イメージ 21
83~87行目を書き足してButton2をクリックすると
 
イメージ 22
クリックするたびに色が追加された
 
 
 
 
参照したところ
WPF の ListBox に対する リストデータ の バインディング と 表示名 の 制御 - galife
https://garafu.blogspot.com/2014/09/wpf-listbox.html
WPF4.5入門 その56「コレクションのバインディング」 - かずきのBlog @hatena
https://blog.okazuki.jp/entry/2014/10/29/220236
第5回 WPFの「データ・バインディング」を理解する(2/3):連載:WPF入門 - @IT
https://www.atmarkit.co.jp/ait/articles/1010/08/news123_2.html
 
 
 
ギットハブ
 
 
 
ブログだから日記
今までは動的に追加削除する要素に対してのBindingはXAMLじゃなくてコードビハインドっていうのかな、C#で書いていたのは今回のようなことができることを知らなかったかというか、理解できていなかったからなんだよなあ、あとXAMLのコードは見づらいとかコメントを挿入しにくいとか入力候補が出ないところがあるからうち間違えしそうとか…結構あるな
でも今回のでListBoxにXAMLで書くのもいいかもと思った、とくに見た目を変更するDataTemplateのあたりはC#だと、どう書いていいのかわからないから使い分けかなあ
 
 
 関連記事
2020/03/17は1年後