ListBoxコントロールのそれぞれの機能を確認することも大切ですが、複数のコントロールを使用して、ListBoxを使用する場面で一般的な機能をどのように実装するかを考えることも大切です。
C# WPFで、ListBoxに期待される基本的な機能を持つサンプルアプリケーションを作成し、どのように実装するかを考えてみましょう。
作成するアプリケーション
ListBoxを使ったUIの典型例として、次の機能を持つアプリケーションを想定しました。
- ListをListBoxに表示する。
- TextBoxに入力した内容をListに追加する
- ListBoxで項目を選択すると、TextBoxに選択した項目が表示される
- ListBoxで項目を選択し、項目を削除できる
- ListBoxで項目を選択し、項目を変更できる
- ListBoxで項目を選択し、△ボタンを押すと項目の順序を上に上げる。
- ListBoxで項目を選択し、▽ボタンを押すと項目の順序を下に下げる。
Listにデータを追加する
C# WPFのListBoxでは、項目の登録の方法に複数の方法があります。
- ItemsSourceに、配列やList型コンテナ、データベースを登録する
- xamlで、ListBoxItem要素を登録する
- Items.Addで項目を追加する
一般的には、ItemsSourceで、リストの項目を指定する方法が使用されます。
今回の例では、問題を招きやすい、「xamlで、ListBoxItem要素を登録する」、「Items.Addで項目を追加する」2つ方法で追加した項目が混在したデータを扱います。
xamlでリストボックスの項目を追加する
項目の内容は、Content属性に指定する方法と、<ListBoxItem>の開始タグと終了タグの間に記入する方法があります。
<ListBox DockPanel.Dock="Bottom" Name="lb" SelectionChanged="Lb_SelectionChanged">
<ListBoxItem Content="なし" />
<ListBoxItem>ぶどう</ListBoxItem>
</ListBox>
C#コードでリストボックスの項目を追加する
lbは、ListBoxの名前です。Addメソッドで追加します。
private void SetInitialListItems()
{
lb.Items.Add("みかん");
lb.Items.Add("りんご");
lb.Items.Add("バナナ");
}
2つの項目の追加方法の違い
xamlコードでは、 ListBoxItem要素を追加しているので、追加した項目は、 ListBoxItemクラスです。一方、C#コードで項目に追加した項目は、テキストつまりString型のデータです。結果として、ListBox内の項目に異なる型のデータが混在した状態になっています。
その結果、データを他のコントロールや変数に格納する際、要素の型の違いが問題を引き起こします。
C#コードで、ListBoxItemクラスの要素を追加するには
ちなみに、C#コードで、ListBoxItemクラスの要素をListBoxの項目として追加するコードは、次のようになります。
ListBoxItem myItem1 = new ListBoxItem();
myItem1.Content = "みかん";
lb.Items.Add(myItem1);
※ lbは、ListBoxコントロールを示します。xaml側で、ListBox要素にName属性としてlbを指定していると仮定しています。
プログラムの実行時に行う動作(イベント)
ListBoxに項目を追加する
今回のプログラムでは、プログラムの実行時、追加ボタンを押したとき、TextBlockに記入されているテキストをListBoxに項目として追加します。
追加ボタンを押したときの動作
- TextBlockにテキストが記入されているか確認する。
- ListBoxのリスト内に、同じ項目が含まれているか確認する(項目を重複させない場合)。
- ListBoxのリストに項目を追加します。
リストに重複した項目を入力しない場合には、リスト内に同じ項目があるか確認し、同じ項目があった場合、項目を追加しない処理を追加する必要があります。(lbは、ListBoxのName属性。InputBoxは、TextBox要素のName属性)
private void AddButton_Click(object sender, RoutedEventArgs e)
{
// 項目を追加
if (InputBox.Text == null)
{
return;
}
if (lb.Items.Contains(InputBox.Text) != true)
{
// リストボックス内に、同じ項目ない場合のみ、項目を追加する
lb.Items.Add(InputBox.Text);
}
// 入力テキストボックスの値を消去
InputBox.Text = null;
}
リストボックスで選択したデータを削除する
ブログラムの実行時に、ListBoxの項目を削除するコードを作成します。
- ListBoxの項目が選択されているか確認する
- 選択されている項目を削除します
ListBoxの項目が選択されているか確認するコード(lbは、ListBoxの名前です。)
if (lb.SelectedItems.Count == 0)
{
// 選択項目がなければ、何もしない。
return;
}
// ListBoxで何も選択されていない場合は何もしない
if (lb.SelectedItem == null) return;
リストボックスで選択された項目を削除する
最終的に、リストボックスで選択された項目を削除するコードは、以下のようにしました。
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
// 項目を削除
if (lb.SelectedItems.Count == 0)
{
// 選択項目がなければ、何もしない。
return;
}
// 選択された項目を削除
lb.Items.RemoveAt(lb.SelectedIndex);
}
リストボックスで選択した項目を表示する
ListBoxで選択した項目をTextBoxに表示します。
- ListBoxで項目が選択されているか確認する(ListBoxのSelectionChangedイベント )
- 選択されたListBoxの項目をTextBoxに表示する
直接的なコードは、以下のようになります。
private void Lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 選択すると入力ボックスに値を表示
InputBox.Text = Convert.ToString(lb.SelectedItem);
}
※ lb.SelectedItemオブジェクト(lbは、ListBoxのName属性)は、選択されたリストの項目です。
今回のコードでは、ListBoxの項目には、「ListBoxItemクラスのオブジェクト」と「String型のデータ」が混在しています。
項目(lb.SelectedItemオブジェクト)から、項目に設定されたテキスト(ListBoxItemのContent属性)を表示したいのですが、そのままでは、表示することができません。ToStringメソッドを使ってListBoxItemのContent属性の内容を取得しようとすると失敗します。
InputBox.Text = lb.SelectedItem.ToString; // エラーが発生して動作しない。
そのため、ToStringメソッドが使えないときに使用する、Convert.ToString()メソッドを使用して、ListBoxItemを文字列に変換しています。
このコードでは、 ListBoxItemのContent属性の内容だけを取得することができません。
C#コードで追加した項目は、意図した通りに表示されますが、xamlで追加した項目を選択すると、例えば、「なし」を選択すると「System.Windows.Controls.ListBoxItem:なし」と表示されます。
ListBoxの項目を選択して、TextBoxに表示される内容は以下のようになります。
- System.Windows.Controls.ListBoxItem:なし
- System.Windows.Controls.ListBoxItem:ぶどう
- みかん
- りんご
- バナナ
ListBoxItem要素とテキスト(String)が混在したリストの項目を表示する
ListBoxItem要素とテキスト(String)が混在したListBoxで、ListBoxItem要素の場合、Content属性の値を、テキスト(String)の場合は、そのまま、テキストをTextBoxに表示する方法について考えてみます。
ListBoxItem要素とテキスト(String)が混在したListBoxで、ListBoxItem要素の場合、Content属性の値を、テキスト(String)の場合は、そのまま、テキストをTextBoxに表示する方法については、以下のサイトの説明が参考になります。
以下のコードで、これを実現できます。
private void Lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 選択した項目を入力ボックスに値を表示
ListBoxItem lbi = (ListBoxItem)(lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex));
InputBox.Text = lbi.Content.ToString();
}
しかし、このコードは、ListBoxの項目を削除すると以下の行で例外が発生します。
ListBoxItem lbi = (ListBoxItem)(lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex));
テキスト(String)の項目が削除された場合発生する例外
ListBoxItem要素の項目が削除された場合発生する例外
例外処理で対応すると、以下のコードで対応できます。(例外処理は、一般的に、処理速度が遅いといわれています)。
try
{
ListBoxItem lbi = (ListBoxItem)(lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex));
InputBox.Text = lbi.Content.ToString();
}
catch (IndexOutOfRangeException)
{
// ListBoxの項目を削除すると例外「System.IndexOutOfRangeException: 'インデックスが配列の境界外です。'」が発生します。
// 何もしない。
// 対処方法が見つかったら対応する事!
}
catch(NullReferenceException)
{
// ListBoxの項目を上下に移動すると例外「System.NullReferenceException: 'オブジェクト参照がオブジェクト インスタンスに設定されていません。'」が発生します。
// 何もしない。
// 対処方法が見つかったら対応する事!
}
例外処理を使用しないコードは、以下のようになります。
private void Lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lb.SelectedIndex != -1) // lb.SelectedIndexが、空の場合(項目が削除された場合)は、何もしない。
{
// 選択した項目を入力ボックスに値を表示
ListBoxItem lbi = (ListBoxItem)(lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex));
InputBox.Text = lbi.Content.ToString();
}
}
あるいは、以下のコードのようになります。
private void Lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 選択した項目を入力ボックスに値を表示
ListBoxItem srcItem = lb.SelectedItem as ListBoxItem;
if (srcItem != null)
{
// ListBoxItem要素の項目を表示する
InputBox.Text = srcItem.Content.ToString();
}
else
{
// srcItemがnull、かつ、lb.SelectedItemがnullつまり、項目が削除された場合は何もしない。
if (lb.SelectedItem != null)
{
// テキスト(String)の項目を表示する
InputBox.Text = lb.SelectedItem.ToString();
}
}
}
リストボックスで選択した項目を修正する
リストの内容を修正したいときもあると思います。修正は、修正する項目を削除して、あらたに修正した項目を追加すれば同じことなのですが、感覚的に違和感があるので修正する機能が求められます。 ListBoxで、選択した項目を修正する動作を以下のように考えました。
- ListBoxで何も選択されていないときは、何もしない。
- 入力ボックスに何も入力されていないときは、何もしない。
- 選択されたアイテムのインディックスを取得する(挿入位置)。
- ListBoxで選択されている項目を取り出す
- listから選択された項目と一致するものを削除する
- SerchReplacePairsに項目を挿入する
private void EditButton_Click(object sender, RoutedEventArgs e)
{
// 選択した項目を修正(選択した項目を変更する)
// ListBoxで何も選択されていない場合は何もしない
if (lb.SelectedItem == null) return;
// 入力ボックスに何も入力されていなければ何もしない
if (InputBox.Text == null) return;
//選択されたアイテムのインディックスを取得する(挿入位置)
int itemIndex = lb.SelectedIndex;
// ListBoxで選択されている項目を取り出す
object item = lb.SelectedItem;
// listから選択された項目と一致するものを削除する
lb.Items.Remove(item);
// SerchReplacePairsに項目を挿入する
lb.Items.Insert(itemIndex, (InputBox.Text));
}
リストボックスで選択した項目を上下に移動する
ListBoxのそれぞれの項目は、順序を持っています。リスト内の順序を利用する場合、項目の順序を変更できる機能が必要です。
実現するには、いろいろな方法があると思いますが、ここでは、以下の方法で行うことを考えます。
- 項目が選択されていることを確認します。(選択されていなければ何もしません。)
UIはイベントドリブンと呼ばれる仕組みで動いています。そのため、処理する必要がないときは、速やかに処理をしないことを判断する必要があります。必要がないときに速やかに処理をやめることが、速度向上に繋がり、使い勝手の向上に結びつきます。(lbは、ListBoxのName属性)
// ListBoxで何も選択されていない場合は何もしない if (lb.SelectedItem == null) return;
- 選択された項目のインデックス番号を取得します。
//選択されたアイテムのインディックスを取得する(挿入位置) int itemIndex = lb.SelectedIndex;
- 選択された項目が先頭(上に移動するとき)、あるいは、末尾(下に移動するとき)であれば何もしません。
// 項目が先頭の場合は何もしない if (itemIndex == 0) return;
// 項目が末尾の場合は何もしない if (itemIndex == lb.Items.Count - 1) return;
- 選択された項目を取得する
// ListViewで選択されている項目を取り出す object item = lb.SelectedItem;
- 選択された項目を削除する
// listから選択された項目と一致するものを削除する lb.Items.Remove(item);
- 選択された項目の1つ上(上に移動するとき)、あるいは、1つ下(下に移動するとき)に追加する
// SerchReplacePairsに項目を挿入する(上に移動) lb.Items.Insert(itemIndex - 1, item);
// SerchReplacePairsに項目を挿入する(下に移動) lb.Items.Insert(itemIndex + 1, item);
- 移動した項目を選択する(「移動した項目を選択する」を参照のこと)
// 移動した項目を選択する(上に移動) lb.SelectedIndex = itemIndex - 1;
// 移動した項目を選択する(下に移動) lb.SelectedIndex = itemIndex + 1;
リストボックスで移動した項目を選択する
上記の方法で、項目を移動できることが確認できましたが、1つ問題があります。項目の選択が解除されしまうことです。連続で移動する際に、いちいち選択し直す必要があります。そこで、C#コードで、項目を選択する必要があります。
ListBox内の項目をC#コードで選択する方法は、かなり探しにくいです。まず、ListBoxで指定するのか、Listbox.Itemsで指定するのか悩みます。
結局、コマンドを探すしか方法はありません。以下のサイトに方法が記述されていることを見つけました。
「ListBox.SelectedIndex = インディックス番号」で、指定したインデックス番号の項目を指定することができます。
List系のコントロールの動作は、ほぼ、同じ
C#でもWPFでも継承やインターフェイスという概念で、よく似た機能が同じように使えるように工夫されています。そのため、List系のコントロールは、ほぼ、同じように利用できます。
ListBoxでも、ListViewでもComboBoxでも同じように使える機能が多いので、ListBoxで実現したい機能があるとき、ListBoxの内容だけを調べるのではなく、他のList系の機能の説明にも視野を広げて調べてみることが大切です。
全体のコード
MainWindow.xaml
<Window x:Class="ListBoxBase01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxの使用例" Height="250" Width="450">
<DockPanel>
<DockPanel DockPanel.Dock="top">
<DockPanel DockPanel.Dock="top" Margin="0,0,5,0">
<Button Name="DeleteButton" DockPanel.Dock="Right"
Margin="5,5,0,0" Height="23" Width="50" Click="DeleteButton_Click">削除</Button>
<Button Name="EditButton" DockPanel.Dock="Right"
Margin="5,5,0,0" Height="23" Width="50" Click="EditButton_Click">修正</Button>
<Button Name="AddButton" DockPanel.Dock="Right"
Margin="5,5,0,0" Height="23" Width="50" Click="AddButton_Click">追加</Button>
<TextBox Name ="InputBox" Margin="5,5,0,0" Height="23"></TextBox>
</DockPanel>
</DockPanel>
<DockPanel DockPanel.Dock="Top">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" Margin="0,5,0,5">
<ListBox DockPanel.Dock="Bottom" Name="lb" SelectionChanged="Lb_SelectionChanged">
<ListBoxItem Content="なし" />
<ListBoxItem>ぶどう</ListBoxItem>
</ListBox>
<StackPanel VerticalAlignment="Center" Margin="5,0,0,0">
<Button Name="upRow" Click="UpRow_Click">△</Button>
<Button Name="downRow" Margin="0,5,0,0" Click="DownRow_Click">▽</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</DockPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
namespace ListBoxBase01
{
/// <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 AddButton_Click(object sender, RoutedEventArgs e)
{
// 項目を追加
if (InputBox.Text == null)
{
return;
}
if (lb.Items.Contains(InputBox.Text) != true)
{
// リストボックス内に、同じ項目ない場合のみ、項目を追加する
lb.Items.Add(InputBox.Text);
}
// 入力テキストボックスの値を消去
InputBox.Text = null;
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
// 項目を削除
if (lb.SelectedItems.Count == 0)
{
// 選択項目がなければ、何もしない。
return;
}
// 選択された項目を削除
lb.Items.RemoveAt(lb.SelectedIndex);
// TextBoxの内容を削除
InputBox.Text = null;
}
private void Lb_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lb.SelectedIndex != -1) // lb.SelectedIndexが、空の場合(項目が削除された場合)は、何もしない。
{
// 選択した項目を入力ボックスに値を表示
ListBoxItem lbi = (ListBoxItem)(lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex));
InputBox.Text = lbi.Content.ToString();
}
}
private void EditButton_Click(object sender, RoutedEventArgs e)
{
// 選択した項目を修正(選択した項目を変更する)
// ListBoxで何も選択されていない場合は何もしない
if (lb.SelectedItem == null) return;
// 入力ボックスに何も入力されていなければ何もしない
if (InputBox.Text == null) return;
//選択されたアイテムのインディックスを取得する(挿入位置)
int itemIndex = lb.SelectedIndex;
// ListBoxで選択されている項目を取り出す
object item = lb.SelectedItem;
// listから選択された項目と一致するものを削除する
lb.Items.Remove(item);
// SerchReplacePairsに項目を挿入する
lb.Items.Insert(itemIndex, (InputBox.Text));
}
private void UpRow_Click(object sender, RoutedEventArgs e)
{
// リストの項目を上に移動する
// ListBoxで何も選択されていない場合は何もしない
if (lb.SelectedItem == null) return;
//選択されたアイテムのインディックスを取得する(挿入位置)
int itemIndex = lb.SelectedIndex;
// 項目が先頭の場合は何もしない
if (itemIndex == 0) return;
// ListBoxで選択されている項目を取り出す
object item = lb.SelectedItem;
// listから選択された項目と一致するものを削除する
lb.Items.Remove(item);
// SerchReplacePairsに項目を挿入する
lb.Items.Insert(itemIndex - 1, item);
// 移動した項目を選択する
lb.SelectedIndex = itemIndex - 1;
}
private void DownRow_Click(object sender, RoutedEventArgs e)
{
// リストの項目を下に移動する
// ListBoxで何も選択されていない場合は何もしない
if (lb.SelectedItem == null) return;
//選択されたアイテムのインディックスを取得する(挿入位置)
int itemIndex = lb.SelectedIndex;
// 項目が末尾の場合は何もしない
if (itemIndex == lb.Items.Count - 1) return;
// ListBoxで選択されている項目を取り出す
object item = lb.SelectedItem;
// listから選択された項目と一致するものを削除する
lb.Items.Remove(item);
// SerchReplacePairsに項目を挿入する
lb.Items.Insert(itemIndex + 1, item);
// 移動した項目を選択する
lb.SelectedIndex = itemIndex + 1;
}
}
}
参考サイト
- Silverlight/WPFで使える逆引きTips集――リストボックス機能
- ListBox Class
- ListBoxItem Class
WPFのListView
このページの内容は、ListViewで作成したUIを作成した際のコードも参考にしています。
ListViewの項目を選択すると内容が、TextBoxに表示されます。TextBoxに項目の内容を入力し、追加ボタンをクリックすると項目が追加されます。項目を選択して、▽や△をクリックすると項目の位置が変化します。