Что такое delphi microsoft excel

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Видимо любители экономить килобайты оперативной памяти могут меня закидать помидорами или ещё чем по-хуже, но все-таки я скажу, что интеграция приложений (мегабайты оперативы) — это большой плюс нынешней разработки приложений.
Как ни крути, а время DOS и килобайтов оперативной памяти прошло. Вряд ли кто-то всерьез сейчас задумывается над тем куда это с винчестера пропал мегабайт? Да и использование в своих приложениях функциональности программ, которых ты не писал, но которые выполняют что-то лучше — это всё-таки больший прогресс, нежели корпеть год-два над программой, а потом узнать, что время-то прошло.

Введение

Итак, цель сегодняшней статьи — поделиться с Вами опытом работы с Microsoft Excel в приложениях, написанных на Delphi.
Вспомнился сейчас один случай. Когда я только начинал работать по своей специальности, пригласили меня написать программу-расчётник для экологов нашего нефтезавода. В принципе ничего серьёзного — программа считает выброс от нагревательной печи и выдает табличку результатов, которую необходимо распечатать и уложить в толстую папку с отчётами. Естественно, что в области разработки подобных приложения я далеко не пионер, поэтому дали взглянуть на аналог будущей программы, который работал ещё под DOS и печатались отчёты на дико скрипящем матричном принтере с 12-ю иголками. Ну посмотрел, элементарная таблица, расчёт немного запутан, но жить можно — начал по-тихоньку писать. И попалась мне тогда на глаза статейка про работу с Excel в Delphi. Вот я и решил попробовать выдавать отчёт не только на форму приложения, а ещё и скидывать весь ход расчёта с формулами и прочим делом в Excel…Надо сказать более сильно детской радости начальника отдела я не видел до сих пор :). Люди, всю жизнь проработавшие в DOS увидели как тот же самый расчёт может выглядеть в современных условиях. Вот теперь при определении технических заданий на каждую новую программу, обязательно присутствует пункт, гласящий, что программа должна передавать данные либо в MS Word либо в MS Excel.Соответственно и цена на разработку возрастает, иногда значительно.

Отсюда можно сделать простой и однозначный вывод — заказчики готовы пожертвовать лишними деньгами только для того, чтобы всё в программе было красиво. Excel может дать вашему приложению ту самую красоту и удобство.

Ну, а для того, чтобы каждый раз не утруждать себя выполнением однотипных операций, я разработал небольшой модуль для работы с Excel. Этот же модуль я в настоящее время дорабатываю под ещё одну задачу, но об этом после. Сегодня основы основ работы с Excel в Delphi.

Создаем новый модуль (unit) и подключаем в uses следующие модули:

uses ComObj, ActiveX, Variants, Windows, Messages, SysUtils, Classes;

теперь объявляем глобальную переменную:

Одну константу (для удобства):

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. В следующий раз займемся работой с конкретной книгой — научимся записывать и читать данные из книг.

Книжная полка

Название:Разработка приложений Microsoft Office 2007 в Delphi

Описание Описаны общие подходы к программированию приложений MS Office. Даны программные методы реализации функций MS Excel, MS Word, MS Access и MS Outlook в среде Delphi.

купить книгу delphi на ЛитРес

5
3
голоса

Рейтинг статьи

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

This step-by-step guide describes how to connect to Microsoft Excel, retrieve sheet data, and enable editing of the data using the DBGrid. You’ll also find a list of the most common errors that might appear in the process, plus how to deal with them.

What’s Covered Below:

  • Methods for transferring data between Excel and Delphi. How to connect to Excel with ADO (ActiveX Data Objects) and Delphi.
  • Creating an Excel spreadsheet editor using Delphi and ADO
  • Retrieving the data from Excel. How to reference a table (or range) in an Excel workbook.
  • A discussion on Excel field (column) types
  • How to modify Excel sheets: edit, add and delete rows.
  • Transferring data from a Delphi application to Excel. How to create a worksheet and fill it with custom data from an MS Access database.

How to Connect to Microsoft Excel

Microsoft Excel is a powerful spreadsheet calculator and data analysis tool. Since rows and columns of an Excel worksheet closely relate to the rows and columns of a database table, many developers find it appropriate to transport their data into an Excel workbook for analysis purposes; and retrieve data back to the application afterwards.

The most commonly used approach to data exchange between your application and Excel is Automation. Automation provides a way to read Excel data using the Excel Object Model to dive into the worksheet, extract its data, and display it inside a grid-like component, namely DBGrid or StringGrid.

Automation gives you the greatest flexibility for locating the data in the workbook as well as the ability to format the worksheet and make various settings at run time.

