15

在 Google Apps 脚本中,基本工具之一是电子表格中的 onEdit 触发器,它使我们能够检测用户何时编辑单元格并对其做出反应。

当用户插入一行或一列时怎么样?有没有办法检测到?

这会触发 onEdit 吗?如果是这样,我想在 ScriptDb 中维护行数或列数的计数,然后每次检查都可以,但是会非常耗时,因为 getMaxRows() 已经很慢了,并且与 ScriptDb 联系是好。

你怎么看 ?

4

5 回答 5

26

Google 添加了一个“更改时”事件,该事件可检测行/列插入/删除以及其他类型的更改,您可以在此处changeType. 以下是此处详细说明如何向项目添加触发器的说明,以便在发生“更改时”事件时调用您的函数。

要通过脚本编辑器中的对话框手动创建可安装触发器,请执行以下步骤:

  1. 从脚本编辑器中,选择Edit > Current project's triggers
  2. 单击显示:未设置触发器的链接。点击这里现在添加一个
  3. Run下,选择要触发的函数的名称。
  4. 事件下,选择时间驱动或脚本绑定到的 Google 应用程序(例如,来自电子表格)。
  5. 选择并配置您要创建的触发器类型(例如,每小时运行的小时计时器打开的触发器)。
  6. 或者,单击通知以配置在触发功能失败时通过电子邮件与您联系的方式和时间。
  7. 单击保存

在第 4 步中,您将选择From 电子表格,在第 5 步中,您将选择On change。这应该有你正在寻找的效果。如果您尝试在要分发给用户的附加组件中使用它,还有以编程方式添加触发器和请求授权的选项。两者都在Installable Triggers文档中有详细说明。

于 2013-12-05T23:11:19.313 回答
20

有许多编辑操作不会触发onEdit(),这不是一个完整的列表,还有更多报告的排除项:

如果您确实想知道电子​​表格中有多少行,则执行大约需要 120 毫秒:

var numCols = SpreadsheetApp.getActiveSheet().getRange("1:1").getLastColumn();
var numRows = SpreadsheetApp.getActiveSheet().getRange("A:A").getLastRow();

我已经展示了将值写入工作表比使用 ScriptDB 更快。您可以预期写入一个小范围的时间很短,大约 1 毫秒。

因此,如果您可以检测到正在添加的行或列,那么注册更改将花费不到十分之二秒的时间。这onEdit()演示了一种测量电子表格范围并报告工作表尺寸变化的技术。(测试、添加或删除行或列,然后进行编辑以触发onEdit().)它还包含计时器 - 随意尝试其他测量和/或存储值的方法,看看哪种方法最适合您。

function onEdit() {
  // Use start & stop to time operations
  var start = new Date().getTime();

  // We want the size of the sheet, so will select ranges across and down the
  // whole sheet. Cannot use getDataRange(), as it selects only occupied cells.
  var numCols = SpreadsheetApp.getActiveSheet().getRange("1:1").getLastColumn()
  var numRows = SpreadsheetApp.getActiveSheet().getRange("A:A").getLastRow();

  var stop = new Date().getTime();
  var timeToMeasure = (stop-start);

  // Did things change?
  var oldSize = SpreadsheetApp.getActiveSheet().getRange("A1:B1").getValues();
  if (oldSize[0][0] != numCols || oldSize[0][1] != numRows) {
    // Yes, they did - Let's store the new dimensions
    start = new Date().getTime();

    SpreadsheetApp.getActiveSheet().getRange("A1:B1").setValues([[numCols,numRows]]);

    var stop = new Date().getTime();
    var timeToStore = (stop-start);  

    Browser.msgBox("Sheet is "+numCols+" by "+numRows+"."
                  +" ("+timeToMeasure+"ms to measure, "+timeToStore+"ms to store.)");
  }
}
于 2013-03-05T21:37:46.433 回答
4

我最近使用了另一种方法。每次触发 onEdit() 时,它都会返回一个事件对象(e),它会为您提供有关正在发生的事情的一些有价值的信息。

例如,它为您提供范围,您可以从 e.range 中检索该范围。从那里您可以通过许多不同的方式进行横向分析,例如了解正在编辑的行。但是在 e 对象中也有更多有用的数据。它为您提供您编辑的单元格的“旧值” ( e.oldValue )和新值(e.value)。

将所有这些信息混合在一起的一种可能方法是获取与您正在编辑的行相对应的范围,然后检查所有单元格是否为空(但您刚刚编辑的那个)以及是否没有 oldValue。

这不一定对应于电子表格的最后一行,而是一个空行。如果您与填写数据的方式一致,这可能对您有用:

