【问题标题】:Synchronous Scrolling across sheets (tabs) in a Google Sheets Spreadsheet在 Google 表格电子表格中的表格(标签)之间同步滚动
【发布时间】:2021-04-13 00:57:54
【问题描述】:

我是 Google 表格、Apps 脚本和 JavaScript 的新手,请多多指教。

我对 Google 表格特别感兴趣,因为它能够控制用户对特定范围的访问以用于数据输入。

我想在电子表格的不同工作表中拆分信息。由于有很多可以分类的详细信息,我计划将信息分隔到每个类别的不同工作表/选项卡中,以便于使用。

由于预期的行数很大,我希望能够在这些类别表中同步滚动以方便使用。我设法在 Excel 上使用宏,please see this excel file 做同样的事情,以了解我打算做什么。

基本上,当在活动类别表中选择一个单元格(甚至行)时,在所有其他非活动类别表中选择同一行。或者,当用户选择一个类别表时,它应该检查在先前选择的类别表中选择了哪一行,并在新激活的表中选择同一行。

我正在使用 Apps 脚本,但无法设置非活动工作表的选择。 Similarly, when selecting another category sheet, I was unable to get the selected range from the previously selected category sheet - getSelection() and getActiveRange() always return selection of the current active sheet.

有什么方法可以实现吗?是否有任何人可以建议达到类似目标的替代解决方案?例如,我也在查看侧边栏,但它减少了可查看的列数......想要最小化水平滚动,因为这是在工作表之间拆分数据的主要目的。

如有任何反馈,我将不胜感激。

提前致谢。

