データベースを参照してみます。

データソースをフォームに貼り付けるという方法は使いません。
VBA応用で作成したサンプルを利用します。   このページで使用するデータベースはMDBですが、VBA応用の「データベース関連」で作成したものを使用します。
使用するデータベース「SampleCorp1.mdb」は上記のリンク先のページの方でダウンロードしたZIP圧縮ファイルに格納されているので、 これをマイドキュメントの下に「SampleMDB」という名前のサブフォルダを作成してその中に保存させて下さい。

 C:\Users\[ユーザーID]\Documents\SampleMDB\SampleCorp1.mdb
通常は、このようなパスに配置されていることを確認してからご利用下さい。



ExcelVBAの方では「Excelワークブックと同じフォルダ」という利用説明にしていましたが、 こちらで作成する実行ファイル(*.exe)はセットアップを作成してインストールすると「C:\Program Files」配下となってしまいます。 実行ファイル(*.exe)と同じフォルダにMDBを配置するのではセキュリティ上の問題で適しません。
そこでこのような配置といたしました。マイドキュメント配下なので、同一PCを他者が利用する場合でも影響がありません。



本ページのサンプルは、データベースアクセスの説明だけでなく、そのデータを展開する先のDataGridViewコントロールについても見やすい一覧を提供する上で 共通関数を使いながら簡単に実現できる方法も紹介しているので、MDBに関心が無い方でもぜひ、ご覧下さい。

なお、現在のサンプルはDPI制御を行なっていませんので、96DPI以外で表示させると、このページの画面サンプルとは異なる表示になる場合があります。

このページからはVisualBasic.NETの開発についての話になります。

Visual Studio 2022

ExcelVBAでもたくさんのプログラムを配布されているケースが見られて、中にはメニューを作って体系立てて作られているケースもあるようですが、 「配布の問題」に遭遇することが避けられないことは説明しているとおりです。

この「配布の問題」の途中のページで紹介している「ClickOnce」は、この後でも詳しく紹介しますが、 「一旦、配布した後のバージョン管理」を自動的に行なえるという、ExcelVBAで行なう方法に比べたら画期的な手段と言えるかも知れません。
この「ClickOnce」を扱うのがこのVisualBasic.NETなのです。

ExcelVBAからVisualBasic.NETという、同じVisualBasic系の言語でもやや記述様式が違うなので、 最初は多少戸惑うことがありますが、最初のバージョンのリリースから十数年経過していて今では情報量も充分にあるので開発上で困ることも少なくなってきています。 Visual Studioも改善されてきており、フォームデザイナ上でのコントロールのドラッグによる整列機能とか、コード入力時の自動補完などで 快適に開発作業が進められるようになっています。

このサイトではVisualBasic.NETを初心者向けに一から説明することはできませんが、 「おいしい使い方に関する事例」と「壁に当たって解決できた事例」等を説明しています。 もちろん、VisualBasic.NET側からのExcelの活用についても説明しています。

手始めにこのページはデータベースから登録一覧をグリッドコントロールに表示させるものです。
各ページのサンプルはサンプル画像をクリックすることによって、開発サンプルソリューションをダウンロードできるようになっていますので、 VisualBasic.NETの環境があれば実際にビルドするまでの過程や動作等を確認できます。(無償版でも可)
なお、「ClickOnce」については無償版には搭載されていないようです。

ダウンロードしたサンプルを実行すると、このような表示になります。

ACCDBの説明はこのページの最後に用意してあります。

配属一覧のサンプル
(画像をクリックすると、このページのサンプルがダウンロードできます)

このサンプルでは、起動したら即座にこの一覧が表示されます。
この「一覧」の画面はExcelではありません。VisualBasic.NETのフォーム画面全面に貼り付けたDataGridViewコントロールに データベースから一覧を読み込んで表示させた状態です。

DataGridViewコントロールを全面に貼ったフォームとなっています。

各列の見出しをクリックすると、その列で並び替えが行なわれます。このような機能になっています。
列見出し 並べ替え機能
社員№ DataGridViewの標準のままで、単に文字列ソートとなります。
桁数が揃った数字によるコードなので問題ありません。
氏名 DataGridViewのプログラムソート機能を利用して「カナ氏名」でソートされます。
部署名 DataGridViewのプログラムソート機能を利用して「部署コード」でソートされます。
役職名 DataGridViewのプログラムソート機能を利用して「役職コード」でソートされます。
入社日、退職日 DataGridViewの標準のままで、単に文字列ソートとなります。
月日に前ゼロを付けて桁数を揃えているので問題ありませんが、こうしないと1月の次に11月が並んでしまいます。

また、「氏名」のどれかの行の漢字氏名のところにマウスカーソルを合わせるとToolTipでカナ氏名が表示されます。



さらに、表示ウィンドウの位置・大きさ、表示されるグリッドの列見出しの幅や列配置は変更が可能で、変更すると最終状態を保持して、次回の起動時に再現されるようになっています。



このサンプルではデータベースからの読み込み動作は起動時の1回だけなのですが、特殊な並べ替え動作、機能の設定保持・再現などを含めても メインソースは300行にも満たないコードで済むように、数年来の経験から共通モジュールや共通クラスを作成してきたので、このページで紹介します。

一覧表示フォームの動作説明です。

※上から下に向かって時系列で内部動作を含めて説明します。

配属一覧のサンプル

表の右側が大きく空いているのは、次ページから「一覧表示フォーム」から呼び出される「詳細登録フォーム」が発生するための余地です。
次ページからもこのフロー表を記載していますが、細かいイベントごとではなく要件的な主要な動作の説明となります。




プログラムの起動時には「一覧表示フォーム」がスタートアップに設定されていて起動されます。
ここで上表の「フォーム起動初期動作」が動作しますが、フォーム起動時の主要なイベントを説明しておきます。 どこに何を記述するかは「どうすべきか」ではなく「私はこうしています」という説明です。 これらのイベントは通常は以下の説明の順位で発生します。
イベント内容説明
 New このイベントはフォーム起因ではなく「クラスの初期化」で他のどのイベントより先に動作します。
フォームデザイナで処置できなかった固定的なフォームの設定やフォーム内で固定的に置かれるクラスの初期化などを記述しており、 ここで記述するものは、実行時エラーを監視する必要がないものに限っています。
その理由は、実行時エラーが発生した場合にフォームを起点としたメッセージボックスが表示できないためです。
一般的なメッセージボックスはスクリーン中央に表示されるので、このイベントで処置しても良いのですが、 当プログラムではフォームを小さくしてスクリーンの隅に配置するような場合に、メッセージボックスはそのフォームの中央で表示するように対応しているためです。



なお、このイベントはフォーム自体をメモリ上に確保して、確保したフォームを起動する場合は次回以降は動作しません。(VBAHideを使うような場合と同様)
この事を除けば、このNewイベントは使わずに次のForm_Loadイベントに委ねても良いと思います。
 Form_Load このイベントはフォームのイベントとしては先頭で発生するものですが、発生時点ではフォームデザイナでの設定やNewイベントは完了しています。
フォームのモジュールレベルでNewキーワードを付けて記述したオブジェクト変数の初期化も完了しています。
ここではフォームの固定的な初期設定でデザイナやNewイベントで処置できないものを記述しています。
このプログラムの例では設定保持したフォームの位置・サイズ等の復旧、共通記述で用意しているフォーム内のイベント生成(AddHandler)を記述しています。



なお、このイベントはフォーム自体をメモリ上に確保して、確保したフォームを起動する場合でも毎回発生します。 また、このイベントの段階では実際にはフォームは表示されていないので、Newイベントと同様にフォームを起点とするメッセージボックスは表示できません。
 Form_Activated 今回のプログラムではこのイベントは記述していません。
このイベントはこのフォームがアクティブになった時点で発生するもので、その意味で「起動時イベント」に含めていますが、 それ以外に他のフォームとアクティブ関係が切り替わった時や、他のプログラムとの選択関係が切り替わった時にも発生します。
ですから、そういったアクティブ関係の切替時に何か処置が必要な場合のみ記述します。
 Form_Shown このイベントはフォームが実際にウィンドウとして表示された段階で起動します。
データベースI/Oなど実行時エラーの監視を行なう場合では、フォームを起点とするメッセージボックスも表示できるので、 起動時にデータベースから読み出す記述はこのイベントで記述させています。
但し、データベースI/Oや、そのデータをフォーム上の各コントロールに表示させる動作がウィンドウ上で「見えてしまう」ので、 データ量が多い場合は一旦フォーム上の描画動作を停止して行なう等の処置が必要になります。
このプログラムではDatGridViewの全面貼りなので、FP_ListUpdateプロシージャ内でDatGridViewの表示を一旦停止(Visible=False)させてから 各セルにデータを貼り付けて、最後に表示再開(Visible=True)させる方法を採っています。
フォームイベントは、この説明及び下記のソースコードでは例えば「Form_Load」というイベント名になっていますが、 VisualBasicのエディタ上でプルダウンからイベントを新規作成する場合、イベント名は「[フォーム名]_Load」がデフォルトです。 ですが、フォームごとにこれらのフォームイベントのイベント名をユニークにする必要はなく、検索する場合にも同じ名前の方が都合が良いので「Form_Load」に変更して統一させています。




次に「フォーム終了動作」に関する各主要イベントを動作順に説明します。
イベント内容説明
 Form_FormClosing 今回のプログラムではこのイベントは記述していません。
