質問するログイン新規登録
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

2回答

1375閲覧

XMLドキュメントを元に作ったTreeViewが更新できない(WPF)

sa_sa

総合スコア15

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

1クリップ

投稿2017/08/30 07:05

編集2017/08/30 07:10

0

1

###前提・実現したいこと
WPFでXMLドキュメントをソースとしてTreeViewを作ろうとしています。
MainWindowViewModelにXmlDocument型プロパティ「PeopleTree」を作ってTreeViewにバインディングしています。
画面内のボタンを押すことでPeopleTreeにノードが追加されるようにしましたが、その変更内容がTreeViewに伝わっていないのか画面の内容が変わりません。

###発生している問題・エラーメッセージ
立ち上げ時にXMLファイルをロードすることで、その内容のツリーを作ることはできています。
しかし、その後ボタンを押してもTreeViewの内容が更新されません。
デバッグして確認したところバインディング元であるPeopleTreeへのノード追加は問題なく行われているようです。
XMLドキュメントへのノード追加はプロパティ変更として扱われないということでしょうか?
初歩的な質問かもしれませんが、解決策を教えていただきたく思います。
よろしくお願いいたします。

###ソースコード
xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication44ViewModel="clr-namespace:WpfApplication44ViewModel;assembly=WpfApplication44ViewModel" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication44.MainWindow" Title="MainWindow" Height="249" Width="276"> <Window.Resources> <HierarchicalDataTemplate x:Key="XmlTemplate" ItemsSource="{Binding }"> <StackPanel> <TextBlock Name="nameTextBlock" Text="{Binding Path=Attributes.ItemOf[Name].Value}" /> </StackPanel> </HierarchicalDataTemplate> </Window.Resources> <Grid> <StackPanel Orientation="Vertical"> <TreeView Name="xmlTree" ItemsSource="{Binding Path=PeopleTree}" ItemTemplate="{StaticResource XmlTemplate}" /> <Button Name="addButton" Command="{Binding AddPersonCommand}" Content="追加"/> </StackPanel> </Grid> </Window>

xaml.cs

using System.Windows; using WpfApplication44ViewModel; namespace WpfApplication44 { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); App app = Application.Current as App; DataContext = app.MainWindowViewModel; } } }

ViewModel