To transfer your data to and from Excel without Automation, you can use other methods such as:

  • Write data into a comma-delimited text file, and let Excel parse the file into cells
  • Transfer data using DDE (Dynamic Data Exchange)
  • Transfer your data to and from a worksheet using ADO

Data Transfer Using ADO

Since Excel is JET OLE DB compliant, you can connect to it with Delphi using ADO (dbGO or AdoExpress) and then retrieve the worksheet’s data into an ADO dataset by issuing an SQL query (just like you would open a dataset against any database table).

In this way, all the methods and features of the ADODataset object are available to process the Excel data. In other words, using the ADO components let you build an application that can use an Excel workbook as the database. Another important fact is that Excel is an out-of-process ActiveX server. ADO runs in-process and saves the overhead of costly out-of-process calls.

When you connect to Excel using ADO, you can only exchange raw data to and from a workbook. An ADO connection cannot be used for sheet formatting or implementing formulas to cells. However, if you transfer your data to a worksheet that is pre-formatted, the format is maintained. After the data is inserted from your application to Excel, you can carry out any conditional formatting using a (pre-recorded) macro in the worksheet.

You can connect to Excel using ADO with the two OLE DB Providers that are a part of MDAC: Microsoft Jet OLE DB Provider or Microsoft OLE DB Provider for ODBC Drivers. We’ll focus on Jet OLE DB Provider, which can be used to access data in Excel workbooks through installable Indexed Sequential Access Method (ISAM) drivers.

Tip: See the Beginners Course to Delphi ADO Database Programming if you’re new to ADO.

The ConnectionString Magic

The ConnectionString property tells ADO how to connect to the datasource. The value used for ConnectionString consists of one or more arguments ADO uses to establish the connection.

In Delphi, the TADOConnection component encapsulates the ADO connection object; it can be shared by multiple ADO dataset (TADOTable, TADOQuery, etc.) components through their Connection properties.

In order to connect to Excel, a valid connection string involves only two additional pieces of information — the full path to the workbook and the Excel file version.

A legitimate connection string could look like this:

ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:MyWorkBooksmyDataBook.xls;Extended Properties=Excel 8.0;';

When connecting to an external database format supported by the Jet, the extended properties for the connection needs to be set. In our case, when connecting to an Excel «database,» extended properties are used to set the Excel file version. 

For an Excel95 workbook, this value is «Excel 5.0» (without the quotes); use «Excel 8.0» for Excel 97, Excel 2000, Excel 2002, and ExcelXP.

Important: You must use the Jet 4.0 Provider since Jet 3.5 does not support the ISAM drivers. If you set the Jet Provider to version 3.5, you’ll receive the «Couldn’t find installable ISAM» error.

Another Jet extended property is «HDR=». «HDR=Yes» means that there is a header row in the range, so the Jet will not include the first row of the selection into the dataset. If «HDR=No» is specified, then the provider will include the first row of the range (or named range) into the dataset.

The first row in a range is considered to be the header row by default («HDR=Yes»). Therefore, if you have column heading, you do not need to specify this value. If you do not have column headings, you need to specify «HDR=No».

Now that you’re all set, this is the part where things become interesting since we’re now ready for some code. Let’s see how to create a simple Excel Spreadsheet editor using Delphi and ADO.

Note: You should proceed even if you lack knowledge on ADO and Jet programming. As you’ll see, editing an Excel workbook is as simple as editing data from any standard database.

Now write codes using above variables on a Button click or other appropriate event.

1. Create and connect with an Excel Application…

    myxlApp := TExcelApplication.Create(Nil);

    myxlApp.Connect;

    myxlApp.Visible[LCID] := True; // will show newly connected Excel application // most of case not required //

2. Close and Free the Excel application….

    myxlApp.Disconnect;

    myxlApp.Quit;

    FreeAndNil(myxlApp);

3. Add a Workbook

    myxlApp.Workbooks.Add(EmptyParam, LCID); //it will also add a default sheet to workbok//

    myxlBook := TExcelWorkbook.Create(myxlApp);

    myxlBook.ConnectTo(myxlApp.ActiveWorkbook);

4. Disconnect Workbook before close

    myxlBook.Close(True,’C:jitendraExcelTest1.xlsx’); //Saves the changes to mentioned file//

    myxlBook.Disconnect;

    FreeAndNil(myxlBook);

5. Add new Worksheet

    myxlSheet11 := TExcelWorksheet.Create(myxlBook);

    myxlSheet11.ConnectTo(myxlBook.ActiveSheet as _worksheet); //connecting with the default worksheet//

    myxlSheet11.Name := ‘Class 11’;

