Pull to refresh

Конфиг-файлы в Delphi без проблем

Reading time6 min
Views17K
Как-то было дело и я задумался над тем, как же удобнее всего настройки пользователя где-нибудь локально, быстренько это дело написать и забыть. Хранить это дело я решил в xml-файле. Куда уж без них.
Главное в этом способе то, что при добавлении каких-то новых параметров или изменении старых, не нужно будет переписывать код сохранения данных и их загрузки. Все будет делаться автоматически. Все, что нам нужно — это создать базовый класс, который будет за нас все делать, а сами данные мы будем хранить в объектах классов-наследников.


В общем, чтобы не пудрить мозг, сразу приведу код базового класса:

unit tlXMLClass; 
 
interface 
 
uses 
  Classes, XMLIntf, 
  // это важно!!! модуль позволяет работать со свойствами объекта 
  TypInfo; 
 
type 
  TXMLClass = class(TPersistent) 
  private 
    // тут у нас будет имя файла с настройками 
    FXMLFilePath: string;
    // а тут - название приложения для идентификации 
    FApplicationName: string; 
    // название корневой ветки файла
    FRootNodeName: string; 
    // версия
    FVersion: byte; 
  protected 
    procedure SaveClass(oObject: TObject; Node: IXMLNode); 
    procedure LoadClass(oObject: TObject; Node: IXMLNode); 
  public 
    constructor Create(const AppName, XMLFilePath: string; RootNodeName: string = 'config');  
 
    procedure Initialize; abstract; 
 
    // загрузка значений из файла
    procedure Load; 
    // и их сохранение
    procedure Save; 

    // виртуальный метод, его мы будем писать в наследнике 
    procedure LoadDefaults; virtual; 
 
    property ApplicationName: string read FApplicationName write FApplicationName; 
    property RootNodeName: string read FRootNodeName; 
    property Version: byte read FVersion write FVersion default 1; 
  end; 
 
implementation 
 
uses 
  // насчет XMLDoc и XMLIntf - эти два модуля появились в Delphi не так давно, 
  // насколько я помню. если у вас их нет, то придется это дело реализовывать как-то по-другому. 
  XMLDoc, SysUtils, Windows, 
  resConfig; 
 
{ TXMLConfig } 
 
{$REGION 'Initialization'} 
constructor TXMLClass.Create(const AppName, XMLFilePath: string; RootNodeName: string = 'config'); 
begin 
  Initialize; 
 
  FApplicationName := AppName; 
  FXMLFilePath := XMLFilePath; 
  FRootNodeName := RootNodeName; 
 
  // задаем настройки по-умолчанию 
  LoadDefaults; 
end; 

procedure TXMLClass.LoadDefaults; 
begin 
 
end; 
{$ENDREGION} 
 
{$REGION 'Loading'} 
procedure TXMLClass.LoadClass(oObject: TObject; Node: IXMLNode); 
 
  // тут мы пробуем найти свойство и задать его значение 
  procedure GetProperty(PropInfo: PPropInfo); 
  var 
    sValue: string; 
    TempNode: IXMLNode; 
    LObject: TObject; 
  begin 
    // пробуем найти ветку с названием свойства 
    TempNode := Node.ChildNodes.FindNode(PropInfo^.Name); 
    // если не нашли, то выходим из функции. значение свойства останется значением по-умолчанию 
    if TempNode = nil then 
      exit; 
 
    // если свойство не является объектом, то получаем значение из ветки
    if PropInfo^.PropType^.Kind <> tkClass then 
      sValue := TempNode.Text; 
 
    // анализируем тип свойства и задаем ему значение в соответствии с ним
    case PropInfo^.PropType^.Kind of 
      tkEnumeration: 
        if GetTypeData(PropInfo^.PropType^)^.BaseType^ = TypeInfo(Boolean) 
          then SetPropValue(oObject, PropInfo, Boolean(StrToBool(sValue))) 
          else SetPropValue(oObject, PropInfo, StrToInt(sValue)); 
      tkInteger, tkChar, tkWChar, tkSet: 
        SetPropValue(oObject, PropInfo, StrToInt(sValue)); 
      tkFloat: 
        SetPropValue(oObject, PropInfo, StrToFloat(sValue)); 
      tkString, tkLString, tkWString: 
        SetPropValue(oObject, PropInfo, sValue); 
      // а вот если свойство - объект, то рекурсивно выполняем процедуру
      // LoadClass, но уже для найденной ветки
      tkClass: 
        begin 
          LObject := GetObjectProp(oObject, PropInfo); 
          if LObject <> nil then 
            LoadClass(LObject, TempNode); 
        end; 
    end; 
  end; 
 
