Below is a generic attempt at creating dependent picklists dynamically from C#. When value 'A' is selected from pick1, pick2 is supposed to display the values from the SecondaryRangeA.
This code almost works, but instead of displaying the SecondaryRangeA it displays the literal value 'A'.
pick2.Validation.Add(XlDVType.xlValidateList,
XlDVAlertStyle.xlValidAlertStop,
XlFormatConditionOperator.xlBetween,
"=INDIRECT(\"A5\")");
When I open excel after it exports and modify the data validation it shows the formula.
=INDIRECT("A5")
If I modify the formula manually in Excel to exclude the quotes it works as expected.
=INDIRECT(A5)
When I modify the code to the following I get an exception. Any ideas?
pick2.Validation.Add(XlDVType.xlValidateList,
XlDVAlertStyle.xlValidAlertStop,
XlFormatConditionOperator.xlBetween,
"=INDIRECT(A5)");
Exception:
System.Runtime.InteropServices.COMException was unhandled
ErrorCode=-2146827284
Message=Exception from HRESULT: 0x800A03EC
Source=""
StackTrace:
at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
at Microsoft.Office.Interop.Excel.Validation.Add(XlDVType Type, Object AlertStyle, Object Operator, Object Formula1, Object Formula2)
at TestExcelValidation.Program.Main(String[] args) in C:\TFS\ExcelInterop\TestExcelValidation\Program.cs:line 44
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
Full Example:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Office.Interop.Excel;
namespace TestExcelValidation
{
class Program
{
static void Main(string[] args)
{
string temporaryPath = Path.GetTempPath();
string temporaryFile = Path.GetTempFileName();
Application appl = new Application();
appl.Visible = true;
Workbook workbook = appl.Workbooks.Open(temporaryFile, 0, true, 5, "", "", true, XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);
Worksheet worksheet = (Worksheet)workbook.Worksheets.Add();
List<string> primaryList = new List<string>();
primaryList.Add("A");
primaryList.Add("B");
List<string> secondaryListA = new List<string>();
secondaryListA.Add("A1");
secondaryListA.Add("A2");
secondaryListA.Add("A3");
List<string> secondaryListB = new List<string>();
secondaryListB.Add("B1");
secondaryListB.Add("B2");
secondaryListB.Add("B3");
Range primaryRange = AddToExcelNamedRange(worksheet, primaryList, 'A', 1, "PrimaryRange");
Range secondaryRangeA = AddToExcelNamedRange(worksheet, secondaryListA, 'B', 1, "A");
Range secondaryRangeB = AddToExcelNamedRange(worksheet, secondaryListB, 'C', 1, "B");
Range pick1 = worksheet.Range["A5"];
pick1.Validation.Add(XlDVType.xlValidateList, XlDVAlertStyle.xlValidAlertStop, XlFormatConditionOperator.xlBetween, "=PrimaryRange");
Range pick2 = worksheet.Range["A6"];
pick2.Validation.Delete();
pick2.NumberFormat = "Text";
pick2.Validation.Add(XlDVType.xlValidateList, XlDVAlertStyle.xlValidAlertStop, XlFormatConditionOperator.xlBetween, "=INDIRECT(\"A5\")");
pick2.Validation.InCellDropdown = true;
pick2.Validation.IgnoreBlank = true;
}
private static Range AddToExcelNamedRange(Worksheet worksheet, List<string> primaryList, char col, int row, string rangeName)
{
Range range = worksheet.Range[col.ToString() + row.ToString(), col.ToString() + primaryList.Count().ToString()];
range.Name = rangeName;
foreach (string item in primaryList)
{
worksheet.Cells[row, col - 64] = item;
row++;
}
return range;
}
}
}
I have a workaround, but I want to know why this doesn't work. I'm sure I will run across this again.
Here is an answer in English, god only knows what the other two are saying.
Adding a cascading drop down list using a validation in Excel via C# (or VBA) fails with a COMException 0x800A03EC.
The reason it doesn't work is because the source is actually empty.
Let me show you how I worked this out. I injected a Macro in Excel and ran it:
Range pick1 = worksheet.Range["A5"];
pick1.Validation.Add(XlDVType.xlValidateList, XlDVAlertStyle.xlValidAlertStop, XlFormatConditionOperator.xlBetween, "=PrimaryRange");
Range pick2 = worksheet.Range["A6"];
StringBuilder sb = new StringBuilder();
sb.Append("Sub InsertCascadingDropDown()" + Environment.NewLine);
sb.Append(" Range(\"A6\").Select" + Environment.NewLine);
sb.Append(" With Selection.Validation" + Environment.NewLine);
sb.Append(" .Delete" + Environment.NewLine);
sb.Append(" .Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= xlBetween, Formula1:=\"=INDIRECT(A5)\"" + Environment.NewLine);
sb.Append(" .IgnoreBlank = True" + Environment.NewLine);
sb.Append(" .InCellDropdown = True" + Environment.NewLine);
sb.Append(" .ShowInput = True" + Environment.NewLine);
sb.Append(" .ShowError = True" + Environment.NewLine);
sb.Append(" End With" + Environment.NewLine);
sb.Append("End Sub" + Environment.NewLine);
//You need to add a COM reference to Microsoft Visual Basic for Applications Extensibility for this to work
var xlmodule = workbook.VBProject.VBComponents.Add(Microsoft.Vbe.Interop.vbext_ComponentType.vbext_ct_StdModule);
xlmodule.CodeModule.AddFromString(sb.ToString());
appl.Run("InsertCascadingDropDown");
This causes a run-time error '1004' when the Macro executes the line to add the Validation:
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:="=INDIRECT(A5)"
So this led me to be believe the selection object was not defined (or more to the point it was empty, depending on how you interpret the word Selection).
I played around with this and eventually halted the code control and added the validation manually when I discovered this:
The source currently evaluates to an error.
That was a smoking gun that the selection object was not null, it was the A5 drop down lists' selection/value was infact empty!
Adding the cascading drop down list needs its parent to have a value!
So this is all you need to do:
pick1.Value2 = "A"; //< set the parent to have a value
pick2.Validation.Delete(); //<- this is not really needed, unless you run this again
pick2.Validation.Add(XlDVType.xlValidateList, XlDVAlertStyle.xlValidAlertStop, XlFormatConditionOperator.xlBetween, "=INDIRECT(A5)");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With