6. Disconnect worksheet before close

    myxlSheet11.Disconnect;

    FreeAndNil(myxlSheet11);

7. Adding a new Worksheet to the Workbook

myxlBook.Worksheets.Add(EmptyParam, EmptyParam, EmptyParam, EmptyParam, LCID);

    myxlSheet12 := TExcelWorksheet.Create(myxlBook);

    myxlSheet12.ConnectTo(myxlBook.ActiveSheet as _worksheet);

    myxlSheet12.Name := ‘Class 12’;

8. Access Sheets by Index or Name

(myxlApp.Worksheets[0] as _Worksheet).Activate(LCID);

Or

(myxlApp.Worksheets[‘Sheet1’] as _Worksheet).Activate(LCID);

9. Assign values to Cell by using Cell or Range property

    myxlApp.Cells.Item[1,1] := ‘Value 1’; //with row, col number//

    myxlApp.Range[‘A3′,’A3’].Value := ‘value 2’; //with cell from, to names//

    myxlSheet11.Cells.Item[1,5] := ‘JITENDRA’; //with row, col number//

    myxlSheet11.Range[‘E3′,’E3’].Value := ‘7834911261’; //with cell from, to names//

10. Change font format of an Excel Range

    with myxlSheet11.Range[‘A1’, ‘B3’] do

    begin

      Font.Name := ‘Verdana’;

      Font.Size := 15;

      Font.Bold := True;

      Font.Strikethrough := True;

      Font.Color := clRed;

    end;

11. Change Background Color of cells

    with myxlSheet11.Range[‘A1’, ‘A1’].Interior.Color := clYellow;

myxlSheet11.Range[‘A5’, ‘D7’].Merge(False);// merge cells and fill color in merged cells//

myxlSheet11.Range[‘A5’, ‘D7’].Interior.Color := clRed;

12. Merge Cells in a range

    myxlSheet11.Range[‘A5’, ‘D7’].Merge(False); //False by default if True it would merge cells row by row//

    myxlSheet11.Range[‘A5’, ‘D7’].Value := ‘Merged data’;

13. Change Column width and Row height

    myxlSheet11.Range[‘B5’, ‘B5’].ColumnWidth := 5; //single column B//

    myxlSheet11.Range[‘J5’, ‘L8’].ColumnWidth := 15; //multiple column J,K,L//

    myxlSheet11.Range[‘B5’, ‘B5’].RowHeight := 50; //single row 5//

    myxlSheet11.Range[‘J10’, ‘J15’].RowHeight := 50; //multiple row 10-15//

14. Open the workbook that already exists: 

myxlApp.Workbooks.Open ( ‘C:jitendraExcelTest1.xlsx’

EmptyParam , EmptyParam , EmptyParam , EmptyParam ,

EmptyParam , EmptyParam , EmptyParam , EmptyParam ,

EmptyParam , EmptyParam , EmptyParam , EmptyParam , 0 );

15. Copy and Paste Data from one cell to another in same sheet…

a. 

myxlSheet11.UsedRange[LCID].Copy(myxlSheet11.Range[‘J10’, ‘J10’]);

myxlSheet11.Range[‘A5’, ‘D7’].Copy(myxlSheet11.Range[‘J10’, ‘J10’]);

b. 

       myxlSheet11.UsedRange[LCID].Copy(EmptyParam);

       myxlSheet11.Range[‘J10’, ‘J10’].PasteSpecial(xlPasteAll,                xlPasteSpecialOperationNone, EmptyParam, EmptyParam);

myxlSheet11.Range[‘A5’, ‘D7’].Copy(EmptyParam);

    myxlSheet11.Range[‘J10’, ‘J10’].PasteSpecial(xlPasteAll, xlPasteSpecialOperationNone, EmptyParam, EmptyParam);

16. Copy and Paste Data from one Sheet to another sheet.

a. 

myxlSheet11.UsedRange[LCID].Copy(myxlSheet12.Range[‘J10’, ‘J10’]);

myxlSheet11.Range[‘A5’, ‘D7’].Copy(myxlSheet12.Range[‘J10’, ‘J10’]);

b. 

    myxlSheet11.UsedRange[LCID].Copy(EmptyParam);

    myxlSheet12.Range[‘J10’, ‘J10’].PasteSpecial(xlPasteAll, xlPasteSpecialOperationNone,EmptyParam, EmptyParam);

myxlSheet11.Range[‘A5’, ‘D7’].Copy(EmptyParam);

    myxlSheet12.Range[‘J10’, ‘J10’].PasteSpecial(xlPasteAll, xlPasteSpecialOperationNone,EmptyParam, EmptyParam);

