четверг, 23 октября 2014 г.

Сохранение свойств контролов aka SaveState

В ХЕ7 добавили SaveState - пародию на SharedPreferences , подробнее тут и тут .
Но в отличии от андроида, если используете Android Studio, где достаточно реализовать только фейс - то в ХЕ7 необходимо помудохаться, особенно на С++Билдере .
Для делфи реализован класс для авто сохранения свойств , но он точно не сохраняет состояние TComboBox.
Вообще в Рад Студии со свойствами намутили , слов нет.
К примеру :

TCheckBox = class(TTextControl, IIsChecked)

То есть к свойству IsChecked можно добраться через интерфейс IIsChecked
А вот для RadioButton к IsChecked через интерфейс IIsChecked не доберешься.
Короче приходится изобретать свой трахтер.

Итак:
Для записи/восстановления будем использовать JSON и XML - кому что больше нравиться. Преимущество XML в том, что можно сохранить состояние нескольких форм,с JSON у меня не получилось записать состояние нескольких форм - файл перезаписывается. При записи текстовых полей будем кодировать в Бейс 64 - так как теоретически могут храниться любые символы, а JSON и XML файлы имеют определенную структуру и есть возможность не прочитать состояние.

С вступлением закончили - идем по коду

SaveStateHelper.h
// ---------------------------------------------------------------------------
#ifndef SaveStateHelperH
#define SaveStateHelperH
// ---------------------------------------------------------------------------
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
// ---------------------------------------------------------------------------
class TSaveStateHelper {
public:
 void SaveFormStateJSON(TForm *forma);
 void LoadFormStateJSON(TForm *forma);
 void SaveFormStateXML(TForm *forma);
 void LoadFormStateXML(TForm *forma);
  
 TStrings* GetPropertyList(TFmxObject *Component,bool includePropType);

};
#endif


SaveStateHelper.срр

//---------------------------------------------------------------------------

#include 
#pragma hdrstop
#include "SaveStateHelper.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
const System::String SavePathJSON=System::Ioutils::TPath::Combine(System::Ioutils::TPath::GetDocumentsPath(), System::Ioutils::TPath::GetFileNameWithoutExtension(ParamStr(0))+".json");
const System::String SavePathXML=System::Ioutils::TPath::Combine(System::Ioutils::TPath::GetDocumentsPath(), System::Ioutils::TPath::GetFileNameWithoutExtension(ParamStr(0))+".xml");

TFmxObject    *FMXObj;
TJSONObject   *FMXJObj;
TJSONObject   *FormJSONObject;
TStrings      *jsonList;
TXMLDocument  *xmlDoc;

