WPF/UWP:Linq-to-XAMLで簡単にUIコントロールへアクセスするには?[C#/VB]:.NET TIPS
Linq-to-XAMLライブラリを使うと、WPFアプリ/UWPアプリのUIコントロールを列挙できる。これを使って無名のコントロールにアクセスする方法を解説する。
対象:.NET 3.5以降、Visual Studio 2012以降
Windowsストアアプリ/Windows Phone 8.1/UWPアプリのXAMLでも利用可
UIコントロールにコードビハインドからアクセスしようとして困ったことはないだろうか? 特に、データテンプレートで自動生成された場合など、UIコントロールに名前が付けられないときは途方に暮れるかもしれない。
それにはVisualTreeHelperクラス(System.Windows.Media名前空間)を利用して、UIコントロールのビジュアルツリーを順に探索していけばよいのだが、コーディングが面倒である。本稿では、「Linq-to-XAML」ライブラリを使って、UIコントロールに簡単にアクセスする方法を紹介する。なお、本稿のサンプルは「Windows desktop code samples:.NET Tips #1124」からダウンロードできる。
Linq-to-XAMLを導入するには?
Linq-to-XAMLはオープンソースのライブラリで、ソースコードはGitHubで公開されている(利用に当たってはライセンス条項を確認していただきたい)。
バイナリーはNuGetで公開されているので、Visual Studioでプロジェクトごとに導入すればよい。メニューバーの[プロジェクト]−[NuGet パッケージの管理]から、オンラインで「Linq-to-XAML」を検索すると見つけられる。
UIコントロールを列挙するには?
Linq-to-XAMLは、指定したUIコントロールの子や親になっているUIコントロールを列挙できる。その中で筆者が最もよく使っているのは、DescendantsAndSelf拡張メソッド(LinqToXaml名前空間)だ。これはビジュアルツリーの階層に関係なく、子になっているUIコントロールを(指定したUIコントロールを含めて)全て列挙してくれるのである。
例えば、コードビハインドから、その画面にある全てのUIコントロールを取得するコードは次のようになる。
using LinqToXaml;
// ↑冒頭に必要
……省略……
// この画面にある全てのUIコントロールを列挙
IEnumerable<DependencyObject> allControls
= (this.Content as DependencyObject).DescendantsAndSelf();
Imports LinqToXaml
' ↑冒頭に必要
……省略……
' この画面にある全てのUIコントロールを列挙
Dim allControls As IEnumerable(Of DependencyObject) _
= CType(Me.Content, DependencyObject).DescendantsAndSelf()
このコードは、Visual Studio 2012のWPFと、Visual Studio 2015のUWPアプリで動作を確認している。
たった1行で、ビジュアルツリーを構成する全てのUIコントロールを列挙できてしまうのだ。
ラジオボタンにアクセスする例
例として、ラジオボタンのアクセスを試してみよう。
次の図とコードのようなUIを考える。<Grid>要素の中に<Border>要素があり、その中に<StackPanel>要素があり、さらにその中に複数の<RadioButton>要素があるというものだ。
<Grid ……省略……>
<Grid.RowDefinitions>……省略……</Grid.RowDefinitions>
<Grid.ColumnDefinitions>……省略……</Grid.ColumnDefinitions>
<Border BorderBrush="Green" BorderThickness="4" Margin="20">
<StackPanel HorizontalAlignment="Center" Margin="8">
<TextBlock>グループ1</TextBlock>
<RadioButton GroupName="Group1">選択肢A1</RadioButton>
<RadioButton GroupName="Group1">選択肢A2</RadioButton>
<RadioButton GroupName="Group1">選択肢A3</RadioButton>
</StackPanel>
</Border>
<Border Grid.Column="1" BorderBrush="SlateBlue" BorderThickness="4" Margin="20">
<StackPanel HorizontalAlignment="Center" Margin="8">
<TextBlock>グループ2</TextBlock>
<RadioButton GroupName="Group2">選択肢B1</RadioButton>
<RadioButton GroupName="Group2">選択肢B2</RadioButton>
<RadioButton GroupName="Group2">選択肢B3</RadioButton>
</StackPanel>
</Border>
……省略……
</Grid>
このコードは、Visual Studio 2012のWPFアプリと、Visual Studio 2015のUWPアプリで動作を確認している。
上のXAMLコードでは、UIコントロールに名前を付けていないことに注目してほしい。UIコントロールの変数名を使わずに、「グループ1」と「グループ2」のラジオボタンにコードビハインドからアクセスしたいのである。
Linq-to-XAMLのDescendantsAndSelf拡張メソッドで、全てのUIコントロールを列挙できるのは前述した。その中からラジオボタンを取り出すには、LINQ to ObjectのOfType拡張メソッド(System.Linq名前空間のEnumerableクラス)を利用する。グループごとに分けるには、同じくWhere拡張メソッド(同じくEnumerableクラス)で、ラジオボタンのGroupNameプロパティを見ればよい(次のコード)。
// ラジオボタンを保持するためのコレクション(メンバ変数)
List<RadioButton> _group1;
List<RadioButton> _group2;
……省略……
// この画面にある全てのUIコントロールを列挙
IEnumerable<DependencyObject> allControls
= (this.Content as DependencyObject).DescendantsAndSelf();
// ラジオボタンだけを取り出す
IEnumerable<RadioButton> radioButtons = allControls.OfType<RadioButton>();
// Group1/Group2を取り出してメンバ変数に格納
_group1 = radioButtons.Where(rb => rb.GroupName == "Group1").ToList();
_group2 = radioButtons.Where(rb => rb.GroupName == "Group2").ToList();
' ラジオボタンを保持するためのコレクション(メンバ変数)
Private _group1 As List(Of RadioButton)
Private _group2 As List(Of RadioButton)
……省略……
' この画面にある全てのUIコントロールを列挙
Dim allControls As IEnumerable(Of DependencyObject) _
= CType(Me.Content, DependencyObject).DescendantsAndSelf()
' ラジオボタンだけを取り出す
Dim radioButtons As IEnumerable(Of RadioButton) _
= allControls.OfType(Of RadioButton)()
' Group1/Group2を取り出してメンバ変数に格納
_group1 = radioButtons.Where(Function(rb) rb.GroupName = "Group1").ToList()
_group2 = radioButtons.Where(Function(rb) rb.GroupName = "Group2").ToList()
このコードは、Visual Studio 2012のWPFアプリと、Visual Studio 2015のUWPアプリで動作を確認している。
このコードは、UIが構築された後ならば、いつ実行してもよい。別途公開のサンプルでは、コンストラクタ内でInitializeComponentメソッドの呼び出しの後に置いている。
このコードの後でUIが変更されることは想定していないので、最後にToListメソッドを呼び出してコレクションの値を確定させている(以降のパフォーマンス向上のため)。
なお、先のコードと同じく、冒頭にLinqToXaml名前空間を使用する宣言の追加が必要だ。
上のようにしてコレクションに格納できれば、後は簡単にコードビハインドからアクセスできる。例えば、「グループ1」の中で選択されているラジオボタンを調べるコードは次のように書ける。
RadioButton selected1
= _group1.Where(rb => rb.IsChecked ?? false).FirstOrDefault();
Dim selected1 As RadioButton _
= _group1.Where(Function(rb) If(rb.IsChecked, False)).FirstOrDefault()
このコードは、Visual Studio 2012のWPFアプリと、Visual Studio 2015のUWPアプリで動作を確認している。
「selected1」変数には、選択されているラジオボタンが入る。ただし、どのラジオボタンも選択されていないときは、「selected1」変数にはnull/Nothingが入る。
まとめ
画面上のUIコントロールを列挙するには、Linq-to-XAMLを利用すると簡単だ。後は、LINQ to Objectで自在にアクセスできるだろう。
利用可能バージョン:.NET Framework 3.5以降
カテゴリ:WPF/XAML 処理対象:コントロール
カテゴリ:オープンソース・ライブラリ 処理対象:コントロール
使用ライブラリ:Enumerableクラス(System.Linq名前空間)
関連TIPS:Windowsフォーム上のすべてのコントロールを列挙するには?
関連TIPS:Windowsフォーム上のコントロールを検索するには?[2.0のみ、C#、VB]
Copyright© Digital Advantage Corp. All Rights Reserved.