質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

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

Q&A

解決済

1回答

1345閲覧

C# ReactiveProperty ObservableCollectionから大分類、中分類、小分類のツリーを同期しながら生成したい

退会済みユーザー

退会済みユーザー

総合スコア0

C#

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

0グッド

0クリップ

投稿2022/04/26 09:06

前提

https://okazuki.jp/ReactiveProperty/features/Collections.html

リンク先を参考に、ObservableCollectionからToReadOnlyReactiveCollectionを使ってツリーを生成しようとしています。
(ObservableCollectionの追加・削除があったらツリーにも同期するように)

根本的な考え方など誤っていましたら、その辺りからご指摘もらえると嬉しいです。

該当のソースコード・試したこと

次のような感じで実装してみました。

cs

1using System.Collections.ObjectModel; 2using System.Text.Json; 3using Reactive.Bindings; 4 5var collection = new ObservableCollection<TestItem> 6{ 7 new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name01", }, 8 new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name02", }, 9 new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name03", }, 10 new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C32", Name = "Name04", }, 11 new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C32", Name = "Name05", }, 12 new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C33", Name = "Name06", }, 13 new TestItem { Category1 = "C11", Category2 = "C22", Category3 = "C31", Name = "Name07", }, 14 new TestItem { Category1 = "C11", Category2 = "C22", Category3 = "C32", Name = "Name08", }, 15 new TestItem { Category1 = "C12", Category2 = "C21", Category3 = "C31", Name = "Name09", }, 16}; 17var top = new TreeItem { Category = "top" }; 18_ = collection.ToReadOnlyReactiveCollection(x => 19{ 20 var c1 = top.Children.Where(y => y.Category == x.Category1); 21 var c2 = c1.SelectMany(y => y.Children).Where(y => y.Category == x.Category2); 22 var c3 = c2.SelectMany(y => y.Children).Where(y => y.Category == x.Category3); 23 if (!c1.Any()) 24 { 25 top.Children.Add(new TreeItem { Category = x.Category1 }); 26 } 27 if (!c2.Any()) 28 { 29 var item = c1.Single(); 30 item.Children.Add(new TreeItem { Category = x.Category2 }); 31 } 32 if (!c3.Any()) 33 { 34 var item = c2.Single(); 35 item.Children.Add(new TreeItem { Category = x.Category3 }); 36 } 37 return x; 38}); 39collection.Add(new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name11", }); 40collection.Add(new TestItem { Category1 = "C11", Category2 = "C21", Category3 = "C34", Name = "Name12", }); 41collection.Remove(collection.Single(x => x.Name == "Name07")); // 削除した時にToReadOnlyReactiveCollectionの中のラムダ式が呼び出されません 42Console.WriteLine(JsonSerializer.Serialize(top)); 43 44class TestItem 45{ 46 public string Category1 { get; set; } = ""; 47 public string Category2 { get; set; } = ""; 48 public string Category3 { get; set; } = ""; 49 public string Name { get; set; } = ""; 50} 51class TreeItem 52{ 53 public string Category { get; set; } = ""; 54 public ObservableCollection<TreeItem> Children { get; set; } = new ObservableCollection<TreeItem>(); 55} 56

ToReadOnlyReactiveCollectionの戻りを破棄してしまっているので、正しい使い方ではないような気がしています。
(ToReadOnlyReactiveCollectionの1つ目の引数がconverterという名前からも正しい使い方ではないような気がしています)

現在は、ObservableCollectionの追加・削除が同期できるところを目指していますが、
この先、ObserveElementPropertyChangedと組み合わせて、
ObservableCollectionの要素の分類が変更された場合にもツリーに同期したいと思っています。
(「ObserveElementPropertyChanged」は https://qiita.com/okazuki/items/6faac7cb1a7d8a6ad0f2 で知りました)

発生している問題・エラーメッセージ

ObservableCollectionからRemoveしてもツリーから削除されません。

現在の結果は次の通りです。
Name07を削除しましたが、大分類「C11」、中分類「C22」、小分類「C31」が削除されません。

json

1{ 2 "Category": "top", 3 "Children": [ 4 { 5 "Category": "C11", 6 "Children": [ 7 { 8 "Category": "C21", 9 "Children": [ 10 { 11 "Category": "C31", 12 "Children": [] 13 }, 14 { 15 "Category": "C32", 16 "Children": [] 17 }, 18 { 19 "Category": "C33", 20 "Children": [] 21 }, 22 { 23 "Category": "C34", 24 "Children": [] 25 } 26 ] 27 }, 28 { 29 "Category": "C22", 30 "Children": [ 31 { 32 "Category": "C31", 33 "Children": [] 34 }, 35 { 36 "Category": "C32", 37 "Children": [] 38 } 39 ] 40 } 41 ] 42 }, 43 { 44 "Category": "C12", 45 "Children": [ 46 { 47 "Category": "C21", 48 "Children": [ 49 { 50 "Category": "C31", 51 "Children": [] 52 } 53 ] 54 } 55 ] 56 } 57 ] 58}

期待する結果は次の通りです。

json

1{ 2 "Category": "top", 3 "Children": [ 4 { 5 "Category": "C11", 6 "Children": [ 7 { 8 "Category": "C21", 9 "Children": [ 10 { 11 "Category": "C31", 12 "Children": [] 13 }, 14 { 15 "Category": "C32", 16 "Children": [] 17 }, 18 { 19 "Category": "C33", 20 "Children": [] 21 }, 22 { 23 "Category": "C34", 24 "Children": [] 25 } 26 ] 27 }, 28 { 29 "Category": "C22", 30 "Children": [ 31 { 32 "Category": "C32", 33 "Children": [] 34 } 35 ] 36 } 37 ] 38 }, 39 { 40 "Category": "C12", 41 "Children": [ 42 { 43 "Category": "C21", 44 "Children": [ 45 { 46 "Category": "C31", 47 "Children": [] 48 } 49 ] 50 } 51 ] 52 } 53 ] 54}

補足情報(FW/ツールのバージョンなど)

  • .NET 6
  • "ReactiveProperty" Version="8.0.5"
  • "PropertyChanged.Fody" Version="3.4.0"

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

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

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

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

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

guest

回答1

0

ベストアンサー

ToReadOnlyReactiveCollectionの戻りを破棄してしまっているので、正しい使い方ではないような気がしています。
(ToReadOnlyReactiveCollectionの1つ目の引数がconverterという名前からも正しい使い方ではないような気がしています)

ToReadOnlyReactiveCollectionconverterは、LINQで言えばSelectselectorです。
xを返しているので元と同じものができていますが、使わずに副作用でツリーを作っています。
xg63ex2bさんの懸念通り、間違った使い方だと思います。

ObservableCollectionからRemoveしてもツリーから削除されません。
// 削除した時にToReadOnlyReactiveCollectionの中のラムダ式が呼び出されません

仮にラムダが呼ばれても、この実装ではツリーから削除されることはなくないですか?(Addしかしていないので)
コレクションの変更時に、topから作り直していいのでしょうか?

全部作り直していいならこんなん?(雑いw

cs

1var top = new TreeItem { Category = "top" }; 2collection.CollectionChangedAsObservable() 3 .Subscribe(_ => 4 { 5 top = new TreeItem { Category = "top" }; 6 foreach (var x in collection) 7 { 8 var c1 = top.Children.Where(y => y.Category == x.Category1); 9 var c2 = c1.SelectMany(y => y.Children).Where(y => y.Category == x.Category2); 10 var c3 = c2.SelectMany(y => y.Children).Where(y => y.Category == x.Category3); 11 if (!c1.Any()) top.Children.Add(new TreeItem { Category = x.Category1 }); 12 if (!c2.Any()) c1.Single().Children.Add(new TreeItem { Category = x.Category2 }); 13 if (!c3.Any()) c2.Single().Children.Add(new TreeItem { Category = x.Category3 }); 14 } 15 });

さすがに効率悪そうなので追加削除をちゃんとやるとなると、枝だけじゃなく葉(Name)の情報もないと削除できませんね。
初回分をどうするか悩ましいですが、面倒なので後からAddしました^^;(AddRangeいつになったら来るのか...

提示jsonにはなったが、あんまあってるか自信なし^^;

cs

1using System.Collections.ObjectModel; 2using System.Collections.Specialized; 3using System.Reactive.Linq; 4using System.Text.Json; 5using System.Text.Json.Serialization; 6using Reactive.Bindings.Extensions; 7 8 9var collection = new ObservableCollection<TestItem>(); 10 11var top = new TreeItem { Category = "top" }; 12collection.CollectionChangedAsObservable().Subscribe(NewMethod); 13 14collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name01", }); 15collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name01", }); 16collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name02", }); 17collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name03", }); 18collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C32", Name = "Name04", }); 19collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C32", Name = "Name05", }); 20collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C33", Name = "Name06", }); 21collection.Add(new() { Category1 = "C11", Category2 = "C22", Category3 = "C31", Name = "Name07", }); 22collection.Add(new() { Category1 = "C11", Category2 = "C22", Category3 = "C32", Name = "Name08", }); 23collection.Add(new() { Category1 = "C12", Category2 = "C21", Category3 = "C31", Name = "Name09", }); 24 25collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C31", Name = "Name11", }); 26collection.Add(new() { Category1 = "C11", Category2 = "C21", Category3 = "C34", Name = "Name12", }); 27collection.Remove(collection.Single(x => x.Name == "Name07")); 28 29Console.WriteLine(JsonSerializer.Serialize(top, new JsonSerializerOptions { WriteIndented = true, })); 30 31 32void NewMethod(NotifyCollectionChangedEventArgs e) // ラムダでは長くて見にくいので出しただけ 33{ 34 if (e.NewItems != null) 35 { 36 foreach (TestItem x in e.NewItems) 37 { 38 var c1 = top.Children.SingleOrDefault(y => y.Category == x.Category1); 39 if (c1 == null) 40 { 41 c1 = new TreeItem { Category = x.Category1, }; 42 top.Children.Add(c1); 43 } 44 var c2 = c1.Children.SingleOrDefault(y => y.Category == x.Category2); 45 if (c2 == null) 46 { 47 c2 = new TreeItem { Category = x.Category2, }; 48 c1.Children.Add(c2); 49 } 50 var c3 = c2.Children.SingleOrDefault(y => y.Category == x.Category3); 51 if (c3 == null) 52 { 53 c3 = new TreeItem { Category = x.Category3, }; 54 c2.Children.Add(c3); 55 } 56 c3.Names.Add(x.Name); 57 } 58 } 59 60 if (e.OldItems != null) 61 { 62 foreach (TestItem x in e.OldItems) 63 { 64 var c1 = top.Children.Single(y => y.Category == x.Category1); 65 var c2 = c1.Children.Single(y => y.Category == x.Category2); 66 var c3 = c2.Children.Single(y => y.Category == x.Category3); 67 c3.Names.Remove(x.Name); 68 if (!c3.Names.Any()) c2.Children.Remove(c3); 69 if (!c2.Children.Any()) c1.Children.Remove(c2); 70 if (!c1.Children.Any()) top.Children.Remove(c1); 71 } 72 } 73} 74 75 76class TestItem 77{ 78 public string Category1 { get; set; } = ""; 79 public string Category2 { get; set; } = ""; 80 public string Category3 { get; set; } = ""; 81 public string Name { get; set; } = ""; 82} 83 84class TreeItem 85{ 86 public string Category { get; set; } = ""; 87 public ObservableCollection<TreeItem> Children { get; } = new(); 88 [JsonIgnore] public List<string> Names { get; } = new(); 89}

