(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Упростите свои Delphi-приложения - Части 3 и 4

Источник: deviabe

Теперь у нас есть идея - то, чего мы хотим достичь и как мы собираемся это сделать, время написать код и спроектировать классы.

1. Введение

В качестве основы нам потребуется класс/объект, который мы сможем использовать для чтения и записи настроек приложения из и в реестр Windows. Звучит вполне просто... но, как вы помните, мы подумали предусмотреть расширение функциональности в дальнейшем.

2. Требования к коду

2.1. Совместимость с Delphi 7

Хотя в последние годы язык пополнился некоторыми новыми элементами, мы же пока использовать их не будем. Наша цель - компиляция кода в Delphi 7.

Вы можете задаться вопросом: "Кто еще работает в этой старой Delphi?". Я заметил, что даже сегодня, некоторые мои клиенты используют Delphi 7 для компиляции своих проектов. В дальнейших статьях я может быть покажу Вам, как сделать то, что мы делаем, с использованием современных методов, но сейчас давайте остановимся на этом.

2.2. Отсутствие "привязки" к реестру Windows

Несмотря на то, что мы будем писать код для чтения и записи данных в реестр Windows, хочется легко адаптировать его для других хранилищ, например XML или INI-файла. Да и кто знает, что будет завтра. Не исключено, что мы получим возможность писать приложения для Windows Mobile, Mac, iPhone или даже iPad (было бы неплохо), и реестра Windows на этих платформах может не оказаться.

Сейчас сфокусируемся на реестре Windows, но, как Вы уже поняли, иметь ввиду другие хранилища - хорошая идея. Главное, о чем должна болеть наша голова в данный момент - возможность хранения/загрузки настроек. Как или где они будут хранится не так важно, сделаем то, что требуется.

2.3. Еще ньюансы

Пока мы знаем, что нам нужно что-то, что сможет хранить наши настройки. Нам понадобится загружать и сохранять их, а также, возможно, наличие имени для каждой настройки или даже значения по умолчанию или ее описание. Мы будем хранить Целые числа, Строки, а может быть и Пароли, Даты, ...

3. Время кодинга!

3.1. ... ну почти ...

Так, ... вообще, до того, как начать писать код, стоит посмотреть, как подобные вещи реализованы в VCL. Конечно, мы все можем сделать и сами, но позволить новым классам наследоваться от существующих было бы неплохой идеей. Т.к. нам нужен список, Вы можете взять, например, класс TList.

В моем случае, я знал, что хочу иметь настройки, в которых будут храниться строки, целые числа и булевы значения. После я решил, что нужны настройки и для хранения значений типа DateTime, а также некоторых других типов. В конце концов я пришел к чему-то похожему на TField и TIntegerField, TStringField, ...

Итак, зная, что я буду использовать различные типы данных в настройках и хочу хранить список этих настроек, я решил, что неплохо было бы подключить к работе TObjectList.

3.2. Создание класса TdvSetting

3.2.1. Преамбула

В общем случае, мне нужен объект со следующими свойствами:

  • Идентификатор (или Имя, Заголовок)
  • Описание (или Подсказка)
  • Значение

Необходимо иметь возможность чтения и записи значения конкретного идентификатора в реестр. Кроме того, при чтении значения, я хочу проверять, если ли уже значение у данного идентификатора, а в случае его отсутствия использовать значение по умолчанию.

Я хотел бы получать значения TdvSetting в виде String или Variant (подобно TField и TStringField), поэтому реализовал эту возможность в коде. Плюс, я хочу устанавливать значение TdvSetting. И наконец, как и с TField в VCL, я добавил код, вызывающий исключение, если потомок не реализует какой-либо метод.

Это может показаться несколько сложным, но давайте сравним TdvSetting с TField и TStringField еще раз. С TStringField Вы можете присвоить значение, используя

aField.Value := theValue

или

aField.AsString := theValue

Оба варианта присваивания верны, но, если aField - экземпляр TField, а не TStringField - возникнет исключение. Ту же функциональность сделал и я.

Сейчас я сосредоточусь на реестре Windows, но как Вы знаете неплохо было бы иметь и другие возможности хранения. В конце концов, единственная вещь, на которой мы сейчас акцентируем внимание - хранение/загрузка некоторых настроек. Как или где они будут храниться не так важно, сначала сделаем то, что хотели.

3.2.2. Код

TdvSetting = class(TObject)
  private
    FValue:  Variant;
    FDefaultValue:  Variant;
    FIdentifier:  string;
    FCaption:  string;
    procedure SetCaption(const Value: string);
    procedure SetVisible(const Value: Boolean);
  protected
    function GetAsBoolean: Boolean; virtual;
    function GetAsDateTime: TDateTime; virtual;
    function GetAsFloat: Double; virtual;
    function GetAsInteger: Longint; virtual;
    function GetAsString: string; virtual;
    function GetAsVariant: Variant; virtual;

    procedure SetAsBoolean(const Value: Boolean); virtual;
    procedure SetAsDateTime(const Value: TDateTime); virtual;
    procedure SetHint(const Value: string);
    procedure SetAsFloat(const Value: Double); virtual;
    procedure SetIdentifier(const Value: string);
    procedure SetAsInteger(const Value: Longint); virtual;
    procedure SetAsString(const Value: string); virtual;
    procedure SetAsVariant(const Value: Variant); virtual;
  protected
    function AccessError(const TypeName: string): Exception; dynamic;
    procedure SetVarValue(const Value: Variant); virtual;
  public
    constructor Create(const aIdentifier, aCaption: string;
                       const aDefaultValue: Variant); virtual;

    destructor Destroy; override;

    procedure SaveToRegIni(aRegIni: TRegistryIniFile;
                           const aSection:  string); virtual;
    procedure LoadFromRegIni(aRegIni: TRegistryIniFile;
                             const aSection: string); virtual;

    procedure Clear; virtual;

    property DefaultValue: Variant read FDefaultValue;
    property AsBoolean: Boolean read GetAsBoolean write SetAsBoolean;
    property AsDateTime: TDateTime read GetAsDateTime write SetAsDateTime;
    property AsFloat: Double  read GetAsFloat write SetAsFloat;
    property AsInteger: Longint read GetAsInteger write SetAsInteger;
    property AsString: string  read GetAsString write SetAsString;
    property AsVariant: Variant read GetAsVariant write SetAsVariant;
    property Identifier: string read FIdentifier write SetIdentifier;
    property Caption: string read FCaption write SetCaption;
    property Value: Variant read GetAsVariant write SetAsVariant;
  end;

...

function TdvSetting.AccessError(const TypeName: string): Exception;
resourcestring
  SSettingAccessError = 'Невозможно получить значение ''%s'' (%s) как %s';
begin
  Result := Exception.CreateResFmt(@SSettingAccessError,
                                    [ Identifier, Caption, TypeName ]);
end;

procedure TdvSetting.Clear;
begin
  FValue := Null;  
end;

constructor TdvSetting.Create(const aIdentifier, aCaption: string;
  const aDefaultValue: Variant);
begin
  Create(aIdentifier, aCaption, aCaption, True, aDefaultValue);
end;

function TdvSetting.GetAsBoolean: Boolean;
begin
  raise AccessError('Boolean'); { Do not localize }
end;

function TdvSetting.GetAsDateTime: TDateTime;
begin
  raise AccessError('DateTime'); { Do not localize }
end;

function TdvSetting.GetAsFloat: Double;
begin
  raise AccessError('Float'); { Do not localize }
end;

function TdvSetting.GetAsInteger: Longint;
begin
  raise AccessError('Integer'); { Do not localize }
end;

function TdvSetting.GetAsString: string;
begin
  Result := ClassName;
end;

function TdvSetting.GetAsVariant: Variant;
begin
  raise AccessError('Variant'); { Do not localize }
end;

procedure TdvSetting.LoadFromRegIni(aRegIni: TRegistryIniFile;
  const aSection: string);
begin
  Assert(Assigned(aRegIni), 'Параметр aRegIni должен содержать экземпляр TRegIni');
end;

procedure TdvSetting.SaveToRegIni(aRegIni: TRegistryIniFile;
  const aSection: string);
begin
  Assert(Assigned(aRegIni), 'Параметр aRegIni должен содержать экземпляр TRegIni');
end;

procedure TdvSetting.SetAsBoolean(const Value: Boolean);
begin
  raise AccessError('Boolean'); { Do not localize }
end;

procedure TdvSetting.SetAsDateTime(const Value: TDateTime);
begin
  raise AccessError('DateTime'); { Do not localize }
end;

procedure TdvSetting.SetAsFloat(const Value: Double);
begin
  raise AccessError('Float'); { Do not localize }
end;

procedure TdvSetting.SetAsInteger(const Value: Longint);
begin
  raise AccessError('Integer'); { Do not localize }
end;

procedure TdvSetting.SetAsString(const Value: string);
begin
  raise AccessError('string'); { Do not localize }
end;

procedure TdvSetting.SetAsVariant(const Value: Variant);
begin
  if (VarIsNull(Value)) then
  begin
    Clear;
  end
  else
  begin
    SetVarValue(Value);
  end;
end;

procedure TdvSetting.SetCaption(const Value: string);
begin
  FCaption := Value;
end;

procedure TdvSetting.SetHint(const Value: string);
begin
  FHint := Value;
end;

procedure TdvSetting.SetIdentifier(const Value: string);
begin
  FIdentifier := Value;
end;

procedure TdvSetting.SetVarValue(const Value: Variant);
begin
  raise AccessError('Variant'); { Do not localize }
end;

3.2.3. Что он делает?

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

3.3. Класс TdvStringSetting

3.3.1. Преамбула

В общем, класс TdvSetting предоставляет нам скелет, который мы можем использовать для создания настроек-потомков. Я уже завершил написание TdvStringSetting, TdvIntegerSetting, TdvBooleanSetting и некоторых других, но давайте начнем с TdvStringSetting.

3.3.2. Код

TdvStringSetting = class(TdvSetting)
private
  function GetDefaultValueAsString: string;
protected
  function GetAsBoolean: Boolean; override;
  function GetAsDateTime: TDateTime; override;
  function GetAsFloat: Double; override;
  function GetAsInteger: Longint; override;
  function GetAsString: string; override;
  function GetAsVariant: Variant; override;

  function GetValue(var Value: string): Boolean;

  procedure SetAsBoolean(const Value: Boolean); override;
  procedure SetAsDateTime(const Value: TDateTime); override;
  procedure SetAsFloat(const Value: Double); override;
  procedure SetAsInteger(const Value: Longint); override;
  procedure SetAsString(const aValue: string); override;
  procedure SetVarValue(const aValue: Variant); override;
public
  procedure SaveToRegIni(aRegIni: TRegistryIniFile; const aSection: string); override;
  procedure LoadFromRegIni(aRegIni: TRegistryIniFile; const aSection: string); override;

  property DefaultValue: string read GetDefaultValueAsString;
  property Value: string read GetAsString write SetAsString;
end;

...

{ TdvStringSetting }

function TdvStringSetting.GetAsBoolean: Boolean;
var
  S: string;
begin
  S := GetAsString;
  Result := (Length(S) > 0) and (S[1] in ['T', 't', 'Y', 'y']);
end;

function TdvStringSetting.GetAsDateTime: TDateTime;
begin
  Result := StrToDateTime(GetAsString);
end;

function TdvStringSetting.GetAsFloat: Double;
begin
  Result := StrToFloat(GetAsString);
end;

function TdvStringSetting.GetAsInteger: Longint;
begin
  Result := StrToInt(GetAsString);
end;

function TdvStringSetting.GetAsString: string;
begin
  if not GetValue(Result) then Result := '';
end;

function TdvStringSetting.GetAsVariant: Variant;
var
  S: string;
begin
  if GetValue(S) then Result := S else Result := Null;
end;

function TdvStringSetting.GetDefaultValueAsString: string;
begin
  Result := FDefaultValue;
end;

function TdvStringSetting.GetValue(var Value: string): Boolean;
begin
  Value  := FValue;
  Result := True;
end;

procedure TdvStringSetting.LoadFromRegIni(aRegIni: TRegistryIniFile;
  const aSection: string);
begin
  inherited LoadFromRegIni(aRegIni, aSection);

  Value := aRegIni.ReadString(aSection, Identifier, DefaultValue);
end;

procedure TdvStringSetting.SaveToRegIni(aRegIni: TRegistryIniFile;
  const aSection: string);
begin
  inherited SaveToRegIni(aRegIni, aSection);

  aRegIni.WriteString(aSection, Identifier, Value);
end;

procedure TdvStringSetting.SetAsBoolean(const Value: Boolean);
const
  Values: array[Boolean] of string[1] = ('F', 'T');
begin
  SetAsString(Values[Value]);
end;

procedure TdvStringSetting.SetAsDateTime(const Value: TDateTime);
begin
  SetAsString(DateTimeToStr(Value));
end;

procedure TdvStringSetting.SetAsFloat(const Value: Double);
begin
  SetAsString(FloatToStr(Value));
end;

procedure TdvStringSetting.SetAsInteger(const Value: Integer);
begin
  SetAsString(IntToStr(Value));
end;

procedure TdvStringSetting.SetAsString(const aValue: string);
begin
  FValue := aValue;
end;

procedure TdvStringSetting.SetVarValue(const aValue: Variant);
begin
  SetAsString(aValue);
end;

3.3.3. Что он делает?

Т.к. у нас уже имеется скелет от TdvSetting, нам остается лишь переопределить некоторые методы и реализовать свои собственные функции. Как видите, мы добавили примерно такой же код, который имеет TStringField в VCL.

Дополнительно реализованы лишь методы SaveToRegIni и LoadFromRegIni. Они позволяют загрузить и сохранить значение настройки в реестр. Кроме того, при загрузке мы используем значение по умолчанию, если настройка не найдена в реестре. Секция реестра, откуда/куда мы будем загружать/сохранять значение, имеет такое же имя, как и идентификатор настройки.

3.4. Создание класса TdvSetting

3.4.1. Преамбула

Теперь, когда мы спроектировали различные типы настроек, нам понадобится какой-либо контейнер для их хранения. Например, приложение может иметь несколько настроек: PrintInColor (Цветная печать), CheckForUpdates (Проверять обновления), AutoConnect (Автоподключение), ..., и мы должны иметь к ним доступ. Как уже было сказано ранее, я создал TdvSettings на основе TObjectList. Так мы сможем хранить ссылку на экземпляр каждого TdvSetting-объекта и иметь к ним доступ.

3.4.2. Код

TdvSettings = class(TObjectList)
private
  FRootKey: string;
protected
  procedure CreateSettings; virtual;

  function GetItems(Index: Integer): TdvSetting;
  procedure SetItems(Index: Integer; ASetting: TdvSetting);
public
  constructor Create(const aRootKey : string);

  function Add(ASetting: TdvSetting): Integer;
  function Extract(Item: TdvSetting): TdvSetting;
  function Remove(ASetting: TdvSetting): Integer;
  function IndexOf(ASetting: TdvSetting): Integer;
  function First: TdvSetting;
  function Last: TdvSetting;
  function SettingByIdentifier(const aIdentifier : string) : TdvSetting;

  procedure LoadFromRegistry;
  procedure SaveToRegistry;

  procedure Insert(Index: Integer; ASetting: TdvSetting);
  property Items[Index: Integer]: TdvSetting read GetItems write SetItems; default;
  property RootKey : string read FRootKey write FRootKey;
end;

  ...

{ TdvSettings }

function TdvSettings.Add(ASetting: TdvSetting): Integer;
begin
  Result := inherited Add(ASetting);
end;

constructor TdvSettings.Create(const aRootKey: string);
begin
  inherited Create(True);
  FRootKey := aRootKey;
  CreateSettings;
  // Читаем значения из реестра при создании списка
  LoadFromRegistry;
end;

procedure TdvSettings.CreateSettings;
begin

end;

function TdvSettings.Extract(Item: TdvSetting): TdvSetting;
begin
  Result := TdvSetting(inherited Extract(Item));
end;

function TdvSettings.First: TdvSetting;
begin
  Result := TdvSetting(inherited First);
end;

function TdvSettings.GetItems(Index: Integer): TdvSetting;
begin
  Result := TdvSetting(inherited Items[Index]);
end;

function TdvSettings.IndexOf(ASetting: TdvSetting): Integer;
begin
  Result := inherited IndexOf(aSetting);
end;

procedure TdvSettings.Insert(Index: Integer; ASetting: TdvSetting);
begin
  inherited Insert(Index, aSetting);
end;

function TdvSettings.Last: TdvSetting;
begin
  Result := TdvSetting(inherited Last);
end;

procedure TdvSettings.LoadFromRegistry;
var
  lIndex: Integer;
  lSetting: TdvSetting;
  lRegIni: TRegistryIniFile;
begin
  lRegIni := TRegistryIniFile.Create('');
  try
    for lIndex := 0 to Pred(Count) do
    begin
      lSetting := Items[lIndex];
      lSetting.LoadFromRegIni(lRegIni, RootKey);
    end;
  finally
    FreeAndNil(lRegIni);
  end;
end;

function TdvSettings.Remove(ASetting: TdvSetting): Integer;
begin
  Result := inherited Remove(aSetting);
end;

procedure TdvSettings.SaveToRegistry;
var
  lIndex: Integer;
  lSetting: TdvSetting;
  lRegIni: TRegistryIniFile;
begin
  lRegIni := TRegistryIniFile.Create('');
  try
    for lIndex := 0 to Pred(Count) do
    begin
      lSetting := Items[lIndex];
      lSetting.SaveToRegIni(lRegIni, RootKey);
    end;
  finally
    FreeAndNil(lRegIni);
  end;
end;

procedure TdvSettings.SetItems(Index: Integer; ASetting: TdvSetting);
begin
  inherited Items[Index] := aSetting;
end;

function TdvSettings.SettingByIdentifier(
  const aIdentifier: string): TdvSetting;
var
  lcv: Integer;
begin
  Result := Nil;

  for lcv := 0 to Pred(Count) do
  begin
    if (Items[lcv].Identifier = aIdentifier) then
    begin
      Result := Items[lcv];
      Break;
    end;
  end;
end;

3.4.3. Что он делает?

Итак, это простой контейнер для нескольких объектов класса TdvSetting. Он предоставляет доступ к каждой настройке по ее индексу в списке или идентификатору (имени). Мы также сможем устанавливать корневой узел (RootKey) TdvSettings-объекта, задающий общий путь в реестре ко всем настройкам. Когда-нибудь мне захочется иметь возможность создавать настройки из базового класса и загружать их значения.

Вы заметите, что я добавил пустой метод CreateSettings. Цель - реализовать его в дочерних классах. На уровне TdvSettings мы не знаем, какие настройки у нас есть, как они называются и какой тип они имеют... В будущем же я хочу иметь возможность создавать настройки из базового класса и загружать их значения.

В TdvMyApplicationSettings я переопределю этот метод и добавлю необходимый код для настройки конкретных TdvSetting-объектов, которые мне нужны в приложении.

4. Что дальше?

На данный момент у нас есть скелет, от которого мы можем оттолкнуться. Если Вы готовы повозиться, можете создать свой собственный TdvSetting и его потомков. Я уже говорил, что мне нужны были Integer, DataTime и Boolean - их и попробуйте реализовать.

Я уже неоднократно отмечал, что не существует единственного подхода к решению нашей проблемы, и старался привести и другие возможные способы. Это позволило мне прийти к тому, что есть сейчас. Например, начальные версии содержали код для TRegistryIniFile непосредственно внутри класса TdvSetting.

Наличие одного и того же кода для LoadFromRegIni и SaveToRegIni во всех потомках TdvSetting, заставило меня в скором времени призадуматься. Я решил, что не стоит создавать и уничтожать TRegistryIniFile для каждой из 20 настроек - так код пришел к своему текущему состоянию.



 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 13.05.2011 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
SAP Crystal Reports XI R2 Dev 2006 INTL WIN NUL License (Version 11)
Купить Антивирус Dr.Web Server Security Suite для сервера
ABBYY Business Card Reader 2.0 for Windows (download), электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Вопросы и ответы по MS SQL Server
Один день системного администратора
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100