【VB.NET】住所を自動入力する機能について

## やりたいこと
郵便番号を入力した時に、テキストボックスに自動で都道府県、市町村、町名を入力したい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/79bf63de-af6c-1a56-1486-2fe15134d6a4.png)

↓ 以下実装方法

<br>

## 全国の住所が書かれたcsvファイルを取得する
https://www.post.japanpost.jp/zipcode/dl/utf-zip.html
日本郵政のサイトでUTF-8のcsvファイルを取得する。

<br>

## csvファイルのいらない列を削除する
csvファイルを取得した直後は、以下のような内容が書かれている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/9bad02e8-8999-ee6a-655c-1e66ef9e258b.png)
欲しいところは郵便番号～町名　までなので、他の文字列を削除する。

### csvファイルをExcelにインポートする
- 文字コードが`Unicode(UTF-8)`になっていることを確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/51206f1d-fa1e-fc48-5271-21bbda4b1814.png)

<br>

-  郵便番号の列はtext型に変更する。（0で始まる郵便番号の場合、0が消えてしまうため。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/60864af3-12ab-7177-85f7-c3a256cc5990.png)

### いらない列を削除する
- A、B、H～最後の列を削除する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/36e1d9c6-881d-7326-eebb-d6cd66ae9ecd.png)

<br>

- ヘッダー列もいらないので削除する。
テーブルデザインタブ > ツール > 範囲に変換　を選択後、ヘッダー列を削除する。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/1e34dd87-fc31-0e33-5c45-d3186f4bc644.png)

<br>

### csvファイルとして保存する
ファイルの種類は、`CSV UTF-8（コンマ区切り）（*.csv）` を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/e001b0ac-89fc-0109-5ade-3b129e7cb45b.png)

<br>

## VisualStudioでcsvファイルを埋め込みリソースにする
ソリューションエクスプローラーで、Resourcesフォルダを選択する。
右クリックで　`追加 > 既存の項目` を選択しcsvファイルを追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/ea536c03-1186-8d0a-0f44-bed7a7349a4b.png)

<br>

追加したファイルを選択し、プロパティでビルドアクションを`埋め込みリソース`にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/9d747ed3-ec1f-ce62-edf3-dbe2ce3af7f6.png)

<br>

## 埋め込みリソース（csvファイル）を読み込む
```vb
Imports System.IO.StreamReader
Imports System.Text.Encoding
Public Class Form1
	Private resourceName As String = "manageBusinessCardApp.test.csv"
	Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' 埋め込んだcsvファイルを取得する
        ' 現在のコードを実行しているアセンブリを取得
        Dim assembly As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly()
        ' 埋め込みリソースを取得する
        Dim sr As New System.IO.StreamReader(assembly.GetManifestResourceStream(resourceName), System.Text.Encoding.GetEncoding("UTF-8"))    
    End Sub
End Class
```
- `resourceName`：リソース(csvファイル)の名前空間を指定。（基本的には`プロジェクトの名前空間`.`ファイル名`）
- `StreamReader()`：csvファイルの読み込み、値の取得に便利な関数が多い。
`- GetManifestResourceStream()`：埋め込みリソースを読み込むための関数
- `System.Text.Encoding.GetEncoding("UTF-8")`：エンコードは必ず指定する。（指定しないと文字化けが発生する。）

<br>

## 埋め込みリソース（csvファイル）をDataTableに変換する
``` vb
Public Class Form1
    ' :
    ' DataTableのカラム名
	Public Enum KenColumnName
		POST_CODE
		PREFECTURE_KANA
		MUNICIPALITIES_KANA
		TOWN_KANA
		PREFECTURE
		MUNICIPALITIES
		TOWN
	End Enum
 
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' :
    	Dim dt As New DataTable
    	dt.Columns.Add(KenColumnName.POST_CODE)
    	dt.Columns.Add(KenColumnName.PREFECTURE_KANA)
    	dt.Columns.Add(KenColumnName.MUNICIPALITIES_KANA)
    	dt.Columns.Add(KenColumnName.TOWN_KANA)
    	dt.Columns.Add(KenColumnName.PREFECTURE)
    	dt.Columns.Add(KenColumnName.MUNICIPALITIES)
    	dt.Columns.Add(KenColumnName.TOWN)
    
    	Dim dtColCount As Integer = dt.Columns.Count()          ' DataTableの列数
    
    	' csvファイルを最後まで読み取る
    	While sr.Peek >= 0
    		Dim line As String() = sr.ReadLine.Split(","c)      ' 1行ずつ読み取る。カンマを区切り文字にして配列に格納する
    		Dim elementCount As Integer = 0                     ' dt作成時の要素数カウント用
    		Dim row As DataRow = dt.NewRow()
    		For Each item As String In line
    			row(elementCount) = item
    			elementCount += 1
    			If elementCount = dtColCount Then
    				dt.Rows.Add(row)                            ' dtに行を追加
    				dt.NewRow()                                 ' 新しい行を作成
    				Exit For
    			End If
    		Next item
    	End While
    	dt.AcceptChanges()
    	' ファイルを閉じる
    	sr.Close()
        
        ' DGVに表示する
        Me.DataGridView1.DataSource = dt
    End Sub
End Class
```
- `sr.Peek`：コンマ区切りの文字列がなくなるまでループする
- `sr.ReadLine.Split(","c)`：1行読み取り、コンマ区切りの文字列をline配列に格納する。
- line配列からDataTableに値を格納する仕組み：
    - DataTableの各セルに値を追加するたびに、`elementCount`をインクリメントする。
    - `elementCount`の値がDataTableの列数と同じになったら、新しい行を追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323019/1981bb83-9832-46dd-dde1-b9702c6523cb.png)