using System.Windows.Input; using Microsoft.Practices.Prism.Mvvm; using Microsoft.Practices.Prism.Commands; using System.Xml; namespace WpfApplication44ViewModel { public class MainWindowViewModel:BindableBase { // TreeViewへバインディングするXmlDocument private XmlDocument peopleTree; public XmlDocument PeopleTree { get { return peopleTree; } set { SetProperty(ref peopleTree, value); } } // コンテキスト public MainWindowViewModel() { PeopleTree = new XmlDocument(); PeopleTree.Load(@"peopleTree.xml"); } // ボタンへバインディングしているコマンド private ICommand addPersonCommand; public ICommand AddPersonCommand { get { return addPersonCommand ?? (addPersonCommand = new DelegateCommand(addPersonMethod)); } } // ツリーの子ノードとして「名無しさん」を追加する private void addPersonMethod() { var ret = peopleTree.CreateElement("person"); ret.SetAttribute("Name", "名無しさん"); peopleTree.DocumentElement.AppendChild(ret); } } }

xmlファイル

<people Name="メンバー"> <person Name="Aさん" /> <person Name="Bさん" /> <person Name="Cさん" /> </people>

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

XMLドキュメントへのノード追加はプロパティ変更として扱われないということでしょうか?

XPathでバインドしたら反映されました。
方法: XMLDataProvider と XPath クエリを使用して XML データにバインドする - WPF .NET Framework | Microsoft Learn

XElementのほうが扱いが楽かもしれません。
方法: XDocument、XElement、または LINQ for XML クエリの結果にバインドする - WPF .NET Framework | Microsoft Learn

xml

1<Window 2 x:Class="Q90457.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 Width="800" 6 Height="450" 7 ThemeMode="System"> 8 <DockPanel> 9 <Button 10 HorizontalAlignment="Stretch" 11 Click="Button_Click" 12 Content="追加" 13 DockPanel.Dock="Bottom" /> 14 15 <UniformGrid Rows="1"> 16 <GroupBox Header="original"> 17 <TreeView ItemsSource="{Binding Path=XmlDocument}"> 18 <TreeView.ItemTemplate> 19 <HierarchicalDataTemplate ItemsSource="{Binding}"> 20 <StackPanel> 21 <TextBlock Text="{Binding Path=Attributes.ItemOf[Name].Value}" /> 22 </StackPanel> 23 </HierarchicalDataTemplate> 24 </TreeView.ItemTemplate> 25 </TreeView> 26 </GroupBox> 27 28 <GroupBox Header="XPath"> 29 <TreeView DataContext="{Binding XmlDocument}" ItemsSource="{Binding XPath=people}"> 30 <TreeView.ItemTemplate> 31 <HierarchicalDataTemplate ItemsSource="{Binding XPath=person}"> 32 <TextBlock Text="{Binding XPath=@Name}" /> 33 </HierarchicalDataTemplate> 34 </TreeView.ItemTemplate> 35 </TreeView> 36 </GroupBox> 37 38 <GroupBox Header="XElement"> 39 <TreeView ItemsSource="{Binding XElement.Elements[people]}"> 40 <TreeView.ItemTemplate> 41 <HierarchicalDataTemplate ItemsSource="{Binding Elements[person]}"> 42 <TextBlock Text="{Binding Attribute[Name].Value}" /> 43 </HierarchicalDataTemplate> 44 </TreeView.ItemTemplate> 45 </TreeView> 46 </GroupBox> 47 </UniformGrid> 48 </DockPanel> 49</Window>

cs

1using System.Windows; 2using System.Xml; 3using System.Xml.Linq; 4 5namespace Q90457; 6 7public partial class MainWindow : Window 8{ 9 public XmlDocument XmlDocument { get; } 10 public XElement XElement { get; } 11 12 public MainWindow() 13 { 14 InitializeComponent(); 15 DataContext = this; 16 17 XmlDocument = new XmlDocument(); 18 XmlDocument.LoadXml(""" 19 <people Name="メンバー"> 20 <person Name="Aさん" /> 21 <person Name="Bさん" /> 22 <person Name="Cさん"> 23 <person Name="Dさん" /> 24 </person> 25 </people> 26 """); 27 28 // XDocumentで読み込んでもいいのだが、XDocumentには「動的プロパティ」がなく 29 // <TreeView ItemsSource="{Binding XDocument.Elements[people]}" /> とは書けない 30 // なのでダミーのルートを持たせたXElementでお茶を濁すw 31 // [LINQ to XML の動的プロパティ リファレンス - WPF .NET Framework | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/data/linq-to-xml-dynamic-properties?view=netframeworkdesktop-4.8) 32 XElement = new XElement("root", XElement.Parse(""" 33 <people Name="メンバー"> 34 <person Name="Aさん" /> 35 <person Name="Bさん" /> 36 <person Name="Cさん"> 37 <person Name="Dさん" /> 38 </person> 39 </people> 40 """)); 41 } 42 43 private void Button_Click(object sender, RoutedEventArgs e) 44 { 45 var xmlElement = XmlDocument.CreateElement("person"); 46 xmlElement.SetAttribute("Name", "名無しさん"); 47 XmlDocument.DocumentElement?.AppendChild(xmlElement); 48 49 var xElement = new XElement("person", new XAttribute("Name", "名無しさん")); 50 XElement.Element("people")?.Add(xElement); 51 } 52}

アプリ動画


どちらもめちゃくちゃ強力な機能だと思いますが、あまり知られていないようですね(わたしも詳細は知りませんでしたw
sa_saさんは自力で到達されたようですね^^
WPFでXMLファイルを読み込んでTreeViewを作りたい

投稿2024/11/21 15:25

TN8001

総合スコア10118

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

以下の場所が原因だと思います。

xml

1<TextBlock Name="nameTextBlock" Text="{Binding Path=Attributes.ItemOf[Name].Value}" />

System.Xml.XmlAttribute は、INotifyPropertyChanged を実装していないので値を変更しても変更通知がView に通知されません。
指定のXML をINotifyPropertyChanged を実装したクラスへ変換し、バインドすれば実現できるはずです。

<追記>
回答欄にコード貼り付けられないのでこちらから回答させていただきます。

まずは、xmlの変換先のクラスを用意します。

cs

1 /// <remarks/> 2 [Serializable()] 3 [DesignerCategory("code")] 4 [XmlType(AnonymousType = true)] 5 [XmlRoot(Namespace = "", IsNullable = false)] 6 public partial class people 7 { 8 9 private peoplePerson[] personField; 10 11 private string nameField; 12 13 /// <remarks/> 14 [XmlElement("person")] 15 public peoplePerson[] person 16 { 17 get { return this.personField; } 18 set { this.personField = value; } 19 } 20 21 /// <remarks/> 22 [XmlAttribute()] 23 public string Name 24 { 25 get { return this.nameField; } 26 set { this.nameField = value; } 27 } 28 } 29 30 /// <remarks/> 31 [SerializableAttribute()] 32 [DesignerCategoryAttribute("code")] 33 [XmlType(AnonymousType = true)] 34 public partial class peoplePerson 35 { 36 private string nameField; 37 38 /// <remarks/> 39 [XmlAttribute()] 40 public string Name 41 { 42 get { return this.nameField; } 43 set { this.nameField = value; } 44 } 45 }

つぎに、xmlのpersonタグの要素と対になる、画面にバインドするためのクラスを作成します。

cs

1 2 public class PersonViewModel : BindableBase 3 { 4 private string name; 5 public string Name 6 { 7 get { return name; } 8 set { SetProperty(ref name, value); } 9 } 10 11 public ObservableCollection<PersonViewModel> Child { get; set; } 12 }

これに合わせてMainViewModelとMainWindow.xamlも以下のように変更します。

cs

1 public class MainWindowViewModel : BindableBase 2 { 3 // TreeViewへバインディングするXmlDocument 4 public ObservableCollection<PersonViewModel> PeopleTree { get; private set; } 5 6 // コンテキスト 7 public MainWindowViewModel() 8 { 9 using (var fs = new FileStream(@"peopleTree.xml", FileMode.Open, FileAccess.Read, FileShare.Read)) 10 { 11 var people = new XmlSerializer(typeof(people)).Deserialize(fs) as people; 12 PeopleTree = new ObservableCollection<PersonViewModel>(people.person.Select(x => new PersonViewModel { Name = x.Name })); 13 } 14 } 15 16 // ボタンへバインディングしているコマンド 17 private ICommand addPersonCommand; 18 public ICommand AddPersonCommand 19 { 20 get { return addPersonCommand ?? (addPersonCommand = new DelegateCommand(addPersonMethod)); } 21 } 22 // ツリーの子ノードとして「名無しさん」を追加する 23 private void addPersonMethod() 24 { 25 PeopleTree.Add(new PersonViewModel { Name = "名無しさん", }); 26 } 27 }

xml

1<HierarchicalDataTemplate 2 x:Key="XmlTemplate" 3 DataType="{x:Type local:PersonViewModel}" 4 ItemsSource="{Binding Child}"> 5 <StackPanel> 6 <TextBlock Name="nameTextBlock" Text="{Binding Name}" /> 7 </StackPanel> 8 </HierarchicalDataTemplate>

これでボタンをクリックすれば"名無しさん"が追加されます。

<追記2>
確かにTree構造になっていませんでしたw

cs

1PeopleTree = new ObservableCollection<PersonViewModel>(people.person.Select(x => new PersonViewModel { Name = x.Name }));

ここを以下のように変更すればxml ファイルのpeople タグにあるName を表示することができます。

cs

1PeopleTree = new ObservableCollection<PersonViewModel>(); 2PeopleTree.Add(new PersonViewModel 3{ 4 Name = people.Name 5 , Child = new ObservableCollection<PersonViewModel>(people.person.Select(x => new PersonViewModel { Name = x.Name })) 6});

cs

1private void addPersonMethod() 2{ 3 PeopleTree.Add(new PersonViewModel { Name = "名無しさん", }); 4}

ついでにこれも以下のように変更したほうがいいでしょう。

cs

1private void addPersonMethod() 2{ 3 PeopleTree[0].Child.Add(new PersonViewModel { Name = "名無しさん", }); 4}

こんな感じですね。

投稿2017/08/30 11:58

編集2017/08/31 13:22
IYEMON018

総合スコア202

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sa_sa

2017/08/30 12:51

ご回答ありがとうございます! 私の知識が足りなすぎて、ご指摘だけでは理解が及びませんでした… 厚かましいですが、今のコード上のどこに何を書くべきか具体的にお教えいただいてもよろしいでしょうか?? 何卒よろしくお願いいたします。
sa_sa

2017/08/31 10:40

具体的なコードの提示までしていただき本当にありがとうございます! xmlをデシアライズしたものがpeopleで、people.personからPeopleTreeが作られ、それをTreeViewにバインディングしていることは理解できたと思います。 xmlドキュメントそのものではなく、xmlを元に作られたPeopleTreeを使うことで変更通知が行われるようになり、追加ボタンにより"名無しさん"が正しく表示されるようになりました。 しかしPeopleTreeの内容的にルートより下の要素しか持たせることができず、ルートである"メンバー"をTreeViewに表示させることができません。 いただいたコードを自分なりに弄ってみましたが上手くいきませんでした。 何か良い解決法があればご教授いただけないでしょうか? 質問ばかりですみませんがよろしくお願いします。
sa_sa

2017/09/01 01:32

言われてみれば簡単なことでした、お恥ずかしいw また何かあれば助けを求めてしまうかもしれませんが、その時も相談に乗っていただけたら嬉しく思いますm(__)m 今回は本当にありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問