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

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

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

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

MVVM

MVVM(Model View ViewModel)は構築上のデザインパターンで、表現ロジック(ViewModel)によってデータ(Model)からページ(View)を分離させます。

WPF

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

Q&A

2回答

6440閲覧

WPF MVVMスタイルでのUserControlに対するデータの入出力について

ruruucky

総合スコア18

C#

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

MVVM

MVVM(Model View ViewModel)は構築上のデザインパターンで、表現ロジック(ViewModel)によってデータ(Model)からページ(View)を分離させます。

WPF

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

0グッド

1クリップ

投稿2020/09/17 12:27

編集2020/09/19 14:43

WPFにてUserControlをV, VM, Model の3階層で作成しています。親WindowのVMからこのUserControlに初期値を与え、終了時に結果を取得をしたいです。
そのデータは、UserControlのModel部のデータ形式 (List<Info>) です。
Infoクラスにはstringやboolなどのメンバーがあります。

追記です。

UserControlにて依存プロパティにより 親Windowからは
<TestControl InfoList="{Binding Path=InfoList, Mode=TwoWay}"/>
の形でバインドが出来ました。

**一方でUserControl内は、依存プロパティの実装箇所からvmと直接やり取りをしています。
この個所をバインドを経由した伝達方法はあるでしょうか。
**
以下、usercontrolのxamlとコードビハインド部分です。

usercontrol xaml

c#

1<UserControl x:Class="Test.TestControl" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:zzz="clr-namespace:Test" 7 mc:Ignorable="d" 8 d:DesignHeight="400" Width="300" 9 > 10 <Grid Name="baseContainer"> 11 <TreeView x:Name="TreeView" ItemsSource="{Binding Path=List}"> 12 <TreeView.Resources> 13 <Style TargetType="TreeViewItem"> 14 <Setter Property="IsExpanded" Value="{Binding Path=Expand,Mode=TwoWay}"/> 15 </Style> 16 </TreeView.Resources> 17 18 <TreeView.ItemTemplate> 19 <HierarchicalDataTemplate DataType= "zzz:List" ItemsSource="{Binding Path=Child}"> 20 <StackPanel Orientation="Horizontal"> 21 <CheckBox IsChecked="{Binding Path=Check, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> 22 <Image Source="{Binding Path=Icon}"/> 23 <TextBlock Text="{Binding Path=Name}"/> 24 </StackPanel> 25 </HierarchicalDataTemplate> 26 </TreeView.ItemTemplate> 27 </TreeView> 28 </Grid> 29</UserControl>

c#

