回答編集履歴

3

DataGridView

2022/05/25 10:04

投稿

TN8001
TN8001

スコア9350

test CHANGED
@@ -62,3 +62,171 @@
62
62
  }
63
63
  }
64
64
  ```
65
+
66
+ ---
67
+
68
+ `DataGridView`がマストとなると、`Datatable`を選択すべきでしょう。
69
+ 当初の回答とは方向性が変わりましたが、やること自体は同じような感じです(`Dictionary`の代わりに`Datatable`になっただけ)
70
+
71
+ どうせなのでより実践的?(エンコードやまともな`Split`)にしました。
72
+ CsvHelperでもなんでもいいのですが、変則csvを扱いやすかったので↓を使用しました。
73
+ [NuGet Gallery | Csv 2.0.84](https://www.nuget.org/packages/Csv/2.0.84)
74
+
75
+ 出番がないのはかわいそうなので、`PropertyGrid`もついでに付けましたw
76
+ `DataGridView`で複数行選択すると、`PropertyGrid`で値を一括変更できます(なぜか単一選択と複数選択で項目の並び順が変わる。なんで??)
77
+
78
+ ```cs
79
+ using System;
80
+ using System.Collections.Generic;
81
+ using System.ComponentModel;
82
+ using System.Data;
83
+ using System.Diagnostics;
84
+ using System.IO;
85
+ using System.Linq;
86
+ using System.Text;
87
+ using System.Windows.Forms;
88
+ using Csv;
89
+
90
+ namespace QQ8aid4d1edch6jx
91
+ {
92
+ public partial class Form1 : Form
93
+ {
94
+ private readonly DataTable dataTable = new DataTable();
95
+
96
+ public Form1()
97
+ {
98
+ InitializeComponent();
99
+ openFileDialog1.Filter = "CSVファイル|*.csv";
100
+ dataGridView1.DataSource = dataTable;
101
+ dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
102
+ propertyGrid1.ToolbarVisible = false;
103
+
104
+ File.WriteAllText("csv1.csv", "場所,東京,取引先,株式会社A,商品,食品", Encoding.GetEncoding("shift_jis"));
105
+ File.WriteAllText("csv2.csv", "実績,あり,商品,文具,取引先,株式会社B,場所,大阪,実店舗,あり", Encoding.GetEncoding("shift_jis"));
106
+ }
107
+
108
+ // 変則csvの読み込み
109
+ private void LoadButton_Click(object sender, EventArgs e)
110
+ {
111
+ if (openFileDialog1.ShowDialog() == DialogResult.OK)
112
+ {
113
+ DataRow row = dataTable.NewRow();
114
+
115
+ // 値にカンマや改行が含まれることも考慮しちゃんとパースする
116
+ string csv = File.ReadAllText(openFileDialog1.FileName, Encoding.GetEncoding("shift_jis"));
117
+ var options = new CsvOptions { HeaderMode = HeaderMode.HeaderAbsent, AllowNewLineInEnclosedFieldValues = true, };
118
+ string[] split = CsvReader.ReadFromText(csv, options).First().Values;
119
+
120
+ // カラム数が奇数の場合はどうすべきか?(とりあえずハブったものは無視)
121
+ for (var i = 0; i < split.Length - 1; i += 2)
122
+ {
123
+ string key = split[i];
124
+ string value = split[i + 1];
125
+
126
+ if (!dataTable.Columns.Contains(key)) // まだ該当カラムがなければ...
127
+ {
128
+ dataTable.Columns.Add(key); // カラム追加
129
+ }
130
+ row[key] = value;
131
+ }
132
+
133
+ dataTable.Rows.Add(row);
134
+ }
135
+ }
136
+
137
+ // DataTableをまともなcsvに出力
138
+ private void SaveButton_Click(object sender, EventArgs e)
139
+ {
140
+ var header = new List<string>(); // csvヘッダー部分
141
+ foreach (DataColumn column in dataTable.Columns)
142
+ {
143
+ header.Add(column.Caption); // カラムヘッダーを順番に追加
144
+ }
145
+
146
+ var data = new List<string[]>(); // csvデータ部分(2次元配列のような状態)
147
+ foreach (DataRow row in dataTable.Rows)
148
+ {
149
+ var items = new List<string>(); // 1行分
150
+ foreach (object item in row.ItemArray)
151
+ {
152
+ items.Add(item.ToString()); // 1セル分
153
+ }
154
+ data.Add(items.ToArray());
155
+ }
156
+
157
+ // ダブルクォート処理等いい感じにやってくれる
158
+ var csv = CsvWriter.WriteToText(header.ToArray(), data, ',');
159
+
160
+ // 上記foreachをワンライナーで書くなら
161
+ //var header = dataTable.Columns.Cast<DataColumn>().Select(x => x.Caption).ToArray();
162
+ //var data = dataTable.Rows.Cast<DataRow>().Select(x => x.ItemArray.Select(y => y.ToString()).ToArray());
163
+ //var csv = CsvWriter.WriteToText(header, data, ',');
164
+
165
+ File.WriteAllText("out.csv", csv, Encoding.GetEncoding("shift_jis"));
166
+ Process.Start("out.csv");
167
+ }
168
+
169
+ private void DataGridView1_SelectionChanged(object sender, EventArgs e)
170
+ {
171
+ propertyGrid1.SelectedObjects = dataGridView1.SelectedRows
172
+ .Cast<DataGridViewRow>()
173
+ .Select(x => new RowWrapper((DataRowView)x.DataBoundItem))
174
+ .ToArray();
175
+ }
176
+
177
+ private void PropertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
178
+ {
179
+ dataGridView1.Refresh();
180
+ }
181
+ }
182
+
183
+
184
+ // DataRowをいい感じにPropertyGridに表示する
185
+ // [C#/winforms: how to best bind a propertygrid and a System.Data.DataRow - Stack Overflow](https://stackoverflow.com/questions/943621/c-winforms-how-to-best-bind-a-propertygrid-and-a-system-data-datarow)
186
+ [TypeConverter(typeof(RowWrapperConverter))]
187
+ class RowWrapper
188
+ {
189
+ public List<string> Exclude { get; } = new List<string>();
190
+ private readonly DataRowView rowView;
191
+ public RowWrapper(DataRowView rowView) => this.rowView = rowView;
192
+ private static DataRowView GetRowView(object component) => ((RowWrapper)component).rowView;
193
+ private class RowWrapperConverter : TypeConverter
194
+ {
195
+ public override bool GetPropertiesSupported(ITypeDescriptorContext context) => true;
196
+ public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
197
+ {
198
+ var rw = (RowWrapper)value;
199
+ var props = TypeDescriptor.GetProperties(GetRowView(value), attributes);
200
+ var result = new List<PropertyDescriptor>(props.Count);
201
+ foreach (PropertyDescriptor prop in props)
202
+ {
203
+ if (rw.Exclude.Contains(prop.Name)) continue;
204
+ result.Add(new RowWrapperDescriptor(prop));
205
+ }
206
+ return new PropertyDescriptorCollection(result.ToArray());
207
+ }
208
+ }
209
+ private class RowWrapperDescriptor : PropertyDescriptor
210
+ {
211
+ private static Attribute[] GetAttribs(AttributeCollection value)
212
+ {
213
+ if (value == null) return null;
214
+ var result = new Attribute[value.Count];
215
+ value.CopyTo(result, 0);
216
+ return result;
217
+ }
218
+ private readonly PropertyDescriptor innerProp;
219
+ public RowWrapperDescriptor(PropertyDescriptor innerProperty) : base(innerProperty.Name, GetAttribs(innerProperty.Attributes)) => innerProp = innerProperty;
220
+ public override bool ShouldSerializeValue(object component) => innerProp.ShouldSerializeValue(GetRowView(component));
221
+ public override void ResetValue(object component) => innerProp.ResetValue(GetRowView(component));
222
+ public override bool CanResetValue(object component) => innerProp.CanResetValue(GetRowView(component));
223
+ public override void SetValue(object component, object value) => innerProp.SetValue(GetRowView(component), value);
224
+ public override object GetValue(object component) => innerProp.GetValue(GetRowView(component));
225
+ public override Type PropertyType => innerProp.PropertyType;
226
+ public override Type ComponentType => typeof(RowWrapper);
227
+ public override bool IsReadOnly => innerProp.IsReadOnly;
228
+ }
229
+ }
230
+ }
231
+ ```
232
+ ![アプリ画像](https://ddjkaamml8q8x.cloudfront.net/questions/2022-05-25/00a7c469-52ae-4502-a1e1-dc83f3c136a9.png)

2

Single

2022/05/22 01:44

投稿

TN8001
TN8001

スコア9350

test CHANGED
@@ -34,7 +34,7 @@
34
34
 
35
35
 
36
36
  var csv1 = new Dictionary<string, string>(); // 辞書(Dictionary)を作る
37
- var line1 = File.ReadLines("csv1.csv").Single(); // 1行読み込む
37
+ var line1 = File.ReadLines("csv1.csv").Single(); // 1行読み込む(1行でなかったらエラーFirstのほうがいいかも?)
38
38
  var split1 = line1.Split(new char[] { ',' }); // カンマで区切る
39
39
  for (var i = 0; i < split1.Length; i += 2) // 2個セットで使うので+=2
40
40
  {

1

C# 7.3

2022/05/22 01:39

投稿

TN8001
TN8001

スコア9350

test CHANGED
@@ -16,34 +16,49 @@
16
16
  どうしてもプロパティっぽく書きたいのであれば`ExpandoObject`が使えますが、便利かどうかはわかりません。
17
17
  [ExpandoObject クラス (System.Dynamic) | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.dynamic.expandoobject)
18
18
 
19
- .NET6です^^
20
19
  ```cs