このイベントは「閉じる」に関するアクションが起きた時点で発生します。 つまり、「閉じる([×])」ボタンがクリックされたとか、記述上の「Closeメソッド」が実行されたなどです。
このイベント内ではこの「閉じる」動作をキャンセルすることができ、次ページ以降の詳細登録フォームではこの「Form_FormClosing」イベントを 共通記述で動的に生成(AddHandler)させていて、登録動作が未完了だった時に「閉じて良いか」の確認を行なう用途で利用しています。
 Form_Deactivate 今回のプログラムではこのイベントは記述していません。
このイベントは起動イベント側の「Form_Activated」イベントの逆で、このフォームがアクティブでなくなった時点で発生します。 同様に終了動作だけでなく、フォームの切替えや他のプログラムとの選択関係が切り替わった時にも発生します。
 Form_FormClosed このイベントはフォームが閉じられた段階で発生します。この段階からのキャンセルはできません。
今回のプログラムでは起動イベント側の「Form_Load」イベントの逆で、フォームの位置・サイズ等の設定退避、共通記述で用意しているフォーム内のイベント破棄(RemoveHandler)を記述しています。
 Finalize 今回のプログラムではこのイベントは記述していません。
このイベントはフォーム起因ではなく「クラスの終了」で他のどのイベントより後に動作します。
私はフォームではこのイベントを記述したことはありません。




それでは、ソースコードの説明に移ります。

ソースコードです。
一覧表示フォームのソースコードです。

'***************************************************************************************************
'   配属一覧サンプル①                                       frmGetMdbDataTest01(Form)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
' 変更日付 Rev     変更履歴内容-------------------------------------------------------------------->
' 17/01/15(1.0.0.0)新規作成
' 17/01/18(1.0.0.0)GetDataTableOleは処理成否を返す仕様に変更する対応
' 17/03/11(1.0.0.0)ACCDBでの変更箇所をコメントで追加する対応
' 18/05/07(1.0.1.0)DataGridViewのスクロールバー表示不正の対応、初期処理をNewに移動させる対応
'***************************************************************************************************
Imports System.IO
Public Class frmGetMdbDataTest01
    '===============================================================================================
    Private Const g_cnsTitle As String = "配属一覧サンプル①"
    ' MDBファイル情報
    Private Const g_cnsMdbFileame As String = "SampleCorp1.mdb"
    'Private Const g_cnsMdbFileame As String = "SampleCorp1.accdb" ' ←ACCDBの場合
    Private Const g_cnsMdbSubFolder As String = "SampleMDB"
    Private Const g_cnsDGVColumnMAX As Integer = 5          ' DataGridView最大カラム(表示)
    '-----------------------------------------------------------------------------------------------
    ' 背景色
    Private ReadOnly g_colorRetire As Color = Color.FromArgb(220, 220, 220) ' 退職色(薄灰)
    '-----------------------------------------------------------------------------------------------
    ' 共通クラス
    Private g_objAboutMDB As clsAboutMDB1                   ' データベースI/Oクラス(MDB用)
    Private g_objAboutWindow As clsAboutWindow1              ' ウィンドウ制御関連クラス
    Private g_objAboutDGV As clsAboutDataGridView1          ' DataGridView制御関連クラス
    '-----------------------------------------------------------------------------------------------
    ' 一覧表示用抽出SQL文共通部
    Private g_strSQL_Base As String = ""                    ' 抽出SQL文共通部
    '-----------------------------------------------------------------------------------------------
    ' デフォルトのカラム幅
    Private g_tblDefaultColumnWidth() As Integer            ' カラム幅テーブル

    '***********************************************************************************************
    ' ■■■ 初期化 ■■■
    '***********************************************************************************************
    '* 処理名 :New
    '* 機能  :初期化
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(なし)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2018年05月07日
    '* 作成者 :井上 治
    '* 更新日 :2018年05月07日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Public Sub New()
        '-------------------------------------------------------------------------------------------
        ' ※Windowsフォームデザイナ初期化(必須)
        Call InitializeComponent()
        ' フォームデザイナモード時は以下をスキップする
        If Me.DesignMode Then Exit Sub
        '-------------------------------------------------------------------------------------------
        ' データベースI/Oクラスの初期化
        g_objAboutMDB = New clsAboutMDB1(Me, g_cnsMdbFileame, g_cnsMdbSubFolder)
        ' ウィンドウ制御関連クラスの初期化
        g_objAboutWindow = New clsAboutWindow1(Me)
        ' DataGridView制御関連クラスの初期化
        g_objAboutDGV = New clsAboutDataGridView1
        '-------------------------------------------------------------------------------------------
        ' DataGridView(登録一覧)のカラム設定
        Dim tblColInfo() As g_typDGVColInfo = Nothing
        With g_objAboutDGV
            Call .SetColumnInfo(tblColInfo, "社員CD", 50, g_cnsDG_MC)   ' (00)社員コード
            Call .SetColumnInfo(tblColInfo, "氏名", 100, , _
                                g_cnsSM_Programmatic)                   ' (01)氏名
            Call .SetColumnInfo(tblColInfo, "部署名", 150, , _
                                g_cnsSM_Programmatic)                   ' (02)部署名
            Call .SetColumnInfo(tblColInfo, "役職名", 100, , _
                                g_cnsSM_Programmatic)                   ' (03)役職名
            Call .SetColumnInfo(tblColInfo, "入社日", 90, g_cnsDG_MC)   ' (04)入社日
            Call .SetColumnInfo(tblColInfo, "退職日", 90, g_cnsDG_MC)   ' (05)退職日
            Call .SetColumnInfo(tblColInfo, "部署CD", 60, g_cnsDG_MC)   ' (06)部署コード(非表示)
            Call .SetColumnInfo(tblColInfo, "役職CD", 50, g_cnsDG_MC)   ' (07)役職コード(非表示)
            Call .SetColumnInfo(tblColInfo, "カナ氏名", 150)            ' (08)カナ氏名(非表示)
            ' 初期カラム幅設定を退避
            g_tblDefaultColumnWidth = .GetDefaultColumnWidth(tblColInfo)
            ' 列幅を設定退避値で置き換える
            Call .AdjustColumnWidth(tblColInfo, My.Settings.ICHIRAN_COL_Width)
            '---------------------------------------------------------------------------------------
            ' DataGridViewの初期設定(一般一覧用)
            Call .InitDataGridView1(DGV_ICHIRAN, tblColInfo, 1)
            '---------------------------------------------------------------------------------------
            ' 半角英数列のフォントをMSゴシック10Pに変更
            Dim objFont10 As Font = New Font(g_cnsStdFontName, g_cnsFontSize975) ' 英数項目用
            ' DataGridViewのその他調整
            With DGV_ICHIRAN
                ' プログラムSORT列指定
                .Columns(1).Tag = 8                 ' 氏名⇒カナ氏名
                .Columns(2).Tag = 6                 ' 部署名⇒部署コード
                .Columns(3).Tag = 7                 ' 役職名⇒役職コード
                ' コード、日付列は当幅フォントに変更
                .Columns(0).DefaultCellStyle.Font = objFont10
                .Columns(4).DefaultCellStyle.Font = objFont10
                .Columns(5).DefaultCellStyle.Font = objFont10
                .Columns(6).DefaultCellStyle.Font = objFont10
                .Columns(7).DefaultCellStyle.Font = objFont10
                ' 部署コード以降は非表示
                .Columns(6).Visible = False
                .Columns(7).Visible = False
                .Columns(8).Visible = False
            End With
            '---------------------------------------------------------------------------------------
            ' 列配置を設定退避値で置き換える
            Call .AdjustDGVColumnDisplayIndex(DGV_ICHIRAN, My.Settings.ICHIRAN_DisplayIndex)
        End With
        '-------------------------------------------------------------------------------------------
        ' 一覧表示用抽出SQL文共通部の編集(WHERE句の前まで)
        g_strSQL_Base = "SELECT H.[SCD]"                                ' (00)社員コード
        g_strSQL_Base &= ",S.[KANJI_SEI]+S.[KANJI_MEI]"                 ' (01)氏名(漢字)
        g_strSQL_Base &= ",B.[BUSYO_NM]"                                ' (02)部署名
        g_strSQL_Base &= ",Y.[YAKU_NM]"                                 ' (03)役職名
        g_strSQL_Base &= ",S.[NYUSYA_YMD]"                              ' (04)入社日
        g_strSQL_Base &= ",S.[TAISYOKU_YMD]"                            ' (05)退職日
        g_strSQL_Base &= ",H.[BUSYO_CD]"                                ' (06)部署コード
        g_strSQL_Base &= ",H.[YAKU_CD]"                                 ' (07)役職コード
        g_strSQL_Base &= ",S.[KANA_SEI]+S.[KANA_MEI]"                   ' (08)氏名(カナ)
        g_strSQL_Base &= " FROM ((([MST_HAIZOKU] AS H"
        g_strSQL_Base &= " INNER JOIN [MST_SYAIN] AS S ON H.[SCD]=S.[SCD])"
        g_strSQL_Base &= " LEFT OUTER JOIN [MST_BUSYO] AS B ON H.[BUSYO_CD]=B.[BUSYO_CD])"
        g_strSQL_Base &= " LEFT OUTER JOIN [MST_YAKU] AS Y ON H.[YAKU_CD]=Y.[YAKU_CD])"
    End Sub

    '***********************************************************************************************
    ' ■■■ フォームイベント ■■■
    '***********************************************************************************************
    '* 処理名 :Form_FormClosed
    '* 機能  :フォーム消失(FormClosed)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(デフォルト)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Sub Form_FormClosed(ByVal sender As Object, _
                                ByVal e As FormClosedEventArgs) Handles Me.FormClosed
        '-------------------------------------------------------------------------------------------
        ' DataGridViewの列幅、列配置等を退避
        With My.Settings
            .ICHIRAN_COL_Width = g_objAboutDGV.GetDGVColumnWidth(DGV_ICHIRAN)
            .ICHIRAN_DisplayIndex = g_objAboutDGV.GetDGVColumnDisplayIndex(DGV_ICHIRAN)
            Call g_objAboutWindow.FormSaveSettings1(.ICHIRAN_FormLocation, _
                                                    .ICHIRAN_FormSize, _
                                                    .ICHIRAN_WindowState)
            ' 設定を保存
            .Save()
        End With
        '-------------------------------------------------------------------------------------------
        ' DataGridViewのColumnHeaderMouseClickイベントハンドラ解放(共通記述)
        RemoveHandler DGV_ICHIRAN.ColumnHeaderMouseClick, _
            AddressOf g_objAboutDGV.DGV_ColumnHeaderMouseClick
    End Sub

    '***********************************************************************************************
    '* 処理名 :Form_Load
    '* 機能  :フォーム初期化(Load)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(デフォルト)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2018年05月07日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Sub Form_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        '-------------------------------------------------------------------------------------------
        ' フォーム位置・サイズ制御
        With My.Settings
            Call g_objAboutWindow.FormAdjustLocationSize1(.ICHIRAN_FormLocation, _
                                                          .ICHIRAN_FormSize, Me.Size)
            Me.WindowState = .ICHIRAN_WindowState
        End With
        '-------------------------------------------------------------------------------------------
        ' DataGridViewのColumnHeaderMouseClickイベントハンドラ追加(共通記述)
        AddHandler DGV_ICHIRAN.ColumnHeaderMouseClick, _
            AddressOf g_objAboutDGV.DGV_ColumnHeaderMouseClick
    End Sub

    '***********************************************************************************************
    '* 処理名 :Form_Shown
    '* 機能  :フォーム初期表示(Shown)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(デフォルト)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Sub Form_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
        '-------------------------------------------------------------------------------------------
        ' 一覧再更新表示
        If Not FP_ListUpdate() Then
            ' 失敗時は終了
            Me.Close()
            Exit Sub
        End If
    End Sub

    '***********************************************************************************************
    '   ■■■ サブ処理 ■■■
    '***********************************************************************************************
    '* 処理名 :FP_ListUpdate
    '* 機能  :一覧再更新表示
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :処理成否(Boolean)
    '* 引数  :(なし)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2018年05月07日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Function FP_ListUpdate() As Boolean
        '-------------------------------------------------------------------------------------------
        ' 配属情報の抽出
        Dim strToday As String = "#" & Today.ToString("yyyy-MM-dd") & "#"   ' 本日日付(編集)
        Dim dbTbl As DataTable = Nothing                            ' DataTable    
        Dim strSQL As String = g_strSQL_Base                        ' SQL文
        strSQL &= " WHERE S.[NYUSYA_YMD]<=" & strToday
        strSQL &= " AND (S.[TAISYOKU_YMD] IS NULL OR S.[TAISYOKU_YMD]>" & strToday & ")"
        strSQL &= " ORDER BY H.[BUSYO_CD],H.[YAKU_CD],H.[SCD];"
        ' DataTable取得(例外は終了)
        If Not g_objAboutMDB.GetDataTableOle(dbTbl, strSQL, "MST_HAIZOKU") Then Return False
        '-------------------------------------------------------------------------------------------
        Dim intIx As Integer = 0                                    ' テーブルINDEX
        With DGV_ICHIRAN
            ' スクロールバーを一旦、非表示にする
            .ScrollBars = ScrollBars.None
            ' DataGridViewを一旦、非表示にする
            .Visible = False
            ' SORTマークを部署の昇順に設定
            .Columns(2).HeaderCell.SortGlyphDirection = g_cnsSO_Ascending
            ' 他のプログラムソート列のSortマーク解除
            .Columns(1).HeaderCell.SortGlyphDirection = g_cnsSO_None
            .Columns(3).HeaderCell.SortGlyphDirection = g_cnsSO_None
            ' 前回の一覧をクリア
            If .Rows.Count <> 0 Then .Rows.Clear()
            ' DataTableの全件を繰り返す
            Do While intIx < dbTbl.Rows.Count
                ' 行を追加
                .Rows.Add()
                ' 行内各カラムの編集
                With .Rows(intIx)
                    .Cells(0).Value = dbTbl.Rows(intIx)(0)          ' (00)社員コード
                    .Cells(1).Value = dbTbl.Rows(intIx)(1)          ' (01)氏名(漢字)
                    ' 氏名(漢字)のToolTipに氏名(カナ)を設定
                    .Cells(1).ToolTipText = dbTbl.Rows(intIx)(8)
                    .Cells(2).Value = dbTbl.Rows(intIx)(2)          ' (02)部署名
                    .Cells(3).Value = dbTbl.Rows(intIx)(3)          ' (03)役職名
                    .Cells(4).Value = FP_EditDate(dbTbl.Rows(intIx)(4)) ' (04)入社日
                    .Cells(5).Value = FP_EditDate(dbTbl.Rows(intIx)(5)) ' (05)退職日
                    .Cells(6).Value = dbTbl.Rows(intIx)(6)          ' (06)部署コード
                    .Cells(7).Value = dbTbl.Rows(intIx)(7)          ' (07)役職コード
                    .Cells(8).Value = dbTbl.Rows(intIx)(8)          ' (08)氏名(カナ)
                    ' 退職者か(退職日有り)
                    If .Cells(5).Value.ToString.Length <> 0 Then
                        ' 退職者はグレーで塗りつぶし
                        For intCol As Integer = 0 To g_cnsDGVColumnMAX
                            .Cells(intCol).Style.BackColor = g_colorRetire
                        Next intCol
                    End If
                End With
                ' 次へ
                intIx += 1
            Loop
            ' DataGridViewを再表示にする
            .Visible = True
            ' スクロールバーを再表示する
            .ScrollBars = ScrollBars.Both
        End With
        ' データテーブルをクリア
        dbTbl.Clear()
        dbTbl.Reset()
        Return True
    End Function

    '----------------------------------------<< End of Source >>------------------------------------
