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

Недавно снова пришлось немного поработать с Excel в Delphi. Суть работы состояла в следующем. Есть документ Excel, состоящий минимум из четырех листов (Sheets), на каждом листе содержатся таблицы практически произвольного (с точки зрения форматирования) состава, в таблицах записаны различные данные. Сам файл может достигать нескольких Mb в размере — реально дофига чисел, текста и т.д. И всю это книгу Excel проверяет простенькая программка на Delphi — определяет возможные опечатки, несоответствия данных, суммирует данные и т.д. Проверка проводится периодически, причем не всегда на одном рабочем месте — иногда один человек проверяет один лист, второй — другой лист и т.д. И вот возник вопрос: как до начала проверки узнать был ли проверен лист и были ли обнаружены ошибки ранее?
Я решил не писать ничего на листах, никакого скрытого текста, примечаний и т.д., а воспользоваться коллекцией дополнительных свойств документа Excel.

План статьи:

Применительно конкретно к моей ситуации использование коллекции свойств документа Excel для хранения данных было предпочтительнее всех остальных по одной причине — далеко не каждый пользователь заглядывает в эти свойства и что-то там меняет, в отличие от основного содержимого документа. Было дело, когда особо «продвинутый» пользователь сносил к чертям собачьим все закладки из документа Word’а, а потом недоумевал «чё-то у меня ничё не печатает» :). Поэтому в последнее время при работе с продуктами MS Office, да и вообще любыми шаблонами, стараюсь «заныкать» служебную информацию так, чтоб никто её вообще не видел.
Но это, небольшой отступление. Перейдем непосредственно к свойствам документа.

Свойства рабочей книги (WorkBook)

У объекта WorkBook определено два свойства, отвечающие за доступ к информации о документе:

  1. ContentTypeProperties — возвращает коллекцию MetaProperties, каждый из элементов которой представляет собой мета-информацию по документу Excel. Свойство доступно только для чтения.
  2. CustomDocumentProperties — возвращает коллекцию DocumentProperties, каждый из элементов которой несет определенную дополнительную информацию по документу. Это те самый свойства, которые Вы можете просмотреть щелкнув правой кнопкой мыши на файле и выбрав «Свойства —> Особые». Свойство CustomDocumentProperties доступно как для чтения, так для записи.

Соответственно, второй коллекцией мы и научимся сегодня пользоваться.

У коллекции DocumentProperties определены следующие свойства:
Application — возвращает объект Application, в котором отрыт документ. Только для чтения.
Count — количество элементов в коллекции
Creator — содержит число (integer), представляющее собой идентификатор создателя свойства. Только для чтения.
Item — объект DocumentProperty, содержащий данные по свойству.
Parent — возвращает родительский элемент для коллекции DocumentProperties — объект рабочей книги WorkBook.
Также у коллекции определен всего один метод:
Add — добавление нового элемента в коллекцию.
Метод Add имеет следующий синтаксис:

  Add(Name, LinkToContent, Type, Value, LinkSource)

где
Name: string — обязательный параметр, определяющий имя нового свойства.
LinkToContent: boolean — обязательный параметр, указывает, связано ли содержимое свойство с содержимым документа. Если равен True, то необходимо передавать параметр LinkSource
Type: enumerator — тип свойства. Может принимать одно из следующих значений: msoPropertyBoolean, msoPropertyDate, msoPropertyFloat, msoPropertyNumber, или msoPropertyString
Value: variant — значение свойства.
LinkSource: variant — определяет какие типы истоника данный будут использоваться.
В принципе, всё, что необходимо знать для того, чтобы записать новое свойство документа и прочитать уже записанные нам известно. Осталось только продемонстрировать это на практике.

Создаем новый проект Delphi, на главной форме приложения размещаем Edit’ы, 2 кнопки Button, ListBox и несколько Label’ов как показано на рисунке.

Теперь приступим к реализации кода. Чтобы не забивать пост уже известными процедурами проверки установлен ли Excel,запуска приложения, открытия рабочей книги и т.д., советую ещё раз просмотреть пост «Работа с Excel в Delphi. Основы основ.«. Перейдем сразу к чтению свойств документа.