17. Insert new columns before b1

myxlSheet11.Range[‘b1’, ‘b1’].Columns.Insert(xlShiftToRight); // we can use xlShiftToLeft//

18. Insert new rows above b2

myxlSheet11.Range[‘b2’, ‘b2’].Rows.Insert(xlShiftDown); // we can use xlShiftUp//

19. Clear a Selected Range Content or Format

    myxlSheet11.Range[‘b3’, ‘b10’].ClearContents;

    myxlSheet11.Range[‘b3’, ‘b10’].ClearFormats;

20.Save, Save as a Sheet / Workbook

  myxlSheet11.SaveAs(‘Filename’);

    myxlBook.Save;

21. Print Preview / Print of a Sheet / Workbook

    myxlSheet11.PrintPreview;

    myxlSheet11.PrintOut;

    myxlBook.PrintPreview;

    myxlBook.PrintOut;

22. Set Sheet PageSetup

myxlSheet11.PageSetup.

A. header: 

myxlSheet11.PageSetup.CenterHeader := » The report shows » ;

B. footer: 

myxlSheet11.PageSetup.CenterFooter := » The & P » ;

The C. header into the top margin 2cm: 

myxlSheet11.PageSetup.HeaderMargin := 2 / 0.035 ;

D. footer bottom margin 3cm: 

myxlSheet11.PageSetup.HeaderMargin := 3 / 0.035 ;

E. top margin 2cm: 

myxlSheet11.PageSetup.TopMargin := 2 / 0.035 ;

The bottom edge of the f. from the 2cm: 

myxlSheet11.PageSetup.BottomMargin := 2 / 0.035 ;

G. left 2cm: 

myxlSheet11.PageSetup.LeftMargin := 2 / 0.035 ;

On the right side of the H. from the 2cm: 

myxlSheet11.PageSetup.RightMargin := 2 / 0.035 ;

I. pages horizontally: 

myxlSheet11.PageSetup.CenterHorizontally := 2 / 0.035 ;

The J. page vertically: 

myxlSheet11.PageSetup.CenterVertically := 2 / 0.035 ;

K. print cell line: 

myxlSheet11.PageSetup.PrintGridLines := True ;

23. Auto Fill of Range. Fills value 1-10 in p1:p10

    myxlSheet11.Range[‘p1’, ‘p1’].Value := 1;

    myxlSheet11.Range[‘p2’, ‘p2’].Value := 2;

    myxlSheet11.Range[‘p1’, ‘p1’].AutoFill(myxlSheet11.Range[‘p1’, ‘p10’], xlFillSeries);

Other fill options

  xlFillCopy 

  xlFillDays 

  xlFillDefault 

  xlFillFormats 

  xlFillMonths 

  xlFillSeries 

  xlFillValues 

  xlFillWeekdays 

  xlFillYears 

  xlGrowthTrend 

  xlLinearTrend 

24. Change Border style of cells in a range

    myxlSheet11.Range[‘p3’, ‘p4’].Borders.Color := clRed;

    myxlSheet11.Range[‘p3’, ‘p4’].Borders.LineStyle := xlDouble;

    myxlSheet11.Range[‘p3’, ‘p4’].Borders.Item[xlEdgeLeft].Color := clBlue;

    myxlSheet11.Range[‘p3’, ‘p4’].Borders.Item[xlEdgeRight].Color := clBlue;

    Other line styles.

xlContinuous 

xlDash 

xlDashDot 

xlDashDotDot 

xlDot 

xlDouble 

xlSlantDashDot 

xlLineStyleNone

25. Fill pattern style of cells in a range

    myxlSheet11.Range[‘p3’, ‘p4’].Interior.Pattern := xlPatternCrissCross;

    myxlSheet11.Range[‘p3’, ‘p4’].Interior.PatternColor := clBlue;

      Other pattern styles

xlPatternAutomatic

xlPatternChecker 

xlPatternCrissCross 

xlPatternDown 

xlPatternGray16 

xlPatternGray25

xlPatternGray50

xlPatternGray75 

xlPatternGray8 

xlPatternGrid

xlPatternHorizontal 

xlPatternLightDown 

xlPatternLightHorizontal 

xlPatternLightUp 

xlPatternLightVertical 

xlPatternNone 

xlPatternSemiGray75 

xlPatternSolid 

xlPatternUp 

xlPatternVertical

