こんにちは、Ryoです。
今回は以前のテーブル関連記事の延長線上になりますが、テーブル上のデータメンテナンスを目的としてユーザーフォームのリストボックスにデータを取込み、その中でデータ入れ替えをテーブルと連動させて実行したり、番号の振り直しや、セルへの書込みなどを活用例の1つとして書いてみましたので、内容を紹介したいと思います。
1.サンプル概要
先ず、以前作成したテーブル関連記事は以下になりますので、よろしければご参考までに。

今回は以下のテーブル(名前:テーブル1)をSheet名「Sample」に作成して準備していますので、これを用いて実行していきます。

次にユーザーフォームですが、構成は以下の通りです。

このUserForm1の起動時に初期設定として、テーブルを読み込んで2列表示設定にしていますので、表示させた時点でListBox1にはテーブル1のデータが2列(「No.」と「Code」)表示されます。

ListBox1には複数項目選択を可能にするMultiSelectを設定してあるので、入れ替えを行いたいデータを2つ選択して「Data入れ替え」ボタンを押します。

「1000」と「1005」を選択して「Data入れ替え」ボタンを押すとこの通りListBox1とテーブル1のデータが入れ替わります。


次に入れ替えたデータに対し、No.の振り直しを行うので「再No.割振り」ボタンを押すと「No.」部分のみソートされてListBox2に表示されます。

番号の振り直し後、テーブル1へ反映させるので「テーブル書込み」ボタンをクリックすると、テーブル1のデータが書き換わります。

以上がサンプル概要になります。
2.サンプルコード
◆UserForm初期設定
この設定はユーザーフォームが表示された時点で実行される初期化処理になります。ここで行うのはListBox1へのデータ追加とListBox2の表示設定です。
Private Sub UserForm_Initialize()
'**ユーザーフォーム初期設定(初期化処理)**
'リストボックス1に対する処理
With ListBox1
'複数項目を選択を可能にする
.MultiSelect = fmMultiSelectMulti
'.ListStyle = fmListStyleOption '参考:チェックボックス表示
'リストボックス1の列を2列に指定
.ColumnCount = 2
'リストボックス1の列幅をそれぞれ指定する
.ColumnWidths = "50;50"
'AddItemメソッドでデータ登録準備
.AddItem ""
'「テーブル1」のデータ範囲を指定し、
'リストボックス1へデータ登録
.List = Range(Range("A5"), _
Cells(Rows.Count, 5).End(xlUp)).Value
End With
'リストボックス2に対する処理
With ListBox2
'リストボックス2も1同様に2列指定
.ColumnCount = 2
'列幅も1と同内容にて指定
.ColumnWidths = "50;50"
End With
End Sub
内容はサンプルコード内に記述しているコメントの通りですが、ListBox1については.MultiSelectを使用することで複数選択を可能としていますが、コメント文で書いてある.ListStyle = fmListStyleOptionを使用するとチェックボックス表示にも変えられます。

次に可視性を良くするために「No.」と「Code」を表示したいので、 .ColumnCount = 2で2列表示とし列幅を.ColumnWidths = “50;50″で指定しています。
後はListBox1にテーブル1のデータを追加するので、AddItemメソッドでデータ登録準備(空データ.AddItem “”)を行い、テーブル1のデータ範囲を指定してListBox1にデータ登録しています。
.List = Range(Range(“A5”), Cells(Rows.Count, 5).End(xlUp)).Valueは始点A5から5列目(E列)の最終入力行を検索させて範囲を指定し、その範囲をデータとして取り込みますので、このような配列データになります。

