顧客マスタ、受注入力、配送先リスト——住所を扱う業務アプリは多いのに、住所はたいてい手で全部打っています。「東京都千代田区千代田」までを毎回タイプするのは遅く、変換ミスも起きやすく、同じ町名が「ヶ」と「ケ」でブレて名寄せが効かなくなる、という話にもつながります。
EC サイトの入力フォームでは「郵便番号を入れると住所が出る」のが当たり前になりました。同じことを、自社の WinForms / WPF アプリの**表(グリッド)**でもやりたい——というのがこの記事のテーマです。ReoGrid なら、セルの編集が終わった瞬間を捕まえて住所を引き、隣のセルに書き込むだけ。外部 API もクラウドも使わず、オフラインで完結します。
この記事は「表記ゆれで壊れない業務データを作る」シリーズの続きです。入力データの正規化は 全角・半角の正規化、CSV の読み込みは エンコーディングと列の型 も合わせてどうぞ。
全体の流れ
やることは 3 ステップだけです。
- 住所辞書を用意する — 日本郵便が無償配布している郵便番号データ(KEN_ALL.CSV)を、起動時に
Dictionaryへ読み込む - 編集の確定を捕まえる — 郵便番号セルの編集が終わったら
AfterCellEditで検知する - 隣のセルへ書き込む — 引けた住所を都道府県・市区町村・町域のセルへ
SetCellDataで反映する
ポイントは、Web API を呼ばないことです。社内ネットワークしかない環境や、オフラインの現場端末でも動きますし、レスポンス待ちのもたつきもありません。郵便番号データは月 1 回更新されるだけなので、アプリに同梱するか共有フォルダに置いておけば十分です。
住所データを用意する
日本郵便が「郵便番号データダウンロード」で配布している KEN_ALL.CSV を使います。1 行が 1 エリアで、必要なのは次の列です(0 始まり)。
| 列 | 内容 | 例 |
|---|---|---|
| 2 | 郵便番号(7 桁) | 1000001 |
| 6 | 都道府県 | 東京都 |
| 7 | 市区町村 | 千代田区 |
| 8 | 町域 | 千代田 |
文字コードは Shift_JIS です。.NET(.NET 8 など)では Shift_JIS を読むのに CodePagesEncodingProvider の登録が必要なので、ここだけ忘れないようにします(CSV 記事で扱ったのと同じ落とし穴です)。
using System.Text;
// .NET Core 以降で Shift_JIS を使うための登録(プログラム起動時に一度だけ)
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// 郵便番号 → (都道府県, 市区町村, 町域)
var zipDict = new Dictionary<string, (string Pref, string City, string Town)>();
var sjis = Encoding.GetEncoding("shift_jis");
foreach (var line in File.ReadLines("KEN_ALL.CSV", sjis))
{
// CSV はダブルクォートで囲まれているので外す
var cols = line.Split(',').Select(c => c.Trim('"')).ToArray();
if (cols.Length < 9) continue;
var zip = cols[2]; // 7 桁の郵便番号
var town = cols[8];
// 「以下に掲載がない場合」などの注記は町域として使わない
if (town.Contains("以下に掲載がない場合")) town = "";
// 同じ郵便番号が複数行ある場合は最初の 1 件を採用(最小実装)
if (!zipDict.ContainsKey(zip))
zipDict[zip] = (cols[6], cols[7], town);
}
これで zipDict["1000001"] から (東京都, 千代田区, 千代田) が引けるようになりました。
KEN_ALL.CSV は、1 つの郵便番号に複数の町域が対応したり、長い町名が複数行に分割されたりする「クセ」があります。上のコードは「最初の 1 件を採用する」最小実装です。1 対多をきちんと扱いたい場合は、
Dictionary<string, List<...>>にして候補を全部持ち、ユーザーに選ばせる UI を足してください。
編集の確定を捕まえる — AfterCellEdit
ReoGrid では、ユーザーがセルの編集を終えた瞬間に AfterCellEdit イベントが発生します。e.Cell で「どのセルが編集されたか」、e.NewData で「入力された値」が分かります。郵便番号の列(ここでは A 列 = 0 列目)だけを対象にします。
using unvell.ReoGrid;
using unvell.ReoGrid.Events;
var sheet = grid.CurrentWorksheet;
const int ColZip = 0; // A 列 = 郵便番号
const int ColPref = 1; // B 列 = 都道府県
const int ColCity = 2; // C 列 = 市区町村
const int ColTown = 3; // D 列 = 町域
sheet.AfterCellEdit += (s, e) =>
{
// 郵便番号の列以外は無視
if (e.Cell.Column != ColZip) return;
if (e.NewData is not string input || string.IsNullOrWhiteSpace(input)) return;
// 「〒」「-」「全角数字」「空白」を取り除いて 7 桁の数字だけにする
var zip = NormalizeZip(input);
if (zip.Length != 7) return; // 7 桁にならなければ何もしない
if (zipDict.TryGetValue(zip, out var addr))
{
int row = e.Cell.Row;
sheet.SetCellData(row, ColPref, addr.Pref);
sheet.SetCellData(row, ColCity, addr.City);
sheet.SetCellData(row, ColTown, addr.Town);
// 入力欄も整形済みの 7 桁に統一しておく
e.NewData = zip;
}
};
e.NewData = zip; の 1 行で、ユーザーが 〒100-0001 や全角で打っても、確定時にセルへ残るのは 1000001 に揃います。受け取る側が黙って整える——表記ゆれを溜めないコツは前回の記事と同じです。
入力のゆれを吸収する
郵便番号は、ハイフン付き・全角・「〒」付きなど、人によって打ち方がバラバラです。辞書を引く前にここを揃えておかないと、100-0001 も 1000001 もヒットしません。
using Microsoft.VisualBasic; // StrConv(WinForms / WPF なら追加参照は不要)
static string NormalizeZip(string input)
{
// 全角数字 → 半角に寄せる(ASC 相当)
var s = Strings.StrConv(input, VbStrConv.Narrow, 0x411);
// 数字以外(〒・ハイフン・空白など)をすべて除去
return new string(s.Where(char.IsDigit).ToArray());
}
Strings.StrConv は ReoGrid の ASC / JIS と同じ全角・半角変換を C# 側で行う標準 API です(VbStrConv.Narrow が ASC 相当)。Windows 前提の WinForms / WPF なら追加パッケージなしで使えます。これで 123-4567・〒123-4567・123 4567 のどれを打っても 1234567 に揃い、辞書が確実に引けます。
番地は消さない — 既存の入力を守る
住所の自動入力で地味に大事なのが、**「都道府県〜町域だけを埋めて、その先(番地・建物名)は触らない」**ことです。番地は別の列(E 列など)に分けておけば、郵便番号を打ち直しても番地が消える事故は起きません。
もし「町域+番地」を 1 つのセルにまとめたい運用なら、上書きではなく追記にします。
// D 列 = 「町域 + 番地」を 1 セルにまとめる運用の場合
var current = sheet.GetCellData<string>(e.Cell.Row, ColTown) ?? "";
// すでに番地まで入っていれば触らない/空のときだけ町域を補完する
if (string.IsNullOrEmpty(current))
sheet.SetCellData(e.Cell.Row, ColTown, addr.Town);
自動入力は「埋めてくれて当然」と思われる一方、「勝手に消すと激怒される」機能です。埋めるのは空欄だけ、を原則にすると安全です。
見つからないときの扱い
存在しない郵便番号や、データ未収録の番号もあります。黙って何もしないより、ひと言フィードバックがあると親切です。
if (zipDict.TryGetValue(zip, out var addr))
{
// ...(住所を埋める)
}
else
{
// 見つからなければ住所列をクリアして、セルに薄く注記する
sheet.SetCellData(e.Cell.Row, ColPref, "");
sheet.SetCellData(e.Cell.Row, ColCity, "");
sheet.SetCellData(e.Cell.Row, ColTown, "該当する住所が見つかりません");
}
実運用では、見つからないセルに背景色を付ける(条件付き書式)と、入力者がひと目で気づけます。
なぜ「自社アプリ側」で持つのか
住所オートコンプリートは Web API でも実現できますが、業務アプリではローカルに辞書を持つほうが向く場面が多いです。
- オフラインで動く — 工場・倉庫・店舗の端末や、社内ネットワーク限定の環境でも止まらない
- 速い —
Dictionary参照は一瞬。API のレスポンス待ちでセル移動がもたつかない - 依存とコストがない — 外部サービスの障害・課金・利用規約に縛られない
- 更新が楽 — 郵便番号データは月 1 回。アプリ同梱か共有フォルダ更新で十分
そして、入力された住所はあくまで自社のグリッド(ReoGrid)のセルデータなので、そのまま XLSX として保存したり、CSV に書き出したり、DataTable 経由で DB に流し込むのも自由です。「入力 → 表 → 保存」が 1 つのコンポーネントで完結します。
まとめ
- 郵便番号 → 住所の自動入力は、
AfterCellEditで編集を捕まえ、辞書を引いて隣のセルへ書くだけで作れる - 住所辞書は日本郵便の KEN_ALL.CSV を
Dictionary化する(Shift_JIS はCodePagesEncodingProviderの登録を忘れずに) - 辞書を引く前に
StrConv+ 数字抽出で入力のゆれ(全角・ハイフン・〒)を吸収する - 埋めるのは空欄だけにして、番地や建物名を勝手に消さない
- 外部 API を使わないので、オフラインで速く・安定して動く
住所入力は「毎日何百回も繰り返される地味な操作」です。そこを 1 秒速く・1 回正確にするだけで、入力担当の負担と後工程のデータ品質が大きく変わります。ReoGrid はセル編集イベントとセル操作 API がそろっているので、こうした「業務の手触り」を作り込むのに向いています。