26. Add calculation function SUM/AVG/MAX/COUNT etc..

    myxlSheet11.Range[‘k1’, ‘k1’].Formula := ‘=Sum(p3:p8)’;

    myxlSheet11.Range[‘k3’, ‘k3’].Formula := ‘=Avg(p3:p8)’;

    myxlSheet11.Range[‘k5’, ‘k5’].Formula := ‘=Max(p3:p8)’;

    myxlSheet11.Range[‘k7’, ‘k7’].Formula := ‘=Count(p3:p8)’;

Здравствуйте, в этой статье я расскажу Вам, как использовать в своем приложении (программе) на Delphi БД в виде MS Excel. Да да именно MS Excel. Тут ничего сложного нету. Для начала давайте заполним наш лист в MS Excel. Первая строка в каждом столбце всегда будет брать наше приложение как название столбцов, то есть оно не берет название столбцов такое — A, B, C и так далее. Так что в первсой строке столбца А и столбца B я написал ФИО и Оценка соответственно для каждого столбца. Это и будут наши заголовки, затем ниже я заполнил данные, которые будут в моей БД, ну это фамилии и оценки сами напиши. Так БД у нас готова. Теперь создадим к ней подключение. создается подключение похоже как и в MS Access, так что тут ничего сложного нету. На форме у нас старые компоненты это

  • TDBGrid
  • TADOQuery
  • TADOConnection
  • TDataSource

Как связывать эти компоненты я расскажу быстро, так как это уже было сказано мною.

  • TADOQuery c TADOConnection в свойстве — Connection
  • TDataSource с TADOQuery в свойстве DataSet
  • TDBGrid c TDataSource в свойстве DataSource

В TADOConnection установим свойство LongPromt в False. Вроде бы небольшую настройку сделали. Теперь приходим к непосредственному подключени. В свойстве TADOConnection ConnectionString нажимаем на кнопку ««, далее у нас появляется окно вида

Нажимаем на кнопку «Build…» и появляется следующее окно

В данном окне выбираем следующего провайдера: Microsoft OLE DB Provider for ODBC Drivers и нажимаем кнопку «Далее>>» и видем следующее окно

В этом окне сразу ставим указатель на «Использовать строку подключения» и нажимаем на кнопку «Сборка«, после чего появляется окно

В этом окне переходим на вкладку Источник данных компьютера, в появившемся списке выбираем — Файлы Excel, а точнее жмем левой кнопкой мыши по этой сроке двойным щелчком и в появившемся окне указываем путь к нашей Excel-книги, которую мы создавали. После этого нажимаем на пноку «Ok«. Все подключение у нас готово. Хочу добавить еще, если у Вас файл Excel, где ваша таблица находится лежит в одном каталоге с программой, то в свойстве компонента TADOConnection ConnectionString будет прописан путь к Вашему Excel-файлу, стерите путь, а оставьте просто имя Вашего Excel файла с расширением, а в свойстве DefaultDataBase этого же компонента, напишите имя вашего Excel-файла с расширением. Тогда при запуске вашего приложения, не будет возникать ошибок с неправильно заданым путем к вашей БД. Далее в TDBGrid нажмем по нему двойным щелчком и в появившемся окне создаим 2 строки, это наши столбцы будут. А создаются они путем нжатия в данном окне на кнопку «Add New (Ins)«. Далее выделив кажду строку в свойстве FieldName для первой строки напишем — ФИО, так как в Excel-файле я именно так написал в первую строку название столбца (для ячейки A1 я так написал), а выделив вторую строку, что создали в TDBGrid, в свойстве FieldName напишем — Оценка, так как именно я такое название столбца написал (в ячейке А2 я так написал). Все теперь нам можно активировать наши данные. Для этого на событие главной формы — OnCreate напишем следующее

procedure TForm1.FormCreate(Sender: TObject);
begin
try
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SELECT * FROM [Лист1$]');
ADOQuery1.Active:=True;
except
on e:Exception do
end;
end;

Как видите все тот же запрос, только вместо имя таблица, как у нас было в MS Access мы указываем имя листа нашего в Excel, заключив его в квадратные скобки и поставив в конце знак $. Как видите ничего сложного нету. В следующей статье про БД MS Excel я расскажу как вставлять данные, редактировать и так далее.

Видимо любители экономить килобайты оперативной памяти могут меня закидать помидорами или ещё чем по-хуже, но все-таки я скажу, что интеграция приложений (мегабайты оперативы) — это большой плюс нынешней разработки приложений.
Как ни крути, а время 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;

Если функция 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;

Т.е. сразу проверяю запущен ли 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. В следующий раз займемся работой с конкретной книгой — научимся записывать и читать данные из книг.

Файлы для загрузки

Модуль uMyExcel

Часть 3. Использование COM-серверов Microsoft Office

Основные объекты серверов Excel и Word

   Объекты Excel

   Объекты Word

