apache poi xssf dropdowns not referencing the right cells - apache

I am having a strange problem referencing the right set of cells to display as dropdowns. My specific scenario is a visible sheet which should contain many various dropdowns and a hidden sheet which contains the various cells that house the dropdown selections.
So, for example, visibleSheet row 1 (cell A1) uses the values in hiddenSheet A1:x1 (x is variable), and visibleSheet row 2 (A2) uses the values in hiddenSheet A2:x2, etc...
But when I open the workbook in Excel (or Office Calc) only the first half (literally) of the dropdowns on the visibleSheet contain any selections at all. I can see that the hiddenSheet has correctly created all the cells. The visibleSheet, however, appears to be skipping every other set of values for the dropdowns....
visibleSheet row 1 (cell A1) displays the values from the hiddenSheet row 1 (correctly), visibleSheet row 2 (cell A2) is displaying the values from hiddenSheet row 3 (NOT 2!), visibleSheet row 3 (cell A3) is displaying hiddenSheet row 5 (not 3), etc..
I have been banging my head against the wall for hours trying to figure out what is wrong in my code. Hopefully someone who knows more about Excel/POI than me can help (I'm certainly no expert)....
EDIT: Here is a better code snippet that illustrates what I'm seeing - it can be pasted without any dependencies (except poi of course) Thanks for anyone taking the time! :)
public void createDropdownXfs2() {
String[][] testValues = {
{"010", "020", "030", "040", "050", "060", "070", "080", "090"},
{"110", "120", "130", "140", "150", "160", "170", "180", "190"},
{"210", "220", "230", "240", "250", "260", "270", "280", "290"},
{"310", "320", "330", "340", "350", "360", "370", "380", "390"},
{"410", "420", "430", "440", "450", "460", "470", "480", "490"},
{"510", "520", "530", "540", "550", "560", "570", "580", "590"},
{"610", "620", "630", "640", "650", "660", "670", "680", "690"},
{"710", "720", "730", "740", "750", "760", "770", "780", "790"},
{"810", "820", "830", "840", "850", "860", "870", "880", "890"},
{"910", "920", "930", "940", "950", "960", "970", "980", "990"}
};
try {
Workbook workbook = new XSSFWorkbook();
XSSFSheet sheet1 = (XSSFSheet) workbook.createSheet("mac_import");
XSSFSheet hiddenSheet = (XSSFSheet) workbook.createSheet("hidden");
//
// our data validation objects
//
XSSFDataValidationHelper validationHelper;
XSSFDataValidationConstraint constraint;
CellRangeAddressList addressList;
XSSFDataValidation dataValidation;
//
// create our ROW variables
//
Row row;
Row currentHiddenRow;
//
// the range template string should end up
// looking something like "hidden!A1:D1"
//
String rangeTemplate = hiddenSheet.getSheetName() + "!" + "A%s" + ":" + "%s%s";
for (int i = 0; i < testValues.length; i++) {
//
// create a new row in the visible sheet and in the hidden sheet
//
row = sheet1.createRow(i);
currentHiddenRow = hiddenSheet.createRow(i);
System.out.println("Create New Rows at 0-based-position: " + i);
String[] listItems = testValues[i];
//
// create the cells with our list values on the hidden sheet
//
for (int j = 0; j < listItems.length; j++) {
Cell cell = currentHiddenRow.createCell(j, Cell.CELL_TYPE_STRING);
cell.setCellValue(listItems[j]);
}
//
// create the namedcell/formula reference string
//
String formulaName = "formula" + i;
//
// prepare the excel-friendly range from the template
//
String endColumnName = CellReference.convertNumToColString(listItems.length - 1);
String rangeRow = String.valueOf(i + 1);
String rangeString = String.format(rangeTemplate, rangeRow, endColumnName, rangeRow);
//
// create a named area on the hidden sheet that covers the area we just created
//
Name namedCell = workbook.createName();
namedCell.setNameName(formulaName);
namedCell.setRefersToFormula(rangeString);
System.out.println(String.format("Cell Range '%s' refers to Range String: '%s'",
namedCell.getNameName(), rangeString));
validationHelper = new XSSFDataValidationHelper(sheet1);
constraint = (XSSFDataValidationConstraint) validationHelper.createFormulaListConstraint(formulaName);
addressList = new CellRangeAddressList(i, i, 0, 0);
dataValidation = (XSSFDataValidation) validationHelper.createValidation(constraint, addressList);
dataValidation.setSuppressDropDownArrow(true);
sheet1.addValidationData(dataValidation);
}
FileOutputStream fileOut = new FileOutputStream("/eelboy/mac/test_xls/list.xlsx");
workbook.write(fileOut);
fileOut.close();
} catch (Exception ex) {
System.out.println(ex);
}
}

Found the problem... the namedCell needs to have the row/column literals preceded with a '$'. So in my case the rangeTemplate definition should look like this:
String rangeTemplate = hiddenSheet.getSheetName() + "!" + "$A$%s" + ":" + "$%s$%s"

Related

How do I use variables to avoid having to create a set of these for all of the columns in my sheet?

I'm trying to get my sheet to automatically recalculate a set of dates within a schedule, in both directions, when a cell is changed.
The code works fine, but I need to add a bunch more columns and I'd really rather not copy/paste/find/replace a load more times. I'm fairly certain I can do this with variables (just looking up the column identifier and feeding that into the code somehow), but I don't know-how.
functJon onEdJt(e) {
var sh = e.source.getActJveSheet();
Jf(sh.getName() === 'Date Calculator' && e.range.getA1NotatJon() === 'C9'
)
{
sh.getRange("C10").setFormula("=WORKDAY(C9,+$C$3)");
sh.getRange("C11").setFormula("=WORKDAY(C10,+10)");
sh.getRange("C12").setFormula("=WORKDAY(C11,+$C$4)");
sh.getRange("C13").setFormula("=WORKDAY(C12,+$C$3)");
sh.getRange("C14").setFormula("=WORKDAY(C13,+10)");
sh.getRange("C15").setFormula("=WORKDAY(C14,+1)");
sh.getRange("C16").setFormula("=WORKDAY(C15,+$C$5)");
}
else Jf (sh.getName() === 'Date Calculator' && e.range.getA1NotatJon()
=== 'C10' )
{
sh.getRange("C9").setFormula("=WORKDAY(C10,-$C$3)");
sh.getRange("C11").setFormula("=WORKDAY(C10,+10)");
sh.getRange("C12").setFormula("=WORKDAY(C11,+$C$4)");
sh.getRange("C13").setFormula("=WORKDAY(C12,+$C$3)");
sh.getRange("C14").setFormula("=WORKDAY(C13,+10)");
sh.getRange("C15").setFormula("=WORKDAY(C14,+1)");
sh.getRange("C16").setFormula("=WORKDAY(C15,+$C$5)");
Ideally the code should then just "work" for any number of columns in the sheet, so I don't need to add more code if I add more columns.
Update
Here's an example of what I'm trying (but it's not working) - attempting to check that the active cell is in row 9 of a specific column before then running the "set.Formula" functions:
function onEdit(e) {
var sh = e.source.getActiveSheet();
var col = e.source.getActiveSheet().getRange().getColumn();
var row = e.source.getActiveSheet().getRange().getRow();
if(sh.getName() === 'Date Calculator' && e.getRange('9',col) )
Event Objects
Even though the code was written as onEdit(e), you didn't take advantage of the Event Objects.
In this answer, the code returns the new value of the edited cell and also the range. The range is then used to work out the row, column and sheet name and these is used for validation as well as for building the ranges and the setFormula
Variables
The code includes variables for the valid range of columns that can be used for data entry (Column C to Column H), and respective input rows (rows 9 and 10). These are expressed as values, but they could just as easily be written into the spreadsheet as assumptions and the values obtained in the code by using getValue.
The absolute cell references used in the setFormula are partly variable (column reference) and part hard-coded (the respective rows-3,4 and 5). If desired, the rows could be variable as well.
Efficiency
There is just one if statement containing one version of the code to build setFormula.
This is achieved by designing the if statement:
1. if the sheet = "Date Calculator" AND
2. if the editColumn is between the valid ColumnStart and ColumnEnd values (Column C to H) AND
3. if the editRow is between the valid Row values (rows 9 or 10) AND
4. if the edited value isn't a blank (length != 0).
The last condition ("edited value is blank") ensures that if cell contents are been deleted (and/or have no value), then the code won't proceed.
Convert column number to letter
I used a routine written by #AdamL found at Convert column index into corresponding column letter; this converts a column number into a letter. It's used to build the "targetcolumn" address in Workdays. It's valid for the letters A-Z; there's a version for letters beyond Z.
Cleanup
If data is entered into row 10 of a given column, then any value in row 9 (of the same column) needs to be deleted. The code does this and also deletes any pre-existing formula dates in the rows below so there is no confusion about the dates derived by the data entry.
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "Date Calculator";
var sheet = ss.getSheetByName(sheetname);
// get the event source data
var editedCell = e.range;
var editRow = editedCell.getRow();
var editCol = editedCell.getColumn();
var eValue = e.value;
var editedSheet = editedCell.getSheet().getName();
//Logger.log("DEBUG: the cell = "+editedCell.getA1Notation()+", the column = "+editCol+", the row is "+editRow+", the value is "+eValue+", the edited sheet is "+editedSheet);
// create some variables for column and row range
var columnStart = 3; // Column C
var columnEnd = 8; // Column H
var rowOption1 = 9; // row 9
var rowOption2 = 10 // row 10
// create some variables for target cells
var absolutecolumn = "C";
//var absoluterow1 = 3; // not used
//var absoluterow2 = 4; // not used
//var absoluterow3 = 5; // not used
// test for valid edit in row option 1 // Row 9
if(editedSheet === sheetname && columnEnd >=editCol && editCol>=columnStart && rowOption2>=editRow && editRow>=rowOption1 && eValue.length !=0 ){
//Logger.log("DEBUG: You got the right sheet, the edit is in the right range of columns and the edited row was = "+rowOption1);
if (editRow == rowOption2){
// clear row 9
sheet.getRange((+editRow-1),editCol).clear();
}
// clear following 8 rows of data
sheet.getRange((+editRow+1),editCol,8).clear();
// set the targetcolumn as a letter
var targetcolumn = columnToLetter(editCol);
// set formula for row+1
sheet.getRange((+editRow+1),editCol).setFormula("=WORKDAY("+targetcolumn+editRow+",$"+absolutecolumn+"$3)"); //
// set formula row +2
sheet.getRange((+editRow+2),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+1)+",+10)");
// set formula row +3
sheet.getRange((+editRow+3),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+2)+",$"+absolutecolumn+"$4)");
// set formula row +4
sheet.getRange((+editRow+4),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+3)+",$"+absolutecolumn+"$3)");
// set formula row + 5
sheet.getRange((+editRow+5),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+4)+",+10)");
// set formula row + 6
sheet.getRange((+editRow+6),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+5)+",+1)");
// set formula row + 7
sheet.getRange((+editRow+7),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+6)+",$"+absolutecolumn+"$5)");
// change the background to show entry in rowoption1
sheet.getRange(editRow,editCol).setBackground("yellow");
sheet.getRange((+editRow+1),editCol).setBackground("white");
}
}
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
Screenshot

