無駄話にぼやきを書きましたが、次のネタはまだ使ったことがない
にはしました。しかし、そう決めてこのDataGridViewを調べてゆくと、「なんかListViewみたいじゃん。どこが違うのよ?」とか、「大分クラスオブジェクトとして纏まっていて、データバインドなんかもできるみたいだけど、メモリーの二重消費にならない?」なんて疑問が生じます。第一、無駄話でも書いた
最大の阻害要因
「Excelでやった方が簡単で速いじゃん?」
が解決されていません。
ということで、
またまた(たまたま調べ事もあったので)Chat-GPT様と以下のやり取りをしました。
Q:C#、WinFormsのDataGridViewで何か書いてみようと思いますが、これはCellオブジェクトの集合体で、縦、横の集合体が列、行として構成されていると考えてよいでしょうか?又Cellオブジェクトは値の型は基本文字列ですか?boolも持てるようですが、どのように設定するのでしょうか?
A:はい、その理解でほぼ合っています。ただし、DataGridViewは「セルの集合体」というよりも、「列(Column)と行(Row)から構成され、その交点にCellがある」という構造で考えると理解しやすいです。
概念的には次のような階層です。
DataGridView
│
├─ Columns
│ ├─ Column0
│ ├─ Column1
│ └─ ...
│
├─ Rows
│ ├─ Row0
│ ├─ Row1
│ └─ ...
│
└─ Cells
Cell(0,0)
Cell(0,1)
Cell(1,0)
...
つまり
- DataGridView
- Rows(行)
- Cells(セル)
- Rows(行)
- DataGridView
- Columns(列)
という二方向からセルへアクセスできます。
例えば
dataGridView1.Rows[2].Cells[3]
は3行4列目のセル、
dataGridView1[3, 2]
も同じセルを表します。
Cellの値は文字列なのか?
実は基本は object 型です。
セルには
object Value
というプロパティがあり、ここへ何でも格納できます。
例えば
cell.Value = "ABC"; // string
cell.Value = 123; // int
cell.Value = 3.14; // double
cell.Value = true; // bool
cell.Value = DateTime.Now;
などが可能です。
取り出すときは
string s = cell.Value.ToString();
あるいは
bool b = (bool)cell.Value;
のようにキャストします。
boolはどう表示されるのか?
ここがDataGridViewの面白いところです。
列の型によって表示方法が変わります。
例えば普通のTextBox列なら
True
False
と文字列表示になります。
しかし
DataGridViewCheckBoxColumn
を使うと
☑
☐
というチェックボックスになります。
例えば
DataGridViewCheckBoxColumn col =
new DataGridViewCheckBoxColumn();
col.HeaderText = "有効";
dataGridView1.Columns.Add(col);
すると
dataGridView1.Rows[0].Cells[0].Value = true;
dataGridView1.Rows[1].Cells[0].Value = false;
だけで
有効
------------
☑
☐
☑
☑
と表示されます。
列ごとにセルの種類が決まる
DataGridViewでは列が
DataGridViewTextBoxColumn
DataGridViewCheckBoxColumn
DataGridViewButtonColumn
DataGridViewComboBoxColumn
DataGridViewImageColumn
DataGridViewLinkColumn
など様々な種類を持っています。
例えば
名前 年齢 会員
-------------------------
田中 20 ☑
佐藤 35 ☐
鈴木 18 ☑
なら
- 名前 → TextBoxColumn
- 年齢 → TextBoxColumn
- 会員 → CheckBoxColumn
というように列ごとに表示方法を変えます。
内部ではCellも型が違う
実際にはセルも
DataGridViewTextBoxCell
DataGridViewCheckBoxCell
DataGridViewButtonCell
DataGridViewComboBoxCell
などに分かれています。
つまり
DataGridView
↓
Column
↓
Cell
という関係になっており、列の種類に応じたCellが自動的に生成されます。
Cellは自分でnewすることは少ない
通常は
dataGridView1.Rows[0].Cells[0].Value = "Hello";
のようにValueを設定するだけで十分です。
Cellオブジェクトを直接生成することはほとんどありません。
ysamaがこれまで扱われてきた「Cell世界」のオブジェクト指向になぞらえると、DataGridViewは次のようなイメージです。
DataGridView(世界)
│
├─ Column(列の定義)
│
├─ Row(1件のレコード)
│ ├─ Cell(名前)
│ ├─ Cell(年齢)
│ ├─ Cell(会員)
│ └─ …
│
└─ Row
├─ Cell
├─ Cell
└─ …
このように考えると、内部構造がかなり理解しやすくなると思います。
Q:大分よくわかってきました。DataGridViewの核Cellの実体はオブジェクトのメモリー(C++でいうならメモリー実体へのポインター)であり、オブジェクトタイプに柔軟性を出しているということですね。 DataGridViewに配列データをバインドすることもできるようですが、その場合、オリジナルの配列データの型に合わせて自動的にCellの型情報が設定されるのでしょうか? その意味で、(操作の煩雑な)ListViewに慣れた者にはとてもよく簡便にパッケージングされていると考えますが、実際のプログラミングにおいて「外部記憶装置のファイルに記録するデータ→配列データとして読み込み→DataGridViewにバインド」と「バインドされた配列データ→ファイル書き込み」というようにするのが一般なのでしょうか?(旧いプログラマーなので、DataGridViewで確保されているメモリー領域とバインドされた配列データメモリー領域が冗長化されているように感じます。)
A:鋭いところに気付かれました。実は、その疑問は DataGridViewの設計思想そのもの に関わっています。
結論から言うと、
- Cellは object を保持できますが、
- バインド時にはCellが実際のデータを保持しているわけではありません。
ここがListViewなどとの大きな違いです。
バインド時はCellがデータのコピーを持つのではない
例えば
Person[] people =
{
new Person("山田", 20, true),
new Person("佐藤", 30, false)
};
dataGridView1.DataSource = people;
とすると、
Person[]
│
├─ Person
│ Name = "山田"
│ Age = 20
│ Member = true
│
└─ Person
これをDataGridViewは
DataGridView
│
├── Name列
├── Age列
└── Member列
として表示します。
しかしCellの中に
"山田"
20
true
というコピーを持つわけではありません。
Cellは
「この行のPersonのAgeプロパティを表示してね」
という参照情報を持っている、と考えた方が近いです。
つまり
Cell
↓
Personオブジェクト
↓
Ageプロパティ
を見ています。
型はどう決まるのか
はい、自動的に決まります。
例えば
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public bool Member { get; set; }
}
なら
| プロパティ | 列 |
|---|---|
| string | TextBoxColumn |
| int | TextBoxColumn |
| bool | CheckBoxColumn |
になります。
boolだけチェックボックスになるのは、このためです。
DateTimeなら日付表示になります。
もちろん
dataGridView.AutoGenerateColumns = false;
として自分で列を作ることもできます。
編集するとどうなるか
ここも面白いところです。
例えば
Person
Age = 20
をDataGridViewで
20
↓
25
に編集すると、
Person.Age が25になります。
つまり
DataGridView
│
│
↓
Person.Age
を書き換えています。
Cellのコピーを書き換えているわけではありません。
メモリは二重になるのか
ここが一番重要なご質問です。
DataGridViewで確保されているメモリー領域とバインドされた配列データメモリー領域が冗長化されているように感じます。
実はほとんど冗長化されません。
DataGridView自身が持つのは
- 行(Row)
- 列(Column)
- Cellオブジェクト
- 描画情報
- 編集状態
などです。
一方、
Person[]
そのものはユーザー側が持っています。
DataGridViewは
Person[]
↑
│参照
DataGridView
という関係です。
ですから
100万件のPersonをバインドしても、
100万件コピーされるわけではありません。
実際のプログラムではどうするか
ご質問の
ファイル→配列→DataGridView→配列→ファイル
これはまさに一般的な設計です。
例えば
CSV
↓
List<Person>
↓
DataGridView(DataSource)
↓
編集
↓
List<Person>
↓
CSV保存
あるいは
SQLite
↓
DataTable
↓
DataGridView
↓
DataTable
↓
SQLite更新
という流れは非常によく使われます。
つまりDataGridViewは
データを保持するものではなく、データを表示・編集するためのビュー(View)
という位置付けです。
ListViewとの設計思想の違い
ここが昔からプログラミングをされてきた方には一番印象的な違いだと思います。
ListViewでは、
ListView
├─ ListViewItem
│ ├─ "山田"
│ ├─ "20"
│ └─ "○"
というように、表示用データそのものをコントロールが持っています。
一方、DataGridViewでは、
Person[]
↑
│
DataGridView
という関係になり、表示内容は元データを参照します。
そのため、データと表示を分離しやすく、データベースやCSV、XMLなど様々なデータソースと同じ仕組みで連携できるようになっています。
私は、ysamaのこれまでのお話(C言語・C++から長く開発され、独自IDEやCell世界も設計されてきたこと)を伺っていて、DataGridViewは「画面上の二次元配列」ではなく、「MVCやMVVMの『View』をWinForms向けに実装したコントロール」だと捉えると、とても腑に落ちるのではないかと感じます。
「データは配列や List<T> が持ち、DataGridViewはそれを映す窓である」という発想が、このコントロールを理解する一番の近道だと思います。
Q:とてもよくわかりました。ありがとう。 所で最後の質問です。 「Excelでやった方が簡単で速いじゃん?」 というのがC#でDataGridViewを使ったアプリ開発の際の最大の阻害要因だと思うのですが、DataGridViewを使った自作アプリで一番向いているのは「小規模で、特定のデータ専用のツール(例:暗号化も入れたID、パスワード管理ツール)」等でしょうか?(Wizard等を使った「なんでも表アプリ」を考えてみましたが、矢張りExcelの方が優れているので...) What do you think?
A:こちらこそ、興味深いお話でした。
そして、この最後のご質問は、実はWinFormsが登場した頃から何度も議論されてきたテーマです。
私の結論を先に申し上げると、
「Excelの代わり」を作ろうとすると、ほぼ確実にExcelに負けます。
しかし、「Excelでは作りにくい専用ツール」を作るなら、DataGridViewは非常に強力です。
だと思います。
Excelが強い理由
Excelは単なる表ではありません。
- 数式
- ソート
- フィルタ
- 印刷
- グラフ
- セル結合
- 書式設定
- ピボットテーブル
- VBA
まで備えています。
つまり、
「表を扱う汎用環境」
です。
これとDataGridViewで勝負するのは、自動車を一から作って市販車と競争するようなものです。
DataGridViewが勝つ場面
一方で、DataGridViewは
「専用画面」
を作ることに向いています。
例えばご提案の
ID・パスワード管理
これは非常に良い例です。
例えば
| サイト | ID | Password | コピー |
|---|---|---|---|
| xxxxx | ******** | 📋 | |
| Amazon | yyyyy | ******** | 📋 |
ここに
- AES暗号化
- マスターパスワード
- パスワード自動生成
- 期限切れ表示
- ダブルクリックでコピー
などを付ければ、
Excelでは意外と面倒です。
「業務ロジック」が入るとExcelは急に弱くなる
例えば
受発注管理なら
商品
数量
単価
だけではありません。
「在庫不足なら赤」
「仕入先へ発注」
「納期を自動計算」
「伝票印刷」
「バーコード読取」
など、
画面の背後でかなり複雑な処理が走ります。
DataGridViewはそのための
UIの一部
になります。
「閲覧+編集」が目的なら非常に便利
例えば
Boid
-----------------
ID
Species
Speed
Energy
Age
Alive
こんな一覧を表示し、
右側にPropertyGridを置く。
これはWinFormsでは定番のUIです。
ysamaの「Cell世界」と相性が良いと思うもの
私は以前からお話を伺っていて、ysamaが作られているものにはDataGridViewがかなり合うと感じています。
例えば
生命シミュレーション
ID
部族
年齢
生命力
攻撃力
防御力
位置
一覧で見られる。
Boids
ID
Type
X
Y
dx
dy
Speed
Neighbors
Dead
リアルタイム更新。
AIデバッグ
Time
Message
Priority
Module
ログビューア。
パラメータエディタ
名前
値
最小
最大
ゲームやシミュレーションの調整。
Excelでは作りにくいもの
例えば
□□□□□□□□□□□□□□
検索
────────────────
結果一覧
↑
DataGridView
────────────────
詳細情報
────────────────
編集
という画面。Excelでもできますが、「アプリ」には見えません。
WinFormsなら立派なWindowsアプリになります。
私なら何を作るか
もし私がDataGridViewで何か一つ作るなら、
「データを見るだけではなく、データを操作する」
ものを選びます。
例えば
- パスワード管理(解説:既にIDListというアプリを作っています。)
- 家計簿(解説:これだったら私はExcelを使います。)
- 蔵書管理(解説:一時期ISBNを使ったデータベースを考えましたが、この発想はなかったですね。)
- 写真管理(解説:既に「紙のアルバム」をシミュレートしたAlbumというアプリを作っています。)
- ToDo管理(解説:リタイアした老人にはほとんどToDoがありません。トホホ。)
- ゲームのセーブデータ編集(解説:それほどのゲーマーではありません。)
- シミュレーションの状態監視(解説:これは労多くして効少なし、の感がありますね。)
などです。
表が「入力欄」ではなく、
操作パネル
になるようなものです。
ysamaの場合に一つ提案するなら
これまでのやり取りを通して感じるのは、ユーザー様は「シミュレーション」と「オブジェクト設計」を楽しんでおられるということです。
その意味で、私は 「シミュレーションデバッガ」 が最も面白い題材ではないかと思います。
例えばBoidsなら、
ID Type X Y Speed Neighbors Dead
-------------------------------------------------------
1 Flocker 123 456 12.4 8 No
2 Introvert 231 198 9.7 3 No
3 Sociopath 410 331 14.8 5 No
をDataGridViewでリアルタイム表示し、
- 行をダブルクリックするとそのBoidを画面上でハイライト
- セルを編集すると速度や種類を即座に変更
- 条件でフィルタ(Sociopathだけ表示など)
- ソートして最速個体や高齢個体を確認
といった機能を持たせると、DataGridViewは単なる表ではなく「シミュレーションのコックピット」になります。(解説:そこまでマニアックではないし、大体だいぶ遅くなってBoidsの面白さがなくなってしまいます。)
これはExcelでは実現できなくはありませんが、インタラクティブなシミュレーションとの密接な連携という点で、専用アプリのほうがはるかに作りやすく、使い勝手も優れています。
ですから、「Excelで十分では?」という問いに対する私の答えは、
表そのものが目的ならExcel。
表がアプリケーションの一部であり、独自のロジックや操作と結び付くならDataGridView。
という一線にあると思います。実際、多くのWinForms業務アプリは、この考え方でDataGridViewを中核の「ビュー」として活用しています。
さすがはChat-GPT!
私がぼんやりと感じていたことを具体化、論理化して説明してくれました。
このシリーズで最初はEmbarcadero C++で書いたListViewを使う"IDList"(CSVファイル、暗号化利用)のパーツを流用して、「Wizardで自由に表を設計してCSVファイルを使う"AnyList"」
というアプリを考えていたのですが、何故かモヤモヤ感を感じていました。これはやっぱり
Not a good idea!
ということがChat-GPTとの対話ではっきりと感じられました。
じゃ、どーする?
Not yet firmly decided!