var 
  i, iCount: integer; 
  PropInfo: PPropInfo; 
  PropList: PPropList;   
begin 
  // получаем количество публичных свойств объекта 
  iCount := GetTypeData(oObject.ClassInfo)^.PropCount; 
 
  if iCount > 0 then 
  begin 
    // запрашиваем кусочек памяти для хранения 
    // списка свойств 
    GetMem(PropList, iCount * SizeOf(Pointer)); 
 
    // и получаем их в PropList 
    GetPropInfos(oObject.ClassInfo, PropList); 
    try 
      // пробегаемся по списку свойств 
      for i := 0 to iCount - 1 do 
      begin 
        PropInfo := PropList^[i]; 
        if PropInfo = nil then 
          break; 
 
        // и для каждого свойства выполняем GetProperty (см.выше) 
        GetProperty(PropInfo); 
      end; 
    finally 
      // и в самом конце освобождаем занятую списком память 
      FreeMem(PropList, iCount * SizeOf(Pointer)); 
    end; 
  end; 
end; 
 
procedure TXMLClass.Load;
// процедура чтения из файла 
var 
  XMLRoot: IXMLNode; 
  XML: IXMLDocument; 
begin 
  LoadDefaults; 
  if not FileExists(FXMLFilePath) then 
    exit; 
 
  try 
    // сам xml-Файл с настройками
    XML := LoadXMLDocument(FXMLFilePath); 
    // корневая ветка xml-документа 
    XMLRoot := XML.DocumentElement; 
 
    // проверка на то, наш ли этот файл
    if (XMLRoot.NodeName <> FRootNodeName) or 
       (XMLRoot.Attributes[rsApplication] <> FApplicationName) then 
      exit; 
 
    FVersion := XMLRoot.Attributes[rsFormat]; 
 
    // пошли загружать
    LoadClass(Self, XMLRoot); 
  except 
    // возникло исключение? загружаем значения по-умолчанию
    LoadDefaults; 
  end; 
end; 
{$ENDREGION} 
 
{$REGION 'Saving'} 
procedure TXMLClass.SaveClass(oObject: TObject; Node: IXMLNode); 
// здесь мы сохраняем значения и процедура эта очень 
// сильно похожа на процедуру загрузки, поэтому комментировать 
// я здесь буду только то, чего нет в той процедуре 
 
  procedure WriteProperty(PropInfo: PPropInfo); 
  var 
    sValue: string; 
    LObject: TObject; 
    TempNode: IXMLNode; 
  begin 
    case PropInfo^.PropType^.Kind of 
      tkEnumeration: 
        if GetTypeData(PropInfo^.PropType^)^.BaseType^ = TypeInfo(Boolean) 
          then sValue := BoolToStr(Boolean(GetOrdProp(oObject, PropInfo)), true) 
          else sValue := IntToStr(GetOrdProp(oObject, PropInfo)); 
      tkInteger, tkChar, tkWChar, tkSet: 
        sValue := IntToStr(GetOrdProp(oObject, PropInfo)); 
      tkFloat: 
        sValue := FloatToStr(GetFloatProp(oObject, PropInfo)); 
      tkString, tkLString, tkWString: 
 
        sValue := GetWideStrProp(oObject, PropInfo); 
      tkClass: 
        if Assigned(PropInfo^.GetProc) and Assigned(PropInfo^.SetProc) then 
        begin 
          LObject := GetObjectProp(oObject, PropInfo); 
          if LObject <> nil then 
          begin 
            TempNode := Node.AddChild(PropInfo^.Name); 
 
            SaveClass(LObject, TempNode); 
          end; 
        end; 
    end; 
 
    // тут мы создаем новую ветку в корне документа 
    // и записываем в него значение свойства 
    if PropInfo^.PropType^.Kind <> tkClass then 
      with Node.AddChild(PropInfo^.Name) do 
        Text := sValue; 
  end; 
 
var 
  PropInfo: PPropInfo; 
  PropList: PPropList; 
  i, iCount: integer; 