Copy value of active cell to a different/non active sheet - How to determine which target/destination sheet to use

This script copies the value of the active cell, to another sheet based on the value in the cell next to it.
If I write the exact cell in my script, it works, but every time the script is run, it will be based on a different cell.
I also need to fine tune the destination.
Here it is:
function copytoTabs() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Booking In');
var data = sheet.getActiveCell();
var value = ss.getSheetByName('Booking In').getActiveCell().getA1Notation();
var operator = data.offset(0, 1).getValue();
if (operator == "Michelle") {
var ts = SpreadsheetApp.getActiveSpreadsheet();
var tss = ts.getSheetByName('MICHELLE Schedule');
ts.setActiveSheet(ts.getSheetByName('MICHELLE Schedule'));
tss.getRange(1, 2).activate();
tss.getRange(value).copyTo(tss.getActiveRange(),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
}
else if (operator == "Georgia") {
ss.setActiveSheet(ss.getSheetByName("GEORGIA Schedule"));
ss.getCurrentCell().offset(0, 1, 4, 1).activate();
ss.getRange('\'Booking In\'!P12').copyTo(ss.getActiveRange(),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
}
else if (operator == "Julie") {
ss.setActiveSheet(ss.getSheetByName("JULIE Schedule"));
ss.getCurrentCell().offset(0, 1, 4, 1).activate();
ss.getRange('\'Booking In\'!P12').copyTo(ss.getActiveRange(),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
}
ss.setActiveSheet(ss.getSheetByName('Booking In'), true);
}
Instead of using multiple if / else blocks, you can association a sheet tab name with the operator name in an object. Then look up the sheet tab name by operator name.
function copytoTabs() {
var activeCell,objectOfMappedValues,operator,sheet,sourceSs,targetSheetTabName,trgtSh;
sourceSs = SpreadsheetApp.getActiveSpreadsheet();
objectOfMappedValues = {//This is an object literal - the word literal means that the values are
//hard coded here in the function as opposed to being set with code
"Michelle":"MICHELLE Schedule",
"Georgia":"GEORGIA Schedule",
"Julie":"JULIE Schedule"
}
activeCell = sourceSs.getActiveCell();
Logger.log('activeCell: ' + activeCell)
operator = activeCell.offset(0, 1).getValue();//Get the value of one cell
targetSheetTabName = objectOfMappedValues[operator];//Get the sheet tab name for this operator
Logger.log('targetSheetTabName: ' + targetSheetTabName)
trgtSh = ts.getSheetByName(targetSheetTabName);//Get the sheet tab to be the target to set a value
Logger.log('trgtSh.getName(): ' + trgtSh.getName())
trgtSh.getRange(activeCell.getA1Notation()).copyTo(trgtSh.getActiveRange())
}
This code may not be everything that you are asking for, but hopefully it will advance you to the final solution.

Increment String Value Google Sheet

Using the following code I cannot increment a value in google sheets to be plus one.
function incrementCellValuesByOne() {
// Increments the values in all the cells in the active range (i.e., selected cells).
// Numbers increase by one, text strings get a "1" appended.
// Cells that contain a formula are ignored.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeRange = ss.getActiveRange();
var cell, cellValue, cellFormula;
// iterate through all cells in the active range
for (var cellRow = 1; cellRow <= activeRange.getHeight(); cellRow++) {
for (var cellColumn = 1; cellColumn <= activeRange.getWidth(); cellColumn++) {
cell = activeRange.getCell(cellRow, cellColumn);
cellFormula = cell.getFormula();
// if not a formula, increment numbers by one, or add "1" to text strings
// if the leftmost character is "=", it contains a formula and is ignored
// otherwise, the cell contains a constant and is safe to increment
// does not work correctly with cells that start with '=
if (cellFormula[0] != "=") {
cellValue = cell.getValue();
cellValue =+cellValue
cell.setValue(cellValue + 1);
}
}
}
}
For example "personalDataDOB_3" needs to become "personalDaTaDOB_4" I'm looking for a fast way to do this as right now I need to replace the value by typing.
You want to modify "personalDataDOB_3" of a certain cell to "personalDaTaDOB_4". If my understanding is correct, how about this modification?
Modification points :
When the retrieved "personalDataDOB_3" is converted to the number using cellValue =+cellValue, NaN is returned. So even if 1 was added, the result is NaN.
If the format of strings you want to modify is always "personalDataDOB_#", how about separating the string by _?
In order to reflect above points, please modify your script as follows.
From :
if (cellFormula[0] != "=") {
cellValue = cell.getValue();
cellValue =+cellValue
cell.setValue(cellValue + 1);
}
To :
if (cellFormula[0] != "=") {
cellValue = cell.getValue();
var temp = cellValue.split("_"); // Added
temp[1] = Number(temp[1]) + 1; // Added
cell.setValue(temp.join("_")); // Modified
}
Note :
If the format of strings you want to modify is always changed, please tell me.
If I misunderstand your question, I'm sorry.

Workbook cell style in POI/NPOI doesn't work properly with multiple styles in workbook

I'm running into strange problem with .Net version of POI library for Excel Spreadsheets. I'm rewriting from text files to Excel 97-2003 documents and I'm like to add some formatting programmatically depend on some values gather at the begging of the program.
At the beginning, in the same method where I was creating a new cell from given value I was creating also a new Workbook CellStyle which was wrong, because I was running out of the styles very quickly (or I was just thought it was the cause of the problem).
Constructor of the class responsible for Excel Workbook:
public OldExcelWriter(TextWriter logger) : base(logger)
{
_workbook = new HSSFWorkbook();
_sheetData = _workbook.CreateSheet("sheet1");
_creationHelper = _workbook.GetCreationHelper();
}
Method that is calling all the chains of operations:
public void Write(string path, Data data)
{
FillSpreadSheetWithData(data, _sheetData);
SaveSpreadSheet(_workbook, path);
}
Long story short, in FillSpreadSheetWithData I have method for creating a row inside which I'm have a loop for each cell, so basically I'm iterating thru every column, passing IRow references to a row, column value, index and formatting information like this:
for (int j = 0; j < column.Count; j++)
{
CreateCell(row, column[j], j, data.Formatting[j]);
}
and while creating a new styles (for first shot I was trying to pass some date time values) I had situation like this in my rewrited Excel: screenshot of excel workbook
So formatting was passed correctly (also Horizontal Aligment etc.) but it get ugly after 15th row (always the same amount).
DateTime dataCell = DateTime.MaxValue;
var cell = row.CreateCell(columnIndex);
_cellStyle = _workbook.CreateCellStyle();
switch (format.Type)
{
case DataType.Date:
_cellStyle.DataFormat = _creationHelper.CreateDataFormat().GetFormat("m/dd/yyyy");
if (value.Replace("\n", "") != string.Empty)
{
dataCell = DateTime.ParseExact(value.Replace("\n", ""), "m/dd/yyyy",
System.Globalization.CultureInfo.InvariantCulture);
}
break;
}
switch (format.HorizontalAlignment)
{
case Enums.HorizontalAlignment.Left:
_cellStyle.Alignment = HorizontalAlignment.LEFT;
break;
case Enums.HorizontalAlignment.Center:
_cellStyle.Alignment = HorizontalAlignment.CENTER;
break;
}
if (dataCell != DateTime.MaxValue)
{
cell.CellStyle = _cellStyle;
cell.SetCellValue(dataCell);
dataCell = DateTime.MaxValue;
}
else
{
cell.CellStyle = _cellStyle;
cell.SetCellValue(value);
}
(It's not the cleanest code but I will don refactor after getting this work).
After running into this issue I thought that maybe I will create _cellStyle variable in the constructor and only change it's value depends on the case, because it's assigned to the new cell anyway and I see while debugging that object values are correct.
But after creating everything, it won't get any better. Styles was override by the last value of the style, and dates are spoiled also, but later: screnshoot of excel workbook after creating one instance of cell style
I'm running out of ideas, maybe I should create every combination of the cell styles (I'm using only few data formats and alignments) but before I will do that (because I'm running out of easy options right now) I wonder what you guys think that should be done here.
cell format is set to custom with date type
I am using this code to create my custom style and format. Its for XSSF Format of excel sheet. but it will work for HSSF format with some modification.
XSSFFont defaultFont = (XSSFFont)workbook.CreateFont();
defaultFont.FontHeightInPoints = (short)10;
defaultFont.FontName = "Arial";
defaultFont.Color = IndexedColors.Black.Index;
defaultFont.IsBold = false;
defaultFont.IsItalic = false;
XSSFCellStyle dateCellStyle = (XSSFCellStyle)workbook.CreateCellStyle();
XSSFDataFormat dateDataFormat = (XSSFDataFormat)workbook.CreateDataFormat();
dateCellStyle.SetDataFormat(dateDataFormat.GetFormat("m/d/yy h:mm")); //Replace format by m/dd/yyyy. try similar approach for phone number etc.
dateCellStyle.FillBackgroundColor = IndexedColors.LightYellow.Index;
//dateCellStyle.FillPattern = FillPattern.NoFill;
dateCellStyle.FillForegroundColor = IndexedColors.LightTurquoise.Index;
dateCellStyle.FillPattern = FillPattern.SolidForeground;
dateCellStyle.Alignment = HorizontalAlignment.Left;
dateCellStyle.VerticalAlignment = VerticalAlignment.Top;
dateCellStyle.BorderBottom = BorderStyle.Thin;
dateCellStyle.BorderTop = BorderStyle.Thin;
dateCellStyle.BorderLeft = BorderStyle.Thin;
dateCellStyle.BorderRight = BorderStyle.Thin;
dateCellStyle.SetFont(defaultFont);
//Apply your style to column
_sheetData.SetDefaultColumnStyle(columnIndex, dateCellStyle);
// Or you can also apply style cell wise like
var row = _sheetData.CreateRow(0);
for (int cellIndex = 0;cellIndex < TotalHeaderCount;cellIndex++)
{
row.Cells[cellIndex].CellStyle = dateCellStyle;
}

Time stamp a cell when multiple columns are updated

I have been checking out multiple codes on trying to update my Google Spreadsheet but have been unsuccessful when trying to do this with multiple cells. On my spreadsheet I have multiple tabs and when I update a row in column 2,3 or 4, I would like it to enter the date in column 5.
Thank you for your help.
Step 1.
In the Google Spreadsheet, click on "Script editor..." under the "Tools" menu.
Step 2.
Remove any sample script that might be in there and paste the following ...
// Sets the targetColumn on the edited row to the current date if the
// edited column in within columnBounds.
// Note: This will only handle single cell editing.
// Columns that need to be monitored for changes. Use CAPITAL letters.
var monitoredColumns = ['B', 'C', 'D'];
// Colum that will receive the date.
var targetColumn = 'E'
// To avoid adding the date in the title row, we need to consider the starting row.
var startingRow = 4
// onEdit() is a reserved function name that will be called every time the sheet will be edited.
function onEdit(e) {
var range = e.range;
// Row of the edited cell.
var row = range.getRow();
// Column of the edited cell.
var col = String.fromCharCode(64 + range.getColumn());
if (row < startingRow) {
// None of the monitored rows have been edited.
return;
}
if (monitoredColumns.indexOf(col) < 0) {
// Column B, C or D (2, 3 or 4) was not modified.
// Do not proceed any further.
return;
}
// Current spreadsheet.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// Date cell.
var dateCell = sheet.getRange(targetColumn + range.getRow());
// Set it to the current date.
dateCell.setValue(new Date());
}
Step 3
Adjust the values of monitoredColumns, targetColumn and startingRow
Step 4
Start entering some content in the cells.