End Class



これが、上記画像のフォームにあるソースコードです。これで全ての動作を担っています。
ご覧のように、初期化、フォームイベント、サブ処理(1)しかなく、コントロールイベントの記述がありません。
このフォームにはメニューもボタンもないので当然ですが、本来であれば氏名、部署名、役職名の列がプログラムソートとなっているため、この対応記述があるはずです。 また、DataGridViewコントロールの各列の設定なども本来は複雑なのですが、これらを処理する共通プロシージャ、共通クラスを定めて利用しているため、 メインソースはこれだけで済むと言うところまで持ってこられたわけです。



実際にデータベースを参照してDataGridViewに展開させている「FP_ShowHaizokuList」プロシージャはある程度の行数になることは予想されると思いますが、 それ以外を「フォーム初期化(Form_Load)」イベントでほとんど済ませてしまうことが可能になりました。
多数の機能メニューを提供するとなった場合は同等処理について「共通化」させるのはあたり前のことですが、 画面上のユーザーインタフェースについてはこの「共通化」が難しいものとされているようなので、関心がある方は参考にしてみて下さい。
「共通化」させるということは、「共通化」させた部分については個々のプロジェクトからは「テスト済み」として扱えるわけですから、開発・テストの工数削減にも寄与できるもののはずです。

ここからは、このサンプルで使用している「共通モジュール」「共通クラス」です。
メインソースがこれだけの行数でありながら、これだけの機能と表現を実現しているのは、ここからの「共通モジュール」「共通クラス」によるところが非常に大きいと言えます。 この「共通モジュール」「共通クラス」は以下の考え方で区別させています。

種別 名称 内容
モジュール 共通モジュール
(modCommonModule1)
以下を収容しています。
・プロジェクト内で共通利用する定数
・プロジェクト内で一意となる定数的な変数
・プロジェクト内で頻繁に利用される一過性の共通プロシージャ(関数)
定数についてはブロック単位で作成しているので使用されていないものも含まれています。
クラス データベースI/Oクラス
(clsAboutMDB1)
MDBI/Oに特化した共通プロシージャ(関数)を配置したクラスで、特化した中で利用される定数があるのでモジュール「modAboutMDB」を併せ持っています。
クラスの初期時にメッセージ表示のための「親フォーム」と、MDBへの接続文字列編集のための要素を引数で受け取るようにしてあります。
ウィンドウ制御クラス
(clsAboutWindow1)
本処理ではウィンドウの位置やサイズを設定に退避させて、次回起動時に再現するようになっています。
ですが、スクリーンの状況が変わっていたりして退避させてあった位置が存在しないことがあるので、 存在しないなら主画面の中央に戻すなどの機能を持たせた共通プロシージャ(関数)を配置しています。
クラスの初期時に「親フォーム」を引数で受け取るようにしてあります。
DataGridView制御クラス
(clsAboutDataGridView1)
DataGridViewは多様な機能があるグリッド表示コントロールで、機能を盛り込めば盛り込むほどにソースコードの「量」や「テスト項目数」にも影響します。
このクラスでは「一般的な一覧表示」にターゲットを絞って必要な機能を網羅させてあるつもりです。
カラムごとの設定を事前にテーブル化させたり、そのテーブルからDataGridViewのカラム設定を一括して行なう機能、 カラムの幅や配置順を設定退避させたり、次回起動時にその設定を再現させる機能に対応するプロシージャ(関数)も含みます。
また、プログラムソートに関する処理も本クラス内で行なえるようになっています。
本クラスは「共通クラス」を考えた場合、フォーム上のDataGridView1つとは限らないので、初期化時にDataGridViewを受け取るようにはしていません。

