C# WPFのListBoxで、項目を追加する際、xamlのListBoxItem要素で登録した項目とC#コードのItems.Addで登録した項目の挙動は異なることを覚えておきましょう
ListBoxに項目を追加し、選択することは、一番基本的な動作です。注意する必要があるのは、「xamlで、ListBoxItem要素を登録する」、「Items.Addで項目を追加する」方法で、ListBoxに登録される要素の型が異なることです。
WPFで、ListBoxを使用する場合、ListBoxの項目を登録する方法は、3つあります。
- ItemsSourceに、配列やList型コンテナ、データベースを登録する
- xamlで、ListBoxItem要素を登録する
- Items.Addで項目を追加する
ItemSorceを使わない場合は、「xamlで、ListBoxItem要素を登録する」、「Items.Addで項目を追加する」方法のどちらかを使用する必要があります。
この2つの項目の追加方法による違いがあるため、選択肢た項目をTextBlockに表示するだけでも問題が生じます。項目の追加方法をどちらかだけを使用する、あるいは、ItemSorceを使用する回避方法があります。
この2つの項目の追加方法による違いをプログラムを作成して確かめることにします。
コード例
以下のコードは、xamlで、そして、C#コードで項目を追加し、選択した項目の内容をTextBlockに表示するだけのプログラムです。
xamlコードで、ListBoxの中に、ListBoxItemを使い、「みかん」、「りんご」、「ばなな」と項目を3つ登録しています。ListBoxのSelectionModeは、Singleに設定しているため、複数選択はできません。
分離コードを使って、C# WPFのListBox内の選択した要素の内容をTextBlockに表示し、ListBoxに、xamlで、ListBoxItemのContent属性で登録した項目と、C#コードで、ListBoxのAddメソッドで登録した項目のオブジェクトが異なることを確認します。
MainWindow.xaml
<Window x:Class="ListBoxBataToTectBlock01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="250" Width="300">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Name="ListItem" Padding="5,0,0,0"/>
<TextBlock DockPanel.Dock="Top" Name="ListItemName" Padding="5,0,0,0"/>
<TextBlock DockPanel.Dock="Top" Name="ListItemNo" Padding="5,0,0,0"/>
<ListBox DockPanel.Dock="Bottom" Name="lb" SelectionMode="Single" SelectionChanged="Listbox_SelectionChanged">
<ListBoxItem Content="みかん"/>
<ListBoxItem Content="りんご"/>
<ListBoxItem>ばなな</ListBoxItem>
</ListBox>
</DockPanel>
</Window>
DockPanelに、TextBlockを3つ、ListBoxを1つ配置しています。ListBoxの中に、ListBoxItemを使い、項目を3つ登録しています。ListBoxのSelectionModeは、Singleに設定しているため、複数選択はできません。
部分コードでは、SetInitialListItemsメソッドの中で、Items.Addを使用して、「もも」、「いちご」、「ぶどう」の3つの項目を追加しています。Listbox_SelectionChangedイベントで、3つのTextBlockに値を表示します。
一番上のTextBlocには、イベントが発生したオブジェクトのToString()メソッドの値を、真ん中のTextBlockには、ListBoxの項目に表示されている値を、一番下のTextBlockには、選択した項目のインディックス番号を表示しています。
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace ListBoxBataToTectBlock01
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// リストの初期値を入力
SetInitialListItems();
}
private void SetInitialListItems()
{
lb.Items.Add("もも");
lb.Items.Add("いちご");
lb.Items.Add("ぶどう");
}
private void Listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 選択したListBoxItemを文字列として表示する
ListItem.Text=lb.SelectedItem.ToString();
// 選択したListBoxItemのContent属性を文字列として表示する
try
{
// xamlで、ListBoxItemのContent属性で追加した項目は、このコードでListBoxで項目に表示されている内容を表示できる
ListItemName.Text = ((sender as ListBox).SelectedItem as ListBoxItem).Content.ToString();
}
catch(System.NullReferenceException)
{
// C#で、ListboxにAddメソッドで追加した項目は、このコードでListBoxで項目に表示されている内容を表示できる
ListItemName.Text = lb.SelectedItem.ToString();
}
// 選択した項目のインディックス番号を取得する
ListItemNo.Text = "選択した項目のインディックス番号は" + (sender as ListBox).SelectedIndex.ToString();
}
}
}
ListBoxで選択された項目を取得する
ListBoxで選択された項目を取得するには、ListBoxコントロールのSelectedItemプロパティを取得することで、項目のオブジェクトが取得できます。複数選択の場合は、ForEach文などを使用して列挙してオブジェクトを取得します。
上記のコードでは、SelectionChangedイベントを使用しており、さらに、ListBoxのSelectionModeをSingleに設定して 、1つしか項目を選択できないため、SelectionChangedイベントの引数、senderやeからも取得できます。senderは、イベントを取得したオブジェクトが、eには、イベントが発生したオブジェクトが入っています。
「xamlで、ListBoxItem要素を登録する」、「Items.Addで項目を追加する」2つの方法の違い
プログラムを実行し、上から順番にListBoxの項目を選択します。一番上のTextBlockで表示される値、言い換えると、選択肢た項目のToString()メソッドの値は、上の3つの項目、「みかん」、「りんご」、「ばなな」と下の3つの項目、「もも」、「いちご」、「ぶどう」で異なることがわかります。
- System.Windows.Controls.ListBoxItem: みかん
- System.Windows.Controls.ListBoxItem: りんご
- System.Windows.Controls.ListBoxItem: ばなな
- もも
- いちご
- ぶどう
言い換えると、 xamlで、ListBoxItem要素に登録した項目は、ListBoxItem要素であり、C#コードで、Items.Addを使用して追加した項目は、テキスト(String)です。
// 選択したListBoxItemを文字列として表示する
ListItem.Text=lb.SelectedItem.ToString();
ListBoxItem要素とテキスト(String)が混在したリストの項目を表示する
ListBoxItem要素とテキスト(String)が混在したListBoxで、ListBoxItem要素の場合、Content属性の値を、テキスト(String)の場合は、そのまま、テキストをTextBlockに表示する場合はどうすればよいのでしょうか?この答えの1つが、2つ目のTextBlockです。
xamlで項目を追加したもののListBoxに表示されている文字列は、ListBoxItem要素の場合、Content属性の値です。この値をTextBlockであるListItemNameに表示するためには、以下のコードを使用します。
ListItemName.Text = ((sender as ListBox).SelectedItem as ListBoxItem).Content.ToString();
しかし、このコードは、ListBoxItem要素のためのコードであるため、テキスト(String)の項目では、System.NullReferenceException例外が発生します。
一方、テキスト(String)の項目を表示するためには、以下のコードを使用します。
ListItemName.Text = lb.SelectedItem.ToString();
単純に、選択された項目を文字列として表示するだけです。
もちろん、このコードに、ListBoxItem要素を与えれば例外が発生します。
そのため、上記のコードでは、例外処理が含まれるコードになっています。
// 選択したListBoxItemのContent属性を文字列として表示する
try
{
// xamlで、ListBoxItemのContent属性で追加した項目は、このコードでListBoxで項目に表示されている内容を表示できる
ListItemName.Text = ((sender as ListBox).SelectedItem as ListBoxItem).Content.ToString();
}
catch(System.NullReferenceException)
{
// C#で、ListboxにAddメソッドで追加した項目は、このコードでListBoxで項目に表示されている内容を表示できる
ListItemName.Text = lb.SelectedItem.ToString();
}
このコードを例外処理を使用しないコードに書き換える場合の1例として、以下のようなコードが考えられます。例外処理を使用しないため、プログラムの動作が早くなることが期待できます。
// 選択したListBoxItemのContent属性を文字列として表示する
ListBoxItem srcItem = lb.SelectedItem as ListBoxItem;
if (srcItem != null)
{
// 項目が、ListBoxItemのときの処理
ListItemName.Text = srcItem.Content.ToString();
}
else
{
// 項目が、テキスト(String)のときの処理
ListItemName.Text = lb.SelectedItem.ToString();
}
選択した要素のインディックス番号を表示する
3つ目のTextBlockでは、選択した要素のインディックス番号を取得しています。
// 選択した項目のインディックス番号を取得する
ListItemNo.Text = "選択した項目のインディックス番号は" + (sender as ListBox).SelectedIndex.ToString();
まとめ
外部に、ListBoxの項目用のコンテナを用意せずにListBoxを使用する際、意図せずに項目に複数の型を使用する状況を作らないように注意しましょう。
最終的なコードを以下に示します。
<Window x:Class="SelectListBoxItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="250" Width="300">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Name="ListItem" Padding="5,0,0,0"/>
<TextBlock DockPanel.Dock="Top" Name="ListItemName" Padding="5,0,0,0"/>
<TextBlock DockPanel.Dock="Top" Name="ListItemNo" Padding="5,0,0,0"/>
<ListBox DockPanel.Dock="Bottom" Name="lb" SelectionMode="Single" SelectionChanged="Listbox_SelectionChanged">
<ListBoxItem Content="みかん"/>
<ListBoxItem Content="りんご"/>
<ListBoxItem>ばなな</ListBoxItem>
</ListBox>
</DockPanel>
</Window>
using System.Windows;
using System.Windows.Controls;
namespace SelectListBoxItem
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// リストの初期値を入力
SetInitialListItems();
}
private void SetInitialListItems()
{
lb.Items.Add("もも");
lb.Items.Add("いちご");
lb.Items.Add("ぶどう");
}
private void Listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 選択したListBoxItemを文字列として表示する
ListItem.Text = lb.SelectedItem.ToString();
// 選択したListBoxItemのContent属性を文字列として表示する
ListBoxItem srcItem = lb.SelectedItem as ListBoxItem;
if (srcItem != null)
{
// 項目が、ListBoxItemのときの処理
ListItemName.Text = srcItem.Content.ToString();
}
else
{
// 項目が、テキスト(String)のときの処理
ListItemName.Text = lb.SelectedItem.ToString();
}
// 選択した項目のインディックス番号を取得する
ListItemNo.Text = "選択した項目のインディックス番号は" + (sender as ListBox).SelectedIndex.ToString();
}
}
}
補足:C#コードでも、ListBoxItemクラスのオブジェクトを追加する
ListBoxItem要素とテキスト(String)が混在したListBoxでは、項目の扱いが複雑になります。
xamlで、ListBoxの項目にテキストの項目を追加することが難しければ、C#コードで、項目を追加するときにListBoxItemクラスのオブジェクトを追加すれば、ListBox内の項目のオブジェクト型が統一されるためコードを単純化することができます。
lb.Items.Add("もも");
項目の中にListBoxItemクラスのオブジェクトを追加するコード
ListBoxItem myItem1 = new ListBoxItem();
myItem1.Content = "もも";
lb.Items.Add(myItem1);