この先、ObserveElementPropertyChangedと組み合わせて、
ObservableCollectionの要素の分類が変更された場合にもツリーに同期したいと思っています。

CSVのようなフラットな構造からツリーを作るってことだと思うんですけど、内部データはツリー状のほうが扱いやすくないですかね?(ファイルに?読み書きのところでフラット化)

どういうUIにするかにもよりますが、マウスで操作にもしやすいと思います。
C#で読み込んだJsonファイルを編集したい

投稿2022/04/26 14:57

編集2023/08/15 15:13
TN8001

総合スコア9326

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

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

退会済みユーザー

退会済みユーザー

2022/04/27 03:30

ご丁寧なご回答ありがとうございます。 CollectionChangedAsObservableというものがあるのですね。 (このあたりの変換するメソッドについてちゃんと理解できていないのかも知れません。。) ご記載いただいた2つ目のコードをベースに実際のコードに実装してみようと思います。 > 仮にラムダが呼ばれても、この実装ではツリーから削除されることはなくないですか?(Addしかしていないので) すみません、確かにAddしかしていなかったですね。。 再現コードを書いているときに自然に抜けてしまいました。。 > コレクションの変更時に、topから作り直していいのでしょうか? topから作り直ししなくて済むのであればそうしたいですが、作り直しでも良いかなとも思っています。 > 初回分をどうするか悩ましいですが、面倒なので後からAddしました^^; new ObservableCollectionの初期化時に要素を指定しない部分ですね。 CollectionChangedAsObservableだと購読前にあった要素が処理できないのですね。 ちょっと調べてみて、IObservable.StartWithメソッドで初期分を最初に発行できそうでしたので、今のところこれで実装してみようと思っています。 https://reactivex.io/documentation/operators/startwith.html (ReactivePropertyが依存するパッケージであるSystem.Reactiveのメソッドなのでしょうか。このあたりのメソッドはどれがどのパッケージなのか理解が難しいです。。ReactiveProperty.Coreとか。。) > (AddRangeいつになったら来るのか... 確かにObservableCollectionにAddRangeないですよね、来る予定はあるのですかね。 (ObservableCollectionにAddRangeはないのに、NotifyCollectionChangedEventArgsのNewItemsとOldItemsプロパティはIListなのですね) > CSVのようなフラットな構造からツリーを作るってことだと思うんですけど、内部データはツリー状のほうが扱いやすくないですかね?(ファイルに?読み書きのところでフラット化) フラットからツリーを作るイメージで合っています。 (質問欄の例ですと、分類の情報を主に扱っているように見えてしまいますが、本当は内部データ{今回のTestItemに相当}はもっと項目があり、分類はもっと小さな意味しか持たない程度の付属項目です。後から分類のツリーを見たいのようになったのだと思います、多分。。ここを今から変えるのは難しそうです。。) ツリーを作ることについて、検索してみてもあまり同じような問題に悩んでいる人がいなそうな印象でしたが、一般的には内部データを最初からツリーにしておくことでそのような問題にもぶつからないということなのでしょうかね。 > どういうUIにするかにもよりますが、マウスで操作にもしやすいと思います。 リンクの情報もいただきましてありがとうございます。 参考にします。 今のところUIはWPFのXAMLにバインディングするだけで、マウスで複雑な操作はしない想定ではあります。
TN8001

