|
06.05.2010, 10:00 | #1 |
Участник
|
Как передать в Excel (через ADO) число типа real так, чтобы оно не преобразовывалось в дату? Я указывают тип поля adDouble, а Excel всё равно преобразует в дату.
|
|
06.05.2010, 10:29 | #2 |
Участник
|
Цитата:
X++: static void Job20100506(Args _args) { #define.adDouble(5) COM Recordset; COM Fields; COM Field; COM Application; COM Workbooks; COM Workbook; COM Worksheets; COM Worksheet; COM Range; ; Recordset = new COM('ADODB.Recordset'); Fields = Recordset.Fields(); Fields.Append('Field1', #adDouble); Recordset.Open(); Recordset.AddNew(); Field = Fields.Item('Field1'); Field.Value(123.45); Recordset.Update(); Application = new COM('Excel.Application'); Workbooks = Application.Workbooks(); Workbook = Workbooks.add(); Worksheets = Workbook.Worksheets(); Worksheet = Worksheets.Item(1); Range = Worksheet.Range('A1'); Range.CopyFromRecordset(Recordset); Recordset.Close(); Application.Visible(true); } |
|
06.05.2010, 10:35 | #3 |
Moderator
|
Тоже не воспроизвелось
X++: static void Job298(Args _args) { #CCADO COM rng = SysExcelApplication::construct().workbooks().add().worksheets().itemFromNum(1).range('A1').comObject(); COM rst = new COM('ADODB.Recordset'); COM flds = rst.Fields(); COM fld; int i; flds.Append('MyReal', #adDouble); rst.Open(); fld = flds.Item('MyReal'); for (i=1; i<=10; i++) { rst.AddNew(); fld.Value(100 + i/10); rst.Update(); } rng.CopyFromRecordset(rst); COM::createFromObject(rng.Application()).Visible(true); // отобразим Excel } |
|
02.10.2008, 17:52 | #4 |
Участник
|
Как перейти к конкретной записи recordset?
Почитал help на эту тему, там есть метод move, но заставить его работать у меня не получилось. Ответ нашелся очень просто: X++: recordset.move( NumRecords, Start ); Последний раз редактировалось Stainless; 02.10.2008 в 18:05. |
|
02.10.2008, 19:02 | #5 |
Moderator
|
хм, в хелпе тоже самое, вроде:
Цитата:
Сообщение от файл ADO210.CHM
Move Method
Moves the position of the current record in a Recordset object. Syntax recordset.Move NumRecords, Start |
|
07.11.2008, 13:21 | #6 |
Moderator
|
э-хе-хе...
Испытываю возобновление интереса к теме и вот в связи с чем. ADODB.Recordset имеет полезнейшие свойства Filter и Sort, которыми можно эффективно пользоваться, фильтруя исходный набор или изменяя порядок записей исходного Recordset'а и далее перебирая в цикле нужные записи в нужном порядке.
Резонно было бы предположить, хотя про это нигде не сказано в справке, что методом CopyFromRecordset должен выгружаться набор записей с учетом фильтрации и сортировки. Увы, при управлении Excel из Аксапты этого не происходит: X++: static void Job103(Args _args) { #CCADO ComExcelDocument_RU doc = new ComExcelDocument_RU(); COM rst = new COM('ADODB.Recordset'); COM xlApp; COM wbook; COM activeSheet; COM range; COM flds,fld; int i; ; doc.NewFile('',false); wbook = doc.getComDocument(); xlApp = wbook.Parent(); activeSheet = xlApp.ActiveSheet(); flds = rst.Fields(); flds.Append('myField1', #adVarChar, 50); flds.Append('myField2', #adInteger); rst.Open(); for (i=1;i<=1000;i++) { rst.AddNew(); fld = flds.Item(0); fld.Value(int2str(i)); fld = flds.Item(1); fld.Value(i*2); rst.Update(); } rst.Filter("myField1 Like '*24*'"); rst.Sort('myField1 DESC'); rst.MoveFirst(); range = activeSheet.Range('A1'); range.CopyFromRecordset(rst, 300); range = activeSheet.Range('A1000'); range.CopyFromRecordset(rst, 300); doc.visible(true); // чтобы показать, что набор действительно фильтруется и сортируется rst.MoveFirst(); while (! rst.EOF()) { info(strFmt('%1 --- %2', new CCADOField( flds.Item(0) ).value(), new CCADOField( flds.Item(1) ).value())); rst.MoveNext(); } } Но этого не происходит. Вместо этого - и начиная с ячейки A1, и начиная с ячейки A1000 - одинаково выводятся первые 300 записей исходного набора. А хотелось бы, чтобы вывелись только 20 записей с ячейки A1, т.е. те записи, которые пример выводит в окно Infolog. Возможно мои претензии необоснованы и CopyFromRecordset должен себя вести именно так, как ведет себя в этом примере? Я уже было засомневался, вернее, почти отказался от своих "претензий", но решился на еще одну проверку - в самом Excel на VBA: Код: Sub Job103_VBA() Dim i As Integer Dim rst As Object Dim flds As Object Dim xlApp As Excel.Application Set rst = CreateObject("ADODB.Recordset") 'я сознательно создал rst через CreateObject, а не как New ADODB.Recordset, 'чтобы не искать ссылку на ADO через Tools\References Set flds = rst.Fields flds.Append "myField1", 200, 50 '200 = adVarChar flds.Append "myField2", 3 '3 = adInteger rst.Open For i = 1 To 1000 rst.AddNew rst.Fields(0).Value = i rst.Fields(1).Value = i * 2 rst.Update Next i rst.Filter = "myField1 Like '*24*'" rst.Sort = "myField1 DESC" rst.MoveFirst 'Set xlApp = New Excel.Application Set xlApp = Application xlApp.Workbooks.Add xlApp.Range("A1").CopyFromRecordset rst, 300 xlApp.Range("A1000").CopyFromRecordset rst, 300 xlApp.Visible = True End Sub Код: Set xlApp = New Excel.Application 'Set xlApp = Application Есть ли у кого какие-нибудь соображения? Может, кто-нибудь интересующийся продвинуто заглянет какими-нибудь крутыми вьюерами в тайны происходящих процессов COM-автоматизации? Может быть удастся победить ситуацию использованием каких-нибудь COMDispFunction? P.S. Проделал даже такой финт. Создал шаблон xlt, в который зашил VBA-процедуру: Код: Sub myCopyFromRecordset(ByVal rng As Range, ByVal rst As ADODB.Recordset, ByVal maxRows As Integer) rng.CopyFromRecordset rst, maxRows End Sub X++: xlApp.Run('myCopyFromRecordset', range, rst, 300); |
|
07.11.2008, 20:12 | #7 |
Administrator
|
Цитата:
Я удивлен другим. Я удивлен - что CopyFromRecordset все-таки может копировать с учетом фильтрации - но об этом видимо "помнит" только текущее приложение - т.е. поддержка реализована не на уровне "ядра" ADO
__________________
Возможно сделать все. Вопрос времени |
|
29.04.2009, 13:16 | #8 |
Участник
|
А я во вспомогательном канале вывода в Excel зашел с другого конца: при создании структуры ADO.Recordset я указываю опциональный признак того, надо ли в соотв. колонке избавиться от "незначащих" значений, и после вывода просто делают замену средствами самого Excel таких значений на то, что возвращает COMVariant::createNoValue()
X++: // очистка ячеек с "пустыми" значениями (ноль для чисел либо 01.01.1900 для дат), чтоб не надо было докручивать шаблон для их сокрытия // _cell - ячейка, на которой в outputReportBody() вызывается метод CopyFromRecordset() protected void clearEmptyCells(COM _cell) { COMVariant cvSrcValue; COMVariant cvDstValue; str strAddr; container conFieldInfo; boolean bClearEmpty; Counter cnRows; Counter n; COM oRng; // область, в которой будет производиться замена ; cnRows = rstAxa.RecordCount(); // количество выведенных строк данных cvDstValue = COMVariant::createNoValue(); // создаем пустое значение for (n = 1; n <= arrFields.lastIndex(); n++) { conFieldInfo = arrFields.value( n ); if (conlen(conFieldInfo) >= #ConPosFieldInfoClearEmpty) // по умолчанию этого поля в контейнеере быть не должно { bClearEmpty = conpeek( conFieldInfo, #ConPosFieldInfoClearEmpty ); if (!bClearEmpty) continue; cvSrcValue = this.getEmptyVariantValue( conpeek( conFieldInfo, #ConPosFieldInfoType ) ); if (!cvSrcValue) // если вернулся null, значит, тип не поддерживается continue; // формируем адрес диапазона ячеек *относительно* _cell strAddr = ComExceldocument_RU::numToNameCell( n, 1 ); if (cnRows > 1) strAddr += @':' + ComExceldocument_RU::numToNameCell( n, cnRows ); oRng = _cell.range( strAddr ); // получаем столбец внутри диапазона _cell oRng.Replace( cvSrcValue, cvDstValue, #xlWhole, #xlByColumns ); } } } // возвращает COMVariant, представляющий "пустое" значение для указанного типа ADO, либо null, если указанный тип не поддерживается protected COMVariant getEmptyVariantValue( Integer _adoType ) { COMVariant ret; ; switch( _adoType ) { case #adDate : case #adDBDate : case #adDBTimeStamp : ret = COMVariant::createFromDateAndTime( datenull(), 0 ); break; case #adSingle : case #adDouble : case #adCurrency : case #adDecimal : case #adNumeric : ret = COMVariant::createFromReal( 0.0 ); break; case #adTinyInt : case #adSmallInt : case #adInteger : case #adBigInt : case #adUnsignedTinyInt : case #adUnsignedSmallInt : case #adUnsignedInt : case #adUnsignedBigInt : ret = COMVariant::createFromInt( 0 ); break; default : ret = null; break; } return ret; } PS. По ходу реализации наткнулся на одни "грабли": оказалось, что COMVariant::createFromDate( datenull() ) возвращает COMVariant не со значением 01.01.1900 00:00, как можно было бы ожидать, а со значением 01.01.1900 <текущее_время>! |
|
29.04.2009, 15:17 | #9 |
Moderator
|
Цитата:
Вот почему мы любим тип #adDBDate (133), который, как положено, сохраняет только дату без времени |
|
|
За это сообщение автора поблагодарили: alex55 (1). |
17.09.2010, 21:22 | #10 |
Участник
|
Извиняюсь, что поднял старую тему, просто что-то поиском не нашел ответа на вроде бы очевидный вопрос, как при экспорте в Excel через RecordSet оставить ячейки не заполненными? Ну, например, если не указана дата, то в ячейке Excel должно быть пусто, а не 00.01.1900.
Разумеется, покопавшись в справке довольно быстро нашел ответ. Но, может, еще кому пригодится. Суть сводится к тому, что соответствующее поле RecordSet должно содержать значение NULL на момент создания новой строки. Вообще-то, по умолчанию, при создании новой строки в RecordSet, если явно не указано значение, то и предпринимается попытка присвоить значение NULL. Однако, опять же по умолчанию, поле RecordSet создается с невозможностью принимать значение NULL и, как следствие, присваивает "пустое" значение Допустимость использования значения NULL задается 4 параметром в методе Fields.Append(). 3 параметр - это размерность поля в байтах. Есть еще 5 параметр - значение по умолчанию, но практического смысла в данном случае 5 параметр не имеет. Тогда код будет выглядеть примерно так X++: static void Job_ADORecordSet2Excel_EmptyCells(Args _args) { Com comRecordSet; Com comFields; Com comField; ComExcelDocument_RU excel; Com comRange; ; #define.adFldIsNullable(32) // Поле может принимать значение NULL #define.adFldMayBeNull(64) // Из поля можно прочитать значение NULL comRecordSet = new COM('ADODB.Recordset'); // формируем структуру Recordset // где каждое поле может принимать значение NULL comFields = comRecordSet.Fields(); comFields.Append("FieldStr", COMVariantType::VT_BSTR, 50, #adFldIsNullable + #adFldMayBeNull); comFields.Append("FieldReal", COMVariantType::VT_R8, 8, #adFldIsNullable + #adFldMayBeNull); // Если нужна только даты без части со временем, то следует указать значение 133 // Такого значения нет в Base Enum с именем COMVariantType comFields.Append("FieldDate", COMVariantType::VT_DATE, 8, #adFldIsNullable + #adFldMayBeNull); // Открываем RecordSet для заполнения comRecordSet.open(); // Формируем первую строку comRecordSet.AddNew(); // Наполняем ее данными для сравнения comField = comFields.Item("FieldStr"); comField.value(comVariant::createFromStr("Первая строка")); comField = comFields.Item("FieldReal"); comField.value(comVariant::createFromReal(123.456)); comField = comFields.Item("FieldDate"); comField.value(comVariant::createFromDate(systemDateGet())); // Формируем вторую строку // В которой нет никаких данных. Ячейки будут пустыми comRecordSet.AddNew(); // Формируем третью строку comRecordSet.AddNew(); // Наполняем ее данными для сравнения comField = comFields.Item("FieldStr"); comField.value(comVariant::createFromStr("Третья строка")); comField = comFields.Item("FieldReal"); comField.value(comVariant::createFromReal(789.012)); comField = comFields.Item("FieldDate"); comField.value(comVariant::createFromDate(systemDateGet()+2)); // Открываем Excel с пустым листом и сразу делаем его видимым excel = new ComExcelDocument_RU(); excel.newFile("",true); // Определяем ячейку, откуда будет осуществляться вывод comRange = excel.findRange("A1"); // Выводим данные comRange.CopyFromRecordset(comRecordSet); } Собственно, это все работает, если ничего не вводить в поле RecordSet. Но, к сожалению не нашел, а как записать в поле RecordSet значение null ? Единственный, не очень хороший вариант - это присвоение исходного (оригинального) значения, исходя из предположения, что это значение NULL X++: comField = comFields.Item("FieldDate");
comField.value(comField.OriginalValue()); |
|
|
За это сообщение автора поблагодарили: S.Kuskov (5). |
18.09.2010, 10:59 | #11 |
Участник
|
Например, так
X++: ComVariant var;
...
var = new ComVariant(COMVariantInOut::In_out, ComVariantType::VT_BOOL);
var.variantType(ComVariantType::VT_NULL);
comField.value(var);
...
__________________
Axapta v.3.0 sp5 kr2 |
|
|
За это сообщение автора поблагодарили: Владимир Максимов (2), Logger (5), Ace of Database (5), gl00mie (3). |
02.03.2011, 11:44 | #12 |
Участник
|
У меня такой вопрос возник. Вроде по теме.
После сбора данных из нескольких таблиц в ADODB.Recordset мне нужно вывести их форму на grid, чтобы потом можно было их просмотреть/редактировать в самой DAX. Как это можно сделать? Ну естественно напрашивается вариант со временной таблицей, ну а будет ли это быстро? И как вообще ее заполнять? |
|
02.03.2011, 13:49 | #13 |
Moderator
|
Если не зацикливаться на grid'е, то можно посадить на форму ActiveX OWC Spreadsheet и в нем смотреть/редактировать. Spreadsheet поддерживает метод CopyFromRecordset.
|
|
02.03.2011, 14:54 | #14 |
Участник
|
Я об этом как раз подумал. Но я к сожалению недавно с Axapta знаком. Как вывести значение на форму? Скажем rstAxa (это сам рекордсет).
Уточню вопрос, допустим я делаю X++: ExcelSheet = tabExcelSheet.addControl(formControlType::ActiveX, "ExcelSheet"); |
|
02.03.2011, 15:20 | #15 |
Moderator
|
Цитата:
Но лучше помещать на нормальную форму, сохраненную в AOT (не диалог) - тогда можно будет воспользоваться его событиями. В диалоге же это не получается: События FormActiveXControl не получается использовать в диалоге |
|
|
За это сообщение автора поблагодарили: refined (1). |
02.03.2011, 15:31 | #16 |
Участник
|
Да, я как раз пытаюсь через нормальную форму. Я делаю следующим образом:
X++: reportEngine = element.args().caller(); super(); ExcelSheet = tabExcelSheet.addControl(formControlType::ActiveX, "ExcelSheet"); ExcelSheet.widthMode(formwidth::ColumnWidth); ExcelSheet.heightMode(formHeight::ColumnHeight); ExcelSheet.CopyFromRecordset(reportEngine.recordset()); X++: COM recordset()
{
;
return rstAxa;
} Всё, я уже разобрался. Добавил еще строчек кода из http://axforum.info/forums/blog.php?b=26 Просто забыл код закомментировать. Последний раз редактировалось refined; 02.03.2011 в 16:22. Причина: выполнил |
|
02.03.2011, 16:25 | #17 |
Moderator
|
ExcelSheet это FormActiveXControl? Конечно, у него такого метода нет. Метод есть у Range. Надо перейти к нему как-то так:
X++: COM range;
range = ExcelSheet.Range("A1");
range.CopyFromRecordset(...); X++: ExcelSheet = tabExcelSheet.addControl(formControlType::ActiveX, "ExcelSheet"); ExcelSheet.className('{0002E541-0000-0000-C000-000000000046}'); // Microsoft Office Spreadsheet 10.0 P.S. ОК! Хорошо, что разобрались. |
|
03.03.2011, 13:48 | #18 |
Участник
|
В итоге оказалось, что намного проще сделать вывод не сразу в эксель а просто в форму. И в ней сразу делать сводную таблицу.
Так вот у меня с ней как раз и проблема. Она не отрабатывает как Сводная. Как сделать так чтобы при перетаскивании столбцов, они *схлопывались* автоматически? т.е. осуществлялась группировка по полю (одному, двум). Ну и соответсвенно если это число то выводилась сумма? И так, вот сам метод выполняемый на форме: PivotTable = grpPivotTableHolder.addControl(formControlType::ActiveX, "PivotTable"); X++: className = CCUtil::getOfficePivotTableVersion(true); PivotTable.className(className); PivotTable.WidthMode(formwidth::ColumnWidth); PivotTable.HeightMode(formHeight::ColumnHeight); PivotTable.allowFiltering(true); PivotTable.AllowGrouping(true); PivotTable.DataSource(reportEngine.ADORecordset()); Смысл в том чтобы не использовать временную таблицу. Последний раз редактировалось refined; 03.03.2011 в 13:57. |
|
12.09.2014, 17:24 | #19 |
Участник
|
Господа. Прошу помочь разобраться с этим ADO.
Делаю джоб (самый первый пример из этой темы). Все отрабатывает. Пытаюсь воспользоваться этим же кодом в Классе, отчете.... во время запуска выдает ошибку на строке rstAxa = new COM('ADODB.Recordset'); пишет "Объект "COM" не может быть создан" Почему один и тот же код не работает в отчете, классе? Цитата:
static void Job_TestADO_2(Args _args)
{ // ---------------------------------------------------------------------------- // ADODB.Recordset в оперативной памяти без привязки к источнику данных // ---------------------------------------------------------------------------- EmplTable emplTable; COM rstAxa; // ADO: Recordset COM flds, fld; // ADO: Fields, Field COM xlApp; // Excel.Application COM wbks, wbk; // Workbooks, Workbook COM wkss, wks; // Worksheets, Worksheet COM rng, cell, rngCR; // все Range COM font; // Range.Font COM entCol; // Range.EntireColumn COM actWin; // Excel.Application.ActiveWindow int i, iMax; // ============================================================================ // СНАЧАЛА ВЛОЖЕННАЯ ФУНКЦИЯ // ---------------------------------------------------------------------------- // функция для задания типа поля нашего Recordset-а в оперативной памяти // в данном демо-джобе используется для наглядности // для простоты используем всего 3 типа данных: число, строка и дата int adoTypeToExcel(str _type) { switch (_type) { // используются значения констант перечисления DateTypeEnum из топика TypeProperty (ADO) // (см. справку по ADO в файле ADO210.CHM - можно поискать на своем компе) case 'num' : return 5; // adDouble case 'str' : return 8; // adBSTR case 'date': return 133; // adDBDate } return 8; } // ============================================================================ // ТЕПЕРЬ ОСНОВНОЙ ПРОЦЕСС // Recordset создается в оперативной памяти - без Connection! rstAxa = new COM('ADODB.Recordset');
__________________
Dynamics AX 2009 SP1, Rollup 5, SQL Server 2008 Хороший ученик во всем найдет себе учителя... |
|
12.09.2014, 19:39 | #20 |
Участник
|
|
|
|
За это сообщение автора поблагодарили: Отшельник (1). |
Теги |
ado, comvariant, excel, faq, odbc, sql, интеграция, прямой доступ, формат дат, экспорт, экспорт в excel |
|
|