//val = inserted value (e.value);
//old = old Value (e.oldValue);
//col = number of column being edited
//arr = array with the indexes of the columns that should be completed so as to make a new row [0,1,2...n]
function isInsert(old, val, col, arr){
   if((typeof val != "object")&&!old&&(arr.some(isNotEmpty, col)))
     return true;
   else
     return false;
}

function isNotEmpty(el){
   if(this == el)
     return true;
}
于 2016-07-13T21:27:58.453 回答
3

在我授予脚本权限之前,我遇到了麻烦。否则 PropertiesService 功能将不起作用。一旦我这样做了,我就能够使用以下代码检测插入了哪一行:

var props = PropertiesService.getUserProperties();

function onEdit(e) {
  props.setProperty("firstRow", e.range.getRow());
  props.setProperty("lastRow", e.range.getLastRow());
}


function onChange(e){
if(e.changeType=="INSERT_ROW")
    SpreadsheetApp.getUi().alert("Inserted Rows: " +
                                 props.getProperty("firstRow") + 
                                 " - " +
                                 props.getProperty("lastRow"));
}
于 2016-11-01T20:42:47.127 回答
1

我一直在玩 onEdit 和 onChange。onEdit 响应允许您访问已编辑的行。不幸的是,onChange 响应不允许您这样做。因此,对于一个强大的解决方案,您似乎需要同时吸引这两个触发器。如果您的工作表不需要空行/列,则以下脚本将删除所有新添加的行/列,删除所有空白行/列(以防用户批量添加行/列),然后警告用户他们无法添加行或列:

//////////////////////
// Global Variables //
//////////////////////

var SHEET = SpreadsheetApp.getActiveSheet();
var PROPERTIES = PropertiesService.getScriptProperties();

////////////////////
// Event Triggers //
////////////////////

/**
 * Track original sheet row/column count and register onChange trigger.
 */
function onOpen()
{
    // Set original dimensions
    PROPERTIES.setProperty('rows', SHEET.getMaxRows());
    PROPERTIES.setProperty('columns', SHEET.getMaxColumns());

    // Create onChange trigger
    ScriptApp
        .newTrigger('deleteNewRowsAndColumns')
        .forSpreadsheet(SpreadsheetApp.getActive())
        .onChange()
        .create();
}

/**
 * If new rows or columns were added to the sheet
 * warn the user that they cannot perform these
 * actions and delete empty (new) rows and columns.
 *
 * @param e
 */
function deleteNewRowsAndColumns(e)
{
    switch(e.changeType) {
        case 'INSERT_COLUMN':
            removeEmptyColumns();
            warn();
            break;
        case 'INSERT_ROW':
            removeEmptyRows();
            warn();
            break;
        default:
            return
    }
}

///////////////
// Utilities //
///////////////

/**
 * Remove empty columns.
 *
 * This function assumes you have a header row in which
 * all columns should have a value. Change headerRow value
 * if your headers are not in row 1.
 */
function removeEmptyColumns() {
    var maxColumns = SHEET.getMaxColumns();
    var lastColumn = SHEET.getLastColumn();
    if (maxColumns - lastColumn != 0) {
        // New column(s) were added to the end of the sheet.
        SHEET.deleteColumns(lastColumn + 1, maxColumns - lastColumn);
    } else {
        // New column was added in the middle of the sheet.
        // Start from last column and work backwards, delete
        // first column found with empty header cell.
        var headerRow = 1;
        var headers =  SHEET.getRange(headerRow, 1, 1, lastColumn).getValues()[0];
        for (var col = lastColumn; col >= 1; col--) {
            if (headers[col -1] == '') {
                SHEET.deleteColumn(col);
                // Since can only insert one column to the left
                // or right at a time, can safely exit here;
                break;
            }
        }
    }
}

/**
 * Remove empty rows.
 *
 * This function assumes that all rows should
 * have data in the first cell.
 */
function removeEmptyRows() {
    var maxRows = SHEET.getMaxRows();
    var lastRow = SHEET.getLastRow();
    if (maxRows-lastRow != 0) {
        // New row(s) were added to the end of the sheet.
        SHEET.deleteRows(lastRow + 1, maxRows - lastRow);
    } else {
        // New row was added in the middle of the sheet.
        // Start from last column and work backwards, delete
        // first empty column found.
        var values = SHEET.getRange('A:A').getValues();
        var startIndex = values.length - 1;
        for (var i = startIndex; i >= 0; i--) {
            if (values[i] && values[i][0] == '') {
                SHEET.deleteRow(i + 1);
                // User can bulk add rows to the bottom of the file
                // but can only add 1 above or below at a time in the
                // middle of the file, so it's safe to exit here.
                break;
            }
        }
    }
}

/**
 * Return user warning message about adding new rows and columns
 */
function warn()
{
    SpreadsheetApp.getUi().alert('You cannot add new rows or columns.');
}
于 2017-03-22T22:51:31.640 回答