サンプルなのでこの「共通モジュール」「共通クラス」はプロジェクト内に配置されており、このプロジェクトで利用されるプロシージャに限って配置していますが、個別プロジェクトの要件での影響を受けないから「共通モジュール」「共通クラス」なのであって、 本来は、このプロジェクトより外側に配置されて本プロジェクトからはリンク参照で用いるべきものです。
ここではこのプロジェクトで利用されるプロシージャ(関数)のみに絞ってコード提示します。



以下でそれぞれのモジュールを紹介します。

まずは「共通モジュール(modCommonModule1)」です。


'***************************************************************************************************
'   サンプル用共通モジュール①                                modCommonModule1(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
' 変更日付 Rev     変更履歴内容-------------------------------------------------------------------->
' 17/01/15(1.0.0.0)新規作成
'***************************************************************************************************
Module modCommonModule1
    '===============================================================================================
    ' [共通定数]
    ' 日付チェック用定数
    Friend Const g_cnsDefaultDate As Date = #12/31/1899#
    Friend Const g_cnsMinimumDate As Date = #1/1/1900#
    Friend Const g_cnsMaximumDate As Date = #12/31/2049#
    ' 日付NULL時の代替値
    Friend Const g_cnsNullDate As Date = Nothing
    ' 標準フォント名
    Friend Const g_cnsStdFontName As String = "MS ゴシック"
    Friend Const g_cnsStdFontNameP As String = "MS Pゴシック"
    ' 標準フォントサイズ
    Friend Const g_cnsFontSize900 As Single = 9.0F
    Friend Const g_cnsFontSize975 As Single = 9.75F
    ' 共通利用固定文字
    Friend Const g_cnsFormatDate As String = "yyyy-MM-dd"
    Friend Const g_cnsFormatDate2 As String = "yyyy/MM/dd"

    '***********************************************************************************************
    '   ■■■ 一般共通サブ処理 ■■■
    '***********************************************************************************************
    '* 処理名 :FP_EditDate
    '* 機能  :日付表示用編集処理(一覧表示用:デフォルトはyyyy/MM/dd編集)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :編集後の日付(String)
    '* 引数  :Arg1 = 日付値(Object:String)
    '*      Arg2 = 日付フォーマット(String)     ※Option
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:Null日付等はブランクが返る
    '***********************************************************************************************
    Friend Function FP_EditDate(ByVal objInDate As Object, _
                                Optional ByVal strDateFormat As String = g_cnsFormatDate2) As String
        '-------------------------------------------------------------------------------------------
        Dim dteDate As Date                                         ' 日付WORK
        Dim strDate As String = String.Empty                        ' 文字列WORK
        ' NULL値でなければ値(文字列)を取り出す
        If ((Not DBNull.Value.Equals(objInDate)) AndAlso (objInDate IsNot Nothing)) Then
            strDate = objInDate.ToString.Trim
        End If
        ' 有効な日付か判定
        If ((strDate.Length <> 0) AndAlso _
            (Date.TryParse(strDate, dteDate)) AndAlso _
            (dteDate >= g_cnsMinimumDate) AndAlso _
            (dteDate < g_cnsMaximumDate)) Then
            ' 日付をフォーマット編集
            Return dteDate.ToString(strDateFormat)
        Else
            ' NGはブランクを返す
            Return String.Empty
        End If
    End Function

    '----------------------------------------<< End of Source >>------------------------------------
End Module

「共通モジュール」は全般共通定数のほか、フォーム等に依存しない「一過性の共通プロシージャ(関数)」を配置しています。
このプロジェクトではDataGridViewのセルへの日付フィールドの編集である「FP_EditDate」プロシージャのみを配置しています。 今回のMDBの日付フィールドはNULL許可フィールドであって、特に「退職日」については1件も有効日付が入っていません。
FP_EditDate」プロシージャの方ではNULLチェックの他に日付有効範囲のチェックを行なっていて、 今回のサンプルでは関係ないのですが、場合によっては「退職日」に日付最大値をデフォルトで入れておくことで有効範囲判定を簡略化したり、 Microsoft Dynamicsのアプリケーションのように「1753/01/01」は日付未登録と扱うというような例があるので、 これらにも対処した結果がこのような記述になっています。

次は「データベースI/Oクラス(clsAboutMDB1)」です。


'***************************************************************************************************
'   サンプル用データベースI/O関連定数(MDB用)                   modAboutMDB1(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'   ※この下に「データベースI/Oクラス(MDB用)(clsAboutMDB)」があります
'***************************************************************************************************
' 変更日付 Rev     変更履歴内容-------------------------------------------------------------------->
' 17/01/15(1.0.0.0)新規作成
' 17/01/18(1.0.0.0)GetDataTableOleは処理成否を返す仕様に変更
' 17/03/11(1.0.0.0)ACCDBでの変更箇所をコメントで追加する対応
'***************************************************************************************************
Imports System.IO
Module modAboutMDB1
    '===============================================================================================
    ' WorkTable名
    Friend Const g_cnsMdbTempTable1 As String = "MdbTempTable1"
    Friend Const g_cnsMdbTempTable2 As String = "MdbTempTable2"
    Friend Const g_cnsMdbTempTable3 As String = "MdbTempTable3"
    ' エラーメッセージ
    Friend Const g_cnsMDBMSG001 As String = "データベースに接続できませんでした。"
    Friend Const g_cnsMDBMSG002 As String = "データベースの更新に失敗しました。"
    Friend Const g_cnsMDBMSG003 As String = "データベースの参照に失敗しました。"
    Friend Const g_cnsMDBMSG011 As String = "このコードのデータは既に登録されています。"
    Friend Const g_cnsMDBMSG012 As String = "このコードのデータは登録されていません。"
    Friend Const g_cnsMDBMSG013 As String = "このコードのデータは既に削除済みです。"
    Friend Const g_cnsMDBMSG021 As String = "出力対象データが存在しません。"
End Module

