LoginSignup
1
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-11-08

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使う

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1