1namespace Test 2{ 3 public partial class TestControl : UserControl 4 { 5 public TestControl() 6 { 7 InitializeComponent(); 8 baseContainer.DataContext = new MainViewModel(this); // vmにvを渡す 9 } 10 11 public static readonly DependencyProperty InfoListProperty = DependencyProperty.Register( 12 "InfoList", // プロパティ名を指定 13 typeof(List<Info>), // プロパティの型を指定 14 typeof(TestControl), // プロパティを所有する型を指定 15 new PropertyMetadata(null, MyPropertyChangedHandler) 16 ); 17 18 private static void MyPropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs args) 19 { 20 TestControl that = (TestControl)sender; 21 MainViewModel vm = (MainViewModel)that.baseContainer.DataContext; 22 vm.ListUpdate((List<Info>)args.NewValue); // v->vmへの通知 23 } 24 25 public List<Info> InfoList 26 { 27 get { return (List<Info>)GetValue(InfoListProperty); } 28 set { SetValue(InfoListProperty, value); } // vm->vへの通知 29 } 30 } 31}

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

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

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

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

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

TN8001

2020/09/17 14:01

よくわかりませんが 「依存関係プロパティ(Dependency Property)」 がお求めのものでしょうか?
ruruucky

2020/09/17 15:32

UserControl内でDependency Property を定義し、親WindowのVMへのBindngができるようになりました。 一方で、UserControl内は酷いありさまで、Vで定義した Dependency Property をModelからダイレクトにセットしています。(vのobjectをModelに渡しています) Dependency Property を、MVVMに落とし込む方法はあるでしょうか。
TN8001

2020/09/17 15:58

うまく言葉で説明できる気がしないのですが、 UserControlがPrism的な部分ビューであるなら、 <DataTemplate DataType="{x:Type vm:ChildViewModel}">  <views:ChildView /> </DataTemplate> のような使い方になるはずで、依存関係プロパティまみれになるようなことはないと思うのですが(MainViewModelがChildViewModelを持っているのでやりとりはそっちですればいい) UserControlが専用のコントロール(例えばNumericUpDownのようなもの)であるなら、機能に必要なだけ依存関係プロパティがあるのは当然ですし、中身はMVVM的に作ることはあまりしません。 ざっくりでもコードがあるといいのですが。
ruruucky

2020/09/19 14:46

usercontrolのview部分のソースを追記で貼り付けました。 prismなどの外部ライブラリは使用しておらずwpfのみで作成しています。
TN8001

2020/09/19 15:16

UserControlにはTreeViewしかないんですか? MainViewModelとは別にWindowのViewModelもあるんですか? > この個所をバインドを経由した伝達方法はあるでしょうか。 <UserControl x:Name="userControl" <TreeView ItemsSource="{Binding InfoList, ElementName=userControl}" とすればバインドされますがそういうことですか? 書かれていない部分もあるんでしょうがこれだけ見ると、UserControlもMainViewModelもいらないように思えます。 UserControlにした意図がよくわかんないです。 MainWindowがごちゃつくので分けたとかですか? それならアリですがMainViewModelはいらないです。 UserControlのvmとMainWindowのvmが無関係に作成されているため、どう渡せばいいんだろう?となっています。 そうではなくMainWindowのvmがMainViewModel(UserControlのvm)を内包し(同列にInfoListを入れるかMainWindowViewModel.MainViewModel.InfoListのようになってもいいですが) <TestControl InfoList="{Binding Path=InfoList, Mode=TwoWay}"/> とするか回答のようにDataTemplateで展開します。 文字で説明するのは難しいですねw
ruruucky

2020/09/19 16:40

ありがとうございます。MVVMに不慣れなためこちらの情報が的確ではなく申し訳ないです。 設計思想は以下になります。 UserControlはTreeViewのみです。専用のデータとセットにして使いまわせるようにコントロール化をしました。データの実体はモデル部より供給しTreeViewで表示します。アプリではTreeViewの各行のCheckBoxの状態を設定値として供給・保存をしています。 データの流れはこんな感じです。 check状態(開始時) App --> UserControl v --> vm --> model check状態(終了時) App <-- UserControl v <-- vm <-- model ツリー構造の実体 UserControl v <-- vm <-- model TreeViewのチェックBoxは、親ノードをチェックすると子ノードもチェックを入れるUIにしています。アプリが設定として必要なのはチェックを入れた最上位の情報だけです。そのためUserControl内で必要な情報を抽出してList構造にしています。それがソース上の List<Info>です。
guest

回答2

0

動くコードがないと話をすり合わせるのが大変なので^^;
今の私の理解をMVVMを意識せずざっくり実装しました。

xml:MainWindow.xaml

1<Window 2 x:Class="Test.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="clr-namespace:Test" 6 Height="450" 7 SizeToContent="Width"> 8 <Window.DataContext> 9 <local:MainWindowViewModel /> 10 </Window.DataContext> 11 <Window.Resources> 12 <DataTemplate x:Key="CheckedInfo" DataType="{x:Type local:Info}"> 13 <TextBlock Text="{Binding Path=Name, StringFormat=CheckedInfo: {0}}" /> 14 </DataTemplate> 15 </Window.Resources> 16 <Grid> 17 <Grid.RowDefinitions> 18 <RowDefinition /> 19 <RowDefinition Height="2*" /> 20 </Grid.RowDefinitions> 21 <ListBox ItemTemplate="{StaticResource CheckedInfo}" ItemsSource="{Binding Path=CheckedInfoList, ElementName=testControl}" /> 22 <local:TestControl 23 x:Name="testControl" 24 Grid.Row="1" 25 InfoList="{Binding Path=InfoList}" /> 26 </Grid> 27</Window>

cs:MainWindowViewModel.cs

1using System.Collections.Generic; 2 3namespace Test 4{ 5 public class MainWindowViewModel //: Observable 6 { 7 public InfoList InfoList { get; } 8 9 //private InfoList _CheckedInfoList; 10 //public InfoList CheckedInfoList { get => _CheckedInfoList; set => Set(ref _CheckedInfoList, value); } 11 12 public MainWindowViewModel() 13 { 14 InfoList = new InfoList 15 { 16 new Info { 17 Name = "1", 18 Child = new InfoList { 19 new Info { 20 Name = "1-1", 21 Child = new InfoList { 22 new Info { Name = "1-1-1", Check = true, }, 23 new Info { Name = "1-1-2", }, 24 }, 25 }, 26 }, 27 }, 28 new Info { 29 Name = "2", 30 Child = new InfoList { 31 new Info { Name = "2-1", Check = true, }, 32 }, 33 }, 34 new Info { Name = "3", Check = true, }, 35 new Info { 36 Name = "4", 37 Child = new InfoList { 38 new Info { Name = "4-1", }, 39 }, 40 }, 41 }; 42 } 43 } 44 45 public class InfoList : List<Info> { } 46 47 public class Info 48 { 49 public string Name { get; set; } 50 //public string Icon { get; set; } 51 public bool Expand { get; set; } = true; 52 public bool Check { get; set; } 53 54 public InfoList Child { get; set; } // Info Child? 55 } 56}

xml:TestControl.xaml

1<UserControl 2 x:Class="Test.TestControl" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:local="clr-namespace:Test" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 x:Name="userControl" 9 Width="300" 10 d:DesignHeight="400" 11 mc:Ignorable="d"> 12 <Grid Name="baseContainer"> 13 <TreeView 14 CheckBox.Checked="TreeView_CheckChanged" 15 CheckBox.Unchecked="TreeView_CheckChanged" 16 ItemsSource="{Binding InfoList, ElementName=userControl, Mode=OneWay}"> 17 <TreeView.Resources> 18 <Style TargetType="TreeViewItem"> 19 <Setter Property="IsExpanded" Value="{Binding Path=Expand, Mode=TwoWay}" /> 20 </Style> 21 </TreeView.Resources> 22 23 <TreeView.ItemTemplate> 24 <HierarchicalDataTemplate DataType="local:InfoList" ItemsSource="{Binding Path=Child}"> 25 <StackPanel Orientation="Horizontal"> 26 <CheckBox IsChecked="{Binding Path=Check, Mode=TwoWay}" /> 27 <!--<Image Source="{Binding Path=Icon}" />--> 28 <TextBlock Text="{Binding Path=Name}" /> 29 </StackPanel> 30 </HierarchicalDataTemplate> 31 </TreeView.ItemTemplate> 32 </TreeView> 33 </Grid> 34</UserControl>

cs:TestControl.xaml.cs

1using System.Windows; 2using System.Windows.Controls; 3 4namespace Test 5{ 6 public partial class TestControl : UserControl 7 { 8 public static readonly DependencyProperty InfoListProperty 9 = DependencyProperty.Register(nameof(InfoList), typeof(InfoList), 10 typeof(TestControl), new PropertyMetadata(null)); 11 public InfoList InfoList 12 { 13 get => (InfoList)GetValue(InfoListProperty); 14 set => SetValue(InfoListProperty, value); 15 } 16 17 public static readonly DependencyProperty CheckedInfoListProperty 18 = DependencyProperty.Register(nameof(CheckedInfoList), typeof(InfoList), 19 typeof(TestControl), new PropertyMetadata(null)); 20 public InfoList CheckedInfoList 21 { 22 get => (InfoList)GetValue(CheckedInfoListProperty); 23 set => SetValue(CheckedInfoListProperty, value); 24 } 25 26 27 public TestControl() 28 { 29 InitializeComponent(); 30 } 31 32 private void TreeView_CheckChanged(object sender, RoutedEventArgs e) 33 { 34 var l = new InfoList(); 35 foreach(var c in InfoList) 36 { 37 if(Dfs(c)) l.Add(c); 38 } 39 CheckedInfoList = l; 40 41 bool Dfs(Info info) 42 { 43 if(info.Check) return true; 44 if(info.Child == null) return false; 45 foreach(var c in info.Child) 46 { 47 if(Dfs(c)) return true; 48 } 49 50 return false; 51 } 52 } 53 } 54}

アプリ画像

動作はこれでいいのでしょうか?
これですとTestControlにVMがありませんが、実際はMainWindowViewModelから渡されたInfoListTestControlInfoListVM等に加工(詰め替え)しているということでしょうか?(何のために??)
それともInfoCheckChildだけで(Nameもか?)、InfoVMIconExpandを生やしてるってことなのかな?

データの実体はモデル部より供給

check状態(開始時) App --> UserControl v --> vm --> model
check状態(終了時) App <-- UserControl v <-- vm <-- model
ツリー構造の実体 UserControl v <-- vm <-- model

このへんのニュアンスもいまいちわかりません。
モデルとはCheckedInfoListのことを指しているんでしょうか?
それともInfoVMのために画像をとってくるような処理の部分?

もしもあまりに乖離しているようですと、VM・Mも出せる範囲で出していただかないと理解できそうにありません^^;

投稿2020/09/22 22:38

編集2023/08/12 09:53
TN8001

総合スコア9326

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

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

ruruucky

2020/09/23 14:56 編集

わざわざサンプルまで作成いただき申し訳ございません。 話をシンプルにするために、UserControlをファイルシステムから複数のディレクトリ選択を行うコントロールと考えていただくと分かりやすいと思います。 ツリー構造の全体は広大なディレクトリ構造になっています。その一部をUserControl内で表示しながら、複数のディレクトリを選択するイメージです。つまり、MainView側からツリービューのすべてのアイテムを渡して表示するのではなく、Expandするとディレクトリ構造からサブディレクトリ情報を取得してTreeViewのItemを追加します。動きとしてはエクスプローラそのものですね。 UserControl内のモデル部は、ディレクトリ構造へのアクセスロジックと、アプリが必要とする設定値である「選択されたディレクトリの一覧」= List<Info> を作成しています。
guest

0

過去の回答の使いまわしなので、例としてはちょっと重めですが

xml

1<!-- 直置きパターン --> 2<local:TreeUserControl DataContext="{Binding Tree}" /> 3<!-- DataTemplateパターン --> 4<ContentPresenter Grid.Column="1" Content="{Binding Tree}" />

この2つの違いと、(結果的には何も変わらないのですが^^; 初めて見たときは「はぁ~なるほど!」と思いました)
TreeViewModelTreeUserControlの関係を見てください。

TreeUserControlTreeViewだけなのは、ちょっともったいない?かもしれません。
ボタン類も一緒に入れると、責務がきれいに分かれたようになると思います。

MainViewModelTreeViewModelを保持しているので、MainViewModelからツリーの状態はいつでも見れます。

DependencyPropertyは特に出番はありませんでした。
左右は同じTreeViewModelを見ているので、自動的に連動します。
これでは意味がないですが、チェックされてるものだけ表示する(TreeViewだと大変すぎるが^^; ListBoxなんかは簡単)等、差をつけるときにDependencyPropertyがあれば便利です。

xml:MainWindow.xaml

1<Window 2 x:Class="Questions292544.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="clr-namespace:Questions292544" 6 Width="800" 7 Height="450"> 8 <Window.DataContext> 9 <local:MainViewModel /> 10 </Window.DataContext> 11 <Window.Resources> 12 <DataTemplate DataType="{x:Type local:TreeViewModel}"> 13 <local:TreeUserControl /> 14 </DataTemplate> 15 <Style TargetType="Button"> 16 <Setter Property="Margin" Value="5" /> 17 </Style> 18 </Window.Resources> 19 <DockPanel> 20 <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> 21 <Button Command="{Binding Tree.AllCheckCmd}" Content="全チェック" /> 22 <Button Command="{Binding Tree.AllUncheckCmd}" Content="全アンチェック" /> 23 <Button Command="{Binding Tree.AllExpandCmd}" Content="全展開" /> 24 <Button Command="{Binding Tree.AllContractCmd}" Content="全畳む" /> 25 <Button Command="{Binding Tree.AddNodeCmd}" Content="ノード追加" /> 26 <Button Command="{Binding PrintCheckedNodesCmd}" Content="チェックノード表示(Debug)" /> 27 </StackPanel> 28 <Grid> 29 <Grid.ColumnDefinitions> 30 <ColumnDefinition /> 31 <ColumnDefinition /> 32 </Grid.ColumnDefinitions> 33 <!-- 直置きパターン --> 34 <local:TreeUserControl DataContext="{Binding Tree}" /> 35 <!-- DataTemplateパターン --> 36 <ContentPresenter Grid.Column="1" Content="{Binding Tree}" /> 37 </Grid> 38 </DockPanel> 39</Window>

xml:TreeUserControl.xaml

1<UserControl 2 x:Class="Questions292544.TreeUserControl" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 7 d:DesignHeight="450" 8 d:DesignWidth="800" 9 mc:Ignorable="d"> 10 <TreeView ItemsSource="{Binding Root.Children}"> 11 <TreeView.ItemTemplate> 12 <HierarchicalDataTemplate ItemsSource="{Binding Children}"> 13 <StackPanel Orientation="Horizontal"> 14 <CheckBox 15 Margin="5" 16 VerticalContentAlignment="Center" 17 IsChecked="{Binding IsChecked}" /> 18 <TextBlock Margin="5" Text="{Binding Name}" /> 19 <Button 20 Margin="5" 21 Command="{Binding DataContext.DeleteNodeCmd, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}" 22 CommandParameter="{Binding}" 23 Content="削除" /> 24 </StackPanel> 25 </HierarchicalDataTemplate> 26 </TreeView.ItemTemplate> 27 <TreeView.ItemContainerStyle> 28 <Style TargetType="{x:Type TreeViewItem}"> 29 <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> 30 <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 31 </Style> 32 </TreeView.ItemContainerStyle> 33 </TreeView> 34</UserControl>

他コード

cs

1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Collections.ObjectModel; 5using System.ComponentModel; 6using System.Diagnostics; 7using System.Linq; 8using System.Runtime.CompilerServices; 9using System.Windows.Input; 10 11namespace Questions292544 12{ 13 class MainViewModel : Observable 14 { 15 public TreeViewModel Tree { get; } = new TreeViewModel(); 16 public RelayCommand PrintCheckedNodesCmd { get; } 17 public MainViewModel() 18 { 19 PrintCheckedNodesCmd = new RelayCommand(() => 20 { 21 Debug.WriteLine("IsChecked"); // チェックを入れた最上位の情報表示 22 23 foreach(var c in Tree.Root.Children) Dfs(c); 24 25 void Dfs(TreeNode node) // 深さ優先探索 26 { 27 if(node.IsChecked == true) 28 { 29 Debug.WriteLine(node.Name); 30 return; 31 } 32 foreach(var c in node.Children) Dfs(c); 33 } 34 }); 35 } 36 } 37 38 class TreeViewModel : Observable 39 { 40 public TreeNode Root { get; } 41 42 public RelayCommand AllCheckCmd { get; } 43 public RelayCommand AllUncheckCmd { get; } 44 public RelayCommand AllExpandCmd { get; } 45 public RelayCommand AllContractCmd { get; } 46 public RelayCommand<TreeNode> DeleteNodeCmd { get; } 47 public RelayCommand AddNodeCmd { get; } 48 49 public TreeViewModel() 50 { 51 Root = new TreeNode("Root") { 52 new TreeNode("Node1") { 53 new TreeNode("Node1-1"), 54 new TreeNode("Node1-2") { 55 new TreeNode("Node1-2-1"), 56 new TreeNode("Node1-2-2"), 57 }, 58 }, 59 new TreeNode("Node2") { 60 new TreeNode("Node2-1") { 61 new TreeNode("Node2-1-1"), 62 new TreeNode("Node2-1-2"), 63 }, 64 }, 65 }; 66 67 AllCheckCmd = new RelayCommand(() => { foreach(var c in Root) c.IsChecked = true; }); 68 AllUncheckCmd = new RelayCommand(() => { foreach(var c in Root) c.IsChecked = false; }); 69 AllExpandCmd = new RelayCommand(() => { foreach(var c in Root) c.IsExpanded = true; }); 70 AllContractCmd = new RelayCommand(() => { foreach(var c in Root) c.IsExpanded = false; }); 71 DeleteNodeCmd = new RelayCommand<TreeNode>((c) => c.Parent.Remove(c)); 72 AddNodeCmd = new RelayCommand(() => 73 { 74 // 選択の子に追加 75 if(Root.FirstOrDefault(x => x.IsSelected) is TreeNode selectedItem) 76 selectedItem.Add(new TreeNode("NewNode")); 77 else Root.Add(new TreeNode("NewNode")); 78 }); 79 } 80 } 81 82 class TreeNode : Observable, IEnumerable<TreeNode> 83 { 84 string _Name; 85 public string Name { get => _Name; set => Set(ref _Name, value); } 86 87 bool _IsExpanded; 88 public bool IsExpanded { get => _IsExpanded; set => Set(ref _IsExpanded, value); } 89 90 bool _IsSelected; 91 public bool IsSelected { get => _IsSelected; set => Set(ref _IsSelected, value); } 92 93 bool? _IsChecked = false; 94 public bool? IsChecked // ThreeState 95 { 96 get => _IsChecked; 97 set 98 { 99 if(_IsChecked == value) return; 100 if(reentrancyCheck) return; // 再入防止 101 reentrancyCheck = true; 102 Set(ref _IsChecked, value); 103 UpdateCheckState(); // チェックが変わったら上下も更新 104 reentrancyCheck = false; 105 } 106 } 107 108 public ObservableCollection<TreeNode> Children { get; } = new ObservableCollection<TreeNode>(); 109 110 public TreeNode Parent { get; private set; } // 追加・削除の簡便さのため親ノードが欲しい 111 112 bool reentrancyCheck; 113 114 public TreeNode(string name) => Name = name; 115 116 public void Add(TreeNode child) 117 { 118 child.Parent = this; 119 Children.Add(child); 120 IsExpanded = true; 121 child.UpdateCheckState(); 122 } 123 public void Remove(TreeNode child) 124 { 125 Children.Remove(child); 126 child.Parent = null; 127 if(0 < Children.Count) IsChecked = DetermineCheckState(); 128 } 129 130 //チェック状態反映 131 // https://docs.telerik.com/devtools/wpf/controls/radtreeview/how-to/howto-tri-state-mvvm 132 void UpdateCheckState() 133 { 134 if(0 < Children.Count) UpdateChildrenCheckState(); 135 if(Parent != null) Parent.IsChecked = Parent.DetermineCheckState(); 136 } 137 void UpdateChildrenCheckState() 138 { 139 foreach(var c in Children) 140 { 141 if(IsChecked != null) c.IsChecked = IsChecked; 142 } 143 } 144 bool? DetermineCheckState() 145 { 146 var checkCount = Children.Count(x => x.IsChecked == true); 147 if(checkCount == Children.Count) return true; 148 149 var uncheckCount = Children.Count(x => x.IsChecked == false); 150 if(uncheckCount == Children.Count) return false; 151 152 return null; 153 } 154 155 // 子孫を全列挙(Children列挙でないので注意) 156 IEnumerable<TreeNode> Descendants(TreeNode node) => node.Children.Concat(node.Children.SelectMany(Descendants)); 157 public IEnumerator<TreeNode> GetEnumerator() => Descendants(this).GetEnumerator(); 158 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 159 } 160 161 class Observable : INotifyPropertyChanged 162 { 163 public event PropertyChangedEventHandler PropertyChanged; 164 protected void Set<T>(ref T storage, T value, [CallerMemberName] string name = null) 165 { 166 if(Equals(storage, value)) return; 167 storage = value; 168 OnPropertyChanged(name); 169 } 170 protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 171 } 172 class RelayCommand : ICommand 173 { 174 readonly Action exec; 175 readonly Func<bool> can; 176 public event EventHandler CanExecuteChanged; 177 public RelayCommand(Action e) : this(e, null) { } 178 public RelayCommand(Action e, Func<bool> c) 179 { 180 exec = e ?? throw new ArgumentNullException(nameof(e)); 181 can = c; 182 } 183 public bool CanExecute(object _) => can == null || can(); 184 public void Execute(object _) => exec(); 185 public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); 186 } 187 class RelayCommand<T> : ICommand 188 { 189 readonly Action<T> exec; 190 readonly Func<T, bool> can; 191 public event EventHandler CanExecuteChanged; 192 public RelayCommand(Action<T> e) : this(e, null) { } 193 public RelayCommand(Action<T> e, Func<T, bool> c) 194 { 195 exec = e ?? throw new ArgumentNullException(nameof(e)); 196 can = c; 197 } 198 public bool CanExecute(object p) => can == null || can((T)p); 199 public void Execute(object p) => exec((T)p); 200 public void OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); 201 } 202}

.xaml.csは初期状態


TreeNodeは厳密にはViewModelになるかもしれませんが、ライブラリなしではめんどくさいのでVM・M兼用です^^;

投稿2020/09/18 08:46

編集2023/07/23 06:17
TN8001

総合スコア9326

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

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

ruruucky

2020/09/19 15:07

難解でなかなか流れをつかみづらいのですが、 TodoViewModel.SaveCommand の流れを見ればよいでしょうか。 Windowのクローズイベントを拾ってICommandを通じて、最終的にModelまで伝達されているのだと思います。 prismなどの外部ライブラリはまだ導入していないため、なかなか分からない部分が多いですが、コードビハインドなしで実現できてしまうのですね。
TN8001

2020/09/19 15:17

TreeViewで素の状態での例に書き直します^^;
TN8001

2020/09/20 02:15

文字数制限がきつくかなり圧縮してしまいました。読みにくくてすいません^^;
ruruucky

2020/09/22 13:04

ありがとうございます。 ModelをMainWindow側で持っている感じなのですね。いろいろなテクニックは参考になります。xamlに関する知識不足なのでじっかり読み解いていきます。 考えていたのは、MainWindow側はツリービューの選択状態の情報のみを設定値として持ち、usercontrol内がTreeViewを使用している事も認識していません。ちょっと一般的な使い方ではないのでしょうか。 check状態(開始時) App --> UserControl v --> vm --> model check状態(終了時) App <-- UserControl v <-- vm <-- model ツリー構造の実体 UserControl v <-- vm <-- model
TN8001

2020/09/22 13:31

ちょっと確認なんですが、 親WindowのVMと TestControl で new MainViewModel(this) しているものは別(クラスも違うしもちろん別のインスタンス)なんですよね? InfoListがツリー構造のデータなんですよね?で、選択項目だけ返ってくればいいと。 ファイル選択ダイアログ的みたいなものと考えればいいのでしょうか?(Windowではないが)
ruruucky

2020/09/22 13:55

はい、そうです。 TestControl内にVM、Modelを内包しています。 ファイル選択ダイアログ的なふるまいを考えています。 InfoListが受け渡し用のインターフェースで、名称とチェック状態のListです。チェックを入れた最上位のノード、チェック状態の下位ノードで未チェック状態の最上位のノードを抽出して、MainWindow側で設定値として保存します。
TN8001

2020/09/22 22:41

> チェックを入れた最上位のノード、チェック状態の下位ノードで未チェック状態の最上位のノードを抽出 配下のノードにチェックが1個でもあるトップノードが、選択結果なんですかね? > TreeViewのチェックBoxは、親ノードをチェックすると子ノードもチェックを入れるUIにしています。 ここだけ読むと親をチェックすると自動的に子孫もチェックが入るようなものを想像して、回答もそのようにしてしまったのですが^^; DependencyProperty List<Info> InfoList のListは、System.Collections.Generic.Listなんでしょうか? そうなるとDataType="zzz:List"は何なんだ?ってことになるんですが、 UserControlのVMで詰め替えてるってことでしょうか? 今の私の理解を別回答したのでご確認いただけますか。
ruruucky

2020/09/23 14:50 編集

たびたび申し訳ございません。こういう意味です。 □A |- ■B1 --> チェックを入れた最上位 | |- ■C1 | |- □C2 --> 親がチェックされているが、未チェックの最上位 | | |- □D1 | |- ■C3 B1とC2のパスとチェック状態があれば、この状態を再現できます。 これは、「B1以下がすべて対象だがC2以下はは除外する」という設定値です。これをアプリが使用します。List<Info> は、System.Collections.Generic.Listでアプリの設定値です。 zzz:Listは、TreeViewのための ObservableCollection です。このListをアプリに直接公開しない理由はアプリは知る必要がないためです。TreeViewの個々の内容を知りたいのではなく、上記のB1とC2の情報のみを必要としています。知らないで済むように、UserControl内にカプセル化しているイメージです。 もう一つの回答にも記載しますが、ファイルシステムのディレクトリの選択コントロールと考えていただくとイメージがつくと思います。現在の選択されたディレクトリのみをコンロールに渡し、ディレクトリ構造はコントロール内からファイルシステムを読み込んで表示します。 イメージをつかむために・・・かなり省略しますが… SelectDirectory obj; obj.Directory = @"c:\temp"; // 選択ディレクトリの初期値 obj.ShowWindow();     // TreeViewを表示して選択してもらう string newdir = obj.Directory; // 今回選択されたディレクトリを取得 イメージは伝わったでしょうか。 よろしくお願いします
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問