2021 年 11 月にリリースされた Visual Studio Community 2022 のテンプレートを使って .NET 6.0 版の Windows Forms アプリを作ってみました。Visual Studio のバージョンはこの記事を書いた時点で最新版の 17.0.2、SDK は 6.0.100、ランタイムは 6.0.0 です。
まず一つ問題があって、プロジェクト作成直後はデザイン画面が表示されません。下の画像のようにソリューションエクスプローラーに表示されるアイコンは C# のクラスファイスのもので、ここからデザイナ画面を表示することもできません。また、Form1.resx ファイルも存在しません。
Developer Community に VS 2022 - WinForms .NET Designer does not automatically show the design surface when creating a new project という報告が上がっていて年内には改善されるとのことです。その記事に workaround が紹介されていますが自分の環境ではダメでした。
自分が試した限りですが、自動生成された Form1.cs を削除し、ソリューションエクスプローラーを操作して「新しい項目の追加」ダイアログから[フォーム (Windows フォーム)]を選んで新たにフォームを追加すればデザイナ画面は表示されました。Form1.resx も生成されました。
ただし、それでだけで全て OK かどうかは分かりません。実際、他の問題として、v17.0.1 の時 Form1.resx に画像を含めると開かないということがありました (v17.0.2 で改善されたのかその問題は出なくなりましたが)。年内に fix すると言っているので、待てるのであればそれを待った方が良いかもしれません。
以下は、自動生成された Form1.cs を削除してから新たに追加するという方法でデザイナ画面の表示と Form1.resx の生成を行ってから進めた時の話です。
SQL Server データベースから Visual Studio 2022 のウィザードを利用して型付 DataSet / DataTable + TableAdapter を作り、テーブルのレコード一覧を DataGridView に表示し、ユーザーが DataGridView に表示されたデータを編集してからデーターベースに編集結果を反映するという Windows Forms 定番のアプリを作ります。
Microsoft のドキュメント「新しいデータ ソースの追加」には ".NET Core 開発ではサポートされていません" と書いてありますが、型付 DataSet / DataTable + TableAdapter を作ることは可能です。ただし、その記事に書いてあるように[データ ソース]ウィンドウからドラッグ&ドロップしてコードを自動生成させることはできませんので、そこは自力でコードを書くことになります。
まず、型付 DataSet / DataTable + TableAdapter を作ります。ソリューションエクスプローラーでプロジェクトを右クリック ⇒[追加(D)]⇒[新しい項目(W)...]で表示されるメニューの中に DataSet という項目がありますのでそれを使います。(注: 上に紹介した Microsoft のドキュメントのように[プロジェクト(P)]⇒[新しいデータソースの追加(N)...]と操作するとサポートされてないというメッセージが出てその先に進めません)
SQL Server サンプルデータベース Northwind の Products, Catagories, Suppliers テーブルから NorthwindDataSet.xsd という名前で DataSet を作ってみました。この記事で使うのは下の画像の中の Products + ProductsTableAdapter です。
ここまでは Windows Forms プロジェクトを作成したデフォルトの状態で進めることができます。しかし、ビルドしようとすると SqlConnection, SqlCommand, SqlDataAdapter, SqlTransaction 等が System.Data.SqlClient に見つからないというエラーになって失敗します。
SqlConnection, SqlCommand 等は .NET Core 3.1 でも .NET 5.0 でもデフォルトでは含まれておらず NuGet で System.Data.SqlClient をインストールする必要がありました。.NET 6.0 でも同じことのようです。
上の画像の System.Data.Common の方は無くてもビルドは通りますが、SystemDBNull とか System.Data.DbType 等が含まれているとのことなので念のため追加しておきました。
この後、.NET Framework であれば作成した DataTable を[データ ソース]ウィンドウからフォームにドラッグ&ドロップするだけで完全な Windows Forms アプリが自動生成されますが、.NET Core 3.1 や .NET 6.0 ではここから先は自力でコードを書くことになります。
一番実装が面倒そうなのが BindingNavigator 周りだと思いますが、それをデザイン画面で実装しようとしても、そもそもツールボックスに BindingNavigator がありません。
どうも特定の OS に依存するアプリを .NET Core 3.1 や .NET 6.0 で作る場合のサポートは限られているということのようです。Windows OS に依存する Windows Forms アプリなら最初から .NET Framework ベースで作るのが正解のようです。
しかし、ツールボックスに BindingNavigator が無いからと言って .NET 6.0 アプリで使えないというわけではないようです。Visual Studio のデザイナが対応してないだけで、.NET Framework ベースで作ったアプリと同じコードを書けば同じように動くはずです。
ということで、既存の .NET Framework ベースで作ったアプリから BindingNavigator 他のコードを移植してきました。
まず、BindingNavigator が使う .png 画像を既存のアプリから取得して Form1.resx にコピーします。
.NET 5.0 以前では画像は base64 形式に変換されて .resx ファイル内に埋め込まれたのですが、.NET 6.0 ではプロジェクトルート直下に Resources という名前のフォルダが作られ、その中に画像ファイルが格納される点が異なります。
その後、既存の .NET Framework アプリからコードを移植します。以下がその例で、この記事の一番上の画像がその実行結果です。
namespace WinFormsApp2
{
public partial class Form1 : Form
{
private NorthwindDataSet northwindDataSet;
private System.Windows.Forms.BindingSource productsBindingSource;
private NorthwindDataSetTableAdapters.ProductsTableAdapter productsTableAdapter;
private NorthwindDataSetTableAdapters.TableAdapterManager tableAdapterManager;
private System.Windows.Forms.BindingNavigator productsBindingNavigator;
private System.Windows.Forms.ToolStripButton bindingNavigatorAddNewItem;
private System.Windows.Forms.ToolStripLabel bindingNavigatorCountItem;
private System.Windows.Forms.ToolStripButton bindingNavigatorDeleteItem;
private System.Windows.Forms.ToolStripButton bindingNavigatorMoveFirstItem;
private System.Windows.Forms.ToolStripButton bindingNavigatorMovePreviousItem;
private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator;
private System.Windows.Forms.ToolStripTextBox bindingNavigatorPositionItem;
private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator1;
private System.Windows.Forms.ToolStripButton bindingNavigatorMoveNextItem;
private System.Windows.Forms.ToolStripButton bindingNavigatorMoveLastItem;
private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator2;
private System.Windows.Forms.ToolStripButton productsBindingNavigatorSaveItem;
private System.Windows.Forms.DataGridView productsDataGridView;
public Form1()
{
InitializeComponent();
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources =
new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.northwindDataSet = new NorthwindDataSet();
this.productsBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.productsTableAdapter = new NorthwindDataSetTableAdapters.ProductsTableAdapter();
this.tableAdapterManager = new NorthwindDataSetTableAdapters.TableAdapterManager();
this.productsBindingNavigator = new System.Windows.Forms.BindingNavigator(this.components);
this.bindingNavigatorAddNewItem = new System.Windows.Forms.ToolStripButton();
this.bindingNavigatorCountItem = new System.Windows.Forms.ToolStripLabel();
this.bindingNavigatorDeleteItem = new System.Windows.Forms.ToolStripButton();
this.bindingNavigatorMoveFirstItem = new System.Windows.Forms.ToolStripButton();
this.bindingNavigatorMovePreviousItem = new System.Windows.Forms.ToolStripButton();
this.bindingNavigatorSeparator = new System.Windows.Forms.ToolStripSeparator();
this.bindingNavigatorPositionItem = new System.Windows.Forms.ToolStripTextBox();
this.bindingNavigatorSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.bindingNavigatorMoveNextItem = new System.Windows.Forms.ToolStripButton();
this.bindingNavigatorMoveLastItem = new System.Windows.Forms.ToolStripButton();
this.bindingNavigatorSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.productsBindingNavigatorSaveItem = new System.Windows.Forms.ToolStripButton();
this.productsDataGridView = new System.Windows.Forms.DataGridView();
((System.ComponentModel.ISupportInitialize)(this.northwindDataSet)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.productsBindingSource)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.productsBindingNavigator)).BeginInit();
this.productsBindingNavigator.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.productsDataGridView)).BeginInit();
this.SuspendLayout();
//
// northwindDataSet
//
this.northwindDataSet.DataSetName = "NorthwindDataSet";
this.northwindDataSet.SchemaSerializationMode =
System.Data.SchemaSerializationMode.IncludeSchema;
//
// productsBindingSource
//
this.productsBindingSource.DataMember = "Products";
this.productsBindingSource.DataSource = this.northwindDataSet;
//
// productsTableAdapter
//
this.productsTableAdapter.ClearBeforeFill = true;
//
// tableAdapterManager
//
this.tableAdapterManager.BackupDataSetBeforeUpdate = false;
this.tableAdapterManager.ProductsTableAdapter = this.productsTableAdapter;
this.tableAdapterManager.UpdateOrder =
NorthwindDataSetTableAdapters.TableAdapterManager.UpdateOrderOption.InsertUpdateDelete;
//
// productsBindingNavigator
//
this.productsBindingNavigator.AddNewItem = this.bindingNavigatorAddNewItem;
this.productsBindingNavigator.BindingSource = this.productsBindingSource;
this.productsBindingNavigator.CountItem = this.bindingNavigatorCountItem;
this.productsBindingNavigator.DeleteItem = this.bindingNavigatorDeleteItem;
this.productsBindingNavigator.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.bindingNavigatorMoveFirstItem,
this.bindingNavigatorMovePreviousItem,
this.bindingNavigatorSeparator,
this.bindingNavigatorPositionItem,
this.bindingNavigatorCountItem,
this.bindingNavigatorSeparator1,
this.bindingNavigatorMoveNextItem,
this.bindingNavigatorMoveLastItem,
this.bindingNavigatorSeparator2,
this.bindingNavigatorAddNewItem,
this.bindingNavigatorDeleteItem,
this.productsBindingNavigatorSaveItem});
this.productsBindingNavigator.Location = new System.Drawing.Point(0, 0);
this.productsBindingNavigator.MoveFirstItem = this.bindingNavigatorMoveFirstItem;
this.productsBindingNavigator.MoveLastItem = this.bindingNavigatorMoveLastItem;
this.productsBindingNavigator.MoveNextItem = this.bindingNavigatorMoveNextItem;
this.productsBindingNavigator.MovePreviousItem = this.bindingNavigatorMovePreviousItem;
this.productsBindingNavigator.Name = "productsBindingNavigator";
this.productsBindingNavigator.PositionItem = this.bindingNavigatorPositionItem;
this.productsBindingNavigator.Size = new System.Drawing.Size(1136, 25);
this.productsBindingNavigator.TabIndex = 0;
this.productsBindingNavigator.Text = "bindingNavigator1";
//
// bindingNavigatorAddNewItem
//
#nullable disable
this.bindingNavigatorAddNewItem.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.bindingNavigatorAddNewItem.Image =
((System.Drawing.Image)(resources.GetObject("bindingNavigatorAddNewItem.Image")));
this.bindingNavigatorAddNewItem.Name = "bindingNavigatorAddNewItem";
this.bindingNavigatorAddNewItem.RightToLeftAutoMirrorImage = true;
this.bindingNavigatorAddNewItem.Size = new System.Drawing.Size(23, 22);
this.bindingNavigatorAddNewItem.Text = "新規追加";
//
// bindingNavigatorCountItem
//
this.bindingNavigatorCountItem.Name = "bindingNavigatorCountItem";
this.bindingNavigatorCountItem.Size = new System.Drawing.Size(29, 22);
this.bindingNavigatorCountItem.Text = "/ {0}";
this.bindingNavigatorCountItem.ToolTipText = "項目の総数";
//
// bindingNavigatorDeleteItem
//
this.bindingNavigatorDeleteItem.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.bindingNavigatorDeleteItem.Image =
((System.Drawing.Image)(resources.GetObject("bindingNavigatorDeleteItem.Image")));
this.bindingNavigatorDeleteItem.Name = "bindingNavigatorDeleteItem";
this.bindingNavigatorDeleteItem.RightToLeftAutoMirrorImage = true;
this.bindingNavigatorDeleteItem.Size = new System.Drawing.Size(23, 22);
this.bindingNavigatorDeleteItem.Text = "削除";
//
// bindingNavigatorMoveFirstItem
//
this.bindingNavigatorMoveFirstItem.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.bindingNavigatorMoveFirstItem.Image =
((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveFirstItem.Image")));
this.bindingNavigatorMoveFirstItem.Name = "bindingNavigatorMoveFirstItem";
this.bindingNavigatorMoveFirstItem.RightToLeftAutoMirrorImage = true;
this.bindingNavigatorMoveFirstItem.Size = new System.Drawing.Size(23, 22);
this.bindingNavigatorMoveFirstItem.Text = "最初に移動";
//
// bindingNavigatorMovePreviousItem
//
this.bindingNavigatorMovePreviousItem.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.bindingNavigatorMovePreviousItem.Image =
((System.Drawing.Image)(resources.GetObject("bindingNavigatorMovePreviousItem.Image")));
this.bindingNavigatorMovePreviousItem.Name = "bindingNavigatorMovePreviousItem";
this.bindingNavigatorMovePreviousItem.RightToLeftAutoMirrorImage = true;
this.bindingNavigatorMovePreviousItem.Size = new System.Drawing.Size(23, 22);
this.bindingNavigatorMovePreviousItem.Text = "前に戻る";
//
// bindingNavigatorSeparator
//
this.bindingNavigatorSeparator.Name = "bindingNavigatorSeparator";
this.bindingNavigatorSeparator.Size = new System.Drawing.Size(6, 25);
//
// bindingNavigatorPositionItem
//
this.bindingNavigatorPositionItem.AccessibleName = "位置";
this.bindingNavigatorPositionItem.AutoSize = false;
this.bindingNavigatorPositionItem.Font = new System.Drawing.Font("Yu Gothic UI", 9F);
this.bindingNavigatorPositionItem.Name = "bindingNavigatorPositionItem";
this.bindingNavigatorPositionItem.Size = new System.Drawing.Size(50, 23);
this.bindingNavigatorPositionItem.Text = "0";
this.bindingNavigatorPositionItem.ToolTipText = "現在の場所";
//
// bindingNavigatorSeparator1
//
this.bindingNavigatorSeparator1.Name = "bindingNavigatorSeparator1";
this.bindingNavigatorSeparator1.Size = new System.Drawing.Size(6, 25);
//
// bindingNavigatorMoveNextItem
//
this.bindingNavigatorMoveNextItem.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.bindingNavigatorMoveNextItem.Image =
((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveNextItem.Image")));
this.bindingNavigatorMoveNextItem.Name = "bindingNavigatorMoveNextItem";
this.bindingNavigatorMoveNextItem.RightToLeftAutoMirrorImage = true;
this.bindingNavigatorMoveNextItem.Size = new System.Drawing.Size(23, 22);
this.bindingNavigatorMoveNextItem.Text = "次に移動";
//
// bindingNavigatorMoveLastItem
//
this.bindingNavigatorMoveLastItem.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.bindingNavigatorMoveLastItem.Image =
((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveLastItem.Image")));
this.bindingNavigatorMoveLastItem.Name = "bindingNavigatorMoveLastItem";
this.bindingNavigatorMoveLastItem.RightToLeftAutoMirrorImage = true;
this.bindingNavigatorMoveLastItem.Size = new System.Drawing.Size(23, 22);
this.bindingNavigatorMoveLastItem.Text = "最後に移動";
//
// bindingNavigatorSeparator2
//
this.bindingNavigatorSeparator2.Name = "bindingNavigatorSeparator2";
this.bindingNavigatorSeparator2.Size = new System.Drawing.Size(6, 25);
//
// productsBindingNavigatorSaveItem
//
this.productsBindingNavigatorSaveItem.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.productsBindingNavigatorSaveItem.Image =
((System.Drawing.Image)(resources.GetObject("productsBindingNavigatorSaveItem.Image")));
this.productsBindingNavigatorSaveItem.Name = "productsBindingNavigatorSaveItem";
this.productsBindingNavigatorSaveItem.Size = new System.Drawing.Size(23, 22);
this.productsBindingNavigatorSaveItem.Text = "データの保存";
this.productsBindingNavigatorSaveItem.Click += ProductsBindingNavigatorSaveItem_Click;
#nullable enable
//
// productsDataGridView
//
//this.productsDataGridView.AutoGenerateColumns = false;
this.productsDataGridView.ColumnHeadersHeightSizeMode =
System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.productsDataGridView.DataSource = this.productsBindingSource;
this.productsDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
this.productsDataGridView.Location = new System.Drawing.Point(0, 25);
this.productsDataGridView.Name = "productsDataGridView";
this.productsDataGridView.RowTemplate.Height = 21;
//this.productsDataGridView.Size = new System.Drawing.Size(1136, 643);
this.productsDataGridView.TabIndex = 1;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.Controls.Add(this.productsDataGridView);
this.Controls.Add(this.productsBindingNavigator);
this.Name = "Form1";
this.Text = "Northwind";
this.Load += Form1_Load;
((System.ComponentModel.ISupportInitialize)(this.northwindDataSet)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.productsBindingSource)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.productsBindingNavigator)).EndInit();
this.productsBindingNavigator.ResumeLayout(false);
this.productsBindingNavigator.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.productsDataGridView)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
private void Form1_Load(object? sender, EventArgs e)
{
this.productsTableAdapter.Fill(this.northwindDataSet.Products);
}
private void ProductsBindingNavigatorSaveItem_Click(object? sender, EventArgs e)
{
this.Validate();
this.productsBindingSource.EndEdit();
this.tableAdapterManager.UpdateAll(this.northwindDataSet);
}
}
}
既存の .NET Framework アプリのコードと違うところは、DataGridView の AutoGenerateColumns プロパティをデフォルトの true として各列を DataTable から自動生成させていることろです。ひょっとしたらそれによる予期せぬ副作用があるかもしれません。
もう一つ、接続文字列が TableAdapter のコードの中にハードコーディングされているところが .NET Framework アプリと違います。.NET Framework アプリの場合、接続文字列は設定ファイルに格納され、TableAdapter はそれから読んでくるのですが、.NET 6.0 版はそこのところにも手を加える必要があります。
こんな手間をかけるなら、最初から .NET Framework 版で作ってコードはすべて Visual Studio のデザイナで自動生成させればいいと言われそうですが、確かにその通りだと思いました。