[WPF] TreeView에 XElement를 속성으로 Binding

17838 단어 바인딩C#TreeViewWPF
C# 초학자용 기사를 여러가지 쓰고 있는 중입니다만, 자신을 위한 비망록.

WPF의 TreeView에 XML 데이터를 그대로 흘려 보내는 방법으로서 XDocument를 적용하는 방법은 몇 가지 발견되었지만, XElement를 원 데이터로서 Binding하는 방법이 좀처럼 발견되지 않았기 때문에 메모
# 그런 틈새 일을 할 녀석이 없습니까?
  • XDocument를 원 데이터로 하는 참고 기사
  • [WPF] [C #] XML 데이터를 TreeView에 표시 그 1 by
  • TreeView의 HierarchicalDataTemplate (XML 표시) by mikeo_410


  • 상기 참고 기사와 마찬가지로 기상청의 샘플 데이터를 사용하게 한다.
    기상청 방재 정보 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();
            }
        }
    }
    

    이제 다음과 같은 화면이 표시됩니다.


    디폴트에서는 트리는 모두 닫은 상태로 시작되기 때문에, 만약 처음부터 열린 상태로 해 두고 싶은 경우,
    먼저 든 참고 기사를 참조하는 방법

    읽고 있는 파일은 적당히 선택하고 있다(너무 크지 않고, 너무 작지 않을 정도의 파일)
    XAML의 코멘트에도 썼지만, 이하 2점이 중요. (자신이 막혔기 때문에)
    * 바인딩하는 것은 IEnumerable
    * XAttribute는 바인드 지원 외부이므로 변환기 사용

    좋은 웹페이지 즐겨찾기