はじめに
前回、Power Automateと組み合わせることで「処理の完了通知・エラー通知」は実現できると解説しました。ただし、その方式では処理中のリアルタイム進捗表示や途中キャンセルまでは代替できません。
今回は、この2つの課題を掘り下げます。進捗表示は前回同様ワークシート上のセルを仲介にした間接的な仕組みで実現しますが、キャンセルについては実際に検証した結果、スクリプトの実行ボタンに標準搭載されている「停止」機能を使うことで、VBAのキャンセルボタンに近い形の中断が実現できることが分かりました。第4回で作成した転記エンジンのコードをベースに、両方の実装方法と「どこまで実用に耐えるか」を検証します。
前回までのおさらい
| 実現したいこと | Office Scripts単体 | Power Automate併用 | 今回のセル連携パターン |
|---|---|---|---|
| 完了・エラー通知 | ❌ | ✅ | – |
| 処理中の進捗表示 | ❌ | △(間接的に可能) | – |
| 処理中のキャンセル | ❌ | △(間接的に可能) | ✅(実行状態バーの「停止」ボタン) |
先に結論を言うと: 進捗表示は依然として「本物のリアルタイムUI」ではなく、ワークシートのセルを介した間接的な表示にとどまります。一方でキャンセルについては、スクリプトを「ワークシートのボタン」から実行した場合に限り、実行中に表示される実行状態バーの「停止」ボタンで実際に処理を中断できることを検証で確認しました。VBAのキャンセルボタンに近い体験が得られますが、Power Automate経由の実行など、ボタンを使わないケースでは使えません。その場合の代替として、セルを使った協調的キャンセルも後述します。
実現方針の全体像
制御用シート(例:「制御」シート)を1枚追加
└─ A1セル:進捗状況(%・件数を書き込む)
メインループの中で定期的にA1へ書き込む
【キャンセル】2つの方法を用途に応じて使い分ける
① スクリプトの実行ボタン(推奨)
→ 実行状態バーの「停止」ボタンでネイティブに中断できる
② セルを使った協調的キャンセル(補足・代替手段)
→ B1セル(TRUE/FALSE)を定期的に読み取り、TRUEならループを中断する
→ Power Automate経由の実行など、①のボタンが使えない場合に使う
進捗表示は「書き込むだけ」というシンプルな仕組みです。キャンセルは、まず①のネイティブな停止ボタンを基本とし、それが使えない実行方法のときだけ②の協調的キャンセルで補うという位置づけになります。
準備:制御用シートを追加する
まず、ブックに「制御」という名前のシートを1枚追加し、以下のようにセルを用意します。
| セル | 用途 | 初期値 |
|---|---|---|
| A1 | 進捗表示(文字列) | "待機中" |
| B1 | キャンセルフラグ(真偽値) | FALSE |
このシートは、貼付元・貼付先とは別の「状態確認専用シート」として使います。ユーザーはこのシートを開いておくことで進捗を確認できます。B1セルは、後述する協調的キャンセル(スクリプト実行ボタンが使えない場合の代替手段)を使うときにのみ必要です。ネイティブの停止ボタンだけを使う場合はA1セルの用意だけで構いません。
Step 1:進捗表示を実装する
第4回のメインループに、進捗書き込み処理を追加します。
function main(workbook: ExcelScript.Workbook) {
// ~ 前回までの準備処理(省略)~
const ctrlSheet = workbook.getWorksheet("制御");
// メインループ = 前回記事の For tgetTmpR = wRow To workShEndR
for (let tgetTmpR = wRow; tgetTmpR <= workShEndR; tgetTmpR++) {
// ~ ID照合・転記処理(省略、前回記事と同じ)~
lngHcount++;
// ★ 進捗表示:1000件ごとに制御シートのA1へ書き込む
if (tgetTmpR % 1000 === 0) {
const percent = Math.round(((tgetTmpR - wRow + 1) / (workShEndR - wRow + 1)) * 100);
ctrlSheet.getRange("A1").setValue(
`${percent}%完了(${tgetTmpR - wRow + 1}/${workShEndR - wRow + 1}件・HIT ${lngHcount}件)`
);
}
}
ctrlSheet.getRange("A1").setValue(`完了!【データ更新件数: ${lngHcount} 件】`);
}console.logは実行後にまとめて表示されるだけですが、ワークシートのセルへの書き込みは実行中に随時反映されるため、進捗の「見える化」としてはこちらの方が実用的です。
どこまでリアルタイムに見えるか
| 実行方法 | 見え方 |
|---|---|
| Excel Onlineの「Automate」タブから自分で実行 | 自分の編集セッションで実行されるため、A1セルの更新がほぼそのまま画面に反映される |
| Power Automate経由で実行 | バックグラウンドセッションでの実行のため、ファイルを開いている別のユーザー・自分自身に共同編集の更新として見える場合があるが、反映タイミングは保証されない |
Excel Onlineから直接実行するケース(VBAの「シートボタンを押して実行」に一番近い使い方)であれば、進捗表示の実用性は高いといえます。
もっと視覚的に見せる:文字バー・データバーの活用
ここまでは「◯%完了」という文字列表示でしたが、視覚的な%バーに近づける方法も2通りあります。
方式1:文字を繰り返して疑似バーを作る
const barLength = 20; // バー全体の長さ
const filled = Math.floor((percent / 100) * barLength);
const bar = "■".repeat(filled) + "□".repeat(barLength - filled);
ctrlSheet.getRange("A1").setValue(`${bar} ${percent}%`);
実装は簡単ですが、見た目はフォント依存です。等幅フォントでないと■の間隔がガタつくことがあり、セル幅を固定しておかないと折り返しで崩れる点にも注意してください。
方式2:Excelネイティブの「データバー」条件付き書式を使う
セルにあらかじめデータバー(条件付き書式)を設定しておき、スクリプト側は数値を書き込むだけにする方法です。実際に検証した構成では、1つのセルに%を書き込むのではなく、「HIT件数」「処理件数」「総件数」の3つの数値セルを縦に並べ、その範囲にデータバーを設定しました。
| セル | 用途 |
|---|---|
| A2 | ラベル文字列「HIT件数」 |
| B2 | HIT件数の値 |
| A3 | ラベル文字列「処理件数」 |
| B3 | 処理件数の値 |
| A4 | ラベル文字列「総件数」 |
| B4 | 総件数の値(データバーの基準となる最大値) |
// B2:B4の3セルに数値を書き込むだけ
ctrlSheet.getRange("B2").setValue(lngHcount); // HIT件数
ctrlSheet.getRange("B3").setValue(tgetTmpR - wRow + 1); // 処理件数
ctrlSheet.getRange("B4").setValue(workShEndR - wRow + 1); // 総件数B2:B4の範囲にあらかじめデータバーを設定しておくと、総件数を基準にHIT件数・処理件数のバーが相対的な長さで描画され、「全体に対してどこまで進んでいるか」が一目で分かるようになります。この条件付き書式はaddConditionalFormat(ConditionalFormatType.dataBar)でスクリプトから設定することも可能です。
| 比較項目 | ①文字の繰り返し | ②データバー(条件付き書式) |
|---|---|---|
| 見た目の滑らかさ | フォント依存でガタつくことがある | Excelネイティブの滑らかなグラデーション |
| 実装の手間 | 文字列を組み立てるロジックが必要 | 数値を書き込むだけ(書式は事前設定) |
| 事前準備 | 不要 | セルにデータバー書式を一度設定しておく必要あり |
| テキストとの併記 | バーと%を同じセルに表示しやすい | 数値と視覚バーが同じセルに重なる(%表示は別セルが必要な場合あり) |
まとめると: VBAのプログレスバーそのものは再現できませんが、文字バーやデータバーを使えば「数値だけの表示」よりもかなり視覚的な進捗表示に近づけられます。
Step 2:スクリプトの実行ボタンを設置してネイティブに中断する
ここからがキャンセルの本題です。結論から言うと、スクリプトをワークシート上のボタンから実行すると、実行中に表示される「実行状態」バーに「停止」ボタンが表示され、これをクリックすると実際に処理を中断できます。VBAのキャンセルボタンにもっとも近い、ネイティブな中断手段です。
ボタンの設置手順
- スクリプトの編集画面を開き、「このスクリプトを共有」セクションまでスクロールする
- 「ブックに関連付ける」のトグルをオンにする
- 「ワークシートにボタンを追加」を選択する
- シート上に配置されたボタンをクリックすると、そのスクリプトが実行される

