#c# #excel #pivot-table #nullreferenceexception #excel-interop
#c# #excel #сводная таблица #исключение nullreferenceexception #excel-interop
Вопрос:
Я получаю следующее сообщение об ошибке:
Это происходит в этом коде:
private bool GetContractForDescription(string desc)
{
int DESCRIPTION_COL_NUM = 2;
int CONTRACT_COL_NUM = 7;
bool contractVal = false;
int rowsUsed = _xlBook.Worksheets["PivotData"].UsedRange.Rows.Count;
int colsUsed = _xlBook.Worksheets["PivotData"].UsedRange.Columns.Count;
string colsUsedAsAlpha
ReportRunnerConstsAndUtils.GetExcelColumnName(colsUsed);
string endRange = string.Format("{0}{1}", colsUsedAsAlpha, rowsUsed);
Range sourceData = _xlBook.Worksheets["PivotData"].Range[string.Format("A2:{0}", endRange)];
// This is blowing up for some reason, so look for more detail on why
string currentDesc = string.Empty;
try
{
for (int i = 1; i <= rowsUsed; i )
{
var dynamicCell = (Range)sourceData.Cells[i, DESCRIPTION_COL_NUM];
currentDesc = dynamicCell.Value2.ToString();
if (currentDesc.Equals(desc))
{
var dynamicContractCell = (Range)sourceData.Cells[i, CONTRACT_COL_NUM];
contractVal = (bool)dynamicContractCell.Value2;
break;
}
}
return contractVal;
}
catch (Exception ex)
{
MessageBox.Show(string.Format("currentDesc is {0}; exception is {1}", currentDesc, ex.Message));
}
return false;
}
И что еще более странно, диапазон строк / столбцов, которые я раскрашиваю при определенных обстоятельствах, имеет «вырезанное отверстие», где оно не окрашивается.
Вот исходные данные, о которых идет речь (средняя строка (156) со значением «TRUE» в последнем столбце, является ключевой):
Вот как эти данные отображаются в сводной таблице (средний блок данных, строки 293-297, являются ключевыми):
Вот код, который я использую для поворота исходных данных:
private void AddPivotTable()
{
int FIRST_DESCRIPTION_ROW = 8;
int LINES_BETWEEN_DESCRIPTION_VALS = 5;
var pch = _xlBook.PivotCaches();
int rowsUsed = _xlBook.Worksheets["PivotData"].UsedRange.Rows.Count;
int colsUsed
_xlBook.Worksheets["PivotData"].UsedRange.Columns.Count;
string colsUsedAsAlpha
ReportRunnerConstsAndUtils.GetExcelColumnName(colsUsed);
string endRange = string.Format("{0}{1}", colsUsedAsAlph
rowsUsed);
Range sourceData
_xlBook.Worksheets["PivotData"].Range[string.Format("A1:{0}", endRange)];
PivotCache pc = pch.Create(XlPivotTableSourceType.xlDatabas
sourceData);
PivotTable pvt = pc.CreatePivotTable(_xlPivotTableSheet.Range["A6"
"PivotTable");
pvt.MergeLabels = true;
pvt.PivotFields("Description").Orientation
XlPivotFieldOrientation.xlRowField;
var monthField = pvt.PivotFields("MonthYr");
monthField.Orientation = XlPivotFieldOrientation.xlColumnField;
monthField.NumberFormat = "mmm yyyy";
monthField.DataRange.Interior.Color
ColorTranslator.ToOle(Color.LightBlue);
pvt.CompactLayoutColumnHeader = "Months";
pvt.CompactLayoutRowHeader = "Description";
pvt.AddDataField(pvt.PivotFields("TotalQty"), "Total Packages
XlConsolidationFunction.xlSum).NumberFormat = "###,##0";
pvt.AddDataField(pvt.PivotFields("TotalSales"), "Total Purchases
XlConsolidationFunction.xlSum).NumberFormat = "$#,##0";
PivotField avg = pvt.CalculatedFields().Add("Average Price
"=TotalSales/TotalQty", true);
avg.Orientation = XlPivotFieldOrientation.xlDataField;
// TODO: This calculation needs to change (it needs to actually
made a calculation, rather than just the TotalSales val)
pvt.CalculatedFields()._Add("PercentOfTotal", "=TotalSales");
pvt.AddDataField(pvt.PivotFields("PercentOfTotal"), "% of Total
Type.Missing).NumberFormat = "###.##";
. . .
…и вот код для раскрашивания рассматриваемого элемента:
for (m = 1; m <= dataCnt 1; m )
{
string descVal = cellRng.Value2.ToString();
bool isContractItem = GetContractForDescription(descVal);
if (isContractItem)
{
mcellRng = _xlPivotTableSheet.Range[
_xlPivotTableSheet.Cells[rowcnt, 1],
_xlPivotTableSheet.Cells[rowcnt 4, _grandTotalsColumn-1]];
mcellRng.Interior.Color = ColorTranslator.ToOle(CONTRACT_ITEM_COLOR);
mcellRng.EntireRow.RowHeight = 15;
}
. . .
Я не понимаю, как код раскрашивания (непосредственно выше) может работать только частично — как видно на втором скриншоте, причем первые несколько ячеек в столбце B являются белыми, а не желтыми, как остальная часть диапазона. Как будто есть
что-то «ядовитое» в этих данных, что мешает их раскрашиванию.
Если я создаю меньше столбцов (используя другой диапазон дат для запуска отчета), он запускается без сообщения об ошибке (но все еще имеет «дыру» в рассматриваемом диапазоне).
Если я просто создаю два месяца, все в порядке; но если я попробую шесть (запись «проблема» существует в любом случае), генерируется исключение …?!?
UPDATE
When I comment out the entire while loop, it runs without the error, but of course I then do not get the ContractItems colorized.
The problematic while loop is:
while ((cellRng.Value2 != null) amp;amp; cellRng.Value2.ToString() != "Total Total Packages")
{
cellRng.RowHeight = 25;
for (m = 1; m <= pivotTableRowCount; m )
{
string descVal = cellRng.Value2.ToString();
bool isContractItem = GetContractForDescription(descVal);
if (isContractItem)
{
mcellRng = _xlPivotTableSheet.Range[
_xlPivotTableSheet.Cells[rowCounter, 1],
_xlPivotTableSheet.Cells[rowCounter 4, _grandTotalsColumn - 1]];
mcellRng.Interior.Color = ColorTranslator.ToOle(CONTRACT_ITEM_COLOR);
mcellRng.EntireRow.RowHeight = 15;
}
rowCounter = rowCounter LINES_BETWEEN_DESCRIPTION_VALS;
cellRng = (Range)_xlPivotTableSheet.Cells[rowCounter, 1];
}
}
Что здесь может быть причиной сообщения об ошибке?
Проблема не в методе GetContractForDescription(), потому что игнорирование его таким образом:
bool isContractItem = false; //GetContractForDescription(descVal);
… не предотвращает сообщение об ошибке.
ОБНОВЛЕНИЕ 2
Поскольку моя голова раскалывается от того, что я так долго стучал ею по обоям, я решил «вернуться к кодированию» и начать заново. Теперь у меня есть этот код для определения того, какие элементы сжаты:
List<String> contractItemDescs = GetContractItemDescriptions();
MessageBox.Show(contractItemDescs.Count.ToString());
. . .
private List<string> GetContractItemDescriptions()
{
int FIRST_PIVOTDATA_DATA_ROW = 2;
int DESCRIPTION_COLUMN = 2;
int CONTRACT_COLUMN = 7;
List<string> descriptionsOfContractedItems = new List<string>();
int pivotDataRowCount = _xlBook.Worksheets["PivotData"].UsedRange.Rows.Count;
// Loop through the data sheet, adding the Descriptions for all Contract Items to the generic list
for (int i = FIRST_PIVOTDATA_DATA_ROW; i <= pivotDataRowCount; i )
{
Range contractItemCell = _xlPivotDataSheet.Cells[i, CONTRACT_COLUMN];
//if (contractItemCell.Value2.ToString() == "TRUE") <= seems to be the same logic as below, but this doesn't work, whereas the below code does...?!?
if (contractItemCell.Value2.ToString().ToUpper() != "FALSE")
{
Range descriptionCell = _xlPivotDataSheet.Cells[i, DESCRIPTION_COLUMN];
String desc = descriptionCell.Value2.ToString();
descriptionsOfContractedItems.Add(desc);
}
}
return descriptionsOfContractedItems;
}
Это работает правильно — окно сообщения показывает мне допустимое количество. Обратите внимание, однако, на закомментированный оператор «if». Используя это, я ничего не добавлял в список; с кажущимся аналогом, который я сейчас использую, я получаю правильное / ожидаемое поведение, хотя я не знаю почему.
Комментарии:
1. TL; DR но более простым способом отладки должно быть закомментирование инструкций try / catch и запуск в режиме отладки, чтобы точно увидеть, где возникает исключение. Я предполагаю, что эта строка
contractVal = (bool)dynamicContractCell.Value2;
2. Я отлаживал его, выполнив пошаговое выполнение, но найти, какое именно условие вызывает исключение, — это проблема.
3. Что заставляет вас подозревать эту строку? Единственными значениями, которые он имеет в тестовом листе, являются «TRUE» и «FALSE»
4. явные приведения в целом выглядят для меня подозрительно :] и сообщение об ошибке, похоже, предполагает, что исключение возникло после
currentDesc = dynamicCell.Value2.ToString();
и доreturn contractVal;
Ответ №1:
Я не совсем понимаю, почему предыдущий код не работал, но я предполагаю, что логика была просто в целом слишком запутанной. Я переработал и упростил его (вместо того, чтобы пытаться адаптировать какой-то существующий код, написанный кем-то другим, я начал заново и сделал это «по-своему»), и теперь он работает просто отлично. Новый код:
private static readonly Color CONTRACT_ITEM_COLOR = Color.FromArgb(255, 255, 204);
. . .
private void AddPivotTable()
{
. . .
List<String> contractItemDescs = GetContractItemDescriptions();
ColorizeContractItemBlocks(contractItemDescs);
. . .
}
private List<string> GetContractItemDescriptions()
{
int FIRST_PIVOTDATA_DATA_ROW = 2;
int DESCRIPTION_COLUMN = 2;
int CONTRACT_COLUMN = 7;
List<string> descriptionsOfContractedItems = new List<string>();
int pivotDataRowCount = _xlBook.Worksheets["PivotData"].UsedRange.Rows.Count;
// Loop through the data sheet, adding the Descriptions for all Contract Items to the generic list
for (int i = FIRST_PIVOTDATA_DATA_ROW; i <= pivotDataRowCount; i )
{
Range contractItemCell = _xlPivotDataSheet.Cells[i, CONTRACT_COLUMN];
if (contractItemCell.Value2.ToString().ToUpper() != "FALSE")
{
Range descriptionCell = _xlPivotDataSheet.Cells[i, DESCRIPTION_COLUMN];
String desc = descriptionCell.Value2.ToString();
descriptionsOfContractedItems.Add(desc);
}
}
return descriptionsOfContractedItems;
}
private void ColorizeContractItemBlocks(List<string> contractItemDescs)
{
int FIRST_DESCRIPTION_ROW = 8;
int DESCRIPTION_COL = 1;
int ROWS_BETWEEN_DESCRIPTIONS = 5;
int rowsUsed = _xlBook.Worksheets["PivotTable"].UsedRange.Rows.Count;
int colsUsed = _xlBook.Worksheets["PivotTable"].UsedRange.Columns.Count;
int currentRowBeingExamined = FIRST_DESCRIPTION_ROW;
while (currentRowBeingExamined < rowsUsed)
{
Range descriptionCell = _xlPivotTableSheet.Cells[currentRowBeingExamined, DESCRIPTION_COL];
String desc = descriptionCell.Value2.ToString();
if (contractItemDescs.Contains(desc))
{
Range rangeToColorize = _xlPivotTableSheet.Range[
_xlPivotTableSheet.Cells[currentRowBeingExamined, 1],
_xlPivotTableSheet.Cells[currentRowBeingExamined 4, colsUsed]];
rangeToColorize.Interior.Color = ColorTranslator.ToOle(CONTRACT_ITEM_COLOR);
}
currentRowBeingExamined = currentRowBeingExamined ROWS_BETWEEN_DESCRIPTIONS;
}
}