顧客マスタ、受注入力、配送先リスト——住所を扱う業務アプリは多いのに、住所はたいてい手で全部打っています。「東京都千代田区千代田」までを毎回タイプするのは遅く、変換ミスも起きやすく、同じ町名が「ヶ」と「ケ」でブレて名寄せが効かなくなる、という話にもつながります。

EC サイトの入力フォームでは「郵便番号を入れると住所が出る」のが当たり前になりました。同じことを、自社の WinForms / WPF アプリの**表(グリッド)**でもやりたい——というのがこの記事のテーマです。ReoGrid なら、セルの編集が終わった瞬間を捕まえて住所を引き、隣のセルに書き込むだけ。外部 API もクラウドも使わず、オフラインで完結します。

この記事は「表記ゆれで壊れない業務データを作る」シリーズの続きです。入力データの正規化は 全角・半角の正規化、CSV の読み込みは エンコーディングと列の型 も合わせてどうぞ。


全体の流れ

やることは 3 ステップだけです。

  1. 住所辞書を用意する — 日本郵便が無償配布している郵便番号データ(KEN_ALL.CSV)を、起動時に Dictionary へ読み込む
  2. 編集の確定を捕まえる — 郵便番号セルの編集が終わったら AfterCellEdit で検知する
  3. 隣のセルへ書き込む — 引けた住所を都道府県・市区町村・町域のセルへ 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-00011000001 もヒットしません。

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.NarrowASC 相当)。Windows 前提の WinForms / WPF なら追加パッケージなしで使えます。これで 123-4567〒123-4567123 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.CSVDictionary 化する(Shift_JIS は CodePagesEncodingProvider の登録を忘れずに)
  • 辞書を引く前に StrConv + 数字抽出で入力のゆれ(全角・ハイフン・〒)を吸収する
  • 埋めるのは空欄だけにして、番地や建物名を勝手に消さない
  • 外部 API を使わないので、オフラインで速く・安定して動く

住所入力は「毎日何百回も繰り返される地味な操作」です。そこを 1 秒速く・1 回正確にするだけで、入力担当の負担と後工程のデータ品質が大きく変わります。ReoGrid はセル編集イベントとセル操作 API がそろっているので、こうした「業務の手触り」を作り込むのに向いています。

ReoGrid の機能を見る / 30 日トライアルを試す


次に読むもの