20
+ using System;
21
+ using System.Collections.Generic;
21
22
  using System.Dynamic;
23
+ using System.IO;
24
+ using System.Linq;
22
25
 
26
+ namespace Q8aid4d1edch6jx
27
+ {
28
+ class Program
29
+ {
30
+ static void Main()
31
+ {
23
- File.WriteAllText("csv1.csv", "場所,東京,取引先,株式会社A,商品,食品");
32
+ File.WriteAllText("csv1.csv", "場所,東京,取引先,株式会社A,商品,食品");
24
- File.WriteAllText("csv2.csv", "実績,あり,商品,食品,取引先,株式会社A,場所,東京,実店舗,あり");
33
+ File.WriteAllText("csv2.csv", "実績,あり,商品,食品,取引先,株式会社A,場所,東京,実店舗,あり");
25
34
 
26
35
 
36
+ var csv1 = new Dictionary<string, string>(); // 辞書(Dictionary)を作る
27
- var csv1 = File.ReadLines("csv1.csv")
37
+ var line1 = File.ReadLines("csv1.csv").Single(); // 1行読み込む
28
- .Single()
38
+ var split1 = line1.Split(new char[] { ',' }); // カンマで区切る
39
+ for (var i = 0; i < split1.Length; i += 2) // 2個セットで使うので+=2
40
+ {
29
- .Split(",") // Stringバーロードは.NET Frameworkはない
41
+ csv1.Add(split1[i], split1[i + 1]); // 2個セット前をキ後ろを値
30
- .Chunk(2) // Chunkは.NET6以降
42
+ }
31
- .ToDictionary(x => x[0], x => x[1]);
32
43
 
33
- Console.WriteLine(csv1["場所"]); // 東京
44
+ Console.WriteLine(csv1["場所"]); // 東京 キーから値を取得(キーがないとKeyNotFoundException)
34
- Console.WriteLine(csv1.GetValueOrDefault("実績") ?? "項目なし"); // 項目なし GetValueOrDefaultは.NET Frameworkにはない
45
+ Console.WriteLine(csv1.TryGetValue("実績", out var value) ? value : "項目なし"); // 項目なし キーがあるかどうかわからない場合はTryGetValue
35
46
 
36
47
 
37
- dynamic csv2 = new ExpandoObject();
48
+ dynamic csv2 = new ExpandoObject();
38
49
 
39
- var line = File.ReadLines("csv2.csv").Single();
50
+ var line2 = File.ReadLines("csv2.csv").Single();
40
- var split = line.Split(new char[] { ',' });
51
+ var split2 = line2.Split(new char[] { ',' });
41
- for (var i = 0; i < split.Length; i += 2)
52
+ for (var i = 0; i < split2.Length; i += 2)
42
- {
53
+ {
43
- ((IDictionary<string, object>)csv2).Add(split[i], split[i + 1]);
54
+ var dic = (IDictionary<string, object>)csv2; // ExpandoObjectはIDictionaryにキャストできる
55
+ dic.Add(split2[i], split2[i + 1]); // Dictionaryと同じようにAddするとプロパティが生えてくるように見える
56
+ }
57
+
58
+ Console.WriteLine(csv2.場所); // 東京 プロパティのようにアクセスできるが、インテリセンスが効くわけでもないので便利かどうかはわからない
59
+ Console.WriteLine(csv2.実績); // あり
60
+ //Console.WriteLine(csv2.ほげ); // RuntimeBinderException
61
+ }
62
+ }
44
63
  }
45
-
46
- Console.WriteLine(csv2.場所); // 東京
47
- Console.WriteLine(csv2.実績); // あり
48
- //Console.WriteLine(csv2.ほげ); // RuntimeBinderException
49
64
  ```