begin 
  iCount := GetTypeData(oObject.ClassInfo)^.PropCount; 
 
  if iCount > 0 then 
  begin 
    GetMem(PropList, iCount * SizeOf(Pointer)); 
    try 
      GetPropInfos(oObject.ClassInfo, PropList); 
 
      for i := 0 to iCount - 1 do 
      begin 
        PropInfo := PropList^[i]; 
        if PropInfo = nil then 
          Break; 
 
        WriteProperty(PropInfo); 
      end; 
    finally 
      FreeMem(PropList, iCount * SizeOf(Pointer)); 
    end; 
  end; 
end; 
 
procedure TXMLClass.Save; 
var 
  FRootNode: IXMLNode; 
  FBackFileName: string; 
  XML: IXMLDocument; 
begin 
  // куда уж без бекапа. на всякий случай не помешает 
  FBackFileName := ChangeFileExt(FXMLFilePath, '.bak'); 
  try 
    // оригинал удаляем 
    if FileExists(FXMLFilePath) then 
      DeleteFile(PChar(FXMLFilePath)); 
 
    try 
      // создаем новый XML-документ 
      XML := NewXMLDocument; 
 
      // задаем ему кодировку и версию 
      with XML do 
      begin 
        Encoding := 'UTF-8'; 
        Version := '1.0'; 
      end; 
 
      // добавляем корневую ветку FRootNodeName
      FRootNode := XML.AddChild(FRootNodeName); 
      FRootNode.Attributes[rsApplication] := FApplicationName; 
      FRootNode.Attributes[rsFormat] := FVersion; 
 
      SaveClass(Self, FRootNode); 
 
      // сохраняем документ 
      XML.SaveToFile(FXMLFilePath); 
    except 
      // а вот если произошла ошибка, то пытаемся 
      // восстановить файл из созданной резервной копии 
      if FileExists(FBackFileName) then 
        RenameFile(FBackFileName, FXMLFilePath); 
    end; 
  finally 
    // и в самом конце удаляем резервную копию 
    if FileExists(FBackFileName) then 
      DeleteFile(PChar(FBackFileName)); 
  end; 
end; 
{$ENDREGION} 
 
end.


Вот такие вот дела. Код не шибко маленький, но, если разобраться, он совсем не сложный. Надеюсь еще и полезный. Для кого-нибудь :)
Да, код работает на D2007, но на версии раньше перевести его не будет проблем. На те версии, где есть поддержка XML.

Пример конфига, сгенерированного классом:

<?xml version="1.0" encoding="UTF-8" ?> 
<config application="test" format="0">
	<Main>
		<HistoryDepth>40</HistoryDepth> 
	</Main>
	<LookAndFeel>
		<WindowWidth>200</WindowWidth> 
		<AlwaysOnTop>True</AlwaysOnTop> 
		<AlphaBlending>False</AlphaBlending> 
		<AlphaBlendValue>245</AlphaBlendValue> 
		<AnimateWithAlpha>False</AnimateWithAlpha> 
		<Elements>
			<ItemDefault>
				<Font>
					<Name>Tahoma</Name> 
					<Size>8</Size> 
					<Color>0</Color> 
					<Bold>False</Bold> 
					<Italic>False</Italic> 
					<Strikeout>False</Strikeout> 
					<Underline>False</Underline> 
				</Font>
			</ItemDefault>
			<ItemChecked>
				<Font>
					<Name>Tahoma</Name> 
					<Size>8</Size> 
					<Color>9079434</Color> 
					<Bold>False</Bold> 
					<Italic>False</Italic> 
					<Strikeout>True</Strikeout> 
					<Underline>False</Underline> 
				</Font>
			</ItemChecked>
		</Elements>
	</LookAndFeel>
	<Confirmation>
		<DeleteElement>True</DeleteElement> 
	</Confirmation>
	<Windows>
		<HelpWindow>
			<Top>182</Top> 
			<Left>73</Left> 
			<Width>1135</Width> 
			<Height>642</Height> 
			<WindowState>0</WindowState> 
			<SplitterLeft>156</SplitterLeft> 
		</HelpWindow>
	</Windows>
</config>


P.S. все это дело поддерживает группировку свойств в отдельные объекты-наследники TPersistent.

страничка проекта на GoogleCode.
Tags:
Hubs:
Total votes 33: ↑23 and ↓10+13
Comments93

Articles