Видимо любители экономить килобайты оперативной памяти могут меня закидать помидорами или ещё чем по-хуже, но все-таки я скажу, что интеграция приложений (мегабайты оперативы) — это большой плюс нынешней разработки приложений.
Как ни крути, а время DOS и килобайтов оперативной памяти прошло. Вряд ли кто-то всерьез сейчас задумывается над тем куда это с винчестера пропал мегабайт? Да и использование в своих приложениях функциональности программ, которых ты не писал, но которые выполняют что-то лучше — это всё-таки больший прогресс, нежели корпеть год-два над программой, а потом узнать, что время-то прошло.
Введение
Итак, цель сегодняшней статьи — поделиться с Вами опытом работы с Microsoft Excel в приложениях, написанных на Delphi.
Вспомнился сейчас один случай. Когда я только начинал работать по своей специальности, пригласили меня написать программу-расчётник для экологов нашего нефтезавода. В принципе ничего серьёзного — программа считает выброс от нагревательной печи и выдает табличку результатов, которую необходимо распечатать и уложить в толстую папку с отчётами. Естественно, что в области разработки подобных приложения я далеко не пионер, поэтому дали взглянуть на аналог будущей программы, который работал ещё под DOS и печатались отчёты на дико скрипящем матричном принтере с 12-ю иголками. Ну посмотрел, элементарная таблица, расчёт немного запутан, но жить можно — начал по-тихоньку писать. И попалась мне тогда на глаза статейка про работу с Excel в Delphi. Вот я и решил попробовать выдавать отчёт не только на форму приложения, а ещё и скидывать весь ход расчёта с формулами и прочим делом в Excel…Надо сказать более сильно детской радости начальника отдела я не видел до сих пор :). Люди, всю жизнь проработавшие в DOS увидели как тот же самый расчёт может выглядеть в современных условиях. Вот теперь при определении технических заданий на каждую новую программу, обязательно присутствует пункт, гласящий, что программа должна передавать данные либо в MS Word либо в MS Excel.Соответственно и цена на разработку возрастает, иногда значительно.
Отсюда можно сделать простой и однозначный вывод — заказчики готовы пожертвовать лишними деньгами только для того, чтобы всё в программе было красиво. Excel может дать вашему приложению ту самую красоту и удобство.
Ну, а для того, чтобы каждый раз не утруждать себя выполнением однотипных операций, я разработал небольшой модуль для работы с Excel. Этот же модуль я в настоящее время дорабатываю под ещё одну задачу, но об этом после. Сегодня основы основ работы с Excel в Delphi.
1. Как проверить установлен ли Excel на компьютере пользователя?
Создаем новый модуль (unit) и подключаем в uses следующие модули:
uses ComObj, ActiveX, Variants, Windows, Messages, SysUtils, Classes;
теперь объявляем глобальную переменную:
var MyExcel: OleVariant;
Одну константу (для удобства):
const ExcelApp = 'Excel.Application';
И пишем простенькую функцию:
function CheckExcelInstall:boolean; var ClassID: TCLSID; Rez : HRESULT; begin // Ищем CLSID OLE-объекта Rez := CLSIDFromProgID(PWideChar(WideString(ExcelApp)), ClassID); if Rez = S_OK then // Объект найден Result := true else Result := false; end;
Или ещё короче:
function CheckExcelInstall: boolean; var ClassID: TCLSID; begin Result:=CLSIDFromProgID(PWideChar(WideString(ExcelApp)), ClassID) = S_OK; end;
Если функция CLSIDFromProgID находит CLSID OLE-объекта, то, соответственно — Excel установлен.
Но проверка на наличие установленного Excel — это только часть необходимых операций перед началом работы. Другой немаловажной проверкой является проверка на наличие уже запущенного экземпляра Excel. Если этого не делать, то может случиться такая нехорошая ситуация, когда в системе будет зарегистрировано …дцать процессов Excel и все оперативная память волшебным образом утекает «в трубу» — за такое могут и по ушам надавать. Но мы-то свои уши бережем. Пишем вторую функцию проверки.
2. Определяем запущен ли Excel
function CheckExcelRun: boolean; begin try MyExcel:=GetActiveOleObject(ExcelApp); Result:=True; except Result:=false; end; end;
Думаю тут лишних объяснений не потребуется? Всё предельно просто — если есть активный процесс Excel, то мы просто получаем на него ссылку и можем использовать Excel для своих корыстных целей. Главное — не забыть проверить — может кто-то этот самый Excel забыл закрыть, но это другой момент. Будем считать, что Excel в нашем полном распоряжении.
3. Как запустить Excel?
Одно дело, когда мы получаем в распоряжение уже запущенный Excel, другое — когда Excel требуется запустить из Delphi. Напишем функцию запуска Excel:
function RunExcel(DisableAlerts:boolean=true; Visible: boolean=false): boolean; begin try {проверяем установлен ли Excel} if CheckExcelInstall then begin MyExcel:=CreateOleObject(ExcelApp); //показывать/не показывать системные сообщения Excel (лучше не показывать) MyExcel.Application.EnableEvents:=DisableAlerts; MyExcel.Visible:=Visible; Result:=true; end else begin MessageBox(0,'Приложение MS Excel не установлено на этом компьютере','Ошибка',MB_OK+MB_ICONERROR); Result:=false; end; except Result:=false; end; end;
Здесь мы в начале проверяем, установлен ли Excel в принципе и, если он все же установлен, запускам. При этом мы можем сразу показать окно Excel пользователю — для этого необходимо выставить параметр Visible в значение True.
Также рекомендую всегда отключать системные сообщения Excel, иначе, когда программа начнет говорить голосом Excel — пользователь может занервничать.
Переходим к следующему этапу работы — созданию рабочей книги.
4. Создаем пустую рабочую книгу Excel
Для создания пустой рабочей книги я обычно использую вот такую функцию:
function AddWorkBook(AutoRun:boolean=true):boolean; begin if CheckExcelRun then begin MyExcel.WorkBooks.Add; Result:=true; end else if AutoRun then begin RunExcel; MyExcel.WorkBooks.Add; Result:=true; end else Result:=false; end;
Второй вариант (более лаконичный):
function AddWorkBook(AutoRun: boolean = true): boolean; begin Result := CheckExcelRun; if (not Result) and (AutoRun) then begin RunExcel; Result := CheckExcelRun; end; if Result then MyExcel.WorkBooks.Add; end;
То есть сразу проверяю запущен ли Excel и, если он не запущен, то либо запускаю и добавляю книгу, либо просто выхожу — всё зависит от ситуации.
Здесь, думаю, следует напомнить, что, если вы выполните эту функцию, например пять раз, то получите пять открытых рабочих книг и работать с ними как Вам захочется. Главное при этом правильно обратиться к необходимой книге, а для этого можно использовать вот такую функцию:
function GetAllWorkBooks:TStringList; var i:integer; begin try Result:=TStringList.Create; for i:=1 to MyExcel.WorkBooks.Count do Result.Add(MyExcel.WorkBooks.Item[i].FullName) except MessageBox(0,'Ошибка перечисления открытых книг','Ошибка',MB_OK+MB_ICONERROR); end; end;
Функция возвращает список TStringList всех рабочих книг Excel открытых в данный момент. Обратите внимание, что в отличие от Delphi Excel присваивает первой книге индекс 1, а не 0 как это обычно делается в Delphi при работе, например, с теми же индексами в ComboBox’ах.
Ну, и наконец, после того, как поработали с книгами — их требуется закрыть. Точнее сохранить, а потом уж закрыть.
5. Сохраняем рабочую книгу и закрываем Excel
Для того, чтобы сохранить рабочую книгу, я использовал такую функцию:
function SaveWorkBook(FileName:TFileName; WBIndex:integer):boolean; begin try MyExcel.WorkBooks.Item[WBIndex].SaveAs(FileName); if MyExcel.WorkBooks.Item[WBIndex].Saved then Result:=true else Result:=false; except Result:=false; end; end;
Если у Вас открыто 10 книг — просто вызываете функцию 10 раз, меняя значение параметра WBIndex и имени файла и дело в шляпе.
А закрывается Excel вот так:
function StopExcel:boolean; begin try if MyExcel.Visible then MyExcel.Visible:=false; MyExcel.Quit; MyExcel:=Unassigned; Result:=True; except Result:=false; end; end;
Вот набор тех основных функций с которых начинается вся интеграция Excel в приложения написанные на Delphi. В следующий раз займемся работой с конкретной книгой — научимся записывать и читать данные из книг.
Книжная полка
Описание Описаны общие подходы к программированию приложений MS Office. Даны программные методы реализации функций MS Excel, MS Word, MS Access и MS Outlook в среде Delphi.
|
Работа с Excel в Delphi. Основы основ. | Delphi в Internet…
Рассматриваем основные функции для работы с Excel из Delphi…
Если документ простой, но объемный — это будет медленно. А при большом количестве таких документов — жизнь пройдет мимо.
Вывести в Excel можно и проще.
Что конкретно будет медленно? :) Запуск Excel или проверка установки Excel? Так тут ускорить процесс негде. Вывода в Excel-то пока в статье нет. Забегая вперед, могу сказать — выводить по-отдельности каждую ячейку — долго, муторно и не верно, но есть вариант как ускорить процесс в несколько десятков, а то и сотен раз.
Да, и ещё — оставлять в комментариях ссылки не стоит. Блог и так DoFollow, кому надо — тот перейдет на ваш блог.
Очень хорошие статьи. Разобрался во всём с первого раза ))) Понравилась схема в mind42 )))
Автору остаётся сказать только спасибо!
не за что, Иван. Всегда рад помочь)
Все хорошо. Но если Excel не запущен — возникает исключение. Что с этим делать? function CheckExcelRun выбрасывает сообщение и все…Хотя по смыслу должно было вернуть False
Странно. Если Excel запущен, то должна создаваться вторая копия приложения без исключений. Код в студию :)
Насчет процедуры CheckExcelRun. Если программа запускается из под Delphi и Excel не запущен, то выскакивает ошибка: Операция GetActiveOleObject недоступна. Если програма запускается не из под Delphi то все в порядке. Наверное эту проблему имел ввиду Алекс. Как решение можно при отладке программы когда возникает исключение нажимать F9.
Собственное говоря, а как при таком связывании отследить «сообщения» от Excel? Например, юзер «случайным» образом закрывает рабочую книгу, а программа все еще «хочет» работать с ней.
Честно говоря, никогда не задумывался о том как отловить сообщение от Excel и возможно ли то сдела вообще. Обычно в случае работы с книгами Excel или документами Word просто не забывал пользоваться try..except..end :) По-моему в данном случае можно покапаться в событиях Excel.
произвожу экспорт в excel из delphi, все проходит как бы ровно. а неровность заключается в том что после экспорта остается жить процесс «excel.exe». пытался юзать (Excel.Quit; Excel:=Unassigned;) не хочет помогать. помогите плиз люди добрые
Вы точно один раз запускаете процесс?
Excel.Quit; — выход из приложения
Excel:=Unassigned; — освобождение переменной
Этих строк 100% достаточно, чтобы завершить процесс правильно.
Вобще очень крутая статья! Долго мучался очень я)
Не компилируется функция Save, «FileName:TFileName» вот этот кусок. Откуда это берется?
По сабжу пока не работал, но скорее всего либо перменная FileName уже объявлена, либо тип TFileName из модуля SysUtils, где-то переопределен.
Сообщение компилятора приведите без него только догадки строить остается.
Готовый модуль скачать не удалось… какието глюки на айфолдерс.
Написал по «инструкции» в посте. Все работает без проблем.
Помогите Вопрос! Я использую шаблоны в своей программе! Можно ли из Дельфи изменть путь Save as..! Что бы при слхранений пользавателем путь сохранения был изменен? Спасибо
Можно менять путь сохранения — в справке по офису посмотрите описание функции SaveAs.
А можно ли проверить, что не просто запущен Excel, а уже имеется открытая конкретная книга.
Например, хочу создать в Excel (усли уже создана, то открыть) книгу table.xls
Так это вообще без проблем:
1. Проверяем запущен ли Excel
2. Если запущен обращаемся к коллекции открытых книг и проверяем в коллекции нужную книгу
3. Если книга найдена — активируем окно/работаем в фоне
Эт конечно всё заебца, но в Д2009 не канает…
Канает в любой версии Delphi, даже в XE.
begin
result:=CheckExcelRun;
if not result then
if AutoRun then
begin
RunExcel;
result:=CheckExcelRun;
end;
if result then MyExcel.WorkBooks.Add;
end;
Или так :-)
begin
result:=CheckExcelRun;
if (not result) and (AutoRun) then
begin
RunExcel;
result:=CheckExcelRun;
end;
if result then MyExcel.WorkBooks.Add;
end;
подскажите плиз,я недавно работаю с Delphi и у меня тут возникла проблемка..когда я использовал ваш код для проверки установлен Excel на компе или нет, у меня вылезла ошибка:
[Error] Unit5.pas(17): Identifier redeclared: ‘Rez’
объясните пожалуйста,в чем проблема?
Ну так отладчик же понятно говорит, что переменная Rez повторно объявлена. Ищите где повторное объявление. Может где в глобальных переменных повторились
как подсчитать кол-во заполненых строк на листе?
в VBA так — Cells(Rows.Count, 1).End(xlUp).Row
а как в Delphi ее описать?
function isExcelInstalled:boolean;
var
ClassID: TCLSID;
begin
Result := CLSIDFromProgID(PWideChar(WideString(ExcelApp)), ClassID) = S_OK;
end;
Понятнее и короче.
Народ , а нельзя вбить какую нить формулу в excel и чтобы результат вычисления выводился в проге в Дельфи? просто я только разбираюсь в дельфи и не знаю как это через дельфи сделать..
Это будет проще сделать в делфи, и на много облегчит программу.
MyExcel.Application.EnableEvents:=DisableAlerts;
Если при поиске и замене ничего не найдено, то выдается сообщение — даже если DisableAlerts в False.
И почему DisableAlerts по умолчанию True — если сообщения лучше не показывать.
В моем случае помогло MyExcel.Application.DisplayAlerts := False.
DisableAlerts и .DisplayAlerts — разницу ощущаете? :)
Хорошо, буду догадываться.
DisplayAlerts по идее отключает сообщения выдаваемые на экран. Но что тогда делает EnableEvents?
В методе CheckExcelRun, если эксель закрыт возникает ошибка «Операция GetActiveOleObject недоступна.» а следом «invalid variant operation»
И вообще работает не совсем корректно, т.к. если открыт другой документ excel то он сохраняет его, а не тот который формирует прога. Я только начинаю писать на делфи. Помогите исправить это
var
MsExcel : Variant;
_Sheet : TStringList;
i, x : integer;
CCR_Ver, lLastRow, Name_Sheet, path : String;
begin
// 1. Открываем форму CCR
XLS_File.Execute();
if XLS_File.FileName <> » then
begin
MsExcel := CreateOleObject(‘Excel.Application’);
MsExcel.Workbooks.Open[XLS_File.FileName, 0, True];
// видимость Excel
MsExcel.Visible := False;
// Закрытие открытой книги
MsExcel.WorkBooks[MsExcel.WorkBooks.Count].Close(false);
// Закрытие Excel
MsExcel.quit;
// Перераспределение памяти
inherited;
close; // Закрытие формы frm_CCR_mos
end;
Предлагаемый вариант конечно работоспособен, но у него есть важные недостатки:
— низкая производительность (попробуйте создать таблицу например размером 100 на 10000 ячеек со сложным форматированием)
— высокие требования к необходимой оперативной памяти (опять же см. пример с размером таблицы)
— необходим установленный Excel (нет экселя, нет итогового файла. актуально, если подготовка отчетов делается на выделенном сервере, где лимитирован список установленного софта)
Лично я использую XLSReadWrite — вышеуказанных недостатков нет (таблица, указанную выше, у меня генерится за 0.4 секунды). Будет интересно — могу выложить реальный код примера
GS, было б неплохо взглянуть на такой пример. Кстати, для ускорения производительности я использую вариантный массив. Считываем за 1 раз весь лист в массив и уже потом в памяти вытаскиваем необходимые данные. Скорость возрастает в разы, а то и в десятки раз. А представленный пример — это действительно пример , который рассчитывался на абсолютно неподготовленного читателя=)
Влад, здравствуйте.
Я могу с Вами как либо связаться и задать пару вопросов?
Можете. Если вопрос чисто по программированию — задавайте здесь или на форуме (http://www.webdelphi.ru/razrabotchiku/forum/). Если по работе (за $$$$), то тогда в скайп — Vlad55ru
Упрощенный пример для Delphi 7 с использованием библиотеки XLSReadWrite: var IntlXls: TXLSReadWriteII2; I, J: Integer; begin // создаем объект IntlXls := TXLSReadWriteII2.Create(nil); // название книги IntlXls.Sheets[0].Name := ‘Название моего отчета’; // добавляем необходимое количество строк и колонок IntlXls.Sheets[0].Rows.AddIfNone(0, 10000); IntlXls.Sheets[0].Columns.AddIfNone(0, 100); // добавляем и заносим ширины ячеек (значение в пикселях) for I := 0 to 99 do IntlXls.Sheets[0].Columns[I].PixelWidth := 150; // заносим высоты строк (значение здесь не в пикселях, поэтому нужно корректировать) for I := 0 to 9999 do IntlXls.Sheets[0].Rows[I].Height := 20 * 14; // настраиваем for J := 0 to… Подробнее »
GS, спасибо большое. Не возражаете, если я, по мере наличия свободного времени, опубликую в блоге отдельным постом эти примеры (Естественно с указанием настоящего автора примеров). Думаю, что такой пост может пригодиться многим.
GS! Подскажите, где взять библиотеку XLSReadWrite?
Буду только рад :)
Ребят, вы даже представить себе не можете, насколько это полезная информация)))
Спасибо!
Нужна помощь!
1) В функции CheckExcelInstall выдает результат false хотя exel установлен(2010)…
2)Ругается на строчку MyExcel:=GetActiveOleObject(ExcelApp); так: «project project.exe raised exception class EOleSysError with message ‘недопустимая строка с указанием класса’.»
Доброго…
извиняюсь вторгнусь в беседу.
подскажите плиз как сделать следующее с помощью компонентов XLSReadWrite
1. открыть книгу
2. сделать поиск построчно в столбце например T при этом нужен поиск с частичным совпадением текста.
3. организовать поиск по многим книгам за 1 проход.
текущий пример как сделано сейчас прилагаю. Открытие книги и поиск в ней выполняются за 9 секунд в среднем в двух книгах за 18. хотелось бы уменьшить данное время на сколько это возможно.
заранее спасибо всем откликнувшимся.
// Создание Excel ExcelApp := CreateOleObject(‘Excel.Application’); // Отключаем реакцию Excel на события, чтобы ускорить вывод информации ExcelApp.Application.EnableEvents := false; // Открываем Книгу (Workbook) в режиме только для чтения ExcelApp.WorkBooks.Open(ExtractFilePath(ExtractFilePath(Application.ExeName)) + Book_Name, ReadOnly:=true); WorkBookCount := ExcelApp.WorkBooks.Count; end; Sheet := ExcelApp.WorkBooks[WorkBookCount].Sheets[Sheet_Name]; // ThisWorkbook.Sheets(«Data»).Cells(1, 1).Select Sheet.Cells[1, 1].select; application.ProcessMessages; // поиск адреса ячейки vFind := Sheet.Range[»+Range_Find+»].Find( What:=Find_Site, LookIn:=xlValues, // поиск в значениях LookAt:=xlPart // частичное совпадение // SearchDirection:=xlNext // направление вверх ).Address; XL_Col := Trim(Copy(vFind, 2)); XL_Col := Trim(Copy(XL_Col, 1, Pos(‘$’, XL_Col) -1) );… Подробнее »
Вопрос поднимался выше, не ответили:
Не компилит функция Save, «FileName:TFileName», пишет:
1. если не определяем параметры функции SaveWorkSheet, пишет что не хватает актуальных параметров
2. ставлю параметры, (индекс), пишет что не соответствие типов String/Integer,
Borodkin, исходник в статье работает отлично. Следовательно — хотелось бы увидеть Ваш исходник для решение проблемы
Vlad огромное спасибо за статью
Просьба есть: нельзя ли перезалить модуль.. как тут уже писали он не скачивается по ссылке :(
Да не за что =)
Модуль уже давно вроде бы перезалит…вот на странице исходников под номером четыре, ну или прямая ссылка http://www.webdelphi.ru/wp-content/plugins/download-monitor/download.php?id=111
При объявлении переменой типа OleVariant, у самой переменой нету никаких методов/классов. После нажатия точки вылазит только (*, {, class, claasc и т.п. Так и должно быть?
Хотя сами методы компилятор видит. WorkBooks, Sheet, Range, Cells и т.п.
Именно так и должно быть, т.к. мы используем раннее связывание.
кто-то может передать экземпляр сломанной ссылке
спасибо
подскажите пож как сделать чтобы когда вбиваешь edit текс он выводился в excel и когда я водил опять он спускался на строчку ниже а не -заменял преждний
procedure TForm1.EXSEL1Click(Sender: TObject); const StepRow=3; StepCol=1; var i:integer; WorkBk : _WorkBook; WorkSheet : _WorkSheet; begin ExcelApplication1.Connect; ExcelApplication1.WorkBooks.Add(xlWBatWorkSheet,0); WorkBk := ExcelApplication1.WorkBooks.Item[1]; WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet; WorkSheet.Name:=’ОТЧЕТ’; with WorkSheet do begin with PageSetup do begin Orientation:=2; CenterFooter:=’ОТЧЕТ’; FooterMargin; TopMargin:=25; LeftMargin:=0; BottomMargin:=100; RightMargin:=0; PageSetup.CenterHorizontally:=True; PageSetup.Zoom:=75; end; end; ExcelApplication1.Visible[0]:=true; with WorkSheet.Cells do begin Item[StepRow,StepCol].value:=’№’; Item[StepRow,StepCol].font.bold:=true; Item[StepRow,StepCol+1].value:=’Пэннін атауы’; Item[StepRow,StepCol+1].font.bold:=true; Item[StepRow,StepCol+2].value:=’Курс’; Item[StepRow,StepCol+2].font.bold:=true; Item[StepRow,StepCol+3].value:=’Болім’; Item[StepRow,StepCol+3].font.bold:=true; Item[StepRow,StepCol+4].value:=’Семестр’; Item[StepRow,StepCol+4].font.bold:=true; Item[StepRow,StepCol+5].value:=’Маман/к’; Item[StepRow,StepCol+5].font.bold:=true; Item[StepRow,StepCol+6].value:=’Лекция’; Item[StepRow,StepCol+6].font.bold:=true; Item[StepRow,StepCol+7].value:=’Практика’; Item[StepRow,StepCol+7].font.bold:=true; Item[StepRow,StepCol+8].value:=’Лабор.’; Item[StepRow,StepCol+8].font.bold:=true; Item[StepRow,StepCol+9].value:=’Рейтинг’; Item[StepRow,StepCol+9].font.bold:=true; Item[StepRow,StepCol+10].value:=’Курс. жумыс’; Item[StepRow,StepCol+10].font.bold:=true; Item[StepRow,StepCol+11].value:=’Консульт.’; Item[StepRow,StepCol+11].font.bold:=true; Item[StepRow,StepCol+12].value:=’Емтихан’; Item[StepRow,StepCol+12].font.bold:=true; Item[StepRow,StepCol+13].value:=’Онд.практика.’; Item[StepRow,StepCol+13].font.bold:=true; Item[StepRow,StepCol+14].value:=’СОЖ.’; Item[StepRow,StepCol+14].font.bold:=true; Item[StepRow,StepCol+15].value:=’Дипломдык жумыс’; Item[StepRow,StepCol+15].font.bold:=true; Item[StepRow,StepCol+16].value:=’Барлыгы:’; Item[StepRow,StepCol+16].font.bold:=true; with dm.QPredmed do begin First; i:=1; while not Eof do begin Item[StepRow+i,StepCol].value:=IntToStr(i); Item[StepRow+i,StepCol+1].value:=FieldByName(‘naz’).AsString;… Подробнее »