- `dt.AcceptChanges()`：DataTableの変更を確定する。

## テキストボックスに住所を表示する
```vb
Dim strPostCode As String = TextBoxPostCode.Text
Dim strAddress As String = ""
For Each row As DataRow In dt.Rows
	If row.Item(KenColumnName.POST_CODE).ToString = strPostCode Then
		strAddress = row.Item(KenColumnName.PREFECTURE)
		strAddress &= row.Item(KenColumnName.MUNICIPALITIES)
		strAddress &= row.Item(KenColumnName.TOWN)
		TextBox5.Text = strAddress
	End If
Next
```
- if文の仕組み
    - 入力した郵便番号とDataTable内の郵便番号が一致した場合
    - 都道府県、市町村、町名を連結してテキストボックスに表示する。

<br>

## すべてのコード
```vb
Imports System.IO.StreamReader
Imports System.Text.Encoding
Public Class Form1
	Public Enum KenColumnName
		POST_CODE
		PREFECTURE_KANA
		MUNICIPALITIES_KANA
		TOWN_KANA
		PREFECTURE
		MUNICIPALITIES
		TOWN
	End Enum
	Private resourceName As String = "manageBusinessCardApp.utf_ken_all_new.csv"
 
	Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
		' 埋め込んだcsvファイルを取得する
		Dim assembly As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly()               ' 現在のコードを実行しているアセンブリを取得
	
		Dim sr As New System.IO.StreamReader(assembly.GetManifestResourceStream(resourceName), System.Text.Encoding.GetEncoding("UTF-8"))
		Dim dt As New DataTable
		dt.Columns.Add(KenColumnName.POST_CODE)
		dt.Columns.Add(KenColumnName.PREFECTURE_KANA)
		dt.Columns.Add(KenColumnName.MUNICIPALITIES_KANA)
		dt.Columns.Add(KenColumnName.TOWN_KANA)
		dt.Columns.Add(KenColumnName.PREFECTURE)
		dt.Columns.Add(KenColumnName.MUNICIPALITIES)
		dt.Columns.Add(KenColumnName.TOWN)

		Dim dtColCount As Integer = dt.Columns.Count()          ' dtの列数

		' csvファイルを最後まで読み取る
		While sr.Peek >= 0
			Dim line As String() = sr.ReadLine.Split(","c)      ' 1行ずつ読み取る。カンマを区切り文字にして配列に格納する
			Dim elementCount As Integer = 0                     ' dt作成時の要素数カウント用
			Dim row As DataRow = dt.NewRow()
			For Each item As String In line
				row(elementCount) = item
				elementCount += 1
				If elementCount = dtColCount Then
					dt.Rows.Add(row)                            ' dtに行を追加
					dt.NewRow()                                 ' 新しい行を作成
					Exit For
				End If
			Next item
		End While
		dt.AcceptChanges()
		' ファイルを閉じる
		sr.Close()

		' DGVに表示する
		Me.DataGridView1.DataSource = dt

		' テキストボックスに住所を表示する
		Dim strPostCode As String = TextBoxPostCode.Text
		Dim strAddress As String = ""
		For Each row As DataRow In dt.Rows
			If row.Item(KenColumnName.POST_CODE).ToString = strPostCode Then
				strAddress = row.Item(KenColumnName.PREFECTURE)
				strAddress &= row.Item(KenColumnName.MUNICIPALITIES)
				strAddress &= row.Item(KenColumnName.TOWN)
				TextBox5.Text = strAddress
			End If
		Next
	End Sub
End Class
```

<br>

## 番外編
csvファイルを埋め込みリソースではなく、csvファイルとして扱う場合は、`TextFieldParser`クラスを使用すると簡単にDataTableにすることが可能。
https://aonopage.com/text-field-parser/
https://dobon.net/vb/dotnet/file/readcsvfile.html

<br>

## 参考サイト
### Excel関連
https://pcmatic.jp/faq/windows/10/#:~:text=CSV%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%86%85%E3%81%AE%E6%97%A5%E6%9C%AC,%E3%81%95%E3%81%9B%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82

https://www.asobou.co.jp/blog/bussiness/csv-excel#:~:text=%E3%81%93%E3%81%AE%E7%8F%BE%E8%B1%A1%E3%81%AFExcel%E3%81%8C,%E7%9C%81%E3%81%8B%E3%82%8C%E3%81%A6%E3%81%97%E3%81%BE%E3%81%84%E3%81%BE%E3%81%99%E3%80%82

### VB.NET関連
https://dreamparadaisu.com/visualbasic_11/

https://dobon.net/vb/dotnet/programing/bitmapresource.html

https://itsakura.com/vbnet-datatable