Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

[WPF]TreeViewにXElementを属性付きBinding

C#初学者用記事をいろいろ書き込んでる最中ですが、自分の為の備忘録。

WPFのTreeViewにXMLデータをそのまま流し込む方法として、XDocumentを適用する方法はいくつか見つかったものの、XElementを元データとしてBindingする方法がなかなか見つからなかったのでメモ
# そんなニッチなことするやつあんまいないのか

上記参考記事と同様に、気象庁のサンプルデータを使わせてもらうことにする
気象庁防災情報XMLフォーマット 技術資料

Viewの実装

基本的には上記参考ページと同じ内容となるので、諸々割愛しつつ、ソースコードを貼る

xamlは以下の通り。
※注意 デフォルトの"MainWindow"という名前から"MainView"という名前に変えている

MainView.xaml
<Window x:Class="WpfTestView.MainView"
        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:WpfTestView"
        xmlns:vm="clr-namespace:WpfTestViewModel;assembly=WpfTestViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:MainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <local:XAttributeConverter x:Key="XAttributeConverter" />
    </Window.Resources>
    <Grid>
        <Border Margin="10" BorderBrush="Black" BorderThickness="1">
            <!-- TreeViewのItemsSourceはIEnumerableでないといけないので、IEnumerable<XElement>-->
            <TreeView ItemsSource="{Binding XTreeRoot, Mode=OneWay}">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Elements}">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock x:Name="TagName" Text="{Binding Name.LocalName}" />
                            <TextBlock x:Name="AttrStart" Text="(" />
                            <!-- XAttributeはBindingサポートしてないのでConverterを使う -->
                            <ItemsControl ItemsSource="{Binding Converter={StaticResource XAttributeConverter}}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal" Margin="2,0">
                                            <TextBlock Text="{Binding Name.LocalName}" />
                                            <TextBlock Text="=&quot;" />
                                            <TextBlock Text="{Binding Value}" />
                                            <TextBlock Text="&quot;" />
                                        </StackPanel>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <StackPanel Orientation="Horizontal" />
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>
                            <TextBlock x:Name="AttrEnd" Text=")" />
                            <TextBlock x:Name="Separater" Text=" : " />
                            <TextBlock x:Name="TagValue" Text="{Binding Value}" />
                        </StackPanel>
                        <!-- ノードによって表示形式を切替え -->
                        <HierarchicalDataTemplate.Triggers>
                            <DataTrigger Binding="{Binding NodeType}" Value="Text">
                                <Setter TargetName="TagName" Property="Text" Value="Value" />
                                <Setter TargetName="AttrStart" Property="Text" Value="" />
                                <Setter TargetName="AttrEnd" Property="Text" Value="" />
                                <Setter TargetName="Separater" Property="Text" Value="" />
                                <Setter TargetName="TagValue" Property="Text" Value="" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding HasAttributes}" Value="False">
                                <Setter TargetName="AttrStart" Property="Text" Value="" />
                                <Setter TargetName="AttrEnd" Property="Text" Value="" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding HasElements}" Value="True">
                                <Setter TargetName="Separater" Property="Text" Value="" />
                                <Setter TargetName="TagValue" Property="Text" Value="" />
                            </DataTrigger>
                        </HierarchicalDataTemplate.Triggers>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </Border>
    </Grid>
</Window>

主に説明しておくべきところとしてはコメントにも書いてるが、XAMLに記載している"XTreeRoot"というプロパティのみがViewModel側とバインドされるもので、それより下階層のBinding要素はXElementオブジェクトにバインドされている
HierarchicalDataTemplate下のStackPanelやTriggersに関しては、完全に個人的な趣向で書いているので自由にカスタマイズ可能

ViewModelの前にXAttributeをバインドする為のConverterを書いておく

XAttributeConverter.cs
public class XAttributeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is XElement x)) return Enumerable.Empty<XAttribute>();
        return x.Attributes();
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

ConvertではXElementオブジェクトのIEnumerableを返すように書いている。

今回の例ではXMLの内容は更新しないのでTreeViewはOneWay(Source→Viewの方向のみ)としている
なのでConvertBackは使用しないのでNotImplementedExceptionとしておく。

ViewModelの実装

MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged == null) return;
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public MainViewModel()
    {
        XDocument xDoc = XDocument.Load(@".\15_12_01_161130_VPWW54.xml");
        XTreeRoot = new List<XElement>() { xDoc.Root };
    }

    private IEnumerable<XElement> _xTreeRoot = null;
    public IEnumerable<XElement> XTreeRoot
    {
        get => _xTreeRoot;
        set
        {
            _xTreeRoot = value;
            OnPropertyChanged();
        }
    }
}

これで次のような画面が表示される
1.PNG

デフォルトではツリーは全て閉じた状態で始まる為、もし最初から開いた状態にしておきたい場合、
最初に挙げた参考記事を参照方

読み込んでいるファイルは適当に選んでいる(大きすぎず、小さすぎないくらいのファイル)
XAMLのコメントにも書いたが、以下2点が肝要。(自分が詰まったので)
* バインドするのはIEnumerable
* XAttributeはバインドサポート外なのでconverter使う

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away