Позднее связывание

Раннее связывание

Практика
показывает, что приложения Microsoft
Office (Excel,
Word, Power
Point и т.п.)
являются одними из наиболее часто
используемых Windows-приложений.
Каждое  из них является СОМ-сервером, а
следовательно, любой входящий в него объект
может быть использован вашей программой
как собственный.

Существуют два способа обращения к методам и свойствам СОМ-объекта: путем ссылки
на его библиотеку типов (раннее связывание) и по имени (позднее связывание).
Для Object Pascal предпочтительным является раннее связывание, так как в этом
случае компилятор может проконтролировать правильность обращения к свойствам
и методам внешних объектов, а создаваемый им код исполняется, как правило, быстрее.
В то же время базовый язык обращения к серверам Microsoft Office — Visual Basic
for Application (VBA)  не поддерживает работу с указателями и, следовательно,
не может использовать интерфейсы. Специально для такого рода языков (помимо
VBA c указателями не работают также языки JavaScript, SmallTalk и некоторые
другие) в технологию СОМ введены диспинтерфейсы, позволяющие обращаться к методам
и свойствам по имени, а не по адресу. При инсталлировании Office можно установить
справку по VBA, в которой детально описываются интерфейсы серверов Microsoft
Office с указанием назначения методов и свойств, а также параметров обращения
к ним. Фактически это единственные доступные программисту документы, на которые
ему следует опираться при программировании доступа к мощным возможностям серверов
Microsoft Office. Замечу, что при стандартном инсталлировании Microsoft Office
справки по VBA не устанавливаются. Если в каталоге Program Files | Microsoft
Office | Office вы не найдете файлов vbaxl8.hlp1
(справка по Excel), vbawrd8.hlp (справка по Word) и т. п., вы должны их
добавить с помощью аплета Пуск | Настройка | Панель управления | Установка и
удаление программ.

В версию 5 Delphi включены компоненты страницы Servers,
позволяющие обращаться к СОМ-объектам этих
серверов с помощью библиотек типов, однако
эти компоненты практически не
документированы. Более того, сами
библиотеки уже внедрены в пакет dclaxserver50,
так что с помощью этой версии Delphi
мне так и не удалось получить их тексты. Во
всех случаях изучение обширных текстов
библиотек (например, файл Excel_TLB.pas
содержит более 20 тыс. строк) мало что дает
даже опытному программисту.

В этом разделе приводятся краткое
описание основных объектов двух наиболее
популярных серверов — Excel
и Word, а
также примеры использования Excel
в стиле VBA
(по имени) и с помощью компонентов страницы Servers.
Поскольку специально для версии MS
Office 97
язык VBA
был существенно расширен, этот материал
нельзя использовать для работы с более
ранними версиями пакета.

Основные объекты серверов Excel и Word

В терминологии VBA
используются
понятия «объект» и «коллекция». Объект —
это обычный интерфейсный объект СОМ,
имеющий свойства, методы и события.
Коллекция — это группа однотипных объектов.
Например, главный объект сервера Excel
— Application 
определяет основные свойства и методы
сервера, а коллекция Worksheets
представляет  собой
набор табличных страниц в текущей рабочей
книге и т.д. Представленные ниже иерархии
объектов и коллекций взяты из файлов vbaXXX.hlp. В отличие от объектов VCL
они построены не по принципу наследования,
а по функциональной подчиненности.

Объекты Excel

Сервер Excel — это мощный табличный процессор,
реализующий размещение и обработку
различного рода данных (как числовых, так и
текстовых), в том числе  построение
на их основе графиков и диаграмм. При работе
с Excel создается
так называемая рабочая книга (файл данных) с
одним или несколькими листами. Все листы
одной рабочей книги могут быть связаны друг
с другом, что позволяет организовать
совместные вычисления над размещенными на
них данными.

На рис. 1 представлена функциональная структура объектов
и коллекций Excel.

Объект Application имеет многочисленные свойства,
методы и события, управляющие сервером в
целом. Только с его помощью, например, можно
визуализировать полнофункциональное окно
текстового процессора. Его центральное
свойство Workbooks предоставляет доступ ко всем
открытым в процессоре рабочим книгам.

