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

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

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

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

WPF

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

Q&A

解決済

1回答

4421閲覧

WPF C# 親ウィンドウと子ウィンドウのデータ共有

nya-3

総合スコア27

C#

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

WPF

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

1グッド

0クリップ

投稿2022/03/30 02:56

前提

以下の続きです。
WPFで、行ボタン押したら上の行と同じ内容を挿入したい、親と子の色情報を共有したい
https://teratail.com/questions/tfv3nqhv521ip3

実現したいこと

詳細と言う項目に「10,20,50,20」というような座標が入る予定です。
StringとしてとりあえずClass上?に入れます。
ただ、ツール上では10、20、50、20それぞれを別WPF画面で
Left,Top,Width,Heightのように認識させたいのです。

ただ、詳細の項目に入れる値としては
「10,20,50,30」などと必ずひとまとめにし、
この形式の文字列て保存したいと思ってます。

最終的には、以下のようにThumbをドラッグで移動させたり
拡縮ができるようにしようとしています。
まずは、データの通りにLeft、Width,Top,Heightを自動適用したいと思いました。
C#のWPFでドラッグできるコントロールを作る

コード抜粋

xaml

1<DataGridTextColumn Binding="{Binding Detail}" Header="詳細" />

C#

1 public ObservableCollection<Item> Items { get; private set; } = new ObservableCollection<Item>(); 2 public class Item 3 { 4 [XmlAttribute] public string Detail { get; set; } 5 6 7 #region INotifyPropertyChanged 8 public event PropertyChangedEventHandler PropertyChanged; 9 protected void Set<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) 10 { 11 if (Equals(storage, value)) return; 12 storage = value; 13 OnPropertyChanged(propertyName); 14 } 15 protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 16 #endregion 17 18 }

試したこと

public string Detail { get; set; }
なんだか面倒になってきたため
public int MyLeft { get; set; }
public int MyTop { get; set; }
public int MyWidth { get; set; }
public int MyHeight { get; set; }
としました。

MainWindowにPublicとして定義しているからか、
子ウィンドウではMyLeftなどの変数が反応しません。
当たり前のことかもしれませんが、
Publicとなっているのに他のWindowからの変数?を
受け取れないのが不思議でなりません。
試しにClassファイルを作成してみましたが、こちらも関係ありませんでした。
どうやったら子ウィンドウと値を共有することが出来るのでしょう?

xaml

1 <Canvas Margin="10" Background="AliceBlue"> 2 <Thumb Width="{Binding MyWidth}" Height="{Binding MyHeight}" 3 Canvas.Left="{Binding MyLeft}" 4 Canvas.Top="{Binding MyTop}" 5 DragStarted="Thumb_DragStarted" 6 DragCompleted="Thumb_DragCompleted" 7 DragDelta="Thumb_DragDelta"> 8 <Thumb.Template> 9 <ControlTemplate> 10 <Border x:Name="Thumb_Border" BorderBrush="Red" BorderThickness="0"> 11 </Border> 12 </ControlTemplate> 13 </Thumb.Template> 14 </Thumb> 15 </Canvas>

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

.Net FrameWork 4.7.2

TN8001👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

MainWindowにPublicとして定義しているからか、
子ウィンドウではMyLeftなどの変数が反応しません。
当たり前のことかもしれませんが、
Publicとなっているのに他のWindowからの変数?を
受け取れないのが不思議でなりません。

nya-3さんご自身が書いたコードがほとんどないのでC#の理解度を測れないのですが、オブジェクトとかインスタンスは理解されていますでしょうか?

{Binding HogeHoge}とすると、そのDataContextのオブジェクトのプロパティにバインドします。
データ バインディングの概要 - WPF .NET | Microsoft Docs

通常はViewModelクラスを作成しWindow.DataContextに設定するのですが、回答では省略することもあります。
2つのウィンドウで共通のViewModelインスタンスをDataContextに設定すれば、「データ共有」することになります。

前回の回答は盛沢山すぎたので、直接関係ないところはばっさりカットします。

INotifyPropertyChanged実装には下記を使用しました。
NuGet Gallery | CommunityToolkit.Mvvm 7.1.2

参考リンクがドラッグ移動のみなので、お手軽に下記を使用しました(リサイズまで考慮すると実用になりません)
NuGet Gallery | Microsoft.Xaml.Behaviors.Wpf 1.1.39

こちらも似たような構成なので参考になるかと(MouseDragElementBehaviorのリベンジ成功!)
【WPF】Dictonaryの値をコンボボックスに表示できない、クラスを参照したい

MainWindow

xml

1<Window 2 x:Class="Qjru5bj6b9xz7g1.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 x:Name="Root" 6 Title="MainWindow" 7 Width="800" 8 Height="450"> 9 <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}"> 10 <DataGrid.Columns> 11 <DataGridTextColumn Binding="{Binding Title}" Header="タイトル" /> 12 <DataGridTextColumn Binding="{Binding Left}" Header="Left" /> 13 <DataGridTextColumn Binding="{Binding Top}" Header="Top" /> 14 <DataGridTextColumn Binding="{Binding Width}" Header="Width" /> 15 <DataGridTextColumn Binding="{Binding Height}" Header="Height" /> 16 <DataGridTemplateColumn Header="追加"> 17 <DataGridTemplateColumn.CellTemplate> 18 <DataTemplate> 19 <!-- 追加ボタンで一部コピーしたアイテムを追加します(コードビハインドでの例) --> 20 <Button Click="Add_Click" Content="+" /> 21 </DataTemplate> 22 </DataGridTemplateColumn.CellTemplate> 23 </DataGridTemplateColumn> 24 <DataGridTemplateColumn Header="削除"> 25 <DataGridTemplateColumn.CellTemplate> 26 <DataTemplate> 27 <!-- 削除ボタンでアイテムを削除します(MVVMコマンドでの例) --> 28 <Button 29 Command="{Binding DataContext.DelCommand, Source={x:Reference Root}}" 30 CommandParameter="{Binding}" 31 Content="-" /> 32 </DataTemplate> 33 </DataGridTemplateColumn.CellTemplate> 34 </DataGridTemplateColumn> 35 </DataGrid.Columns> 36 </DataGrid> 37</Window>

cs

1using System.Windows; 2using System.Windows.Controls; 3 4namespace Qjru5bj6b9xz7g1 5{ 6 public partial class MainWindow : Window 7 { 8 private readonly ViewModel vm = new ViewModel(); 9 10 11 public MainWindow() 12 { 13 InitializeComponent(); 14 15 DataContext = vm; 16 new ChildWindow { DataContext = vm, }.Show(); 17 } 18 19 private void Add_Click(object sender, RoutedEventArgs e) 20 { 21 if (((Button)sender).DataContext is Item item) 22 { 23 //クリックした行と部分的に同じものを、1つ下の行に挿入 24 var i = vm.Items.IndexOf(item); 25 vm.Items.Insert(i + 1, new Item { Title = item.Title, Source = item.Source, }); 26 } 27 } 28 } 29}

ChildWindow

xml

1<Window 2 x:Class="Qjru5bj6b9xz7g1.ChildWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors" 6 x:Name="Root" 7 Title="ChildWindow" 8 Width="800" 9 Height="450"> 10 <ItemsControl Background="White" ItemsSource="{Binding Items}"> 11 <ItemsControl.ContextMenu> 12 <ContextMenu> 13 <!-- 何もないところの右クリックメニューで新規アイテムを追加します(コードビハインドでの例) --> 14 <MenuItem Click="Add_Click" Header="追加" /> 15 </ContextMenu> 16 </ItemsControl.ContextMenu> 17 <ItemsControl.ItemsPanel> 18 <ItemsPanelTemplate> 19 <Canvas /> 20 </ItemsPanelTemplate> 21 </ItemsControl.ItemsPanel> 22 <ItemsControl.ItemTemplate> 23 <DataTemplate> 24 <Grid> 25 <Grid.ContextMenu> 26 <ContextMenu> 27 <!-- アイテムの右クリックメニューでアイテムを削除します(MVVMコマンドでの例) --> 28 <MenuItem 29 Command="{Binding DataContext.DelCommand, Source={x:Reference Root}}" 30 CommandParameter="{Binding}" 31 Header="削除" /> 32 </ContextMenu> 33 </Grid.ContextMenu> 34 <StackPanel> 35 <StackPanel.RenderTransform> 36 <TranslateTransform X="{Binding Left}" Y="{Binding Top}" /> 37 </StackPanel.RenderTransform> 38 <Behaviors:Interaction.Behaviors> 39 <Behaviors:MouseDragElementBehavior X="{Binding Left, Mode=TwoWay}" Y="{Binding Top, Mode=TwoWay}" /> 40 </Behaviors:Interaction.Behaviors> 41 <Image 42 Width="{Binding Width}" 43 Height="{Binding Height}" 44 Source="{Binding Source}" 45 Stretch="Fill" /> 46 <TextBlock HorizontalAlignment="Center" Text="{Binding Title}" /> 47 </StackPanel> 48 </Grid> 49 </DataTemplate> 50 </ItemsControl.ItemTemplate> 51 </ItemsControl> 52</Window>

cs

1using System.Windows; 2 3namespace Qjru5bj6b9xz7g1 4{ 5 public partial class ChildWindow : Window 6 { 7 public ChildWindow() => InitializeComponent(); 8 9 private void Add_Click(object sender, RoutedEventArgs e) 10 { 11 // DataContextがViewModelのはずなのでキャストして使用する 12 ((ViewModel)DataContext).Items.Add(new Item()); 13 } 14 } 15}

ViewModel

cs

1using System.Collections.ObjectModel; 2using System.Windows; 3using System.Windows.Input; 4using CommunityToolkit.Mvvm.ComponentModel; 5using CommunityToolkit.Mvvm.Input; 6 7namespace Qjru5bj6b9xz7g1 8{ 9 public class ViewModel 10 { 11 public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>(); 12 public ICommand DelCommand { get; } 13 14 public ViewModel() 15 { 16 Items.Add(new Item { Title = "TN8001", Left = 0, Top = 0, Width = 200, Source = "https://teratail-v2.storage.googleapis.com/uploads/avatars/u13/132786/KnkDDC5A_thumbnail.jpg", }); 17 Items.Add(new Item { Title = "nya-3", Source = "https://www.gravatar.com/avatar/4d48208fd97fc8a0d8d0765a11592aa5?d=identicon", }); 18 19 DelCommand = new RelayCommand<Item>(Del); 20 } 21 22 private void Del(Item item) 23 { 24 // MVVM的にはよろしくないが... 25 var r = MessageBox.Show("削除します。いいですか?", "", MessageBoxButton.YesNo, MessageBoxImage.Information); 26 if (r == MessageBoxResult.Yes) 27 { 28 Items.Remove(item); // 当該アイテムを削除 29 } 30 } 31 } 32 33 public class Item : ObservableObject // INotifyPropertyChanged 34 { 35 public string Title { get => _Title; set => SetProperty(ref _Title, value); } 36 private string _Title = "タイトル"; 37 38 public string Source { get => _Source; set => SetProperty(ref _Source, value); } 39 private string _Source; 40 41 public int Left { get => _Left; set => SetProperty(ref _Left, value); } 42 private int _Left = 100; 43 44 public int Top { get => _Top; set => SetProperty(ref _Top, value); } 45 private int _Top = 100; 46 47 public int Width { get => _Width; set => SetProperty(ref _Width, value); } 48 private int _Width = 100; 49 50 public int Height { get => _Height; set => SetProperty(ref _Height, value); } 51 private int _Height = 100; 52 } 53}

アプリ画像


ただ、詳細の項目に入れる値としては
「10,20,50,30」などと必ずひとまとめにし、
この形式の文字列て保存したいと思ってます。

まあそういうことも可能ですが、入力しにくいだけではないですか?
↓こういうのでやりたいですね(個人的には数字を入力するだけでもだるいです^^;
IntegerUpDown · xceedsoftware/wpftoolkit Wiki

「ファイル保存時にそういう形式にしたい」ということならわかります。

最終的には、以下のようにThumbをドラッグで移動させたり
拡縮ができるようにしようとしています。

結構こういうことがしたいって人多いですね。
しかし「これが決定版」みたいな実装がなくて、皆さんそれぞれ頑張っているという印象です(ちゃんとやろうとするとかなり難しいです)

日本語で検索する場合は、「wpf ラバーバンド」がいいです。
英語で検索する場合は、「wpf resizing adorner」がいいです。
英語で「rubber band」は、範囲選択の四角の話です(日本人が勘違いしているのでしょうか?^^;

C# wpf InkCanvasで四角、丸を描写後に消しゴムで消したい

InkCanvas.Childrenに入れたコントロールが、(何もしなくても)移動・リサイズできちゃう

これで妥協できるなら一番楽そうな気はします。

投稿2022/03/30 10:52

編集2023/07/30 05:30
TN8001

総合スコア9326

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

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

nya-3

2022/03/31 06:05

TN8001様 回答ありがとうございます。 オブジェクトとかインスタンスとか、いろんな資料を読み漁っていますが 未だに理解できたとは思えません。頭が固いのか、凄く難しいのです…。 Bindingの値とDetaContextの値が連動している?ようなイメージは分かります。 親を起動→ViewModelのItems定義→ViewModelのItems.Addで追加 →xamlファイルの{Binding Items}の値と同期をとる こんな感じのイメージでしょうか? データの流れを1つ1つ追って学んでいきたいのですが、 WPFのBindingとかMVVMとかってそういう感じでは無いですよね…。 理解できていないところ ①MainWindowからItemsを編集する場合、どのように記載するのか(追加、編集、削除) var vm = new ViewModel(); DataContext = vm; と親ウィンドウで定義しているけれど、外側でPublic定義しないと追加編集はNG? ②SubWindowからItemsを編集する場合、どのように記載するのか ③コンボボックス用のだが、共通で利用するColorなどの選択肢は  どのように定義するのか(ViewModelに記載するのか public static string[] Colors { get; } = { "Transparent","Black","DimGray","DarkGray","White","Magenta","DeepPink","Crimson","Red","DarkViolet","Indigo","Blue","RoyalBlue","DodgerBlue","SkyBlue","Cyan","Teal","DarkSlateGray","SpringGreen","Green","GreenYellow","Gold","Orange", };
TN8001

2022/04/01 10:27

すいませんコメントに気が付くのが遅れました(通知が全然来ないですorz > Bindingの値とDetaContextの値が連動している?ようなイメージは分かります。 前回の回答では DataContext = Items; としたので、ItemsSource="{Binding}" となりました。 今回の回答では DataContext = vm; としたので、ItemsSource="{Binding Items}" となります。 DataGridやListBoxなどのコレクションを表示するもの(≒ItemsSourceがあるもの)は、DataGridTextColumnやItemTemplateのようなところではDataContextが個々のアイテムになっているので "{Binding Title}" のようになります。 文章力がないんで言葉でうまく説明できませんが、そんなに難しい話ではないです^^ [【WPF】Binding入門1。DataContextの伝搬 | さんさめのC#ブログ](https://threeshark3.com/wpf-binding-datacontext/) > こんな感じのイメージでしょうか? そんな感じでいいと思います。 > WPFのBindingとかMVVMとかってそういう感じでは無いですよね…。 Binding自体裏で勝手にいろいろやってくれちゃうので、コードで追うのは難しいかもしれないですね。 仕組み自体は単純な話で、例えばINotifyPropertyChangedだったら 1. (あなたのコードで)何かのプロパティを変更する 2. (あなたのコードで)PropertyChangedイベントを発砲する(前回の回答で言うとSetメソッド) 3. WPFがPropertyChangedイベントを購読しており、その変更を画面に反映する ってだけのことです。普段のイベントの使い方と逆になっていますがボタンなんかと大差ありません^^ ボタンの場合はこうですよね? 1. ユーザーがボタンを押す 2. WPFがClickイベントを発砲する 3. (あなたのコードで)Clickイベントを購読しているのでイベントハンドラが呼ばれる 4. (あなたのコードで)なにかする > ①MainWindowからItemsを編集する場合、どのように記載するのか(追加、編集、削除) > ②SubWindowからItemsを編集する場合、どのように記載するのか ちょっと削りすぎましたかね^^; 追加削除の例を付けた回答に編集しました。 コードビハインドとMVVM(コマンド)での2パターンです。 コマンドはちょっと条件が悪い(ItemがDetaContextになっているところでViewModelに定義したコマンドを呼ぶ)ため、xamlが冗長な記述になっていますが本来はプロパティのバインドと同じくらい簡単です(何を言っているかわからないとは思いますが^^; > ③コンボボックス用のだが、共通で利用するColorなどの選択肢は >  どのように定義するのか(ViewModelに記載するのか public staticならまあどこに書いても変わらないですが、普通は定数用のクラスを作るんでしょうかね? MainWindowのままでいいと思います^^;
nya-3

2022/04/05 05:09

TN8001様 申し訳ありません、私も返信遅れてしまいました。 返信があるだけ嬉しいので、遅くなろうが1ヵ月後だろうが大歓迎です。ありがとうございます。 ViewModelのなかにItemsがあり、その中にItemがあるというイメ―ジですかね? 少し分かってきたような気がします。 DataContextは、中身の名前まで見れると…。 丁寧にありがとうございます! ただ、1つ困ったことがありまして。 前回の質問で、xmlファイルを読込むLoadボタンを用意していたのですが それが利用できなくなりました。Saveボタンは機能しそうです。 以下のように修正してみましたが、 vmが読み取り専用のため定義し直しが出来なさそうなのです。 private void LoadButton_Click(object sender, RoutedEventArgs e) { var serializer = new XmlSerializer(vm.Items.GetType()); using (var fs = new FileStream("mydata.xml", FileMode.Open)) { vm.Items = (ObservableCollection<Item>)serializer.Deserialize(fs); // Items自体の変更は通知されないので明示的に入れ替え // INotifyPropertyChangedを実装するか、Clear・Addするか DataContext = null; DataContext = vm; } } うーん。 なんだか私には難しそうな気がしてきました。 丁寧に説明頂き、馬鹿な私でも分かりそうなんですけど、 何かあっても自分では対処できない代物になりそうです。 とりあえず、xmlファイルを適宜更新するような仕組みにしようかなと思います。 それか、Winfomも検討しようかなと思います。難しいので…。
TN8001

2022/04/07 10:32

> vmが読み取り専用のため定義し直しが出来なさそうなのです。 あぁそうですね。。。すいません^^; // INotifyPropertyChangedを実装するか、Clear・Addするか とあるようにItems自体の入れ替えを通知するようにするなら public class ViewModel : ObservableObject { public ObservableCollection<Item> Items { get => _Items; set => SetProperty(ref _Items, value); } private ObservableCollection<Item> _Items = new ObservableCollection<Item>(); こうなります。 DataContext = null; DataContext = vm; これは不要になります。 > それか、Winfomも検討しようかなと思います。難しいので…。 まあなんにしろ難しいとは思いますけどね^^; 「こういうことがしたい。でも難しそう...」ってことが案外簡単だったり。 逆に「ここをこうしたいだけなんだけど...(簡単にできるよね?)」ってことが超絶難しい(めんどくさい)ってこともあります。 ある程度経験を積んでくるとその辺を見積もれるように(あるいはピンポイントで試してみる)なるんですが、なかなか教えられるようなものでもないんですよね... 譲れない点・妥協できる点をメリハリつけて、地道に頑張っていくしかないと思います。 がんばってください^^
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問