2022/04/27 08:39

> topから作り直ししなくて済むのであればそうしたいですが、作り直しでも良いかなとも思っています。 数がそんなにないなら一番楽ですね。 > IObservable.StartWithメソッドで初期分を最初に発行できそうでしたので、今のところこれで実装してみようと思っています。 なるほど。わたしもRxはからっきしです^^; > 確かにObservableCollectionにAddRangeないですよね、来る予定はあるのですかね。 何年もずっと議論されているのですが、なかなか来ないですね^^; 要望も多いしやりたいつもりなのは確かだと思いますが、WPFにも手を入れなくてはならないようで(結果的に)優先度が下がっているような感じ?(自動翻訳で流し見なので間違ってるかもしれません) [Collection<T> and ObservableCollection<T> do not support ranges · Issue #18087 · dotnet/runtime](https://github.com/dotnet/runtime/issues/18087) > 後から分類のツリーを見たいのようになったのだと思います、多分。。ここを今から変えるのは難しそうです。。) 分類の編集で付けかえるのがめんどくさそうだなと思ったんですが、いったん削除して入れなおせばいいのかな??最悪作り直しでもいいなら気楽ではありますねw >ツリーを作ることについて、検索してみてもあまり同じような問題に悩んでいる人がいなそうな印象でしたが、一般的には内部データを最初からツリーにしておくことでそのような問題にもぶつからないということなのでしょうかね。 (やってる人はいると思いますけど)汎用的?(よくあるか)って言うとそうでもないですし、例もそれなりにヘビーになるので記事にはしにくいかもしれないですね。
退会済みユーザー

退会済みユーザー

2022/04/27 12:35

コメントありがとうございます。 (回答のコメントはメールが届かないのですね。スルーしそうになりました。。) > 要望も多いしやりたいつもりなのは確かだと思いますが、WPFにも手を入れなくてはならないようで(結果的に)優先度が下がっているような感じ? Issueのリンクありがとうございます。 結構激しい議論があるのですね。。 > いったん削除して入れなおせばいいのかな?? ありがとうございます! これで進めてみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問