У каждой рабочей книги есть
свойства Worksheets
и Charts,
представляющие собой коллекции листов и
диаграмм. Первоначально коллекция Workbooks
пуста. Чтобы создать хотя бы одну рабочую
книгу, нужно обратиться к методу Workbook.Add,
который создает рабочую книгу с
количеством пустых листов, определяемым
значением свойства Application.SheetsInNewWorkbook. У
каждого рабочего листа есть свойство Cells(I,J), определяющее содержимое
ячейки, лежащей на пересечении I-го
ряда с J-м
столбцом (нумерация рядов и столбцов
начинается с 1). Если при обращении к Cells
номера столбца и ряда опущены, считается,
что речь идет о текущем диапазоне ячеек,
заданном значением свойства Worksheets.Range.
Если необходимо изменить умалчиваемые
свойства столбца или ряда, используются
объекты Worksheets.Columns и
Worksheets.Rows. Помимо рабочих листов с рабочей книгой
связывается объект Charts,
представляющий собой коллекцию диаграмм. С
каждой диаграммой связывается объект SeriesCollection,
хранящий данные, по которым строится
диаграмма.

Объекты Word

Текстовый процессор Word
является популярнейшим средством создания
и оформления (форматирования) текстовых
документов. При работе с Word
фундаментальными понятиями являются
документ, абзац и стиль. Документ
определяет файл данных. Абзац — это
совокупность символов, обрамленная
служебными символами конца строк, разрыва
колонки или разрыва раздела. Наконец, стиль
— это совокупность признаков оформления
текста: его шрифт, положение на странице,
выравнивание и т.п. Стиль — непременный
атрибут каждого абзаца, то есть изменение
стиля абзаца автоматически приводит к его
переформатированию. Однако стиль может
изменяться внутри абзаца — для выделения
группы символов шрифтом, цветом символов и (или)
фона и т.п.

На рис. 2 показана функциональная иерархия объектов Word.

Центральный объект Application
имеет такое же назначение, что и
одноименный объект Excel,
– он определяет свойства, методы и события
на уровне всего сервера. Его свойство Documents представляет собой коллекцию открытых
документов. Посредством метода Open этого объекта можно открыть ранее
созданный документ, а метода Add — создать новый документ, основанный на
стандартном шаблоне Normal.dot. Каждый документ имеет коллекцию абзацев
Paragraphs.
С помощью таких методов этого объекта, как Add, InsertParagraph,
InsertParagraphAfter,
InsertParagraphBefore,
можно вставить новый абзац в уже
существующий текст или добавить абзац в
конец документа. В свою очередь, каждый
абзац имеет многочисленные свойства,
позволяющие нужным образом
отформатировать текст. Как и в Excel,
важную роль в иерархии объектов Word
играет объект Range, определяющий диапазон абзацев. Свойство
Text этого объекта содержит текст диапазона.

Позднее связывание

Приведенный ниже пример взят из моей
практики и, думаю, сможет пригодиться и вам.
В нем прайс-лист крупного оптового
поставщика книг создается с помощью Excel.
Необходимость в обращении к Excel возникла по той причине, что прайс-лист
периодически (примерно раз в две недели)
рассылается многочисленным покупателям,
которые составляют на его основе заказы на
поставку книг. У получателей прайс-листов
нет средств прочтения отчетов в
стандартном для Delphi
формате Quick Report
(файлы с расширением qrp). Экспорт прайс-листов в файлы других
форматов не позволяет получать документы
высокого качества, поэтому я решил для
создания прайс-листов использовать
средства Excel.

На рис. 3 показан прайс-лист в окне Excel, а на рис.
4 — вид формы примера на этапе конструирования.

Перед началом работы над проектом следует скопировать все файлы BOOKS.*
в отдельную папку на жестком диске (потребуется чуть больше 800 Кбайт
свободного пространства) и связать с папкой псевдоним BDE BIBLDATA типа Standard.
Эту процедуру упростит программа SetupBooks.exe, расположенная в том же каталоге
CD-ROM.

Начните новый проект и поместите на форму компоненты Query1, Label1, Button1 и ProgressBar1.
Для компонента Query1 измените значение свойства Name на Books, свяжите его
с псевдонимом BIBLDATA и поместите в свойство SQL такой запрос:

SELECT  
  BookID, bName, bAuthor, bPublish, bOpt, bPages, bYear  
FROM   
  Books  
WHERE  
  bQuan>0  
ORDER BY  
  bName  

Создайте для него все объекты-поля.
В свойство Caption
компонента Label1
поместите значение Щелкните по кнопке Пуск,
чтобы создать таблицу Excel,
в такое же свойство кнопки — значение Пуск и измените имя компонента ProgressBar1 на pb.

В окне кода в разделе private класса TForm1 поместите поле Excel типа Variant.
В предложении uses укажите ссылку на модуль COMObj и напишите следующий обработчик
события OnClick кнопки Button1 (см. листинг 1).