'***************************************************************************************************
'   サンプル用データベースI/Oクラス(MDB用)                     clsAboutMDB1(Class)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
' 変更日付 Rev     変更履歴内容-------------------------------------------------------------------->
' 17/01/15(1.0.0.0)新規作成
' 17/01/18(1.0.0.0)GetDataTableOleは処理成否を返す仕様に変更
' 17/03/11(1.0.0.0)ACCDBでの変更箇所をコメントで追加する対応
'***************************************************************************************************
Friend Class clsAboutMDB1
    '===============================================================================================
    Private Const g_cnsMDB_Connect1 = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source='"
    'Private Const g_cnsMDB_Connect1 = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" ' ←ACCDBの場合
    '-----------------------------------------------------------------------------------------------
    Private g_strConnectionString As String = ""                    ' 接続文字列
    Private g_objOwnerForm As Form = Nothing                        ' 親フォーム

    '***********************************************************************************************
    '   ■■■ 初期化 ■■■
    '***********************************************************************************************
    '* 処理名 :New
    '* 機能  :初期化
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = 親フォーム(Object)
    '*      Arg2 = MDBファイル名(String)
    '*      Arg3 = MDBサブフォルダ名(String)
    '*      Arg4 = MDBの接続ユーザーID(String)     ※Option
    '*      Arg5 = MDBの接続パスワード(String)     ※Option
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Friend Sub New(ByVal objOwnerForm As Form, _
                   ByVal strMdbFilename As String, _
                   ByVal strMdbSubFolder As String, _
                   Optional ByVal strMdbUserId As String = "", _
                   Optional ByVal strMdbPassword As String = "")
        '-------------------------------------------------------------------------------------------
        g_objOwnerForm = objOwnerForm
        ' MDB接続文字列の編集
        g_strConnectionString = FP_GetMdbConnectionString(strMdbFilename, _
                                                          strMdbSubFolder, _
                                                          strMdbUserId, _
                                                          strMdbPassword)
    End Sub

    '***********************************************************************************************
    '   ■■■ OleDbアクセス関連共通サブ処理 ■■■
    '***********************************************************************************************
    '* 処理名 :GetDataTableOle
    '* 機能  :データテーブルを取得(OLE非接続処理)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :処理成否(Boolean)
    '* 引数  :Arg1 = DataTable(Object)                ※Ref参照(戻り値)
    '*      Arg2 = SQL文(String)
    '*      Arg3 = 参照テーブルID(String)           ※カッコ付きテーブル名
    '*      Arg4 = エラーメッセージ(String)         ※Option(エラー表示させない時の通知用)
    '*      Arg5 = データテーブル名(String)         ※Option
    '*      Arg6 = エラー表示スイッチ(Boolean)      ※Option(内部でエラー表示させる)
    '*      Arg7 = 無データエラースイッチ(Boolean)  ※Option(0件をエラー扱いにしない)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月18日
    '* 更新者 :井上 治
    '* 機能説明:データテーブル名は"MdbTempTable1"がデフォルト
    '* 注意事項:
    '***********************************************************************************************
    Friend Function GetDataTableOle(ByRef dbTbl As DataTable, _
                                    ByVal strSQL As String, _
                                    ByVal strTableName As String, _
                                    Optional ByRef strFatalErrMSG As String = "", _
                                    Optional ByVal strWorkTable As String = g_cnsMdbTempTable1, _
                                    Optional ByVal swDispError As Boolean = True, _
                                    Optional ByVal swNoDataError As Boolean = False) As Boolean
        '-------------------------------------------------------------------------------------------
        Dim strMSG As String = g_cnsMDBMSG001                       ' エラーメッセージ
        dbTbl = Nothing
        Using dbCon As New OleDb.OleDbConnection, dbDSet As New DataSet
            Try
                '-----------------------------------------------------------------------------------
                ' MDBコネクションを取得
                dbCon.ConnectionString = g_strConnectionString
                '-----------------------------------------------------------------------------------
                ' 参照SQLの発行(DataAdapter)
                strMSG = g_cnsMDBMSG003
                Using dbDAdp As New OleDb.OleDbDataAdapter(strSQL, dbCon)
                    ' DataSetを取得
                    dbDAdp.Fill(dbDSet, strWorkTable)
                    ' DataTableを返す
                    dbTbl = dbDSet.Tables(strWorkTable)
                    ' 0件確認
                    If (swNoDataError AndAlso (dbTbl.Rows.Count = 0)) Then
                        ' 0件をエラーとする場合の処置
                        strFatalErrMSG = g_cnsMDBMSG021 & FP_ChangeRoundBrackets(strTableName)
                        ' メッセージ表示
                        If swDispError Then
                            MessageBox.Show(g_objOwnerForm, _
                                            strFatalErrMSG, _
                                            g_objOwnerForm.Text, _
                                            MessageBoxButtons.OK, _
                                            MessageBoxIcon.Error)
                        End If
                        Return False
                    End If
                End Using
                Return True

            Catch ex As Exception
                '-----------------------------------------------------------------------------------
                ' 接続・参照不成功(一般例外)
                strFatalErrMSG = strMSG & FP_ChangeRoundBrackets(strTableName) & _
                    ControlChars.CrLf & ex.Message
                ' メッセージ表示
                If swDispError Then
                    MessageBox.Show(g_objOwnerForm, _
                                    strFatalErrMSG, _
                                    g_objOwnerForm.Text, _
                                    MessageBoxButtons.OK, _
                                    MessageBoxIcon.Error)
                End If
                Return False
            End Try
        End Using
    End Function

    '***********************************************************************************************
    '   ■■■ 共通サブ処理(Private) ■■■
    '***********************************************************************************************
    '* 処理名 :FP_GetMdbConnectionString
    '* 機能  :MDB接続文字列の編集
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :MDB接続文字列(String)
    '* 引数  :Arg1 = MDBファイル名(String)
    '*      Arg2 = マイドキュメント配下のサブフォルダ(String)
    '*      Arg3 = ユーザーID(String)
    '*      Arg4 = パスワード(String)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:マイドキュメント配下のサブフォルダを指定してMDB接続文字列を編集
    '* 注意事項:
    '***********************************************************************************************
    Private Function FP_GetMdbConnectionString(ByVal strMDBName As String, _
                                               ByVal strSubFolder As String, _
                                               ByVal strUserId As String, _
                                               ByVal strPassword As String) As String
        '-------------------------------------------------------------------------------------------
        Dim strPathname As String = My.Computer.FileSystem.SpecialDirectories.MyDocuments ' フォルダ
        ' サブフォルダ指定あり
        If strSubFolder.Length <> 0 Then
            strPathname = Path.Combine(strPathname, strSubFolder)
        End If
        ' MDBファイル名を接続したフルパス名を編集
        Dim strFilename As String = Path.Combine(strPathname, strMDBName) ' ファイル名
        ' MDBの接続文字列を編集
        Dim strConnectionString As String = g_cnsMDB_Connect1       ' 接続文字列
        strConnectionString &= strFilename & "';"
        ' ユーザーIDが指定されている
        If strUserId.Length <> 0 Then
            strConnectionString &= "User ID='" & strUserId & "';"
        End If
        ' パスワードが指定されている
        If strPassword.Length <> 0 Then
            strConnectionString &= "Password='" & strPassword & "';"
        End If
        Return strConnectionString
    End Function

    '***********************************************************************************************
    '* 処理名 :FP_ChangeRoundBrackets
    '* 機能  :鍵カッコを丸カッコに変換(共通処理)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :変換後文字列(String)
    '* 引数  :Arg1 = 変換前文字列(String)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:例:"[TableName]"を"(TableName)"に変換する(例外メッセージ表示用)
    '* 注意事項:先頭文字が"["でない場合はそのまま返す
    '***********************************************************************************************
    Private Function FP_ChangeRoundBrackets(ByVal strInTableName As String) As String
        '-------------------------------------------------------------------------------------------
        Const cnsKA As String = "("
        Const cnsKO As String = ")"
        Const cnsKA2 As String = "["
        Const cnsKO2 As String = "]"
        Const cnsKOKA2 As String = "].["
        Const cnsDOT As String = "."
        If strInTableName.Length = 0 Then Return strInTableName
        ' 先頭文字が"["か
        If strInTableName.StartsWith(cnsKA2) Then
            ' 中間の"].["を"."のみに変換
            Dim strText As String = strInTableName.Replace(cnsKOKA2, cnsDOT)
            ' 先頭文字が"["の場合は丸カッコに変換する
            Return cnsKA & strText.Substring(1, strText.Length - 2) & cnsKO
        ElseIf strInTableName.StartsWith(cnsKA) Then
            ' 先頭文字が"("の場合はそのまま返す
            Return strInTableName
        ElseIf strInTableName.EndsWith(cnsKO2) Then
            ' 右端のみ"]"が付いている場合の対応
            Dim strText As String = strInTableName.Substring(0, strInTableName.Length - 1)
            Return cnsKA & strText & cnsKO
        Else
            ' 上記以外の場合は"("~")"で挟む
            Return cnsKA & strInTableName & cnsKO
        End If
    End Function

    '----------------------------------------<< End of Source >>------------------------------------
End Class

今回のプロジェクトではデータベース(MDB)に対しては参照しか行なわないので、外部から呼び出すプロシージャは「データテーブルを取得(GetDataTableOle)」だけです。 データベース(MDB)は起動時に設定できるので、初期化(New)プロシージャで指定する方法を採っています。
このサンプルでのデータテーブルの参照は「非接続」という方法で、コネクションに対する「Open」「Close」を記述しない方法です。 結果は「DataTable」というJAG配列で返されますが、「DataTable」自体は値の配列だけではなく、 フィールドの名前や型などもプロパティで参照できる特殊なオブジェクトになっています。



外部から見ると「clsAboutMDB」はひとつに見えますが、内部はこのように「modAboutMDB」モジュールと「clsAboutMDB」クラスに分かれています。
modAboutMDB」モジュールの方はデータベース(MDB)に関する定数を置いているだけです。

次は「ウィンドウ制御クラス(clsAboutWindow1)」です。