【问题讨论】:

    标签: google-apps-script google-sheets scroll synchronous


    【解决方案1】:

    如果我理解正确,您希望以编程方式将所有工作表滚动到与活动范围相同的行。

    如果是这种情况,您只需查看所有工作表并将相应的行(使用活动范围中的rowIndex)设置为活动范围,使用Sheet.setActiveRange(range),如下所示:

    function scrollAllSheets() {
      const rowIndex = SpreadsheetApp.getActiveRange().getRow();
      const sheets = SpreadsheetApp.getActive().getSheets();
      sheets.forEach(sheet => {
        const row = sheet.getRange(rowIndex, 1, 1, sheet.getLastColumn());
        sheet.setActiveRange(row);
        //SpreadsheetApp.flush();
      });
    }
    

    如果您希望在单击新单元格时运行脚本,请使用onSelectionChange(e) 触发器。为此,只需将您的函数名称更改为 onSelectionChange,并且可以选择使用 event object(参数 e):

    function onSelectionChange(e) {
      const rowIndex = e.range.getRow();
      const sheets = e.source.getSheets();
      sheets.forEach(sheet => {
        const row = sheet.getRange(rowIndex, 1, 1, sheet.getLastColumn());
        sheet.setActiveRange(row);
      });
    }
    

    注意:

    • 我认为没有必要,但为了确保为所有工作表更新电子表格选择,您可能会发现SpreadsheetApp.flush() 很有用。如果它不起作用,请从上面的代码中取消注释。
    • 在这里使用RangeList 不是一个选项,因为它指的是同一工作表中的范围列表。

    【讨论】:

    • 非常感谢您的反馈 Iamblichus,对于延迟响应,我很抱歉,因为我是 Apps 脚本的新手,我花了一些时间来理解和运行。我稍微修改了脚本,以便在完成所有其他工作表后选择初始选择,是的,需要 flush() 才能达到预期的效果。没有刷新,setActiveRange 不会在循环中运行。
    • 虽然这可行,但如果在每个单元格选择上运行它可能会非常消耗资源。有什么方法可以将其限制为仅在更改行时?另外,如果我们只在选择不同的工作表时循环遍历所有工作表,效率会不会高得多?我看过一些代码,我们可以在其中检测到先前选择的工作表是什么。然后我们可以在该工作表中找到选定的行并将其应用于新选择的工作表吗?我未能获得非活动工作表的选定行。只有当一个新的工作表被选中时,它才应该从前一个工作表中获取行并循环遍历。
    • @Shalin onSelectionChange 事件对象不包含有关前一行和工作表的信息,只是当前一个(选择更改后),因此没有直接的方法来检索它。您可以通过PropertiesService 存储上一个选定的行,并在触发函数时将其与当前行进行比较,但我不确定这是否值得(存储和检索属性也需要时间)。
    • @Shalin 如果您认为这(使用PropertiesService)可能比遍历所有工作表更好(如果您有很多工作表,可能是),我建议添加我的答案的选项。你怎么看?
    • 嗨 Iamblichus,我一直在做同样的事情,并将在单独的答案中发布代码。我提供了两种解决方案,一种是通过菜单(我认为这是最节省资源的,只需要用户单击 2 次),另一种是通过 ProperiesService,这样只有在更换工作表时才会循环通过工作表。
    【解决方案2】:

    只是为了扩展 Iamblichus 的回答,请注意 this spreadsheet 有 5 张 Sheets1-5,其中只有 Sheets1-3 需要同步。

    我研究了两种解决方案,一种通过自定义菜单同步表,另一种通过 onSelectionChange。请注意以下代码:

        // all sheets
    var syncSheet1 = "Sheet1"
    var syncSheet2 = "Sheet2"
    var syncSheet3 = "Sheet3"
    var syncSheet4 = "Sheet4"
    var syncSheet5 = "Sheet5"
    
    // array of sync sheets
    var syncedSheets = new Array (syncSheet1, syncSheet2, syncSheet3);
    
    function onOpen(e) {
      // Add a custom menu to the spreadsheet.
      SpreadsheetApp.getUi() // Or DocumentApp, SlidesApp, or FormApp.
          .createMenu("Sync Sheets")
          .addItem("Sync Sheets","scrollAllSheets")
          .addToUi();
    
      SpreadsheetApp.getUi() // Or DocumentApp, SlidesApp, or FormApp.
          .createMenu("Goto 10,5")
          .addItem("Goto 10,5","goto10_5")
          .addToUi();
    
      const prop = PropertiesService.getScriptProperties();
      const sheetName = e.range.getSheet().getSheetName();
      const sheetRowIndex = e.range.getRow();
      prop.setProperty("previousSheet", sheetName);
      prop.setProperty("previousSheetRow", sheetRowIndex);
    }
    
    function goto10_5(){
      const row = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(10,5);
      SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().setActiveRange(row);
      // SpreadsheetApp.flush();
    }
    
    function scrollAllSheets() {
      const currentRange = SpreadsheetApp.getActiveRange();
      const rowIndex = SpreadsheetApp.getActiveRange().getRow();
      const currentSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetName();
      if (syncedSheets.indexOf(currentSheet) !=  -1){
        const sheets = SpreadsheetApp.getActive().getSheets();
        sheets.forEach(sheet => {
          if (syncedSheets.indexOf(sheet.getSheetName()) != -1){
            const row = sheet.getRange(rowIndex, 1);
            row.setValue(sheet.getSheetName() + " ;row: " + rowIndex);
            sheet.setActiveRange(row);
            SpreadsheetApp.flush();
          }
        });
        SpreadsheetApp.getActiveSpreadsheet().getSheetByName(currentSheet).setActiveRange(currentRange);
      } else {
        currentRange.setValue(currentSheet + " is not a synced sheet");
      }
    }
    
    function onSelectionChange(e) {
      const prop = PropertiesService.getScriptProperties();
      const previousSheet = prop.getProperty("previousSheet");
      const previousSheetRow = prop.getProperty("previousSheetRow");
      const range = e.range;
      const a1Notation = range.getA1Notation();
      const rowIndex = range.getRow();
      const sheet = range.getSheet();
      const sheetName = sheet.getSheetName();
      if (sheetName != previousSheet) {
        if (syncedSheets.indexOf(previousSheet) != -1 && syncedSheets.indexOf(sheetName) != -1){
          if (rowIndex != parseInt(previousSheetRow)){
            range.setValue(previousSheet + " row: " + previousSheetRow + "; dif " + rowIndex + " " + sheetName);
            var row = sheet.getRange(parseInt(previousSheetRow),2);
            row.setValue(sheetName);
            sheet.setActiveRange(row);
            // row.activate();
            SpreadsheetApp.flush();
          } else {
            range.setValue("same " + rowIndex + " " + sheetName);
          }
        } else {
          range.setValue("from not synced sheet: " + previousSheet);
        }
          
      } else {
          range.setValue(a1Notation);
      }
      prop.setProperty("previousSheet", sheetName);
      prop.setProperty("previousSheetRow",rowIndex);
      SpreadsheetApp.flush();
    }
    

    非常感谢您的帮助 Iamblichus。我会让最终用户选择实施哪种解决方案。

    【讨论】:

    • 考虑到您的原始问题(如何在选择新单元格时自动滚动不同的工作表),特别是通过菜单触发功能,我认为这个答案不会增加任何与我相关的内容。使用PropertiesService 存储/检索先前选择的行/工作表是一个很好的补充,但在我看来不值得一个新的答案。因此,我认为它更适合作为我的答案的编辑。
    • 注明。自从我在stackoverflow上发布以来已经有一段时间了。会留意的。最后,我相信通过菜单触发功能是最资源友好的解决方案。再次感谢您的宝贵帮助!
    猜你喜欢
    • 1970-01-01
    • 2020-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多