実行中に表示される「停止」ボタン
ボタンをクリックしてスクリプトが実行されると、画面上部に以下のような実行状態バーが表示されます。

この「停止」ボタンをクリックすると、実行中のスクリプトが実際に中断されます。第4回の転記エンジンのように大量データをループ処理するスクリプトでも、途中でこのボタンを押せばその場で止められることを確認しました。
- この停止ボタンが使えるのは、スクリプトをワークシートのボタンから実行した場合です。Power Automate経由の実行(第5回)ではこの実行状態バー自体が表示されないため、使えません
- 停止した時点までの書き込み(転記結果や進捗セルの値)はそのまま残ります。処理を再開する場合は、途中の行から再実行できるような設計(第4回で触れたN1セルによる開始行の上書き指定など)と組み合わせるとよいでしょう
- 停止のタイミングは「停止ボタンをクリックした瞬間」に近く、次のチェックポイントまで待つ必要がある後述の協調的キャンセルよりも反応が速いです
Step 1のコードをそのままボタンに割り当てる
特別なコードの変更は不要です。付録に掲載している完全なコードをそのままスクリプトとして保存し、上記の手順でボタンを設置すれば、進捗表示とネイティブなキャンセルの両方が使える状態になります。
Step 3:セルを使った協調的キャンセル(補足・代替手段)
前述のとおり、通常はStep 2のネイティブな停止ボタンで十分です。ここから解説するセルを使ったキャンセルは、Power Automate経由の実行など、停止ボタンが表示されない実行方法のときの代替手段として押さえておいておきましょう。
同じループに、キャンセルフラグの確認処理を追加します。
for (let tgetTmpR = wRow; tgetTmpR <= workShEndR; tgetTmpR++) {
// ★ キャンセル確認:1000件ごとにB1を読み取る
if (tgetTmpR % 1000 === 0) {
const cancelFlag = ctrlSheet.getRange("B1").getValue();
if (cancelFlag === true) {
ctrlSheet.getRange("A1").setValue(
`中断しました(${tgetTmpR - wRow + 1}件処理・HIT ${lngHcount}件時点)`
);
console.log("ユーザーの指示により中断しました");
return; // ここでスクリプトを終了する
}
}
// ~ ID照合・転記処理(省略)~
}ユーザーが処理を中断したい場合、このコードでは「制御」シートのB1セルに直接TRUEを入力することで、次のチェックタイミングでループが終了します。
成立条件と限界
| 項目 | 内容 |
|---|---|
| 反応タイミング | 即時ではなく、次のチェック(例:1000件ごと)まで待つ |
| フラグの設定方法 | Excel Online上で直接セルを編集、または別のスクリプト・Power Automateフローからセルを書き換える |
| VBAとの違い | VBAのDoEventsは毎ループでイベント処理を挟めるが、Office Scriptsに相当する仕組みはなく、チェック頻度を上げるほどホスト呼び出し回数が増えて処理速度が落ちる |
つまり、Step 2のネイティブな停止ボタンのような即時性はなく、次のチェックポイントまで待つ緩やかな中断という位置づけです。スクリプト実行ボタンが使える環境では、基本的にはStep 2を優先してください。
チェック頻度のトレードオフ
進捗表示・キャンセル確認の頻度(何件ごとにチェックするか)は、以下のトレードオフで決めます。
| チェック頻度 | 進捗の見え方・反応速度 | 処理速度への影響 |
|---|---|---|
| 100件ごと | 細かく反映される・中断も早く反応 | セル読み書きの回数が増え、全体処理が遅くなる |
| 1000件ごと | ある程度まとまった間隔で反映 | 影響は小さい |
| 10000件ごと | ほぼ気付けない | 影響はほぼない |
getValue・setValueのような個別セルへのアクセスは、範囲をまとめて読み書きするgetValues・setValuesに比べてホスト呼び出しのコストが高くなります。進捗・キャンセル用のセルアクセスも例外ではないため、データ件数や許容できる処理時間に応じてチェック件数を調整するのが現実的です。
チェック間隔を自動計算・手動指定できるようにする
固定値の1000をそのまま使うと、データが少ない場合は更新回数が少なすぎ、逆に非常に多い場合は更新頻度が高くなりすぎることがあります。そこで、総件数から自動計算しつつ、制御シートのC1セルで手動指定も可能にする方式に改善できます。
// チェック間隔を決定(1~1000件の範囲)
// ・制御シートのC1に数値が入っていればそれを優先(手動指定)
// ・未入力の場合はデータ量から自動計算(全体で約50回更新される間隔)
const totalRows = workShEndR - wRow + 1;
const manualInterval = ctrlSheet.getRange("C1").getValue();
const checkInterval = typeof manualInterval === "number" && manualInterval > 0
? Math.min(1000, Math.max(1, Math.round(manualInterval)))
: Math.min(1000, Math.max(1, Math.ceil(totalRows / 50)));
判定も、絶対行番号(tgetTmpR)ではなく処理済み件数を基準にすると、間隔が小さい場合でも意図通りに動作します。
const processedCount = tgetTmpR - wRow + 1;
if (processedCount % checkInterval === 0) {
// ここに進捗表示・キャンセル確認の処理
}| データ量 | 自動計算されるチェック間隔 |
|---|---|
| 100件 | 2件ごと(100÷50) |
| 5,000件 | 100件ごと |
| 100,000件 | 1000件ごと(上限でクランプ) |
| 30件 | 1件ごと(下限でクランプ) |
データ量に応じて更新回数がほぼ一定になるため、「小さいデータでは頻繁に、大きいデータでは間隔を空けて」チェックが行われ、処理速度への影響を一定に保てます。このcheckIntervalとprocessedCountは、Step 1の進捗表示だけでなくStep 3の協調的キャンセル(B1セルの確認)にも同じように適用できます。
できること・できないことの整理
| 実現したいこと | 今回の方法 |
|---|---|
| 処理件数・進捗率の可視化 | ✅ 制御シートのセルで表示可能 |
| キャンセルボタンのクリックによる即時停止 | ✅ スクリプト実行ボタンの「停止」で可能(ボタン実行時のみ) |
| 処理中のユーザーによる中断指示(ボタンが使えない場合) | △ フラグセルへの入力で代替可能(即時ではない) |
| VBAのプログレスバーのような視覚的な%バー | △ 文字バー・データバーで疑似再現可能(VBA本来のUIとは異なる) |
| Power Automate実行中の中断指示 | ❌ 実行状態バー自体が表示されないため停止ボタンは使えない。フラグセルによる代替のみ |
よくある質問・エラー対処
Q. 進捗セルを更新しても画面に反映されない
→ Power Automate経由で実行している場合、実行セッションと閲覧セッションが分かれているため、反映が遅れることがあります。確実に見たい場合は、Excel Onlineの「Automate」タブから自分のセッションで直接実行してください。
Q. B1にTRUEと入力しても処理が止まらない
→ チェック件数(今回の例では1000件ごと)に達するまでは判定されません。データ件数が少ない場合はチェック間隔を短くする(例:100件ごと)ことで反応を早められますが、処理速度とのトレードオフになります。
Q. 進捗表示用のシートは削除してもいい?
→ スクリプト実行時に「制御」シートが存在しないとエラーになります。削除する場合はコード内のシート名参照も含めて見直してください。
Q. 実行状態バーに「停止」ボタンが表示されない
→ スクリプトをワークシートのボタンから実行していない(コードエディタの「実行」から直接実行している、またはPower Automate経由で実行している)可能性があります。「停止」ボタンが使えるのは、スクリプトを共有して「ワークシートにボタンを追加」した状態で、そのボタンから実行した場合のみです。
Q. 「停止」ボタンを押すと、書き込み中のデータはどうなる?
→ 停止した時点までにセルへ書き込まれた内容(転記結果・進捗セルの値)はそのまま残ります。中途半端な状態でデータが残るため、再実行する際は処理済みの範囲と未処理の範囲を混同しないよう、テスト環境で挙動を確認してから本番データに使うことをおすすめします。
まとめ
- Office ScriptsにはUserForm相当のAPIがないため、進捗表示はワークシートのセルを仲介にした間接的な方法でのみ代替できる
- 進捗表示はセルへの書き込みで実現できるが、Excel Onlineから直接実行した場合に最も実用的に機能する
- 「視覚的な%バー」は文字の繰り返しや、HIT件数・処理件数・総件数の3セルにデータバー(条件付き書式)を組み合わせることでかなり近づけられる
- キャンセルは、スクリプトをワークシートのボタンから実行すれば、実行状態バーの「停止」ボタンでネイティブに即時中断できる(VBAのキャンセルボタンにもっとも近い方法)
- Power Automate経由の実行など、停止ボタンが表示されない場合に限り、セルのフラグを定期的に読み取る「協調的キャンセル」を代替手段として使う
- 協調的キャンセルはチェック頻度を上げるほど反応は良くなるが、処理速度とのトレードオフがある
関連記事
- 【Web版Excel】VBAをOffice Scriptsに移行する方法
- 【保存版】VBA→Office Scripts 書換チートシート
- 生成AIでVBAをOffice Scriptsに変換できるか?
- 【検証】生成AIはVBAをOffice Scriptsに変換できるか?
- 【Office Scripts×Power Automate】UserFormの代替で完了通知を自動化する
付録:Step 1(進捗表示)の完全なコード
本文中のStep 1のコードは説明のため一部を省略していました。第4回の転記エンジン全体に、進捗表示(A1)・データバー用の数値書き込み(B2:B4)・チェック間隔の自動計算/手動指定(C1)を組み込んだ、コピペでそのまま使える完全版は以下のとおりです(貼付元シートの取得は、第5回で確認したgetActiveWorksheet()の落とし穴を避けるためgetWorksheet("貼付元")で直接指定しています)。
function main(workbook: ExcelScript.Workbook) {
const ctrlSheet = workbook.getWorksheet("制御");
// 貼付元シート(実行シート)= VBAの prefSh
// ★ getActiveWorksheet()は実行時のアクティブシートに依存するため使わない(第5回参照)
const prefSh = workbook.getWorksheet("貼付元");
// K1から貼付先シート名を取得 = VBAの sName
const sName = prefSh.getRange("K1").getValue() as string;
const workSh = workbook.getWorksheet(sName);
if (!workSh) {
console.log(`シート "${sName}" が見つかりません`);
return;
}
// ▼マーク列の自動検出 = VBAの WorksheetFunction.Match("▼", ...)
const ptCol = findValueInRow(prefSh, 1, "▼");
const wtCol = findValueInRow(workSh, 1, "▼");
if (ptCol === -1 || wtCol === -1) {
console.log("▼マークが見つかりません(2行目を確認してください)");
return;
}
// マッピング列数の取得 = VBAの WorksheetFunction.Max(...)
const pMCol = getMaxInRow(prefSh, 1);
const wMCol = getMaxInRow(workSh, 1);
if (pMCol !== wMCol) {
console.log("引き当てる列の数が不整合のため中止します!");
return;
}
// データ開始行の取得 = VBAの prefSh.Range("G1") / workSh.Range("G1")
const pRow = prefSh.getRange("G1").getValue() as number;
let wRow = workSh.getRange("G1").getValue() as number;
// 列マッピング配列の構築 = VBAの pCol() / wCol()
const pCol: number[] = [];
let pFlgCol = 0;
for (let i = 1; i <= pMCol; i++) {
const col = findValueInRow(prefSh, 1, i);
pCol.push(col);
if (i > 1 && col !== pCol[i - 2] + 1) pFlgCol = 1;
}
const wCol: number[] = [];
let wFlgCol = 0;
for (let i = 1; i <= wMCol; i++) {
const col = findValueInRow(workSh, 1, i);
wCol.push(col);
if (i > 1 && col !== wCol[i - 2] + 1) wFlgCol = 1;
}
// 最終行の取得 = VBAの Cells(Rows.Count, col).End(xlUp).Row
const workShEndR = getLastRow(workSh, wtCol);
const prefShEndR = getLastRow(prefSh, ptCol);
// ID照合用にターゲット列を配列として一括取得
const tgetRng = prefSh
.getRangeByIndexes(pRow - 1, ptCol, prefShEndR - pRow + 1, 1)
.getValues();
// 開始行の上書き指定(N1セル)= VBAの再開始行機能
const n1Val = prefSh.getRange("N1").getValue();
if (typeof n1Val === "number" && n1Val > wRow) wRow = n1Val;
let lngHcount = 0;
// チェック間隔を決定(1~1000件の範囲)
// ・制御シートのC1に数値が入っていればそれを優先(手動指定)
// ・未入力の場合はデータ量から自動計算(全体で約50回更新される間隔)
const totalRows = workShEndR - wRow + 1;
const manualInterval = ctrlSheet.getRange("C1").getValue();
const checkInterval = typeof manualInterval === "number" && manualInterval > 0
? Math.min(1000, Math.max(1, Math.round(manualInterval)))
: Math.min(1000, Math.max(1, Math.ceil(totalRows / 50)));
// メインループ = VBAの For tgetTmpR = wRow To workShEndR
for (let tgetTmpR = wRow; tgetTmpR <= workShEndR; tgetTmpR++) {
const tmpStr = workSh
.getRangeByIndexes(tgetTmpR - 1, wtCol, 1, 1)
.getValue();
// ID照合 = VBAの WorksheetFunction.Match(tmpStr, tgetRng, 0)
let matchRng = -1;
for (let r = 0; r < tgetRng.length; r++) {
if (tgetRng[r][0] === tmpStr) {
matchRng = r + pRow;
break;
}
}
if (matchRng === -1) continue;
// 転記元データの取得
const myArray: (string | number | boolean)[] = [];
if (pFlgCol === 1) {
for (let i = 0; i < pMCol; i++) {
myArray.push(
prefSh.getRangeByIndexes(matchRng - 1, pCol[i], 1, 1)
.getValue() as string | number | boolean
);
}
} else {
const vals = prefSh
.getRangeByIndexes(matchRng - 1, pCol[0], 1, pMCol)
.getValues()[0];
myArray.push(...(vals as (string | number | boolean)[]));
}
// 貼付先への書き込み
if (wFlgCol === 1) {
for (let i = 0; i < wMCol; i++) {
workSh.getRangeByIndexes(tgetTmpR - 1, wCol[i], 1, 1)
.setValue(myArray[i]);
}
} else {
workSh.getRangeByIndexes(tgetTmpR - 1, wCol[0], 1, pMCol)
.setValues([myArray]);
}
lngHcount++;
// ★ 進捗表示:checkInterval件ごとに制御シートのA1・B2:B4へ書き込む
const processedCount = tgetTmpR - wRow + 1;
if (processedCount % checkInterval === 0) {
const percent = Math.round((processedCount / totalRows) * 100);
ctrlSheet.getRange("A1").setValue(
`${percent}%完了(${processedCount}/${totalRows}件・HIT ${lngHcount}件)`
);
// B2:B4の3セルに数値を書き込むだけ(データバーで視覚的に表示)
ctrlSheet.getRange("B2").setValue(lngHcount); // HIT件数
ctrlSheet.getRange("B3").setValue(processedCount); // 処理件数
ctrlSheet.getRange("B4").setValue(totalRows); // 総件数
}
}
ctrlSheet.getRange("A1").setValue(`完了!【データ更新件数: ${lngHcount} 件】`);
ctrlSheet.getRange("B2").setValue(lngHcount);
ctrlSheet.getRange("B3").setValue(totalRows);
ctrlSheet.getRange("B4").setValue(totalRows);
}
// ── ヘルパー関数 ──────────────────────────────────
// 行内で値を検索して列インデックスを返す(0-indexed)
// = VBAの WorksheetFunction.Match の代替
function findValueInRow(
sheet: ExcelScript.Worksheet,
rowIndex: number,
value: string | number
): number {
const colCount = sheet.getUsedRange().getColumnCount();
const rowValues = sheet
.getRangeByIndexes(rowIndex, 0, 1, colCount)
.getValues()[0];
return rowValues.indexOf(value);
}
// 行の数値の最大値を返す
// = VBAの WorksheetFunction.Max の代替
function getMaxInRow(sheet: ExcelScript.Worksheet, rowIndex: number): number {
const colCount = sheet.getUsedRange().getColumnCount();
const rowValues = sheet
.getRangeByIndexes(rowIndex, 0, 1, colCount)
.getValues()[0];
const nums = rowValues.filter(v => typeof v === "number") as number[];
return nums.length > 0 ? Math.max(...nums) : 0;
}
// 最終行を返す(1-indexed)
// = VBAの Cells(Rows.Count, col).End(xlUp).Row の代替
function getLastRow(sheet: ExcelScript.Worksheet, colIndex: number): number {
const rowCount = sheet.getUsedRange().getRowCount();
const colValues = sheet
.getRangeByIndexes(0, colIndex, rowCount, 1)
.getValues();
for (let r = rowCount - 1; r >= 0; r--) {
if (colValues[r][0] !== "" && colValues[r][0] !== null) return r + 1;
}
return 1;
}当サイトの記事で使用したVBAなどのサンプルをDLできます
この記事のサンプルはありません!
ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!