Пример чтения свойств документа

Листинг «Чтение свойств документа»:

uses ... ComObj...;
 
const
  // свойства документа
  msoPropertyTypeBoolean = 2; // Boolean value.
  msoPropertyTypeDate = 3; // Date value.
  msoPropertyTypeFloat = 5; // Floating point value.
  msoPropertyTypeNumber = 1; // Integer value.
  msoPropertyTypeString = 4; // String value.
 
  StampProp = 'Проверено';
 
//вспомогательные классы
type
  TFileProperty = class
  private
    FItemIndex: integer;
    FCreator: integer;
    FName: string;
    FPropertyType: integer;
    FValue: variant;
    procedure SetCreator(const Value: integer);
    procedure SetItemIndex(const Value: integer);
    procedure SetName(const Value: string);
    procedure SetPropertyType(const Value: integer);
    procedure SetValue(const Value: variant);
  public
    property ItemIndex: integer read FItemIndex write SetItemIndex;
    property Creator: integer read FCreator write SetCreator;
    property Name: string read FName write SetName;
    property PropertyType: integer read FPropertyType write SetPropertyType;
    property Value: variant read FValue write SetValue;
  end;
 
type
  TExcelFile = class
  private
    FFileName: string; // имя файла
    FExcelObject: OleVariant; // объект Excel
    FListsNames: TStringList;//имена всех листов книги
    FCustomProperties: array of TFileProperty;
    procedure SetExcelObject(const Value: OleVariant);
    procedure SetFileName(const Value: string);
    function GetListCount: integer;
    function GetPropertyList:TStringList;
  public
    constructor Create(const aFileName: string);
    destructor Destroy;
    procedure GetFileInfo;
    procedure ReadProperties;
    function  LastCheck():TDate;
    procedure AddProperty(const aName: string; aType: byte; aValue: variant; Rewrite:boolean=true);
    function  PropertyExist(const aName:string):integer;
    property FileName: string read FFileName write SetFileName;// имя файла с инвентаризацией
    property ExcelObject: OleVariant read FExcelObject write SetExcelObject;// объект Excel
    property ListsNames: TStringList read FListsNames;
    property ListCount: integer read GetListCount;
    property PropertyList: TStringList read GetPropertyList;
  end; 
 
[...]
 
procedure TExcelFile.ReadProperties;
var
  PropVar: OleVariant;
  i: integer;
begin
  FCustomProperties:=nil;
  for i := 1 to FExcelObject.ActiveWorkBook.CustomDocumentProperties.Count do
  begin
    SetLength(FCustomProperties, length(FCustomProperties) + 1);
    FCustomProperties[length(FCustomProperties) - 1] := TFileProperty.Create;
    PropVar := FExcelObject.ActiveWorkBook.CustomDocumentProperties.Item[i];
    FCustomProperties[length(FCustomProperties) - 1].FItemIndex := i;
    FCustomProperties[length(FCustomProperties) - 1].FCreator :=
      PropVar.Creator;
    FCustomProperties[length(FCustomProperties) - 1].FName := PropVar.Name;
    FCustomProperties[length(FCustomProperties) - 1].FPropertyType :=
      PropVar. Type ;
    FCustomProperties[length(FCustomProperties) - 1].FValue := PropVar.Value;
  end;
end;
 
function TExcelFile.GetPropertyList: TStringList;
var i:integer;
begin
  Result:=TStringList.Create;
  for I := 0 to Length(FCustomProperties) - 1 do
     Result.Add(FCustomProperties[i].FName+' = '+VarToStr(FCustomProperties[i].FValue));
end;
 
procedure TForm11.Button1Click(Sender: TObject);
var i:integer;
begin
  if OpenDialog1.Execute then
    begin
      ExcelFile:=TExcelFile.Create(OpenDialog1.FileName);
      ExcelFile.ReadProperties;
      ListBox1.Items.Clear;
      ListBox1.Items.Assign(ExcelFile.PropertyList);
    end;
