C#のListViewを使ってみた
C#のWPFアプリでListViewを使ってみたメモです。追加、削除、ソート、アイテムの選択をやってみました。
目次
- こんなアプリを作ってみた
- ListViewにバインディングするコレクションを作る
- ListViewとコレクションのバインディング
- アイテムを追加する
- 選択したアイテムのフィールドを表示する
- アイテムを削除する
- ListViewのヘッダをクリックしたときにソートさせる
- コード全文
- 動かしてみた
- まとめ
こんなアプリを作ってみた
ListViewを1つ、Buttonが2つ、TextBoxが1つ、TextBlockが1つからなるアプリです。下記の機能を持ちます。
「Add」ボタンを押すと、Field1にTextBoxの内容が入ったアイテムが追加される。
アイテムを選択すると、選択したアイテムのフィールドがTextBlockに表示される。
「Del」ボタンを押すと、選択したアイテムが削除される。
ListViewを扱うアプリって、だいたいこういう機能を持ってますよね。
ListViewにバインディングするコレクションを作る
まず、アイテムの入れ物となるコレクションを作らないと話になりません。
アイテムの型を下記のようなクラスにします。
public class HogeHoge
{
public Int64 Id { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
ネーミングセンスが枯渇してます。IdがInt64なのは、System.Data.SQLiteといろいろしたいという思惑からです。
このHogeHoge型のコレクションのインスタンスを作ります。
ObservableCollection<HogeHoge> list = new ObservableCollection<HogeHoge>();
ObservableCollectionにするのはListViewにバインディングする際のお約束のようなもののようなのですが、MSDNによれば、ListBoxやListViewなどに動的バインディングをしてコレクションの変更を自動的にUIに反映させたい場合はINotifyCollectionChangedインターフェースを持つコレクションをバインドする必要があって、ObservableCollectionにはINotifyCollectionChangedインターフェースが実装されているのだそうです。確かにMSDNによると、ObservableCollectionクラスにはCollectionChangedとPropertyChangedというイベントが記載されてますが、Collectionクラスにはイベントの記載がありません。
というわけで、ListViewにバインディングするコレクションにはObservableCollectionクラスを使います。
ListViewとコレクションのバインディング
MainWindow.xamlのListView要素にバインディングの設定をします。ListView要素のItemsSource="{Binding}"
と各GridViewColumn要素のDisplayMemberBinding="{Binding Path=xx}"
部分がバインディング用の記述です。
<ListView x:Name="listView" ItemsSource="{Binding}" HorizontalAlignment="Left" Height="109" Margin="10,10,0,0" VerticalAlignment="Top" Width="175" SelectionChanged="listView_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Id}">
<GridViewColumnHeader Content="Id" Tag="Id" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Field1}">
<GridViewColumnHeader Content="Field1" Tag="Field1" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Field2}">
<GridViewColumnHeader Content="Field2" Tag="Field2" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
MainWindow.xaml.csの方では、WindowのLoadedイベントのところで、ListViewのDataContextプロパティにバインディングするコレクションを指定します。
private void Window_Loaded(object sender, RoutedEventArgs e)
{
listView.DataContext = list;
}
アイテムを追加する
アイテムを追加します。Idが重複しないように、既存のアイテムのIdの最大値に1を足したものを新しいアイテムのIdとします。既存のコレクションの中から指定したプロパティの最大値を求める計算には、Linqを使ってみました。
private void button_Click(object sender, RoutedEventArgs e)
{
// 追加する項目のIdの値を計算する
Int64 newId;
if (list.Count > 0)
{
// 既存の項目のIdの最大値をを求めて、それに+1する
var query = from p in list select p.Id;
newId = query.Max() + 1;
}
else
{
newId = 1;
}
HogeHoge item = new HogeHoge { Id = newId, Field1 = textBox.Text, Field2 =null }; // 追加する項目の内容を設定する
list.Add(item); // listに項目を追加する
}
listコレクションにアイテムを追加すると、ListViewにもアイテムの追加が自動的に反映されます。
選択したアイテムのフィールドを表示する
ListViewのSelectionChangedイベントにコードを書いてみました。どうやってアイテムを取り出すかちょっと悩みました。ListView.SelectedItemプロパティで選択されたアイテムを取り出しできます。取り出したアイテムを、アイテムの型(今回はHogeHoge型)に変換して、そのインスタンスのプロパティにアクセスするとアイテムの中身を取り出せます。
private void listView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (listView.SelectedItem == null) return; // ListViewで何も選択されていない場合は何もしない
HogeHoge item = (HogeHoge)listView.SelectedItem; // ListViewで選択されている項目を取り出す
textBlock.Text = item.Field1; // 取り出された項目のプロパティをTextBlockに表示する
}
ListViewのSelectionChangedイベントは、ユーザーがListViewをクリックする以外の操作でも発生する(例えば、アイテムを削除したとき)ようで、SelectionChangedイベントですがSelectedItemがnullの場合は何もしないようにしておかないと、エラーになります。
アイテムを削除する
選択したアイテムを削除します。LitView.SelectedItemプロパティで選択されたアイテムを取り出して、Remove()メソッドでそのアイテムに一致するものをコレクションから削除します。コレクションからアイテムが削除されると、ListViewにも自動的に反映されます。
private void buttonRemove_Click(object sender, RoutedEventArgs e)
{
if (list.Count < 1) return; // listに項目が無い場合は何もしない
HogeHoge item = (HogeHoge)listView.SelectedItem; // ListViewで選択されている項目を取り出す
list.Remove(item); // listから選択された項目と一致するものを削除する
}
Remove()メソッドは引数に一致する最初のアイテムをコレクションから削除するということなので、操作するコレクションとListViewの項目が一致しないような作りになっている場合は要注意です。
ListViewのヘッダをクリックしたときにソートさせる
DetaGridにはヘッダをクリックするとそのフィールドを基準にしてアイテムをソートする機能があります。ですが、ListViewのグリッド表示モードにはその機能がありません。でも、ListViewのようなリスト表示をされたら、ヘッダをクリックしてソートしようとしますよね。
「だったらDataGridを使えば良いじゃん」と思うのですが、確かWPFのDataGridって.Net 3.5ではサポートされていなかったような。そして、世のPCの大半(特に仕事用)はWindows7だったりします。OfficeとかAcrobat Readerが.Net 4.5あたりを自動インストールしてくれれば良いのに。
というわけで、ヘッダをクリックしたらそのフィールドを基準にしてソートするようにしてみます。ただし、好みのソート順になるまで、2回くらいクリックするかもしれないというなんちゃって仕様です。
XAMLへの仕込み
XAMLにクリックした時のイベントを設定します。各GridViewColumnHeader要素にTag=xxx Click="GridViewColumnHeader_Click"
というTagとClick属性を追加します。Click属性はコードへのコマンドバインディングのようなもので、Tag属性はどのカラムのヘッダがクリックされたのかを判断するために使います。
<ListView x:Name="listView" ItemsSource="{Binding}" HorizontalAlignment="Left" Height="109" Margin="10,10,0,0" VerticalAlignment="Top" Width="175" SelectionChanged="listView_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Id}">
<GridViewColumnHeader Content="Id" Tag="Id" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Field1}">
<GridViewColumnHeader Content="Field1" Tag="Field1" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Field2}">
<GridViewColumnHeader Content="Field2" Tag="Field2" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
コード部分
XAMLにClick属性を追加すると、コードの方にもClickされたときのイベントが自動的に追加されます。GridViewColumnHeaderクラスのインスタンスを作って、そのTagプロパティを参照すると、XAMLで指定したTag属性の値が得られます。今回は、実際にソートをする部分は別のメソッドにしましたので、ソートのメソッドを呼び出します。
private void GridViewColumnHeader_Click(object sender, RoutedEventArgs e)
{
GridViewColumnHeader columnHeader = sender as GridViewColumnHeader;
string tag = columnHeader.Tag as string; // ヘッダーに設定してあるtagを取り出す
SortListView(listView, tag, false); // 並び替えのメソッドを呼び出す
}
ListView.Items.SortDescriptionsプロパティにソートするカラムとソート順の情報を設定すると、それに従ってソートされます。本当は複数のカラムを組み合わせてソートできるはずなのですが、今回は1カラムだけでソートするようにします。いろいろしてる感じのコードですが、基本的には既存の登録がAscendingかDecendingか調べて逆のものをセットしているだけです。
public void SortListView(ListView listView, string tag, bool IsMultiSort)
{
if (listView.Items.Count < 2) return; // ListViewの項目が0個または1個の場合は何もしない
// なんちゃってソート
ListSortDirection direction;
// SortDescriptionsに何も登録されていない場合はSortDescriptionsにDescendingを登録して(Descendingで並び替えて)処理を終わる
if (listView.Items.SortDescriptions.Count==0)
{
direction = ListSortDirection.Descending;
listView.Items.SortDescriptions.Add(new SortDescription(tag, direction));
return;
}
// 最後に登録されているSortDescriptionに合わせて、AscendingかDescendingか選択する
if (listView.Items.SortDescriptions.Last().Direction == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
// SortDescriptionをクリアして、選択したdirectionを登録する(選択したdirectionで並び替える)
listView.Items.SortDescriptions.Clear();
listView.Items.SortDescriptions.Add(new SortDescription(tag, direction));
}
コード全文
コードをまとめておきます。
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="200" Loaded="Window_Loaded">
<Grid>
<ListView x:Name="listView" ItemsSource="{Binding}" HorizontalAlignment="Left" Height="109" Margin="10,10,0,0" VerticalAlignment="Top" Width="175" SelectionChanged="listView_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Id}">
<GridViewColumnHeader Content="Id" Tag="Id" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Field1}">
<GridViewColumnHeader Content="Field1" Tag="Field1" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Field2}">
<GridViewColumnHeader Content="Field2" Tag="Field2" Click="GridViewColumnHeader_Click" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<Button x:Name="button" Content="Add" HorizontalAlignment="Left" Margin="110,144,0,0" VerticalAlignment="Top" Width="37" Click="button_Click"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="18" Margin="10,144,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="95"/>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="10,124,0,0" TextWrapping="Wrap" Text="{Binding}" VerticalAlignment="Top" Width="175" Height="15"/>
<Button x:Name="buttonRemove" Content="Del" HorizontalAlignment="Left" Margin="152,144,0,0" VerticalAlignment="Top" Width="33" Click="buttonRemove_Click"/>
</Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public partial class MainWindow : Window
{
ObservableCollection<HogeHoge> list = new ObservableCollection<HogeHoge>();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
listView.DataContext = list;
}
// Addボタンがクリックされたときの処理
private void button_Click(object sender, RoutedEventArgs e)
{
// 追加する項目のIdの値を計算する
Int64 newId;
if (list.Count > 0)
{
// 既存の項目のIdの最大値をを求めて、それに+1する
var query = from p in list select p.Id;
newId = query.Max() + 1;
}
else
{
newId = 1;
}
HogeHoge item = new HogeHoge { Id = newId, Field1 = textBox.Text, Field2 = null }; // 追加する項目の内容を設定する
list.Add(item); // listに項目を追加する
}
// Delボタンをクリックしたときの処理
private void buttonRemove_Click(object sender, RoutedEventArgs e)
{
if (list.Count < 1) return; // listに項目が無い場合は何もしない
HogeHoge item = (HogeHoge)listView.SelectedItem; // ListViewで選択されている項目を取り出す
list.Remove(item); // listから選択された項目と一致するものを削除する
}
// ListViewのヘッダーがクリックされたときの処理
private void GridViewColumnHeader_Click(object sender, RoutedEventArgs e)
{
GridViewColumnHeader columnHeader = sender as GridViewColumnHeader;
string tag = columnHeader.Tag as string; // ヘッダーに設定してあるtagを取り出す
SortListView(listView, tag, false); // 並び替えのメソッドを呼び出す
}
public void SortListView(ListView listView, string tag, bool IsMultiSort)
{
if (listView.Items.Count < 2) return; // ListViewの項目が0個または1個の場合は何もしない
// なんちゃってソート
ListSortDirection direction;
// SortDescriptionsに何も登録されていない場合はSortDescriptionsにDescendingを登録して(Descendingで並び替えて)処理を終わる
if (listView.Items.SortDescriptions.Count == 0)
{
direction = ListSortDirection.Descending;
listView.Items.SortDescriptions.Add(new SortDescription(tag, direction));
return;
}
// 最後に登録されているSortDescriptionに合わせて、AscendingかDescendingか選択する
if (listView.Items.SortDescriptions.Last().Direction == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
// SortDescriptionをクリアして、選択したdirectionを登録する(選択したdirectionで並び替える)
listView.Items.SortDescriptions.Clear();
listView.Items.SortDescriptions.Add(new SortDescription(tag, direction));
}
// リストビューの項目が選択されたときの処理
private void listView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (listView.SelectedItem == null) return; // ListViewで何も選択されていない場合は何もしない
HogeHoge item = (HogeHoge)listView.SelectedItem; // ListViewで選択されている項目を取り出す
textBlock.Text = item.Field1; // 取り出された項目のプロパティをTextBlockに表示する
}
}
public class HogeHoge
{
public Int64 Id { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
}
動かしてみた
起動直後の状態です。XAMLの方にListViewのヘッダのタイトルを記述したので、ヘッダの文字列が表示されています。
TextBoxに「いろはに」と入力して、「Add」ボタンを押した状態です。ListViewに項目が追加されました。
続けて「ほへと」と「ちりぬるを」を追加して、「ほへと」を選択した状態です。TextBlockに「ほへと」が表示されます。
ヘッダ「Field1」をクリックすると、下図の様にアイテムが並び替わります。
「ちりぬるを」を選択して「Del」をクリックすると、下図の様に「ちりぬるを」のアイテムが削除されます。
まとめ
ListViewについてググってみて感じたのですが、WindowFormのListViewとWPFのListViewで結構違います。WPFの方はMVVMも入ってきたりして、いろいろな書き方があるようです。プロの人には便利でしょうが、初心者には取っ付きにくいですね。
更新日
公開日
広告
C#のコントロールカテゴリの投稿
- C#でコレクションの要素の変更をバインド先のコントロールに反映する方法
- C#のComboBoxを使ってみた
- C#のDataGridで右クリックメニューを作ってみた
- C#のDataGridの罫線と背景色を変えてみた
- C#のListBoxでCheckBoxを並べてみた
- C#のListBoxを使ってみた
- C#のListViewで列のタイトルを変える
- C#のListViewで選択したアイテムを取得する方法
- C#のListViewに文字を入力する方法
- C#のListViewのヘッダーをクリックして列をソートする方法
- C#のListViewを使ってみた
- C#のRadioButtonで選択された項目を調べる(foreach編)
- C#のRadioButtonを試してみた
- C#のTextBoxで最下行に自動でスクロールする方法
- C#のWPFのコントロール一覧
- C#のスライダコントロールを試してみた
- C#のタブをコードから切り替える
- C#のメニューのイベントを1つにまとめてみた