'***************************************************************************************************
'   サンプル用ウィンドウ制御関連クラス                       clsAboutWindow1(Class)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
' 変更日付 Rev     変更履歴内容-------------------------------------------------------------------->
' 17/01/15(1.0.0.0)新規作成
'***************************************************************************************************
Friend Class clsAboutWindow1
    '===============================================================================================
    Private Const g_cnsArrowPoint As Integer = 15                   ' はみ出し許容ポイント
    '-----------------------------------------------------------------------------------------------
    Private g_objOwnerForm As Form = Nothing                        ' 親フォーム

    '***********************************************************************************************
    '   ■■■ 初期化 ■■■
    '***********************************************************************************************
    '* 処理名 :New
    '* 機能  :初期化
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = 親フォーム(Object)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Friend Sub New(ByVal objOwnerForm As Form)
        '-------------------------------------------------------------------------------------------
        g_objOwnerForm = objOwnerForm
    End Sub

    '***********************************************************************************************
    '   ■■■ ウィンドウ制御関連共通サブ処理 ■■■
    '***********************************************************************************************
    '* 処理名 :FormAdjustLocationSize1
    '* 機能  :フォーム位置・サイズ制御
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = 設定退避位置(Point)
    '*      Arg2 = 設定退避サイズ(Size)
    '*      Arg3 = デザイン時サイズ(Size)      ※DPI更新後であること
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:フォーム退避値がスクリーンから外れている時はメインスクリーン中央に戻す機能
    '* 注意事項:以下の条件の場合に使用できる
    '*      ・Windows上に直接出ることができるフォーム(MDI子フォーム等でない)
    '*      ・親ウィンドウを持たない(隠しベースフォームを除く)
    '*      ・Sizableフォーム
    '*      ・設定に位置・サイズを保持させており、この復旧のLoadプロシージャで行なう
    '***********************************************************************************************
    Friend Sub FormAdjustLocationSize1(ByVal objSettingLocation As Point, _
                                       ByVal objSettingSize As Size, _
                                       ByVal objDesigndSize As Size)
        '-------------------------------------------------------------------------------------------
        ' 退避位置未設定、フォーム起動位置手動以外は除外
        If Not FP_CheckEnableAdjust(objSettingLocation) Then Exit Sub
        '-------------------------------------------------------------------------------------------
        Dim objSize As Size = objSettingSize                        ' 設定サイズ
        ' 退避サイズ未設定の場合はデザインサイズを充当
        If ((objSize.Width = 0) AndAlso (objSize.Height = 0)) Then
            objSize = objDesigndSize
        End If
        Dim objLocation As Point = objSettingLocation               ' 設定位置
        Dim objMainScreen As Screen = Nothing                       ' メインスクリーン
        '-------------------------------------------------------------------------------------------
        ' 各スクリーンを巡回してどれかのスクリーン内に収まっているかを確認
        Dim blnOK As Boolean = FP_CheckFormInScreens(objLocation, objSize, objMainScreen) ' 正常判定
        ' どのスクリーンにも収まっていない場合はメインスクリーン内(中央)に移動
        If Not blnOK AndAlso objMainScreen IsNot Nothing Then
            With objMainScreen.WorkingArea
                objLocation = New Point(.Left + (.Width - g_objOwnerForm.Width) \ 2, _
                                        .Top + (.Height - g_objOwnerForm.Height) \ 2)
            End With
            ' サイズは対象フォームのサイズに戻す(変更しない)
            objSize = g_objOwnerForm.Size
        End If
        ' 位置・サイズを設定(サイズが後であること)
        g_objOwnerForm.Location = objLocation
        g_objOwnerForm.Size = objSize
    End Sub

    '***********************************************************************************************
    '* 処理名 :FormSaveSettings1
    '* 機能  :フォーム位置・サイズ・ウィンドウ状態の退避
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = 設定退避位置(Point)
    '*      Arg2 = 設定退避サイズ(Size)
    '*      Arg3 = 設定退避ウィンドウ状態(Integer)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:フォーム位置・サイズを設定に退避(最大化を伴う場合に使用)
    '* 注意事項:最大化時は原則として退避せずスクリーンが異なる時のみ代替値を退避させる
    '***********************************************************************************************
    Friend Sub FormSaveSettings1(ByRef objSettingLocation As Point, _
                                 ByRef objSettingSize As Size, _
                                 ByRef intWindowState As Integer)
        '-------------------------------------------------------------------------------------------
        ' WindowStateによる判定
        Select Case g_objOwnerForm.WindowState
            Case FormWindowState.Minimized                          ' 最小化
                ' (何もしない)
            Case FormWindowState.Maximized                          ' 最大化
                ' サイズは退避しない(位置は前回退避値が同スクリーンでなければ代替値)
                objSettingLocation = FP_GetPositionDummy(objSettingLocation, _
                                                         objSettingSize)
                intWindowState = g_objOwnerForm.WindowState
            Case Else                                               ' 通常
                ' 全て退避
                objSettingLocation = g_objOwnerForm.Location
                objSettingSize = g_objOwnerForm.Size
                intWindowState = g_objOwnerForm.WindowState
        End Select
    End Sub

    '***********************************************************************************************
    '   ■■■ 共通サブ処理(Private) ■■■
    '***********************************************************************************************
    '* 処理名 :FP_CheckEnableAdjust
    '* 機能  :フォーム位置制御可能か判定
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :処理可能判定(Boolean)
    '* 引数  :Arg1 = 設定退避位置(Point)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Function FP_CheckEnableAdjust(ByVal objSettingLocation As Point) As Boolean
        '-------------------------------------------------------------------------------------------
        ' 退避位置未設定、フォーム起動位置手動以外は除外
        If ((objSettingLocation.X = 0) AndAlso _
            (objSettingLocation.Y = 0) AndAlso _
            (g_objOwnerForm.StartPosition <> FormStartPosition.Manual)) Then
            Return False
        End If
        ' フォーム起動位置手動以外は「手動」にする
        If g_objOwnerForm.StartPosition <> FormStartPosition.Manual Then
            g_objOwnerForm.StartPosition = FormStartPosition.Manual
        End If
        Return True
    End Function

    '***********************************************************************************************
    '* 処理名 :FP_CheckFormInScreens
    '* 機能  :指定位置・サイズがどれかのスクリーンに収まっているかの判定
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :正常判定(Boolean)
    '* 引数  :Arg1 = 指定位置(Point)
    '*      Arg2 = 指定サイズ(Size)
    '*      Arg3 = メインスクリーン(Screen)            ※Option
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Private Function FP_CheckFormInScreens(ByVal objLocation As Point, _
                                           ByVal objSize As Size, _
                                           Optional ByRef objMainScreen As Screen = Nothing) As Boolean
        '-------------------------------------------------------------------------------------------
        ' 位置未設定は失敗
        If ((objLocation.X = 0) AndAlso (objLocation.Y = 0)) Then Return False
        '-------------------------------------------------------------------------------------------
        Dim objBottomRight As Point = New Point(objLocation.X + objSize.Width, _
                                                objLocation.Y + objSize.Height) ' 右下位置
        Dim blnOK As Boolean = False                                ' 設定スクリーン内判定
        '-------------------------------------------------------------------------------------------
        ' 各スクリーンを巡回してどれかのスクリーン内に収まっているかを確認
        For Each objScreen As Screen In Screen.AllScreens
            ' メインスクリーンは別変数に退避
            If objScreen.Primary Then objMainScreen = objScreen
            With objScreen.WorkingArea
                ' フォームがスクリーン内に収まっているか
                If ((objLocation.X >= .Location.X - g_cnsArrowPoint) AndAlso _
                    (objBottomRight.X <= (.Location.X + .Width + g_cnsArrowPoint)) AndAlso _
                    (objLocation.Y >= .Location.Y - g_cnsArrowPoint) AndAlso _
                    (objBottomRight.Y <= (.Location.Y + .Height + g_cnsArrowPoint))) Then
                    blnOK = True
                End If
            End With
        Next objScreen
        Return blnOK
    End Function

    '***********************************************************************************************
    '* 処理名 :FP_GetPositionDummy
    '* 機能  :代替フォーム位置の取得(最大化時)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :フォーム位置(Point)
    '* 引数  :Arg1 = 設定退避位置(Point)
    '*      Arg2 = 設定退避サイズ(Size)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:フォームが収容されているスクリーンの中央の位置を返す
    '* 注意事項:元の設定値と同じスクリーンの場合は元の設定値を返す
    '***********************************************************************************************
    Private Function FP_GetPositionDummy(ByVal objSettingLocation As Point, _
                                         ByVal objSettingSize As Size) As Point
        '-------------------------------------------------------------------------------------------
        Dim objLoc1 As Point = g_objOwnerForm.Location              ' 現在位置
        Dim objLoc2 As Point = objSettingLocation                   ' 退避位置
        Dim strScr1 As String = String.Empty                        ' 現在スクリーン名
        Dim strScr2 As String = String.Empty                        ' 退避スクリーン名
        Dim objCurrScreen As Screen = Nothing                       ' 現在スクリーン
        '-------------------------------------------------------------------------------------------
        ' 最大化(8P程度マイナスになる)のため誤認を避けるため100Pシフトさせておく
        objLoc1.X += 100
        objLoc1.Y += 100
        ' 各スクリーンを巡回してどれかのスクリーン内に収まっているかを確認
        For Each objScreen As Screen In Screen.AllScreens
            With objScreen.WorkingArea
                ' 現在位置が所属しているか
                If ((objLoc1.X >= .Location.X) AndAlso _
                    (objLoc1.X < (.Location.X + .Width)) AndAlso _
                    (objLoc1.Y >= .Location.Y) AndAlso _
                    (objLoc1.Y < (.Location.Y + .Height))) Then
                    objCurrScreen = objScreen
                    strScr1 = objScreen.DeviceName
                End If
                ' 退避位置が所属しているか
                If ((objLoc2.X >= .Location.X) AndAlso _
                    (objLoc2.X < (.Location.X + .Width)) AndAlso _
                    (objLoc2.Y >= .Location.Y) AndAlso _
                    (objLoc2.Y < (.Location.Y + .Height))) Then
                    strScr2 = objScreen.DeviceName
                End If
            End With
        Next objScreen
        '-------------------------------------------------------------------------------------------
        ' 現在スクリーンと退避スクリーンが同じなら退避値のままとする
        If strScr1 = strScr2 Then
            Return objLoc2
        End If
        '-------------------------------------------------------------------------------------------
        ' 退避サイズがゼロの場合はデザインサイズで充当
        If ((objSettingSize.Width = 0) OrElse (objSettingSize.Height = 0)) Then
            objSettingSize = g_objOwnerForm.Size
        End If
        ' 現在スクリーン上の中央に退避サイズのウィンドウがあるとしてその位置を算出
        With objCurrScreen.WorkingArea
            objLoc2 = New Point(.Location.X + (.Width - objSettingSize.Width) \ 2, _
                                .Location.Y + (.Height - objSettingSize.Height) \ 2)
        End With
        Return objLoc2
    End Function

    '----------------------------------------<< End of Source >>------------------------------------
End Class

このクラスは対象フォームの位置・サイズを設定退避させたり、次回起動時に復旧させるためのプロシージャを持たせているものです。
特に退避、復旧間でスクリーン環境が異なる(例えばノートPCで外部ディスプレィに接続があるかどうかなど)場合などで、 存在するスクリーンの中央に表示を戻す働きをさせるようにしてあります。
本来はこの他にスクリーンのDPI制御まで行なえると良いのですが、サンプルとして複雑になりすぎるので割愛しています。

最後は「DataGridView制御クラス(clsAboutDataGridView1)」です。