Теперь небольшие пояснения. Переменные Sheet и Range
введены только для сокращения текста
программы: везде вместо Sheet,
например, можно писать Excel.Workbooks[1].Sheets[1]. С версией Delphi 4 поставлялись файлы XLCONST.PAS и XLCONST.DCU, в которых определены используемые в
документации vbaxl8.hlp константы xlXXX.
С версией 5 эти файлы не поставляются,
поэтому я использую их числовые
эквиваленты. Ширина полей печатного
документа Excel
задается во внутренних единицах,
соответствующих приблизительно 3,5 мм, так
что указанные в операторах Sheet.PageSetup.ХХХMargin значения установят левое, нижнее и
правое поля шириной 1,1 см, а верхнее — 1,4 см.
Ширина столбца определяется в символах
текста, умещающегося в столбце без
отсечения.

Переменная Excel
определяет поле класса TForm1. При создании класса в него
автоматически помещается значение VarEmpty. После завершения работы с Excel
пользователь может закрыть его. Но в моей
программе Excel не визуализировался, его работа
проходила «за кулисами», а созданная
таблица записывалась в указанный
пользователем файл с помощью оператора Excel.Workbooks[1].SaveAs(FileName).

После этого Excel закрывался. Поскольку в нашем случае Excel
показывает свое окно, а пользователь может
его не закрыть, полезно написать такой
обработчик события OnDestroy формы:

procedure   TForm1.FormDestroy(Sender: TObject);      
begin  
  if not VarIsEmpty(Excel) then  
    Excel.Quit  
end;      

Запуская пример, помните, что
создание прайс-листа с помощью Excel
— процесс достаточно длительный. На моем
компьютере (400 МГц, 64 Мбайт) он занял около
минуты (для примера — аналогичный прайс-лист
средствами Quick Report
создается менее чем за 2 с). В конце
обработчика в метку lb
помещается общее время работы.

Раннее связывание

Следующий пример в функциональном плане повторяет предыдущий. В нем также с помощью Excel
создается прайс-лист, но на этот раз используется доступ непосредственно через
интерфейсы сервера. Вас ожидает «сюрприз»: время выполнения второго примера
на 40 с больше! Я не смог найти разумного объяснения этому феномену, но оба
примера находятся на сопровождающем диске, так что вы в любой момент можете
убедиться в этом сами.

Поскольку форма второго примера в точности повторяет форму первого, я не буду
объяснять, что нужно сделать для ее создания. Добавьте только на форму компонент
TExcelApplication и настройте его свойства: Name=Excel, AutoConnect=True, AutoQuit=True.
Если вы используете форму предыдущего примера как шаблон, не вставляйте поле
Excel в класс TForm1. Обработчик Button1Click должен выглядеть так (см. листинг
2).

Как видите, он во многом напоминает
обработчик предыдущего примера. Поэтому
остановлюсь на различиях.

При обращении к свойству SheetsInNewWorkbook,
как и во многих других случаях обращения к
интерфейсным свойствам и методам,
требуется указание идентификатора языка
локализации (lcid).
Значением 0 кодируется умалчиваемый язык.
Этот же идентификатор передается вторым
параметром обращения к методу Excel.Workbooks.Add. Первым параметром нужно указать имя
файла (в формате WideString),
если рабочая книга уже была ранее создана,
или «пустой» параметр EmptyParam,
если книга создается впервые.

Все мои попытки работать с объектами Range оказались неудачными. Чтобы вы не
слишком осуждали меня, я поместил
библиотеку типов Excel_TLB.pas
в каталог размещения примера — полистайте
ее на досуге и попробуйте найти нужное
решение для изменения ширины колонок и
полей листа, а также для раскрашивания
диапазона, выравнивания текста и т.п.

Есть свои нюансы и при обращении к
ячейкам. Во-первых, ими владеет объект Application,
а не Sheet.
Во-вторых, обращение к конкретному элементу
коллекции Cells (как и любой другой коллекции) возможно
только через ее свойство Item.

Подводя итоги, еще раз хочу обратить ваше
внимание на то, что по времени выполнения
позднее связывание хотя бы не проигрывает
раннему — во всяком случае, для
рассмотренных примеров. Если учесть, что
единственными доступными подавляющему
большинству программистов документами по
серверам MS
Office
являются справочные файлы vbaXXX.hlp, можно сделать вывод: использование
вариантов (позднее связывание) проще,
удобнее, а главное — намного понятнее, чем
непосредственная работа с интерфейсами (раннее
связывание).

КомпьютерПресс 6’2001

Like this post? Please share to your friends:
  • Что такое dax excel
  • Что такое 12 кегль в word это
  • Что такое 109 в excel
  • Что такое cube в excel
  • Что такое cross the odd word out