社内でツールなどを作っている者です、他に訪ねる宛てもなく
この方法が正しいのか間違っているのかわかりません
一般的な方法など教えて頂ければと思います
C# + WPF + Prism な環境で DDD的手法で開発を行っています
DataGridにデータクラスのコレクションをバインドして表示、編集を行いたいと考えております
データクラスは完全コンストラクタパターンにするため、各プロパティは読み取り専用ですので
ViewでのバインディングモードをOneWayやOneTimeとしたところテキストボックスの編集は可能ですが
DataGridのRowChanged後に編集前のテキストに戻ってしまいます
幸い入力値はDataGridのLostFocusイベントのRoutedEventArgs引数のOriginalSourceプロパティにありましたので
これを元にデータクラスを新たに作成しDataGridのItemSourceを再指定することで、データクラス、Viewが入力したものに
なることをできたのですが、Viewの編集が無効になる点、LostFocusイベントでしか入力値を取得出来ない点を考えると
そもそもDataGridでの編集に読み取り専用のデータクラスは利用しないほうがいいのかと思えます
その他に考えられる方法は
1DataGridの編集にのみ利用するクラスなのだから読み取り専用にしない
2読み書き可能なデータクラスを作っておき、ViewModeで載せ替える
ぐらいしか思いつかないのですが、このような場合はどのようにするのが一般的なのでしょうか
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/11/23 03:14
回答2件
0
ベストアンサー
私はアマチュアなので一般的な方法はわかりませんが、C# 9.0でレコード型が入ったことですしちょっとやってみました。
- C# 9.0以上
Prism
(面倒なのでDIしません)ReactiveProperty
(後片付けしてません)
xml
1<Window 2 x:Class="Questions305906.Views.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 Width="525" 6 Height="350"> 7 <DockPanel> 8 <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> 9 <Button Command="{Binding AddCommand}" Content="アイテム追加" /> 10 <Button Command="{Binding DispCommand}" Content="モデル確認" /> 11 </StackPanel> 12 <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}"> 13 <DataGrid.Columns> 14 <DataGridTextColumn Binding="{Binding Id}" Header="ID" IsReadOnly="True" /> 15 <DataGridTextColumn Width="200" Binding="{Binding Name}" Header="名前" /> 16 <DataGridTextColumn Width="100" Binding="{Binding Price}" Header="値段" /> 17 </DataGrid.Columns> 18 </DataGrid> 19 </DockPanel> 20</Window>
cs
1namespace Questions305906.Models 2{ 3 using System.Collections.ObjectModel; 4 using System.Diagnostics; 5 using System.Linq; 6 7 public record ItemModel(int Id, string Name, int Price); 8 9 public class Provider 10 { 11 public ReadOnlyObservableCollection<ItemModel> Items { get; } 12 private readonly ObservableCollection<ItemModel> items; 13 14 public Provider() 15 { 16 items = new() 17 { 18 new(Id: 1, Name: "aaa", Price: 123), 19 new(Id: 2, Name: "bbbb", Price: 456), 20 new(Id: 3, Name: "ccccc", Price: 7890), 21 }; 22 23 Items = new(items); 24 } 25 26 public void Replace(ItemModel oldValue, ItemModel newValue) 27 { 28 var index = items.IndexOf(oldValue); 29 items.Remove(oldValue); 30 items.Insert(index, newValue); 31 } 32 33 public void Add() 34 { 35 var id = items.Max(x => x.Id) + 1; 36 items.Add(new(Id: id, Name: "", Price: 0)); 37 } 38 39 public void Disp() 40 { 41 foreach (var item in items) Debug.WriteLine(item); 42 } 43 } 44} 45 46namespace Questions305906.ViewModels 47{ 48 using Prism.Mvvm; 49 using Questions305906.Models; 50 using Reactive.Bindings; 51 using Reactive.Bindings.Extensions; 52 using System; 53 54 public class ItemViewModel : BindableBase 55 { 56 public int Id { get; } 57 58 private string _Name; 59 public string Name { get => _Name; set => SetProperty(ref _Name, value); } 60 61 private int _Price; 62 public int Price { get => _Price; set => SetProperty(ref _Price, value); } 63 64 internal ItemModel Item; 65 internal ItemModel NewItem => Item = Item with { Name = Name, Price = Price }; 66 67 68 public ItemViewModel(ItemModel item) => (Item, Id, Name, Price) = (item, item.Id, item.Name, item.Price); 69 } 70 71 public class MainWindowViewModel : BindableBase 72 { 73 public ReadOnlyReactiveCollection<ItemViewModel> Items { get; } 74 public ReactiveCommand AddCommand { get; } = new(); 75 public ReactiveCommand DispCommand { get; } = new(); 76 77 private readonly Provider provider = new(); 78 79 80 public MainWindowViewModel() 81 { 82 Items = provider.Items.ToReadOnlyReactiveCollection(x => new ItemViewModel(x)); 83 Items.ObserveElementPropertyChanged().Subscribe(x => provider.Replace(x.Sender.Item, x.Sender.NewItem)); 84 85 AddCommand.Subscribe(() => provider.Add()); 86 DispCommand.Subscribe(() => provider.Disp()); 87 } 88 } 89} 90// .NET5でない場合 91//namespace System.Runtime.CompilerServices 92//{ 93// public class IsExternalInit { } 94//}
同値があるとおかしくなるのは目に見えているので、Id
をユニークにしました。
DataGrid
のプレースホルダも使用しませんでした(方法はありそうですが未調査)
結局どうにかして詰め替えるしかないわけですが、ReactiveProperty
のおかげでそれほど面倒というわけでもなかったです(M
→VM
の詰め替え・Provider.Items
の入れ替え、どちらも)
しかしこれは筋がいいとは思えないです(NewItem
の雑さ^^;
レコード型が浸透してきてライブラリ等のサポートが入ってくれば、また変わると思います。まあこれからですね^^
追記
.NET Core 3.1でのcsproj(↑提示コードの最下段System.Runtime.CompilerServices.IsExternalInit
のコメントを外してください)
xml:.csproj
1<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> 2 <PropertyGroup> 3 <OutputType>WinExe</OutputType> 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <UseWPF>true</UseWPF> 6 <AssemblyName>Questions305906</AssemblyName> 7 <LangVersion>9.0</LangVersion> 8 </PropertyGroup> 9 <ItemGroup> 10 <PackageReference Include="Prism.DryIoc" Version="8.0.0.1909" /> 11 <PackageReference Include="ReactiveProperty" Version="7.5.1" /> 12 </ItemGroup> 13</Project>
投稿2020/11/23 17:05
編集2023/08/13 08:35総合スコア9855
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/11/24 02:55
2020/11/24 08:58
2020/11/25 05:23 編集
2020/11/25 09:09
2020/11/26 07:02
2020/11/27 23:43 編集
2020/11/28 06:59
0
こんばんは。
Prismを利用されているとのことなので、MVVMパターンに従って開発しているということで宜しいでしょうか。
完全コンストラクタを遵守するとして私が作るとしたら、
- Model…完全コンストラクタのクラス
- ViewModel…DataGridにバインドし、書き換えられたらModelのクラスを更新するクラス
とすると思います。
多分これが一番綺麗にまとまります。
##サンプルコード
###Model (C#)
C#
1// 参照関連面倒なので構造体にしてあります。クラスに書き換える場合は参照渡しになるので注意して下さい。 2public struct NameIndexPair 3{ 4 // プロパティの名前は適当です 5 public string Name { get; } 6 public int Index { get; } 7 8 public NameIndexPair(string name, int index) 9 { 10 Name = name; 11 Index = index; 12 } 13} 14 15public class MainClass 16{ 17 public MainClass() 18 { 19 public List<NameIndexPair> NameIndexPairs { get; } = new List<NameIndexPair>() 20 { 21 new NameIndexPair("あああ", 123), 22 new NameIndexPair("いいい", 456), 23 }; 24 } 25}
###MainWindow (XAML)
XAML
1<Window …略…> 2 <StackPanel> 3 <DataGrid ItemsSource="{Binding Items}" /> 4 </StackPanel> 5</Window>
###MainWindow (C#)
C#
1public class MainWindow : Window 2{ 3 public MainWindow() 4 { 5 InitializeComponent(); 6 DataContext = new MainWindowViewModel(); 7 } 8}
###ViewModel
C#
1//using Prism.Mvvm; 2//using Prism.Commands; 3 4public class MainWindowViewModel : BindableBase 5{ 6 public MainClass M { get; } = new MainClass(); 7 public ObservableCollection<NameIndexPair> Items { get; } = new ObservableCollection<NameIndexPair>(); 8 9 public MainWindowViewModel() 10 { 11 M.NameIndexPairs.ForEach(p => Items.Add(p)); 12 Items.CollectionChanged += (sender, e) => 13 { 14 M.NameIndexPairs = new List(); 15 Items.ForEach(p => M.NameIndexPairs.Add(p)); 16 }; 17 } 18}
実行環境が手元に無いので動作確認出来ていません。ごめんなさい。
もし動かなかったら言って下さい。
投稿2020/11/23 07:34
編集2020/11/23 13:03総合スコア313
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/11/23 12:16
2020/11/23 12:27
2020/11/24 01:58
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。