はじめに
リスト項目を階層状に表示できるツリービューコントロールは何かと便利なコントロールですが、リストの動作はノードの展開と折りたたみしかありません。例えば、ユーザーが自由にリスト項目の順序を入れ替えられれば、アプリケーションの幅はぐっと広がるのではないでしょうか。
ComponentOne Studio for Silverlightに収録されているC1TreeViewコントロールは、階層ツリーを作成する際、好きな画像をアイコンに設定でき、文字色を選択してカラフルに設定できます。さらに、リスト項目をドラッグ&ドロップで入れ替えることもできます。
今回は、このC1TreeViewコントロールを使って、見た目が綺麗で、ドラッグ&ドロップもできるツリービューを持ったSilverlightアプリケーションを作ってみました。
対象読者
Visual Basic/Visual C# 2010を使ってプログラムを作ったことのある人。また、SilverlightおよびXAMLに対する基礎的な知識が必要になります。
必要な環境
Visual Basic 2010、Visual C# 2010、Visual Studio 2010でプログラムが作れる環境。また、Silverlight 4 Tools for Visual Studio 2010をインストールしていることが必須条件です。
なお、本プログラムはWindows Vista上で動作するVisual Studio 2010およびSilverlight 4 Tools for Visual Studio 2010を使用して作成し、Internet Explorer 8で動作確認を行っています(※注意)。
今回の記事から、Visual Studio 2010+Silverlight 4 Tools for Visual Studio 2010の開発環境になっています。
これまでのVisual Studio 2008+Silverlight 3 Tools for Visual Studio 2008の環境では、サンプルソースは動作しないので注意してください。
サンプルソースを実行するには、C#、VBともに次の設定を行う必要があるため、注意してください(※○○にはcsまたはvbのいずれかが入ります)。
- ソリューションエクスプローラでASP.NET Webサイトプロジェクト(sl_TreeView2011_○○.Web)を右クリックし、[スタートアッププロジェクトに設定]を選択
- ソリューションエクスプローラでsl_TreeView2011_○○TestPage.aspxを右クリックし、[スタートページに設定]を選択
コンポーネントのインストール
ComponentOne Studio for Silverlightを使用する方は、Visual Studio、Visual Basic、Visual C#の開発環境にComponentOne Studio Enterpriseをインストールする必要があります。
インストーラは、グレープシティのWebページからダウンロードできます。製品ページの[申込フォーム]をクリックし、グレープシティのWebサイトへ必要情報を登録すると、添付トライアルライセンスキーファイルとダウンロードサイトを記載したE-Mailが送られてきますので、ここからダウンロードします。制限事項などの詳細については、インストーラに同梱されているリリースノートを参照ください。
C1TreeViewコントロールについて
TreeView for Silverlightは、データ項目を階層的に表示するコントロールです。WPFやWindowsフォーム、Silverlightに標準で装備されているTreeViewコントロールに似ていますが、標準コントロールよりもさらに多い、次のような機能を備えています。
ノードのドラッグ&ドロップ
C1TreeViewコントロールは、ツリー内のドラッグ&ドロップ操作をサポートします。AllowDragDropプロパティを「true」に設定するだけで、ツリー内のノードをドラッグして並べ替えることができます。また、ドラッグ&ドロップの操作中にイベントを発生し、このイベントを使って一部のノードのドラッグを禁止したり、一部のノードがドロップ先にならないように指定できます。
キーボードによる移動
カーソルキーを使用して、ノード間の移動や、ノードの展開・折りたたみができます。また、自動検索機能を使用して、特定のノードをすばやく簡単に見つけることができます。
ノードのカスタマイズ
リスト項目となっているノードヘッダーはコンテンツ要素なので、任意の種類の要素をホストできます。画像、チェックボックスなど、アプリケーションに必要なあらゆる要素を追加できます。
Silverlight Toolkitテーマのサポート
ExpressionDark、ExpressionLight、WhistlerBlue、RainierOrange、ShinyBlue、BureauBlackなど、よく使用されているMicrosoft Silverlight Toolkitテーマが組み込みでサポートされており、それを使ってUIにスタイルを追加できます。
C1TreeViewクラスは、次の2つの要素からなる、1つのStackPanelです。
- 実際のノードを表すヘッダー
- 別のStackPanelからなる本体
各リスト項目はC1TreeViewItemクラスになっており、それぞれにHeaderオブジェクトを持ちます。このHeaderオブジェクト内にStackPanelコントロールを組み込むことで、リスト項目にImage、TextBlock、CheckBoxなどのコントロールを組み込むことができます。C1TreeViewクラスは、C1TreeViewItemクラスを入れ子状に配置することで、リスト項目を階層状に表示します。
C1TreeViewコントロールのAllowDragDropプロパティを「true」に設定すると、ノード、ノードの間、または1つのツリーから別のツリーにドラッグ&ドロップできます。
実行時にユーザーがノードをクリックすると、そのノードは自動的に選択中としてマークされ、C1TreeViewコントロールにSelectionChangedイベントが発生します。現在選択中の項目を取得するには、SelectedItemプロパティを使用します。ノードの操作では、マウスとキーボードによる移動をサポートします。それぞれの操作とコントロールのアクションは、次表のようになります。
アクション | マウスコマンド |
ノードの展開 | ノード名の左側にあるプラス記号をクリック |
ノードの折りたたみ | ノード名の左側にあるマイナス記号をクリック |
ノードの選択 | ノード名をクリック |
アクション | キーボードコマンド |
ノードの展開 | [+]キー |
ノードの折りたたみ | [-]キー |
ノードを上に移動 | [↑]キー |
ノードを下に移動 | [↓]キー |
複数ノードの選択 | [Ctrl]キー+クリック |
今回は、このC1TreeViewコントロールの機能の中で、ドラッグ&ドロップによるリスト項目の移動を使ったアプリケーションを作成します。
Webページの作成
では、Webページを作成してみましょう。まず、グリッドを1行2列に設定します。左側の列にC1TreeViewコントロールを、右側の列にTextBoxコントロールを配置します。C1TreeViewコントロールの設定は、この後説明します。
Silverlightプロジェクトの作成
まずは、Silverlightプロジェクトを作成します。Visual Studio 2010 Silverlight Tools 4.0をインストールし、Visual Studioで新しいプロジェクトを作成すると、[プロジェクトの種類]に[Silverlight]が追加されています。これを選んで[テンプレート]から[Silverlightアプリケーション]を選びます。
「新しいSilverlightアプリケーション」というダイアログボックスが表示されるので、[新しいWebプロジェクトの種類]でリストから[ASP.NET Webサイト]を選びます。
プロジェクトが作成され、新しいWebサイトに「MainPage.xaml」が作られてXAMLのコードが表示されます。このXAMLコードに、以下の名前空間を追加します。
xmlns:c1="http://schemas.componentone.com/winfx/2006/xaml"
この名前空間の追加によって、XAMLでComponentOne Studio for Silverlightの各コントロールを使うことができるようになります。プロジェクトに次の参照を追加します。
C1.Silverlight.dll
全体のレイアウト作成
今回は、「交通違反の反則金」を表示するアプリケーションを作成します。反則行為の種類をC1TreeViewコントロールで表示し、リスト項目をクリックすると、反則金がTextBoxコントロールで表示されます。
グリッドとC1TreeViewコントロールの作成
まず、グリッドを設定します。「ColumnDefinitions」プロパティを使用し、2列のグリッドにします。
<Grid x:Name="LayoutRoot" Background="White" ShowGridLines="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="136*"/> <ColumnDefinition Width="264*"/> </Grid.ColumnDefinitions> </Grid>
続いて、C1TreeViewコントロールを記述します。入力候補がリストで表示されるので、順番に選んでいきます。
<c1:C1TreeView></c1:C1TreeView>
<c1:C1TreeView>を、次のXAMLに書き換えます。なお、Marginプロパティは、Visual StudioのデザイナでC1TreeViewコントロールの位置を変えると値が変化します。また、C1TreeViewコントロールのSelectionChangedイベントを使うため、このイベントハンドラを作成しておきます。リストのドラッグ&ドロップを有効にするために、「AllowDragDrop」プロパティを「True」にセットします。これで、C1TreeViewコントロールのすべてのノードがドラッグ&ドロップで移動可能になります(※注意)。
<c1:C1TreeView x:Name="TreeView1" AllowDragDrop="True" Margin="42,41,20,165" FontSize="14" SelectionChanged="TreeView1_SelectionChanged"> </c1:C1TreeView>
個々のノードのドラッグ&ドロップ操作の可否を設定したい場合は、それぞれのノードのC1TreeViewItemクラスの「AllowDragDrop」プロパティを使用します。
ツリーの階層を作成する
次に、ツリーの階層を作成します。トップレベルの階層と次の階層で、以下のようなリスト項目を作成します。
-
反則行為の種類
- 速度超過
- 積載物重量制限超過
- 信号無視
- 免許証不携帯
- 追越し違反
C1TreeViewコントロールの中に、C1TreeViewItemクラスを入れ子状にして記述していきます。リスト項目の文字列やアイコンはHeaderプロパティを使用して組み込みます。
トップレベルの階層は「反則行為の種類」で「大見出し」にあたる部分になり、C1TreeViewコントロールの中にC1TreeViewItemクラスを作成します。
<c1:C1TreeView x:Name="TreeView1" AllowDragDrop="True" Margin="42,41,20,165" FontSize="14" SelectionChanged="TreeView1_SelectionChanged"> <c1:C1TreeViewItem Foreground="SteelBlue" > </c1:C1TreeViewItem> </c1:C1TreeView>
そして、C1TreeViewItemクラスの中に、さらに次の階層となるC1TreeViewItemクラスを作成します。
<c1:C1TreeViewItem Foreground="SteelBlue" > <c1:C1TreeViewItem Foreground="DarkGreen"> </c1:C1TreeViewItem> </c1:C1TreeViewItem>
ここでは、第2階層に5つの項目を組み込むため、XAMLのコードは次のようになります。
<c1:C1TreeView x:Name="TreeView1" AllowDragDrop="True" Margin="42,41,20,165" FontSize="14" SelectionChanged="TreeView1_SelectionChanged"> <c1:C1TreeViewItem Foreground="SteelBlue" > <!-- グループ 1 --> <c1:C1TreeViewItem Foreground="DarkGreen"> </c1:C1TreeViewItem> <!-- グループ 2 --> <c1:C1TreeViewItem Foreground="Coral"> </c1:C1TreeViewItem> <!-- グループ 3 --> <c1:C1TreeViewItem Foreground="DeepPink"> </c1:C1TreeViewItem> <!-- グループ なし --> <c1:C1TreeViewItem x:Name="Item12" Foreground="DarkViolet"> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item13" Foreground="mediumblue"> </c1:C1TreeViewItem> </c1:C1TreeViewItem> </c1:C1TreeView>
ヘッダー部にアイコンと項目名を設定する
続いて、作成したノードにアイコンと項目名を設定していきます。これは、各C1TreeViewItemオブジェクトのHeaderプロパティを利用しますが、テキストだけのリストであれば、Headerプロパティに項目名を設定するだけです。
<c1:C1TreeViewItem Header="反則行為の種類" />
テキストとアイコンを組み込むというように、複数のコントロールを使用したい場合は、StackPanelにImageコントロールとTextBlockコントロールを組み込みます。
<c1:C1TreeViewItem Foreground="SteelBlue" > <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/List_BulletsHS.png"/> <TextBlock Text=" 反則行為の種類"/> </StackPanel> </c1:C1TreeViewItem.Header>
Imageコントロールを使う場合は、プロジェクトの「ClientBin」フォルダに使用するイメージ画像を追加しておきます(ここでは、アプリケーション全体で7つのイメージ画像を使用するためまとめて追加)。
同様に、第2階層の5つのノードも同じように、StackPanel、Image、TextBlockコントロールを使ってリスト項目を作成します。
<!-- グループ 1 --> <c1:C1TreeViewItem Foreground="DarkGreen"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/book_reportHS.png"/> <TextBlock Text=" 速度超過"/> </StackPanel> </c1:C1TreeViewItem.Header>
第3階層のノードを設定する
ここまでできたら、後は簡単です。第2階層の3つに、さらにもう1階層下のノードを作成します。そして、それぞれのC1TreeViewItemクラスの中に、さらにC1TreeViewItemクラスを作成していきます。アイコンやテキストの設定方法も同様で、C1TreeViewItemクラスの中にC1TreeViewItemクラスを作っていけば、どんどん深い階層のノードを作成できます。
<!-- グループ 1 --> <c1:C1TreeViewItem Foreground="DarkGreen"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/book_reportHS.png"/> <TextBlock Text=" 速度超過"/> </StackPanel> </c1:C1TreeViewItem.Header> <c1:C1TreeViewItem x:Name="Item1" > <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 高速道路35以上40未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item2" > <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 高速道路30以上35未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> ......... ......... </c1:C1TreeViewItem>
<Grid x:Name="LayoutRoot" Background="White" ShowGridLines="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="136*"/> <ColumnDefinition Width="264*"/> </Grid.ColumnDefinitions> <c1:C1TreeView x:Name="TreeView1" AllowDragDrop="True" Margin="42,41,20,165" FontSize="14" SelectionChanged="TreeView1_SelectionChanged"> <c1:C1TreeView.Effect> <DropShadowEffect BlurRadius="5" Color="DarkGray" /> </c1:C1TreeView.Effect> <c1:C1TreeViewItem Foreground="SteelBlue" > <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/List_BulletsHS.png"/> <TextBlock Text=" 反則行為の種類"/> </StackPanel> </c1:C1TreeViewItem.Header> <!-- グループ 1 --> <c1:C1TreeViewItem Foreground="DarkGreen"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/book_reportHS.png"/> <TextBlock Text=" 速度超過"/> </StackPanel> </c1:C1TreeViewItem.Header> <c1:C1TreeViewItem x:Name="Item1" > <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 高速道路35以上40未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item2" > <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 高速道路30以上35未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item3"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 25以上30未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item4"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 20以上25未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item5"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 15以上20未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item6"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 15未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> </c1:C1TreeViewItem> <!-- グループ 2 --> <c1:C1TreeViewItem Foreground="Coral"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/FillDownHS.png"/> <TextBlock Text=" 積載物重量制限超過"/> </StackPanel> </c1:C1TreeViewItem.Header> <c1:C1TreeViewItem x:Name="Item7"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 普通等10割以上"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item8"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 5割以上10割未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item9"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/Flag_redHS.png"/> <TextBlock Text=" 5割未満"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> </c1:C1TreeViewItem> <!-- グループ 3 --> <c1:C1TreeViewItem Foreground="DeepPink"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/RadialChartHS.png"/> <TextBlock Text=" 信号無視"/> </StackPanel> </c1:C1TreeViewItem.Header> <c1:C1TreeViewItem x:Name="Item10" Foreground="IndianRed"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/RadialChartHS.png"/> <TextBlock Text=" 信号無視(赤色等)違反"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item11" Foreground="Indigo"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/RadialChartHS.png"/> <TextBlock Text=" 信号無視(点滅)違反"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> </c1:C1TreeViewItem> <!-- グループ なし --> <c1:C1TreeViewItem x:Name="Item12" Foreground="DarkViolet"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/BreakpointHS.png"/> <TextBlock Text=" 免許証不携帯"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> <c1:C1TreeViewItem x:Name="Item13" Foreground="mediumblue"> <c1:C1TreeViewItem.Header> <StackPanel Orientation="Horizontal"> <Image Source="/DoubleRightArrowHS.png"/> <TextBlock Text=" 追越し違反"/> </StackPanel> </c1:C1TreeViewItem.Header> </c1:C1TreeViewItem> </c1:C1TreeViewItem> </c1:C1TreeView>
TextBoxコントロールとイベントハンドラの処理
最後に、反則金を表示するTextBoxコントロールをグリッドの右側に配置し、イベントハンドラにその処理を作成します。
<TextBox Grid.Column="1" FontSize="14" Name="TextBox1" Height="104" HorizontalAlignment="Left" Margin="21,44,0,0" VerticalAlignment="Top" Width="207" AcceptsReturn="True"> <TextBox.Effect> <DropShadowEffect BlurRadius="5" Color="DarkGray" /> </TextBox.Effect> </TextBox>
イベントハンドラの処理では、リスト項目が選択されると、C1TreeViewコントロールのSelectedItemプロパティに選択されたC1TreeViewItemオブジェクトへの参照が格納されます。ここでは、各C1TreeViewItemオブジェクトに名前を付けているため、そのNameプロパティでどのノードが選択されたのかを識別し、TextBoxコントロールへの表示を振り分けています。
なお、すべてのC1TreeViewItemオブジェクトに名前を付けていないので、名前のないノード(C1TreeViewItemオブジェクト)が選択されると「NullReferenceException」というエラーが発生します。そのため、Tryステートメントを使ってエラー回避の処理を組み込んでいます。
Imports C1.Silverlight Partial Public Class MainPage Inherits UserControl Public Sub New() InitializeComponent() End Sub Private Sub TreeView1_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Try Dim si As C1TreeViewItem = TreeView1.SelectedItem Dim kingaku As String = "" If si.Name = "" Then Exit Sub End If Select Case si.Name Case "Item1" kingaku = 35 Case "Item2" kingaku = 25 Case "Item3" kingaku = 18 Case "Item4" kingaku = 15 Case "Item5" kingaku = 12 Case "Item6" kingaku = 9 Case "Item7" kingaku = 35 Case "Item8" kingaku = 30 Case "Item9" kingaku = 25 Case "Item10" kingaku = 9 Case "Item11" kingaku = 7 Case "Item12" kingaku = 3 Case "Item13" kingaku = 9 End Select TextBox1.Text = "普通車の反則金額は" + kingaku + "千円" Catch ex As NullReferenceException End Try End Sub End Class
using C1.Silverlight; namespace sl_TreeView2011_cs { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void TreeView1_SelectionChanged(object sender, SelectionChangedEventArgs e) { try { String si = TreeView1.SelectedItem.Name; String kingaku = ""; if (si == "") { return; } switch (si) { case "Item1": kingaku = "35"; break; case "Item2": kingaku = "25"; break; case "Item3": kingaku = "18"; break; case "Item4": kingaku = "15"; break; case "Item5": kingaku = "12"; break; case "Item6": kingaku = "9"; break; case "Item7": kingaku = "35"; break; case "Item8": kingaku = "30"; break; case "Item9": kingaku = "25"; break; case "Item10": kingaku = "9"; break; case "Item11": kingaku = "7"; break; case "Item12": kingaku = "3"; break; case "Item13": kingaku = "9"; break; } TextBox1.Text = "普通車の反則金額は" + kingaku + "千円"; } catch (NullReferenceException ex) { } } } }
まとめ
今回は、C1TreeViewコントロールを使ってアイコンと文字色を組み合わせ、見栄えの良い綺麗なツリーを作成してみました。リスト項目がドラッグ&ドロップで入れ替えられるようになると、例えば、組織図を階層ツリーにしてメンバーの入れ替えをシミュレートしたり、ユーザーごとにリストの順序を入れ替える、などという形でアプリケーションを作ることができます。標準のTreeViewコントロールでは物足りないという方は、C1TreeViewコントロールを使ってみてはいかがでしょうか。