'***************************************************************************************************
'   サンプル用DataGridView制御関連定数・変数                  modAboutDataGridView1(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'   ※この下に「DataGridView制御関連クラス(clsAboutDataGridView)」があります
'***************************************************************************************************
' 変更日付 Rev     変更履歴内容-------------------------------------------------------------------->
' 17/01/15(1.0.0.0)新規作成
'***************************************************************************************************
Module modAboutDataGridView1
    '===============================================================================================
    ' DataGridViewの位置
    Friend Const g_cnsDG_ML As DataGridViewContentAlignment = DataGridViewContentAlignment.MiddleLeft
    Friend Const g_cnsDG_MC As DataGridViewContentAlignment = DataGridViewContentAlignment.MiddleCenter
    Friend Const g_cnsDG_MR As DataGridViewContentAlignment = DataGridViewContentAlignment.MiddleRight
    ' DataGridViewのSortOrder
    Friend Const g_cnsSO_Ascending As SortOrder = Windows.Forms.SortOrder.Ascending
    Friend Const g_cnsSO_Descending As SortOrder = Windows.Forms.SortOrder.Descending
    Friend Const g_cnsSO_None As SortOrder = Windows.Forms.SortOrder.None
    ' DataGridViewのSortMode
    Friend Const g_cnsSM_Programmatic As DataGridViewColumnSortMode = _
        DataGridViewColumnSortMode.Programmatic
    Friend Const g_cnsSM_NotSortable As DataGridViewColumnSortMode = _
        DataGridViewColumnSortMode.NotSortable
    '-----------------------------------------------------------------------------------------------
    ' DataGridViewのカラム設定用ユーザー定義
    Friend Structure g_typDGVColInfo
        Dim dgvCaption As String                        ' 見出し表示名
        Dim dgvWidth As Integer                         ' カラム表示幅
        Dim dgvAlign As DataGridViewContentAlignment    ' 水平配置
        Dim dgvSortMode As DataGridViewColumnSortMode   ' Sortモード
    End Structure

    '----------------------------------------<< End of Source >>------------------------------------
End Module