end;

Полный исходник приложения вы можете скачать по ссылке в конце поста. В представленном листинге после запуска Excel и открытия файла считывается вся коллекця дополнительных свойств документа (метод ReadProperties) и выводится в список ListBox в формате «Имя = Значение». С чтением свойств покончено, теперь попробуем записать новое свойство книги WorkBook.

Запись нового свойства книги Excel

При записи нового свойства следует предварительно проверить существование свойства в коллекции, чтобы избежать исключительной ситуации. Чтобы не обращаться лишний раз к коллекции DocumentProperties, будем проверять свойство в массиве FCustomProperties класса TExcelFile.
Листинг функции проверки:

function TExcelFile.PropertyExist(const aName: string): integer;
var i:integer;
begin
Result:=-1;
  for I := 0 to Length(FCustomProperties) - 1 do
    begin
      if LowerCase(aName)=LowerCase(FCustomProperties[i].FName) then
        begin
          Result:=i;
          break;
        end;
    end;
end;

Если свойство с именем aName найдено в массиве, то возвращается индекс элемента, иначе -1. Теперь можно добавить новое свойство в коллекцию:
Листинг добавления нового свойства для WorkBook:

procedure TExcelFile.AddProperty(const aName: string; aType: byte;
  aValue: variant; Rewrite:boolean=true);
var index:integer;
begin
try
  index:=PropertyExist(aName);
  if (index>-1) and Rewrite then
    FExcelObject.ActiveWorkBook.CustomDocumentProperties.Item[aName].Delete
  else
    if (index>-1) and (not Rewrite) then
      Exit;
  case aType of
    msoPropertyTypeBoolean,msoPropertyTypeFloat:FExcelObject.ActiveWorkBook.CustomDocumentProperties.Add
    (Name:=aName, LinkToContent:=false,Type:=aType, Value:=aValue);
    msoPropertyTypeDate:FExcelObject.ActiveWorkBook.CustomDocumentProperties.Add
    (Name := aName, LinkToContent := false, Type := aType, Value := VarToDateTime(aValue));
    msoPropertyTypeNumber:begin
                            index:=aValue;
      FExcelObject.ActiveWorkBook.CustomDocumentProperties.Add
    (Name := aName, LinkToContent := false, Type := aType, Value:=index);
                          end;
    msoPropertyTypeString:FExcelObject.ActiveWorkBook.CustomDocumentProperties.Add
    (Name := aName, LinkToContent := false, Type := aType, Value := VarToStr(aValue));
  end;
  ReadProperties;
except
  MessageBox(0,PChar('Свойство '+aName+' уже записано'),'Ошибка',MB_OK+MB_ICONERROR)
end;
end;

Здесь вначале проверяется существование свойства в массиве. Если свойство найдено и Rewrite=true, то оно удаляется из коллекции и записывается по новой:

FExcelObject.ActiveWorkBook.CustomDocumentProperties.Add
    (Name := aName, 
     LinkToContent := false, 
     Type := aType, 
     Value := aValue);

Применительно к нашей программе добавление нового свойства может выглядеть, например так:

procedure TForm11.Button2Click(Sender: TObject);
begin
ExcelFile.AddProperty(Edit2.Text,ComboBox1.ItemIndex+1,Edit3.Text);
ListBox1.Items.Assign(ExcelFile.PropertyList);
end;

Также следует отметить, что при добавлении нового свойства в коллекцию параметр Value метода Add следует всегда приводить к типу, заданному в Type, иначе, например, при добавлении строки вы получите исключение «Неверное значение параметра».

Скачать исходник: Исходники —> Excel в Delphi

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

Автор: Юрий Магда
Название:Разработка приложений Microsoft Office 2007 в Delphi
Описание Описаны общие подходы к программированию приложений MS Office. Даны программные методы реализации функций MS Excel, MS Word, MS Access и MS Outlook в среде Delphi.
Купить на ЛитРес
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии