【google 表單】用 A1 控制 A2:A100的核取方塊
Q1: 假設 A欄內容是核取方塊, 可以讓A1的核取方塊控制 A2~A100的核取方塊, 就統一ture/false嗎?
開啟指令碼編輯器: 在你的 Google Sheet 文件中,點擊頂部選單的 擴充功能 (Extensions) >Apps Script 。這會在新的瀏覽器分頁中開啟指令碼編輯器。
貼上指令碼: 刪除編輯器中任何預設的程式碼 (例如 function myFunction() { ... })。 將下面的指令碼完整複製並貼到編輯器中。
修改工作表名稱 (如果需要): 在指令碼中,有一行 var sheetName = "工作表1";。如果你的工作表名稱 不是 「工作表1」,請將引號中的文字修改成你實際的工作表名稱。
儲存指令碼: 點擊編輯器上方的儲存圖示 (💾)。 系統會提示你為專案命名,可以取一個容易識別的名稱,例如「Checkbox Controller」或「核取方塊控制器」,然後按「確定」。
返回試算表並測試: 回到你的 Google Sheet。 確保 A1 和 A2 到 A100 都已經設定為核取方塊 (可以選取這些儲存格,然後到 插入 (Insert) >核取方塊 (Checkbox) )。現在,試著勾選或取消勾選 A1 的核取方塊。指令碼應該會自動執行,並將 A2 到 A100 的核取方塊狀態設定成與 A1 一致。
/**
* 當工作表被編輯時自動觸發此函數。
* 如果編輯發生在指定工作表的 A1 儲存格,
* 則將 A2:A100 的值設置為與 A1 相同。
* @param {Object} e 事件物件,包含編輯的相關資訊。
*/
function onEdit(e) {
// --- 請修改這裡 ---
var sheetName = "工作表1"; // <<<=== 將這裡改成你實際的工作表名稱
var masterCheckboxCell = "A1"; // 控制的主核取方塊位置
var controlledRange = "A2:A100"; // 被控制的核取方塊範圍
// --- 修改結束 ---
var range = e.range; // 取得被編輯的儲存格範圍
var sheet = range.getSheet(); // 取得被編輯的工作表
// 檢查編輯是否發生在指定工作表,且位置是 A1
if (sheet.getName() === sheetName && range.getA1Notation() === masterCheckboxCell) {
var masterValue = range.getValue(); // 取得 A1 的新值 (true 或 false)
// 檢查 A1 的值是否確實是布林值 (避免意外輸入文字)
if (typeof masterValue === 'boolean') {
var targetRange = sheet.getRange(controlledRange); // 取得要控制的目標範圍 A2:A100
targetRange.setValue(masterValue); // 將目標範圍的值設定為 A1 的值
// 或者使用 check/uncheck 方法,效果相同:
// if (masterValue === true) {
// targetRange.check();
// } else {
// targetRange.uncheck();
// }
} else {
// 如果 A1 被輸入非布林值 (例如文字),可以選擇還原或提示錯誤
// 這裡選擇簡單地不動作,或者你可以取消註解下一行來還原A1
// range.setValue(e.oldValue); // 還原 A1 的舊值
SpreadsheetApp.getUi().alert("請確保 A1 儲存格是一個核取方塊 (TRUE/FALSE)。");
}
}
}
onEdit(e): 這是一個特殊的函數名稱,Google Sheets 會在每次有儲存格被使用者編輯時自動執行它。e 是一個包含事件資訊的物件。 var sheetName = "工作表1";: 設定你的目標工作表名稱。 請務必確認這裡的名稱與你的工作表分頁名稱完全一致。 var masterCheckboxCell = "A1"; 和 var controlledRange = "A2:A100";: 定義主控和被控的儲存格範圍。 e.range: 取得剛才被編輯的儲存格或範圍。 range.getSheet(): 取得被編輯儲存格所在的工作表。 sheet.getName(): 取得工作表的名稱。 range.getA1Notation(): 取得被編輯儲存格的 A1 表示法 (例如 "A1", "B5" 等)。 if (sheet.getName() === sheetName && range.getA1Notation() === masterCheckboxCell): 這個條件判斷確保指令碼只在指定工作表的 A1 被編輯時才執行後續動作。 range.getValue(): 取得被編輯儲存格 (A1) 的新值。對於核取方塊,勾選時是 true,未勾選時是 false。 typeof masterValue === 'boolean': 檢查確保 A1 的值是 true 或 false,而不是使用者可能意外輸入的文字。 sheet.getRange(controlledRange): 取得 A2:A100 這個範圍。 targetRange.setValue(masterValue): 將 A2:A100 範圍內所有儲存格的值設定為 A1 的值 (true 或 false)。這會自動讓這些儲存格的核取方塊狀態改變。
onEdit 觸發器有時可能會有輕微的延遲。 確保 A1 到 A100 都已經插入了核取方塊,否則指令碼只會填入 TRUE 或 FALSE 的文字。 第一次執行指令碼(也就是第一次編輯 A1 儲存格在儲存指令碼後)時,Google 可能會跳出一個授權請求,要求允許指令碼修改你的試算表。你需要點擊同意才能讓指令碼正常運作。
======================
Q2: 一個指令就一個專案, 還是整個表單有用到指令都寫在這個專案?
綁定專案 (Bound Project): 當你從 Google Sheet 內部透過「擴充功能 > Apps Script」開啟編輯器時,你建立的是一個「綁定」到該試算表的指令碼專案。這個專案與你的試算表文件是連結在一起的。單一專案,多個指令碼/函式: 一個 Apps Script 專案可以包含:多個 .gs 檔案: 你可以在專案中建立不同的檔案來組織你的程式碼 (例如:一個檔案處理核取方塊,另一個處理資料驗證,再一個處理報表生成)。每個檔案中可以有多個函式 (function): 就像我們剛才寫的 onEdit(e) 函式,你可以在同一個檔案或專案中的其他檔案裡再定義其他函式,例如 sendEmailReport()、sortMyData() 等。
為什麼要放在同一個專案? 管理方便: 所有與此試算表相關的自動化功能都在一個地方,容易尋找、修改和維護。共用功能: 不同函式可以呼叫同一個專案中的其他輔助函式。觸發器管理: 所有時間驅動 (time-driven) 或事件驅動 (event-driven,例如 onEdit, onOpen) 的觸發器都在這個專案中設定和管理。授權: 通常只需要為這個專案授權一次,即可執行專案內所有需要權限的函式 (除非它們需要不同的服務權限)。簡單觸發器限制: 像 onEdit(e) 和 onOpen(e) 這樣的簡單觸發器,只有在綁定到文件的專案中才能可靠地自動運作。
Q3: 如果好幾個工作表都有同樣的需求, 怎麼寫比較方便維護?
好的,如果多個工作表都有完全相同的需求(例如,都是用 A1 控制 A2:A100 的核取方塊),並且你想讓維護更方便,避免在指令碼中重複寫幾乎一樣的邏輯,有幾種推薦的做法:
修改現有 onEdit 函數: 回到你的 Apps Script 專案。 修改 onEdit(e) 函數,讓它檢查被編輯的工作表名稱是否存在於一個預先定義好的列表中。
/**
* 當工作表被編輯時自動觸發此函數。
* 如果編輯發生在指定列表內的工作表的 A1 儲存格,
* 則將該工作表的 A2:A100 的值設置為與 A1 相同。
* @param {Object} e 事件物件,包含編輯的相關資訊。
*/
function onEdit(e) {
// --- 請修改這裡 ---
// 在這個列表中加入所有需要此功能的 "工作表名稱"
var targetSheetNames = ["工作表A", "工作表B", "訂單表", "庫存表"]; // <<<=== 修改這裡
var masterCheckboxCell = "A1"; // 控制的主核取方塊位置
var controlledRange = "A2:A100"; // 被控制的核取方塊範圍
// --- 修改結束 ---
var range = e.range; // 取得被編輯的儲存格範圍
var sheet = range.getSheet(); // 取得被編輯的工作表
var currentSheetName = sheet.getName(); // 取得當前工作表的名稱
// 檢查:編輯是否發生在列表中的工作表,且位置是 A1
if (targetSheetNames.includes(currentSheetName) && range.getA1Notation() === masterCheckboxCell) {
var masterValue = range.getValue(); // 取得 A1 的新值 (true 或 false)
// 檢查 A1 的值是否確實是布林值
if (typeof masterValue === 'boolean') {
var targetRange = sheet.getRange(controlledRange); // 取得該工作表要控制的目標範圍 A2:A100
targetRange.setValue(masterValue); // 將目標範圍的值設定為 A1 的值
} else {
// 可選:處理非布林值輸入的情況
SpreadsheetApp.getUi().alert("請確保 " + masterCheckboxCell + " 儲存格是一個核取方塊 (TRUE/FALSE)。");
// 可以考慮還原舊值: range.setValue(e.oldValue);
}
}
}
優點: 所有邏輯仍在單一 onEdit 函數中,易於理解。 維護相對簡單,只需要更新 targetSheetNames 這個陣列列表即可新增或移除適用的工作表。
缺點: 如果將來不同工作表需要稍微不同的邏輯(例如不同的控制範圍),這個方法會變得複雜。 所有工作表必須使用完全相同的儲存格位置 (A1 控制 A2:A100)。
/**
* 當工作表被編輯時自動觸發此函數。
* 判斷是否需要觸發核取方塊同步邏輯。
* @param {Object} e 事件物件。
*/
function onEdit(e) {
// --- 請修改這裡 ---
var targetSheetNames = ["工作表A", "工作表B", "訂單表", "庫存表"]; // <<<=== 修改這裡
var masterCheckboxCell = "A1";
var controlledRange = "A2:A100";
// --- 修改結束 ---
var range = e.range;
var sheet = range.getSheet();
var currentSheetName = sheet.getName();
// 檢查是否滿足觸發條件
if (targetSheetNames.includes(currentSheetName) && range.getA1Notation() === masterCheckboxCell) {
// 呼叫核心處理函數
syncCheckboxes(sheet, range, controlledRange, e.oldValue); // 傳遞 sheet, range, controlledRange 和 oldValue
}
}
/**
* 核心邏輯:同步指定工作表上的核取方塊狀態。
* @param {Sheet} sheet 要操作的工作表物件。
* @param {Range} masterRange 主控核取方塊的範圍物件 (例如 A1)。
* @param {string} controlledRangeA1Notation 被控範圍的 A1 表示法 (例如 "A2:A100")。
* @param {any} oldValue (可選) 主控儲存格的舊值,用於錯誤處理。
*/
function syncCheckboxes(sheet, masterRange, controlledRangeA1Notation, oldValue) {
var masterValue = masterRange.getValue();
if (typeof masterValue === 'boolean') {
var targetRange = sheet.getRange(controlledRangeA1Notation);
targetRange.setValue(masterValue);
} else {
// 錯誤處理
SpreadsheetApp.getUi().alert("請確保 " + masterRange.getA1Notation() + " 儲存格是一個核取方塊 (TRUE/FALSE)。");
// 可選:還原舊值
// if (oldValue !== undefined) {
// masterRange.setValue(oldValue);
// }
}
}
優點: 程式碼更清晰: onEdit 只負責事件分派,syncCheckboxes 負責核心功能。易於維護和擴充: 如果 syncCheckboxes 的邏輯需要修改,只需要改一個地方。未來如果需要針對不同情況呼叫不同的同步邏輯,也更容易擴充。可測試性更好: 可以獨立測試 syncCheckboxes 函數。
缺點: 稍微多一點程式碼結構,對於非常簡單的場景可能感覺 overkill。 同樣假設所有工作表使用相同的儲存格位置。
建立配置工作表: 新增一個隱藏的工作表(例如命名為 "Config"),在上面列出需要套用規則的工作表名稱、對應的主控儲存格、被控範圍等。修改 onEdit: 讓 onEdit 函數讀取這個 "Config" 工作表的內容,來判斷當前編輯是否符合某條規則,然後執行相應操作。
使用指令碼屬性 (Script Properties): 將配置資訊(例如工作表名稱列表)儲存在指令碼的屬性中,而不是硬編碼在程式碼裡。可以透過指令碼編輯器的「專案設定」>「指令碼屬性」來管理。修改 onEdit: 讀取指令碼屬性來判斷是否需要執行。
優點: 高度靈活: 可以為每個工作表定義不同的規則,甚至完全不同的行為。易於配置 (對使用者): 如果使用配置工作表,非技術人員也能修改設定。
缺點: 實作較複雜: 需要編寫讀取和解析配置的程式碼。效能考量: 每次編輯都去讀取配置可能會稍微影響效能(雖然通常不明顯)。
對於你目前描述的「好幾個工作表都有 同樣 的需求 (A1 控制 A2:A100)」,方法一 (檢查列表) 或方法二 (抽離函數) 是最合適的。方法二 的程式結構更好,更易於長期維護,是稍微更推薦的做法。如果你預期未來不同工作表的需求會產生差異(例如變成 B1 控制 B2:B50),那麼從一開始就考慮 方法二 ,甚至研究方法三 會更有利。
在你的 Google Sheet 文件中,新增一個工作表。 將這個新工作表重新命名為 Config (或者你喜歡的其他名稱,但要確保與之後指令碼中的名稱一致)。 在 Config 工作表中建立以下欄位標題 (從 A1 開始): 欄 A 欄 B 欄 C 欄 D SheetName MasterCell ControlledRange Enabled (工作表名稱) (主控儲存格) (被控範圍) (啟用) 填寫規則: 在標題下方,每一列代表一條規則。SheetName: 輸入需要套用此規則的工作表確切名稱 。MasterCell: 輸入該工作表中作為主控的儲存格位置 (例如 A1, B5)。ControlledRange: 輸入該工作表中需要被控制的儲存格範圍 (例如 A2:A100, C2:C50)。Enabled: 插入一個核取方塊 (選取儲存格 > 插入 > 核取方塊)。勾選 (TRUE) 代表此規則啟用,不勾選 (FALSE) 代表停用。
範例 Config 工作表內容: SheetName MasterCell ControlledRange Enabled 工作表A A1 A2:A100 ☑ (TRUE) 訂單 B1 B2:B100 ☑ (TRUE) 庫存總覽 A1 C2:C50 ☐ (FALSE) 出貨紀錄 C5 D6:D100 ☑ (TRUE) ... ... ... ...
開啟指令碼編輯器 (擴充功能 > Apps Script)。 用下面的程式碼替換掉編輯器中現有的內容。
/**
* 當工作表被編輯時自動觸發此函數。
* 讀取 'Config' 工作表的設定,並根據設定同步核取方塊。
* @param {Object} e 事件物件,包含編輯的相關資訊。
*/
function onEdit(e) {
// --- 配置區域 ---
var configSheetName = "Config"; // 設定檔工作表的名稱
// Config 工作表中各欄位的索引 (從 0 開始)
var COL_SHEET_NAME = 0;
var COL_MASTER_CELL = 1;
var COL_CONTROLLED_RANGE = 2;
var COL_ENABLED = 3;
// --- 配置結束 ---
var range = e.range; // 取得被編輯的儲存格範圍
var editedSheet = range.getSheet(); // 取得被編輯的工作表
var editedSheetName = editedSheet.getName();
var editedCellA1 = range.getA1Notation(); // 取得被編輯儲存格的 A1 表示法
// 如果編輯發生在 Config 工作表本身,則不執行任何操作
if (editedSheetName === configSheetName) {
return;
}
// --- 讀取 Config 工作表設定 ---
var ss = SpreadsheetApp.getActiveSpreadsheet();
var configSheet = ss.getSheetByName(configSheetName);
if (!configSheet) {
Logger.log("錯誤:找不到名為 '" + configSheetName + "' 的設定工作表。");
SpreadsheetApp.getUi().alert("錯誤:找不到設定工作表 '" + configSheetName + "'。請檢查工作表名稱。");
return;
}
// 取得 Config 工作表的所有資料 (從第二列開始,跳過標題)
// getLastRow() 可能會取到空白列,用 getDataRange 較保險
var configData = configSheet.getDataRange().getValues();
// var configData = configSheet.getRange(2, 1, configSheet.getLastRow() - 1, configSheet.getLastColumn()).getValues(); // 另一種讀取方式
// --- 逐一檢查 Config 中的規則 ---
for (var i = 1; i < configData.length; i++) { // 從索引 1 開始,跳過標題列
var rule = configData[i];
var targetSheetName = rule[COL_SHEET_NAME];
var masterCell = rule[COL_MASTER_CELL];
var controlledRangeA1 = rule[COL_CONTROLLED_RANGE];
var isEnabled = rule[COL_ENABLED]; // 核取方塊的值會是 true 或 false
// 檢查:規則是否啟用? 工作表名稱是否相符? 編輯的儲存格是否為此規則的主控儲存格?
if (isEnabled === true && editedSheetName === targetSheetName && editedCellA1 === masterCell) {
var masterValue = range.getValue(); // 取得主控儲存格的新值
// 驗證主控儲存格的值是否為布林值 (核取方塊)
if (typeof masterValue === 'boolean') {
try {
var targetRange = editedSheet.getRange(controlledRangeA1); // 取得被控範圍
targetRange.setValue(masterValue); // 設定被控範圍的值
// 或者使用 check/uncheck
// if (masterValue) {
// targetRange.check();
// } else {
// targetRange.uncheck();
// }
Logger.log("已根據規則 #" + (i + 1) + " 更新工作表 '" + targetSheetName + "' 的範圍 '" + controlledRangeA1 + "' 為 " + masterValue);
// 找到匹配的規則並執行後,就可以停止檢查其他規則了
break;
} catch (error) {
// 處理範圍無效等錯誤
Logger.log("錯誤:處理規則 #" + (i + 1) + " 時發生錯誤。工作表: '" + targetSheetName + "', 主控: '" + masterCell + "', 被控: '" + controlledRangeA1 + "'. 錯誤訊息: " + error);
SpreadsheetApp.getUi().alert("處理 '" + targetSheetName + "' 的規則時發生錯誤:\n被控範圍 '" + controlledRangeA1 + "' 可能無效。\n請檢查 Config 工作表設定。\n錯誤:" + error);
break; // 發生錯誤也停止
}
} else {
// 主控儲存格被輸入非布林值
Logger.log("警告:規則 #" + (i + 1) + " 的主控儲存格 '" + masterCell + "' (工作表 '" + targetSheetName + "') 被輸入非核取方塊值。");
SpreadsheetApp.getUi().alert("請確保 '" + targetSheetName + "' 工作表的儲存格 '" + masterCell + "' 是一個核取方塊 (TRUE/FALSE)。");
// 可選:還原舊值
// range.setValue(e.oldValue);
break; // 輸入錯誤也停止
}
}
}
}
配置區域: configSheetName: 指定你的 Config 工作表名稱。 COL_...: 定義了 Config 工作表中每一欄代表什麼資訊的索引(從 0 開始)。這樣即使調整欄位順序,也只需要修改這裡的數字(但保持欄位順序不變更方便)。
基本檢查: 獲取被編輯的儲存格、工作表等資訊。 如果編輯的是 Config 工作表本身,則直接退出,避免無限循環或意外操作。
讀取 Config: ss.getSheetByName(configSheetName): 獲取 Config 工作表物件。 configSheet.getDataRange().getValues(): 獲取 Config 工作表所有有內容的儲存格的值,回傳一個二維陣列。 包含錯誤處理,如果找不到 Config 工作表會提示。
遍歷規則: for (var i = 1; i < configData.length; i++): 迴圈從索引 1 開始,跳過第 0 列(標題列)。 rule = configData[i]: 獲取當前迴圈處理的規則(Config 中的一列)。 targetSheetName = rule[COL_SHEET_NAME], masterCell = rule[COL_MASTER_CELL], ... : 根據前面定義的欄位索引,從 rule 陣列中取出對應的值。
匹配規則: if (isEnabled === true && editedSheetName === targetSheetName && editedCellA1 === masterCell): 這是核心判斷: isEnabled === true: 檢查 Config 中該規則的 "Enabled" 核取方塊是否被勾選。 editedSheetName === targetSheetName: 檢查被編輯的工作表名稱是否與規則中指定的名稱相符。 editedCellA1 === masterCell: 檢查被編輯的儲存格是否與規則中指定的 MasterCell 相符。
執行操作: 如果三個條件都滿足,表示這次編輯觸發了這條規則。 range.getValue(): 取得被編輯的主控儲存格的新值 (true 或 false)。 typeof masterValue === 'boolean': 驗證值是否為布林,確保是核取方塊操作。 editedSheet.getRange(controlledRangeA1): 在被編輯的工作表 (editedSheet) 上,獲取規則中指定的 ControlledRange。targetRange.setValue(masterValue): 將被控範圍的值設定為主控儲存格的值。 try...catch: 用於捕捉 getRange 可能出現的錯誤(例如範圍字串寫錯)。 break: 找到並成功執行一個匹配的規則後,就跳出迴圈,不需要再檢查後面的規則了。
記錄與提示: Logger.log(): 在 Apps Script 的執行記錄中輸出資訊,方便除錯。 SpreadsheetApp.getUi().alert(): 在發生錯誤或需要使用者注意時,跳出提示框。
新增規則: 只需要在 Config 工作表中新增一列,填寫好工作表名稱、主控儲存格、被控範圍,並勾選 "Enabled"。不需要修改程式碼。 修改規則: 直接在 Config 工作表中修改對應儲存格的內容即可。例如,想改變 訂單 工作表的被控範圍,只需修改 訂單 那一列的 ControlledRange 儲存格。不需要修改程式碼。 停用規則: 取消勾選 Config 工作表中對應規則的 "Enabled" 核取方塊即可。不需要修改程式碼。 刪除規則: 直接刪除 Config 工作表中對應的列即可。不需要修改程式碼。
留言
張貼留言