'***************************************************************************************************
'   サンプル用DataGridView制御関連クラス                      clsAboutDataGridView1(Class)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
' 変更日付 Rev     変更履歴内容-------------------------------------------------------------------->
' 17/01/15(1.0.0.0)新規作成
'***************************************************************************************************
Friend Class clsAboutDataGridView1

    '***********************************************************************************************
    '   ■■■ DataGridView関連共通サブ処理 ■■■
    '***********************************************************************************************
    '* 処理名 :SetColumnInfo
    '* 機能  :DataGridViewのカラム情報テーブル登録
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = カラム情報テーブル(Array:g_typDGVColInfo)
    '*      Arg2 = 見出し表示文字列(String)
    '*      Arg3 = カラム表示幅(Integer)
    '*      Arg4 = 水平配置(DataGridViewContentAlignment)   ※Option
    '*      Arg5 = Sortモード(DataGridViewColumnSortMode)   ※Option
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:カラム情報の見出し名、表示幅、配置、Sortモードをカラム順にセット
    '* 注意事項:INDEXは順次加算されるのでテーブルはデフォルトをNothingとする
    '***********************************************************************************************
    Friend Sub SetColumnInfo(ByRef DGVColInfo() As g_typDGVColInfo, _
                             ByVal Caption As String, _
                             ByVal Width As Integer, _
                             Optional ByVal Align As DataGridViewContentAlignment = g_cnsDG_ML, _
                             Optional ByVal SortMode As DataGridViewColumnSortMode = _
                                 DataGridViewColumnSortMode.Automatic)
        '-------------------------------------------------------------------------------------------
        ' テーブル要素を追加
        Dim intIx As Integer = 0                                    ' テーブルINDEX
        ' 既にテーブルが作成済みか
        If DGVColInfo IsNot Nothing Then
            ' [作成済]要素を追加
            intIx = DGVColInfo.Length
            ReDim Preserve DGVColInfo(intIx)
        Else
            ' [未作成]初期化
            ReDim DGVColInfo(intIx)
        End If
        '-------------------------------------------------------------------------------------------
        ' テーブルに追加
        With DGVColInfo(intIx)
            .dgvCaption = Caption           ' 見出し表示名
            .dgvWidth = Width               ' カラム表示幅
            .dgvAlign = Align               ' 水平配置
            .dgvSortMode = SortMode         ' Sortモード
        End With
    End Sub

    '***********************************************************************************************
    '* 処理名 :GetDefaultColumnWidth
    '* 機能  :カラム情報テーブルよりカラム幅一次テーブルを作成
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :カラム幅一次テーブル(Array:Integer)
    '* 引数  :Arg1 = カラム情報テーブル(Array:g_typDGVColInfo)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:「列リセット」用カラム幅一次テーブルを作成する
    '* 注意事項:
    '***********************************************************************************************
    Friend Function GetDefaultColumnWidth(ByRef DGVColInfo() As g_typDGVColInfo) As Integer()
        '-------------------------------------------------------------------------------------------
        Dim intIx As Integer = 0                                    ' テーブルINDEX
        Dim intIxMax As Integer = DGVColInfo.GetUpperBound(0)       ' テーブルINDEX上限
        Dim tblDefaultColumnWidth(intIxMax) As Integer              ' カラム情報テーブルWORK
        ' 全件繰り返す
        Do While intIx <= intIxMax
            tblDefaultColumnWidth(intIx) = DGVColInfo(intIx).dgvWidth
            ' 次へ
            intIx += 1
        Loop
        Return tblDefaultColumnWidth.Clone
    End Function

    '***********************************************************************************************
    '* 処理名 :GP_AdjustColumnWidth
    '* 機能  :カラム情報テーブルのカラム幅再調整
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = カラム情報テーブル(Array:g_typDGVColInfo)
    '*      Arg2 = 設定文字列(String)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:列幅の配列を設定退避値で置き換える
    '* 注意事項:
    '***********************************************************************************************
    Friend Sub AdjustColumnWidth(ByRef DGVColInfo() As g_typDGVColInfo, _
                                 ByVal strSettingString As String)
        '-------------------------------------------------------------------------------------------
        ' 設定退避値が登録済みか
        If ((strSettingString IsNot Nothing) AndAlso (strSettingString.Length <> 0)) Then
            ' 設定文字列を配列に変換(セパレータはTab)
            Dim tblWidth2() As String = strSettingString.Split(ControlChars.Tab) ' 一時テーブル
            ' 配列要素の一致を確認
            If ((DGVColInfo.Length > 2) AndAlso (tblWidth2.Length > 2)) Then
                Dim intIx As Integer = 0                                ' テーブルINDEX
                Dim intIxMax As Integer = DGVColInfo.GetUpperBound(0)   ' テーブルINDEX上限
                ' 設定値で配列を置き換える
                If tblWidth2.GetUpperBound(0) < intIxMax Then
                    intIxMax = tblWidth2.GetUpperBound(0)
                End If
                ' 全件繰り返す
                Do While intIx <= intIxMax
                    DGVColInfo(intIx).dgvWidth = Integer.Parse(tblWidth2(intIx).Trim)
                    ' 次へ
                    intIx += 1
                Loop
            End If
        End If
    End Sub

    '***********************************************************************************************
    '* 処理名 :InitDataGridView1
    '* 機能  :DataGridViewの初期設定(一般一覧用)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = DataGridView(Object)
    '*      Arg2 = カラム情報テーブル(Array:g_typDGVColInfo)
    '*      Arg3 = 固定見出し列INDEX(Integer)       ※Option(デフォルト:-1)
    '*      Arg4 = 見出し高さ(Integer)              ※Option(デフォルト:20)
    '*      Arg5 = 選択モード(DGVSelectionMode)     ※Option(デフォルト:FullRow)
    '*      Arg6 = 読み取り専用(Boolean)            ※Option(デフォルト:True)
    '*      Arg7 = 列幅変更可否(Boolean)            ※Option(デフォルト:True)
    '*      Arg8 = 列位置変更可否(Boolean)          ※Option(デフォルト:True)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:
    '* 注意事項:
    '***********************************************************************************************
    Friend Sub InitDataGridView1(ByRef objDGV As DataGridView, _
                                 ByRef DGVColInfo() As g_typDGVColInfo, _
                                 Optional ByVal FrozenColIndex As Integer = -1, _
                                 Optional ByVal ColumnHeadersHeight As Integer = 20, _
                                 Optional ByVal SelectionMode As DataGridViewSelectionMode = _
                                     DataGridViewSelectionMode.FullRowSelect, _
                                 Optional ByVal blnReadOnly As Boolean = True, _
                                 Optional ByVal AllowUserToResizeColumns As Boolean = True, _
                                 Optional ByVal AllowUserToOrderColumns As Boolean = True)
        '-------------------------------------------------------------------------------------------
        Dim intIx As Integer = 0                                    ' テーブルINDEX
        Dim intIxMax As Integer = DGVColInfo.GetUpperBound(0)       ' テーブルINDEX上限
        '-------------------------------------------------------------------------------------------
        ' ForeColor、BackColorの調整
        Dim objBackColor As Color = Color.FromArgb(240, 240, 240)
        Dim objForeColor As Color = Color.Green
        '-------------------------------------------------------------------------------------------
        ' DataGridViewの初期設定
        With objDGV
            .AutoSize = False                                       ' 自動サイズ無し(固定)
            .AllowUserToResizeColumns = AllowUserToResizeColumns    ' 列幅変更可否
            .AllowUserToOrderColumns = AllowUserToOrderColumns      ' 列位置変更可否
            .AllowUserToResizeRows = False                          ' 行高変更不可(固定)
            .EnableHeadersVisualStyles = False                      ' Visualスタイルを使用しない(固定)
            .RowHeadersVisible = False                              ' 行見出し有無
            .ColumnHeadersVisible = True                            ' 列見出し有無
            .ReadOnly = blnReadOnly                                 ' 読み取り専用
            .ColumnHeadersHeightSizeMode = _
                DataGridViewColumnHeadersHeightSizeMode.DisableResizing ' (固定)
            With .ColumnHeadersDefaultCellStyle
                .Alignment = DataGridViewContentAlignment.MiddleCenter  ' (固定)
                .BackColor = objBackColor                           ' 列見出し背景色
                .ForeColor = objForeColor                           ' 列見出し文字色
            End With
            .ColumnHeadersHeight = ColumnHeadersHeight              ' 列見出し高さ
            .SelectionMode = SelectionMode                          ' 選択モード
            .MultiSelect = False                                    ' 複数選択
            .ColumnCount = intIxMax + 1                             ' 列数
            ' 各列の設定
            Do While intIx <= intIxMax
                With .Columns(intIx)
                    .Width = DGVColInfo(intIx).dgvWidth
                    .HeaderText = DGVColInfo(intIx).dgvCaption
                    .AutoSizeMode = DataGridViewAutoSizeColumnMode.None
                    .DefaultCellStyle.Alignment = DGVColInfo(intIx).dgvAlign
                    .SortMode = DGVColInfo(intIx).dgvSortMode
                End With
                ' 次列へ
                intIx += 1
            Loop
            If FrozenColIndex >= 0 Then
                .Columns(FrozenColIndex).Frozen = True              ' 設定列で列固定
            End If
        End With
    End Sub

    '***********************************************************************************************
    '* 処理名 :AdjustDGVColumnDisplayIndex
    '* 機能  :DataGridViewのカラム配置再調整
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :Arg1 = DataGridView(Object)
    '*      Arg2 = 設定文字列(String)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:列配置を設定退避値で置き換える
    '* 注意事項:
    '***********************************************************************************************
    Friend Sub AdjustDGVColumnDisplayIndex(ByRef objDGV As DataGridView, _
                                           ByVal strSettingString As String)
        '-------------------------------------------------------------------------------------------
        If ((strSettingString IsNot Nothing) AndAlso (strSettingString.Length <> 0)) Then
            ' 設定文字列を配列に変換(セパレータはTab)
            Dim tblDisplayIndexS() As String = strSettingString.Split(ControlChars.Tab) ' WORK
            Dim intIxMax As Integer = tblDisplayIndexS.GetUpperBound(0) ' テーブルINDEX上限
            Dim tblDisplayIndex(intIxMax) As Integer                ' 設定値テーブル
            Dim tblIndex(intIxMax) As Integer                       ' 位置テーブル
            Dim intIx As Integer = 0                                ' テーブルINDEX
            Do While intIx <= intIxMax
                tblDisplayIndex(intIx) = Integer.Parse(tblDisplayIndexS(intIx).Trim)
                tblIndex(intIx) = intIx
                ' 次へ
                intIx += 1
            Loop
            ' 表示順の若い方からセットするために並替え
            Array.Sort(tblDisplayIndex, tblIndex)
            ' DataGridView
            With objDGV
                ' カラム数調整
                If intIxMax >= .Columns.Count Then
                    intIxMax = .Columns.Count - 1
                End If
                intIx = 0
                ' 全列を巡回
                Do While intIx <= intIxMax
                    ' 位置が上限以内
                    If tblIndex(intIx) <= intIxMax Then
                        .Columns(tblIndex(intIx)).DisplayIndex = intIx
                    End If
                    ' 次列へ
                    intIx += 1
                Loop
            End With
        End If
    End Sub

    '***********************************************************************************************
    '* 処理名 :GetDGVColumnWidth
    '* 機能  :DataGridViewのカラム幅の取得
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :設定退避用に文字列(String)
    '* 引数  :Arg1 = DataGridView(Object)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:DataGridViewのカラム幅を設定退避用に文字列で取得
    '* 注意事項:
    '***********************************************************************************************
    Friend Function GetDGVColumnWidth(ByRef objDGV As DataGridView) As String
        '-------------------------------------------------------------------------------------------
        Dim strTEXT As String = String.Empty                        ' テキストWORK
        Dim intIx As Integer = 1                                    ' テーブルINDEX
        ' DataGridView
        With objDGV
            strTEXT = .Columns(0).Width.ToString
            ' 全列の幅をTabを挟んで接合
            Do While intIx < .Columns.Count
                strTEXT &= ControlChars.Tab & .Columns(intIx).Width.ToString
                ' 次へ
                intIx += 1
            Loop
        End With
        Return strTEXT
    End Function

    '***********************************************************************************************
    '* 処理名 :GetDGVColumnDisplayIndex
    '* 機能  :DataGridViewのカラム配置の取得
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :設定退避用に文字列(String)
    '* 引数  :Arg1 = DataGridView(Object)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:DataGridViewのカラム配置を設定退避用に文字列で取得
    '* 注意事項:
    '***********************************************************************************************
    Friend Function GetDGVColumnDisplayIndex(ByRef objDGV As DataGridView) As String
        '-------------------------------------------------------------------------------------------
        Dim strTEXT As String = String.Empty                        ' テキストWORK
        Dim intIx As Integer = 1                                    ' テーブルINDEX
        ' DataGridView
        With objDGV
            strTEXT = .Columns(0).DisplayIndex.ToString
            ' 全列の表示順INDEXをTabを挟んで接合
            Do While intIx < .Columns.Count
                strTEXT &= ControlChars.Tab & .Columns(intIx).DisplayIndex.ToString
                ' 次へ
                intIx += 1
            Loop
        End With
        Return strTEXT
    End Function

    '***********************************************************************************************
    '* 処理名 :DGV_ColumnHeaderMouseClick
    '* 機能  :グリッド見出しクリック(ColumnHeaderMouseClick)
    '-----------------------------------------------------------------------------------------------
    '* 返り値 :(なし)
    '* 引数  :(デフォルト)
    '-----------------------------------------------------------------------------------------------
    '* 作成日 :2017年01月15日
    '* 作成者 :井上 治
    '* 更新日 :2017年01月15日
    '* 更新者 :井上 治
    '* 機能説明:SortMode.Programmatic列を含む一覧の並替えを行なう
    '* 注意事項:Programmatic列は実Sort列IndexをTagにセットしておくこと
    '***********************************************************************************************
    Friend Sub DGV_ColumnHeaderMouseClick(ByVal sender As Object, _
                                          ByVal e As DataGridViewCellMouseEventArgs)
        '-------------------------------------------------------------------------------------------
        With CType(sender, DataGridView)
            Dim intCol As Integer = e.ColumnIndex                   ' カラムINDEX
            If .Columns(intCol).SortMode = g_cnsSM_Programmatic Then
                '-----------------------------------------------------------------------------------
                ' 当該カラムがProgrammaticに場合は指定列でSort
                Dim intCol2 As Integer = .Columns(intCol).Tag       ' カラムINDEX
                If .Columns(intCol).HeaderCell.SortGlyphDirection = g_cnsSO_Ascending Then
                    ' 降順に並替え及びソートマーク切替え
                    .Sort(.Columns(intCol2), System.ComponentModel.ListSortDirection.Descending)
                    .Columns(intCol).HeaderCell.SortGlyphDirection = g_cnsSO_Descending
                Else
                    ' 昇順に並替え及びソートマーク切替え
                    .Sort(.Columns(intCol2), System.ComponentModel.ListSortDirection.Ascending)
                    .Columns(intCol).HeaderCell.SortGlyphDirection = g_cnsSO_Ascending
                End If
                ' 他のProgrammatic列のソートマークを消す
                For Each objCol As DataGridViewColumn In .Columns
                    If ((objCol.SortMode = g_cnsSM_Programmatic) AndAlso _
                        (objCol.Index <> intCol)) Then
                        objCol.HeaderCell.SortGlyphDirection = g_cnsSO_None
                    End If
                Next objCol
            Else
                '-----------------------------------------------------------------------------------
                ' 当該カラムがProgrammaticでない場合は全Programmatic列のソートマークを消す
                For Each objCol As DataGridViewColumn In .Columns
                    If objCol.SortMode = g_cnsSM_Programmatic Then
                        objCol.HeaderCell.SortGlyphDirection = g_cnsSO_None
                    End If
                Next objCol
            End If
        End With
    End Sub

    '----------------------------------------<< End of Source >>------------------------------------
End Class

今回のサンプルではこのクラスが一番ソースコードの行数が長いです。おそらく呼び元のフォームのソースコードより行数が多くなっています。
このクラスの実装機能で、見出しを含めた初期的なグリッドの設定を始め、各列幅、列配置の変更を設定保持させて次回起動で再現させるための対応、さらにはプログラムソートにも対応しています。

ACCDBで実行したい方へ  
サンプルをダウンロードすると、ACCDB用に変更するための記述を2箇所コメントで埋め込んであります。

    Private Const g_cnsMDB_Connect1 = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source='"
    'Private Const g_cnsMDB_Connect1 = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" ' ←ACCDBの場合


    ' MDBファイル情報
    Private Const g_cnsMdbFileame As String = "SampleCorp1.mdb"
    'Private Const g_cnsMdbFileame As String = "SampleCorp1.accdb" ' ←ACCDBの場合
この2箇所をコメント側の記述に変更して、先頭で説明している「SampleMDB」フォルダ内に「SampleCorp1.accdb」を配置して実行してみて下さい。 「SampleCorp1.accdb」は、こちらの頁末から定義文書とセットでダウンロードできます。