ListBox2については、2列表示指定と列幅指定をListBox1同様に実行しています。
◆CommandButton1「Data入れ替え」
ここではリスト上の選択データ入れ替え処理を行います。
Private Sub CommandButton1_Click()
'**リスト上選択データの入れ替え処理を行う**
Dim i, nmA, nmB, cnt As Long
Dim DtA, DtB As Variant
With Worksheets("Sample").ListObjects("テーブル1")
'リストボックス1全データに対する処理
For i = 0 To ListBox1.ListCount - 1
'データが選択されている場合の分岐処理
If ListBox1.Selected(i) Then
'選択されたデータ数をカウント
cnt = cnt + 1
Select Case cnt
'1データ目の処理
Case 1
'nmA=テーブルの行番号
nmA = i + 1
'テーブル上の該当データ行を変数DtAに格納
DtA = .ListRows(nmA).Range
'2データ目の処理
Case 2
'nmB=テーブルの行番号
nmB = i + 1
'テーブル上の該当データ行を変数DtBに格納
DtB = .ListRows(nmB).Range
'リストボックス上のデータ選択が2を超えている場合は
'アラート表示⇒終了
Case Is > 2
MsgBox "データ数が多すぎます", vbCritical
End
End Select
End If
Next i
If cnt = 2 Then
'各選択データの入れ替え処理を行い、テーブルへ書き込む
.ListRows(nmA).Range = DtB
.ListRows(nmB).Range = DtA
Else
MsgBox "データ数が不足しています", vbCritical
End
End If
End With
'処理実行後にユーザーフォーム初期処理を実行することで
'テーブルの入れ替え後データをリストボックス1へ反映する
Call UserForm_Initialize
End Sub
リスト上のデータ入れ替えに併せてシート上のテーブル1のデータも入れ替えますので、With Worksheets(“Sample”).ListObjects(“テーブル1”)と指定しておきます。
次にListBox1全体に対する繰り返し処理を行いますが、このような流動的なデータを扱う際はTo ListBox1.ListCountと指定すれば自動的に登録データMax数となるので便利です。
ListBoxのデータは先頭から1で始まるのではなく「0」から始まるので、i=0とする関係上ListBoX1.ListCount-1としています。
リストボックス上のデータが選択されているかどうかは、 If ListBox1.Selected(i) Thenで判定しています。このサンプルでは2つのデータ入れ替えを目論んでいるので、Select Caseを用いて1データ目と2データ目のデータをそれぞれ取込み、データ不足や過多の場合は異常終了という流れにしています。
後は取り込んだデータを用いてテーブル1のデータを書き換え(入れ替え)を.ListRows(nmA).Range = DtB/.ListRows(nmB).Range = DtAで行い、最後に改めてUserForm1の初期処理Call UserForm_Initializeを実行することでListBox1へ書換データを反映させています。
◆CommandButton2「番号振り直し」
入れ替えたデータに対し、番号のみをソートして書き換える処理を行います。
Private Sub CommandButton2_Click()
'**入れ替えデータに対し、番号を振り直す**
Dim i, j, n As Long
Dim rep, Num(100) As Long
'リストボックス2のクリア
ListBox2.Clear
'リストボックス1の全データに対する処理
For i = 0 To ListBox1.ListCount - 1
'リストボックス1の「No.」データを読み込む
Num(i) = ListBox1.List(i, 0)
Next i
'読み込んだ「No.」データに対し並べ替え(ソート)を行う
'先頭行に対し、末尾データから降順で参照しながら
'データの入れ替えを行うことでソートする
For i = 0 To ListBox1.ListCount - 1
For j = ListBox1.ListCount - 1 To i Step -1
If Num(i) > Num(j) Then
rep = Num(i)
Num(i) = Num(j)
Num(j) = rep
End If
Next j
'リストボックス2に対する処理
With ListBox2
'リストボックス1の「No.」データを追加
.AddItem ListBox1.List(i, 0)
'追加した「No.」データをNum(i)に置換
.List(i, 0) = Num(i)
For n = 1 To 4
'リストボックス1の「No.以外」全データを
'リストボックス2に追加
.List(i, n) = ListBox1.List(i, n)
Next n
End With
Next i
Erase Num
End Sub
先ず最初にListBox2.Clearを実行し、データをクリアします。次にListBox1の「No.」データを全てNum(n)配列に読み込みます。
読み込んだ「No.」データに対し、For~Next文を用いてソート処理を行い、ListBox2へ順次書き込んでいきます。本サンプルではListBox1の全データをListBox2に移すので、For n=1 To 4~でListBox2へ追加する処理を行っていますが、ここは必要に応じて適宜変更しても良いと思います。
この処理を実行することで、ListBox2の「No.」が振り直されます。
◆CommandButton3「テーブルへ書込み」
ここではListBox2のデータをシート上のテーブル1に書込みを行います。
Private Sub CommandButton3_Click()
'**リストボックス2のデータをテーブル1へ書き込む**
Dim rng As Range
Dim i As Long
Dim hr As Variant
'テーブル1の見出し位置(セルアドレス)を取得
For Each rng In Worksheets("Sample"). _
ListObjects("テーブル1").HeaderRowRange
If rng.Value = "No." Then
hr = rng.Address(False, False)
Exit For
End If
Next
'セルを選択(見出し位置)
Range(hr).Select
For i = 0 To ListBox1.ListCount - 1
'リストボックス2の「No.」データを順にテーブル1に
'書込み処理を行う
ActiveCell.Offset(i + 1, 0) = ListBox2.List(i, 0)
Next i
'書込み後にユーザーフォーム初期設定を実行して
'リストボックス1に反映させる
Call UserForm_Initialize
End Sub
書込みを行う上でテーブル1の見出し位置をFor~Eachを使って検索しセルアドレスを取得していますが、見出し位置が固定であればRange(**).Selectから始めても良いと思います。
処理としては選択した見出し位置を基点としてOffsetで順に「No.」データをテーブルに書込んでいくものです。その後UserForm1の初期処理を再実行することで、ListBox1にデータを反映させています。
【参考】ListBoxでのマウススクロール有効化について
普通にListBoxを設置した場合はマウススクロールが使えませんので、データ量が多い場合などに不便を感じるものです。使用したい場合は以下のコードをそれぞれUserFormと標準モジュールに追加すれば使用できるようになりますので、ご参考までに。
参考元サイトはこちらです。
≪UserFormへ追加≫
Private Sub ListBox1_MouseMove( _
ByVal Button As Integer, ByVal Shift As Integer, _
ByVal x As Single, ByVal y As Single)
HookListBoxScroll
End Sub
Private Sub ListBox2_MouseMove( _
ByVal Button As Integer, ByVal Shift As Integer, _
ByVal x As Single, ByVal y As Single)
HookListBoxScroll
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
UnhookListBoxScroll
End Sub
≪標準モジュールへ追加≫
Option Explicit
Private Type POINTAPI
x As Long
y As Long
End Type
Private Type MOUSEHOOKSTRUCT
pt As POINTAPI
hwnd As Long
wHitTestCode As Long
dwExtraInfo As Long
End Type
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function GetWindowLong Lib "user32.dll" _
Alias "GetWindowLongA" ( _
ByVal hwnd As Long, _
ByVal nIndex As Long) As Long
Private Declare Function SetWindowsHookEx Lib "user32" _
Alias "SetWindowsHookExA" ( _
ByVal idHook As Long, _
ByVal lpfn As Long, _
ByVal hmod As Long, _
ByVal dwThreadId As Long) As Long
Private Declare Function CallNextHookEx Lib "user32" ( _
ByVal hHook As Long, _
ByVal nCode As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function UnhookWindowsHookEx Lib "user32" ( _
ByVal hHook As Long) As Long
Private Declare Function PostMessage Lib "user32.dll" _
Alias "PostMessageA" ( _
ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Private Declare Function WindowFromPoint Lib "user32" ( _
ByVal xPoint As Long, _
ByVal yPoint As Long) As Long
Private Declare Function GetCursorPos Lib "user32.dll" ( _
ByRef lpPoint As POINTAPI) As Long
Private Const WH_MOUSE_LL As Long = 14
Private Const WM_MOUSEWHEEL As Long = &H20A
Private Const HC_ACTION As Long = 0
Private Const GWL_HINSTANCE As Long = (-6)
Private Const WM_KEYDOWN As Long = &H100
Private Const WM_KEYUP As Long = &H101
Private Const VK_UP As Long = &H26
Private Const VK_DOWN As Long = &H28
Private Const WM_LBUTTONDOWN As Long = &H201
Private mLngMouseHook As Long
Private mListBoxHwnd As Long
Private mbHook As Boolean
Sub HookListBoxScroll()
Dim lngAppInst As Long
Dim hwndUnderCursor As Long
Dim tPT As POINTAPI
GetCursorPos tPT
hwndUnderCursor = WindowFromPoint(tPT.x, tPT.y)
If mListBoxHwnd <> hwndUnderCursor Then
UnhookListBoxScroll
mListBoxHwnd = hwndUnderCursor
lngAppInst = GetWindowLong(mListBoxHwnd, GWL_HINSTANCE)
PostMessage mListBoxHwnd, WM_LBUTTONDOWN, 0&, 0&
If Not mbHook Then
mLngMouseHook = SetWindowsHookEx( _
WH_MOUSE_LL, AddressOf MouseProc, lngAppInst, 0)
mbHook = mLngMouseHook <> 0
End If
End If
End Sub
Sub UnhookListBoxScroll()
If mbHook Then
UnhookWindowsHookEx mLngMouseHook
mLngMouseHook = 0
mListBoxHwnd = 0
mbHook = False
End If
End Sub
Private Function MouseProc( _
ByVal nCode As Long, ByVal wParam As Long, _
ByRef lParam As MOUSEHOOKSTRUCT) As Long
On Error GoTo errH 'Resume Next
If (nCode = HC_ACTION) Then
If WindowFromPoint(lParam.pt.x, lParam.pt.y) = mListBoxHwnd Then
If wParam = WM_MOUSEWHEEL Then
MouseProc = True
If lParam.hwnd > 0 Then
PostMessage mListBoxHwnd, WM_KEYDOWN, VK_UP, 0
Else
PostMessage mListBoxHwnd, WM_KEYDOWN, VK_DOWN, 0
End If
PostMessage mListBoxHwnd, WM_KEYUP, VK_UP, 0
Exit Function
End If
Else
UnhookListBoxScroll
End If
End If
MouseProc = CallNextHookEx( _
mLngMouseHook, nCode, wParam, ByVal lParam)
Exit Function
errH:
UnhookListBoxScroll
End Function
これらを追加することでListBoxでマウスホイールを使ってスクロールできて便利なので、私も今後使って行こうと思います。
3.まとめ
今回はお問い合わせいただいた内容を基に、テーブル上のデータをユーザーフォーム上で扱い、編集することを主としてサンプルを書いています。
各記述の構文などまで書いていくと内容が煩雑で冗長になるので割愛していますから判り難い部分もあるかもしれませんが、何となくTableとListBoxデータの扱い方がサンプルを通じて伝わってくれると嬉しいなと思います。
以上、テーブルのデータをリストボックスで編集する方法についてでした!今回の記事が何かの参考になれば幸いです。
Ryo