//---------------------------------------------------------------------------
TStrings* TSaveStateHelper::GetPropertyList(TFmxObject *Component,bool includePropType)
{
Typinfo::TTypeKinds SupportKinds;
SupportKinds << tkUnknown << tkInteger   << tkChar    << tkEnumeration << tkFloat <<
    tkString  << tkSet       << tkClass   << tkMethod      << tkWChar <<
    tkLString << tkWString   << tkVariant << tkArray <<
    tkRecord  << tkInterface << tkInt64   << tkDynArray <<
    tkUString << tkClassRef  << tkPointer << tkProcedure;
Typinfo::TPropList pList;
TStrings *propList =  new TStringList();
AnsiString propName,propType;
//получаем список свойств
int nPropCount = GetPropList((Typinfo::PTypeInfo)(Component->ClassInfo()),
         SupportKinds,
        ((Typinfo::PPropList)(&pList)));

for( int j = 0; j < nPropCount;  j++)
{
propName=pList[j]->Name;
propType=(*(pList[j]->PropType))->Name;
if (includePropType)propList->Add(propName + " : "+ propType);
else propList->Add(propName);
}
return propList;
}
//---------------------------------------------------------------------------
void  TSaveStateHelper::SaveFormStateJSON(TForm *forma)
{
jsonList =  new TStringList();
FormJSONObject = new TJSONObject();
FMXJObj = new TJSONObject();

   for (int i = 0; i < forma->ComponentCount; i++)
   {
 if (dynamic_cast(forma->Components[i]))
 {
 FMXObj=dynamic_cast(forma->Components[i]);

  if (GetPropertyList(FMXObj,false)->IndexOf("Text")>0  && FMXObj->ClassName().Pos("Edit")>0)      FMXJObj->AddPair(FMXObj->Name,EncodeString(GetPropValue(FMXObj,"Text",true)));
  if (GetPropertyList(FMXObj,false)->IndexOf("ItemIndex")>0  && FMXObj->ClassName().Pos("Combo")>0)FMXJObj->AddPair(FMXObj->Name,GetPropValue(forma->Components[i],"ItemIndex",true));
  if (GetPropertyList(FMXObj,false)->IndexOf("IsChecked")>0 && (FMXObj->ClassName().Pos("Check")>0  || FMXObj->ClassName().Pos("Radio")>0 || FMXObj->ClassName().Pos("Switch")>0) ) if (BOOL(GetPropValue(FMXObj,"IsChecked",true))) FMXJObj->AddPair(FMXObj->Name, new TJSONTrue()); else FMXJObj->AddPair(FMXObj->Name, new TJSONFalse());
  if (GetPropertyList(FMXObj,false)->IndexOf("Date")>0  && (FMXObj->ClassName().Pos("Calendar")>0 || FMXObj->ClassName().Pos("Calendar")>0))  FMXJObj->AddPair(FMXObj->Name,GetPropValue(FMXObj,"Date",true));
  if (GetPropertyList(FMXObj,false)->IndexOf("Time")>0  && FMXObj->ClassName().Pos("Time")>0)  FMXJObj->AddPair(FMXObj->Name,GetPropValue(FMXObj,"Time",true));
  if (GetPropertyList(FMXObj,false)->IndexOf("Value")>0  )  FMXJObj->AddPair(FMXObj->Name,GetPropValue(FMXObj,"Value",true));
 }
   }

  try
  {
  FormJSONObject->AddPair(forma->Name, FMXJObj->ToJSON());

  jsonList->Text=FormJSONObject->ToJSON();
  jsonList->SaveToFile(SavePathJSON);
  }
  __finally
  {
  FormJSONObject->Free();
  FMXJObj->Free();
  jsonList->Free();
  }
}
//---------------------------------------------------------------------------
void  TSaveStateHelper::LoadFormStateJSON(TForm *forma)
{
if (FileExists(SavePathJSON))
{
jsonList =  new TStringList();
jsonList->LoadFromFile(SavePathJSON);
TStringBuilder *jsonStr = new TStringBuilder(jsonList->Text.Trim());
try
{
if (jsonList->Text.Pos("\\")>0) {
jsonStr->Replace("\\","");
}

if (jsonStr->Chars[forma->Name.Length()+4]=='\"') {
jsonStr->Remove(forma->Name.Length()+4,1);
jsonStr->Remove(jsonStr->Length-2,1);
}

TJSONObject *JSON = (TJSONObject*)TJSONObject::ParseJSONValue(jsonStr->ToString());

TJSONPair *formPair = JSON->Get(forma->Name);
FormJSONObject = (TJSONObject*) formPair->JsonValue;
TJSONPair *compPair;
 for (int i = 0; i < forma->ComponentCount; i++)
 {
  if (dynamic_cast(forma->Components[i]))
  {
  FMXObj=dynamic_cast(forma->Components[i]);
  compPair = FormJSONObject->Get(FMXObj->Name);

  if (GetPropertyList(FMXObj,false)->IndexOf("Text")>0  && FMXObj->ClassName().Pos("Edit")>0)  SetPropValue(FMXObj,"Text",DecodeString(compPair->JsonValue->Value()));
  if (GetPropertyList(FMXObj,false)->IndexOf("ItemIndex")>0  && FMXObj->ClassName().Pos("Combo")>0)SetPropValue(FMXObj,"ItemIndex",compPair->JsonValue->Value());
  if (GetPropertyList(FMXObj,false)->IndexOf("IsChecked")>0 && (FMXObj->ClassName().Pos("Check")>0  || FMXObj->ClassName().Pos("Radio")>0 || FMXObj->ClassName().Pos("Switch")>0) )  SetPropValue(FMXObj,"IsChecked",compPair->JsonValue->Value());
  if (GetPropertyList(FMXObj,false)->IndexOf("Date")>0  && (FMXObj->ClassName().Pos("Calendar")>0 || FMXObj->ClassName().Pos("Calendar")>0))  SetPropValue(FMXObj,"Date",compPair->JsonValue->Value());
  if (GetPropertyList(FMXObj,false)->IndexOf("Time")>0  && FMXObj->ClassName().Pos("Time")>0)  SetPropValue(FMXObj,"Time",compPair->JsonValue->Value());
  if (GetPropertyList(FMXObj,false)->IndexOf("Value")>0  )  SetPropValue(FMXObj,"Value",compPair->JsonValue->Value());
  }
 }
  }
 __finally {jsonList->Free();jsonStr->Free();}
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void  TSaveStateHelper::SaveFormStateXML(TForm *forma)
{

xmlDoc = new TXMLDocument(Application);
xmlDoc->Active = true;
xmlDoc->Encoding = "UTF-8";
xmlDoc->Options = xmlDoc->Options << doNodeAutoIndent;
_di_IXMLNode parentNode,formaNode;
if (FileExists(SavePathXML))
{
xmlDoc->LoadFromFile(SavePathXML);
parentNode=xmlDoc->DocumentElement;
}
else
{
parentNode=xmlDoc->AddChild("xml");
}

if(xmlDoc->DocumentElement->ChildNodes->FindNode(forma->Name))formaNode=xmlDoc->DocumentElement->ChildNodes->FindNode(forma->Name);
else  formaNode = parentNode->AddChild(forma->Name);

try
{
 for (int i = 0; i < forma->ComponentCount; i++)
 {

  if (dynamic_cast(forma->Components[i]))
  {
  FMXObj=dynamic_cast(forma->Components[i]);
  _di_IXMLNode nodNew = formaNode->ChildNodes->FindNode(FMXObj->Name);
  if (!nodNew)nodNew = formaNode->AddChild(FMXObj->Name);

  if (GetPropertyList(FMXObj,false)->IndexOf("Text")>0  && FMXObj->ClassName().Pos("Edit")>0)       nodNew->NodeValue=EncodeString(GetPropValue(FMXObj,"Text",true));
  if (GetPropertyList(FMXObj,false)->IndexOf("ItemIndex")>0  && FMXObj->ClassName().Pos("Combo")>0) nodNew->NodeValue=GetPropValue(forma->Components[i],"ItemIndex",true);
  if (GetPropertyList(FMXObj,false)->IndexOf("IsChecked")>0 && (FMXObj->ClassName().Pos("Check")>0  || FMXObj->ClassName().Pos("Radio")>0 || FMXObj->ClassName().Pos("Switch")>0) )nodNew->NodeValue=GetPropValue(FMXObj,"IsChecked",true);
  if (GetPropertyList(FMXObj,false)->IndexOf("Date")>0  && (FMXObj->ClassName().Pos("Calendar")>0 || FMXObj->ClassName().Pos("Calendar")>0))  nodNew->NodeValue=GetPropValue(FMXObj,"Date",true);
  if (GetPropertyList(FMXObj,false)->IndexOf("Time")>0  && FMXObj->ClassName().Pos("Time")>0)       nodNew->NodeValue=GetPropValue(FMXObj,"Time",true);
  if (GetPropertyList(FMXObj,false)->IndexOf("Value")>0  )                                          nodNew->NodeValue=GetPropValue(FMXObj,"Value",true);



  }
 }
   xmlDoc->SaveToFile(SavePathXML);
}__finally {
xmlDoc->Free();
  }
}
//---------------------------------------------------------------------------
void  TSaveStateHelper::LoadFormStateXML(TForm *forma)
{
if (FileExists(SavePathXML))
{
xmlDoc =new TXMLDocument(Application);
try
{
 xmlDoc->Active = true;
 xmlDoc->Encoding = "UTF-8";
 xmlDoc->Options = xmlDoc->Options << doNodeAutoIndent;
 xmlDoc->LoadFromFile(SavePathXML);
 _di_IXMLNode formaNode=xmlDoc->DocumentElement->ChildNodes->FindNode(forma->Name);

if(formaNode)
{
 for (int i = 0; i < forma->ComponentCount; i++)
 {
  if (dynamic_cast(forma->Components[i]))
  {
  FMXObj=dynamic_cast(forma->Components[i]);
  _di_IXMLNode nodNew = formaNode->ChildNodes->FindNode(FMXObj->Name);
   if (nodNew )
   {
    if (nodNew->Text!="")
    {
    if (GetPropertyList(FMXObj,false)->IndexOf("Text")>0  && FMXObj->ClassName().Pos("Edit")>0)  SetPropValue(FMXObj,"Text",DecodeString(nodNew->Text));
    if (GetPropertyList(FMXObj,false)->IndexOf("ItemIndex")>0  && FMXObj->ClassName().Pos("Combo")>0)SetPropValue(FMXObj,"ItemIndex",nodNew->Text);
    if (GetPropertyList(FMXObj,false)->IndexOf("IsChecked")>0 && (FMXObj->ClassName().Pos("Check")>0  || FMXObj->ClassName().Pos("Radio")>0 || FMXObj->ClassName().Pos("Switch")>0) )  SetPropValue(FMXObj,"IsChecked",nodNew->Text);
    if (GetPropertyList(FMXObj,false)->IndexOf("Date")>0  && (FMXObj->ClassName().Pos("Calendar")>0 || FMXObj->ClassName().Pos("Calendar")>0))  SetPropValue(FMXObj,"Date",nodNew->Text);
    if (GetPropertyList(FMXObj,false)->IndexOf("Time")>0  && FMXObj->ClassName().Pos("Time")>0)  SetPropValue(FMXObj,"Time",nodNew->Text);
    if (GetPropertyList(FMXObj,false)->IndexOf("Value")>0  )                                     SetPropValue(FMXObj,"Value",nodNew->Text);
    }
   }
  }
 }
}
} __finally {xmlDoc->Free();}
}
}



Вызов

// ---------------------------------------------------------------------------
void __fastcall TfrmMain::SaveXMLButtonClick(TObject *Sender) {
 TSaveStateHelper *st;
 st->LoadFormStateXML(frmMain);
}

// ---------------------------------------------------------------------------
void __fastcall TfrmMain::LoadXMLButtonClick(TObject *Sender) {
 TSaveStateHelper *st;
 st->SaveFormStateXML(frmMain);
}
// ---------------------------------------------------------------------------
void __fastcall TfrmMain::SaveJSONButtonClick(TObject *Sender) {
 TSaveStateHelper *st;
 st->LoadFormStateJSON(frmMain);
}

// ---------------------------------------------------------------------------
void __fastcall TfrmMain::LoadXMLButtonJSON(TObject *Sender) {
 TSaveStateHelper *st;
 st->SaveFormStateJSON(frmMain);
}

Как то так, по идее должно работать на всех платформах

Комментариев нет:

Отправить комментарий