Russian Qt Forum
Май 05, 2024, 05:02 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: QSettings + Xml = CXmlSettings  (Прочитано 17417 раз)
break
Гипер активный житель
*****
Offline Offline

Сообщений: 846


Просмотр профиля
« : Январь 17, 2010, 06:20 »

выкладываю:
H файл: XmlSettings.h
Код
C++ (Qt)
 
#ifndef __XML_SETTINGS_H__
#define __XML_SETTINGS_H__
 
#include <QString>
#include <QSettings>
 
class QDomDocument;
class QDomElement;
class QIODevice;
 
class CXmlSettings
{
QSettings * m_pSettings;
public:
CXmlSettings( QString fname = "" );
~CXmlSettings();
 
inline QSettings& settings() const { return * m_pSettings; }
 
// при использование CXmlSettings::value конфиг создастся автоматически, если его не было
QVariant value( const QString & key, const QVariant & defaultValue = QVariant() );
void setValue ( const QString & key, const QVariant & value );
 
private:
static bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map);
static bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map);
static void processWriteKey( QDomDocument& doc, QDomElement& domElement, QString key, const QVariant& value );
static void processReadKey( QString key, QSettings::SettingsMap &map, QDomElement& domElement );
};
 
#endif // __XML_SETTINGS_H__
 
 

CPP файл: XmlSettings.cpp
Код
C++ (Qt)
 
#include "XmlSettings.h"
#include "FileUtils.h"
#include <QDomDocument>
#include <QDebug>
 
CXmlSettings::CXmlSettings( QString fname )
{
if ( fname.isEmpty() )
fname = settingsPath() + appBaseName() + ".xml";
 
static const QSettings::Format XmlFormat = QSettings::registerFormat("xml", readXmlFile, writeXmlFile);
 
m_pSettings = new QSettings( fname, XmlFormat );
}
 
CXmlSettings::~CXmlSettings()
{
delete m_pSettings;
}
 
QVariant CXmlSettings::value( const QString & key, const QVariant & defaultValue )
{
if ( !settings().contains( key ) )
settings().setValue( key, defaultValue );
return settings().value( key );
}
void CXmlSettings::setValue ( const QString & key, const QVariant & value )
{
settings().setValue( key, value );
}
 
bool CXmlSettings::readXmlFile(QIODevice &device, QSettings::SettingsMap &map)
{
qDebug() << "-----readXmlSettings------";
 
QDomDocument doc("");
if ( !doc.setContent( &device ) ) return false;
 
QDomElement root = doc.documentElement();
 
processReadKey( "", map, root );
 
/*
// Для теста использовалось при отладке
QMap<QString, QVariant>::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
qDebug() << i.key() << ": " << i.value() << endl;
++i;
}
*/

return true;
}
 
bool CXmlSettings::writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map)
{
qDebug() << "-----writeXmlSettings-----";
 
/*
// Для теста использовалось при отладке
QMap<QString, QVariant>::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
qDebug() << i.key() << ": " << i.value() << endl;
++i;
}
*/

 
QDomDocument doc("");
QDomElement root = doc.createElement("Main");
doc.appendChild(root);
 
QMapIterator<QString, QVariant> i(map);
while ( i.hasNext() )
{
i.next();
 
QString  sKey = i.key();
QVariant value = i.value();
processWriteKey( doc, root, sKey, i.value() );
};
 
QDomNode xmlNode = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\"");
doc.insertBefore(xmlNode, doc.firstChild());
 
QTextStream out( &device );
doc.save(out, 4);
 
return true;
}
 
void CXmlSettings::processWriteKey( QDomDocument& doc, QDomElement& domElement, QString key, const QVariant& value )
{
int slashPos = key.indexOf( '/' );
 
// переданный ключ является параметром
if ( slashPos < 0 )
{
// не пишем в конфиг параметр size (является ограничением - нельзя исп. пар-тр с таким именем)
if ( key == "size" ) return;
domElement.setAttribute( key, value.toString() );
return;
};
 
// получение имени группы соответствующей xml ноде
QString groupName = key.left( slashPos );
// если в качестве имени использован числовой параметр - это табличная строка, преобразуем ее в row_?
if ( groupName.toInt() )
{
groupName = "row_" + groupName;
domElement.toElement().setAttribute("table", "1");
};
 
// поиск/создание ноды соответствующей ключу
QDomElement groupElement;
 
QDomNode findedGroupNode = domElement.namedItem( groupName );
 
if ( findedGroupNode.isNull() )
{
groupElement = doc.createElement( groupName );
domElement.appendChild( groupElement );
}
else
groupElement = findedGroupNode.toElement();
 
// готовим обрезанную часть ключа
key = key.right( key.size() - slashPos - 1 );
 
// продолжение обработки (создание/поиск групп) - пока не найдется параметр
processWriteKey( doc, groupElement, key, value );
}
 
void CXmlSettings::processReadKey( QString key, QSettings::SettingsMap &map, QDomElement& domElement )
{
 
QDomNamedNodeMap namedNodeMap = domElement.attributes();
 
// Добавление всех атрибутов элемента в качестве значений
for (int i = 0; i < namedNodeMap.count(); ++i)
{
QString name = namedNodeMap.item( i ).toAttr().name();
QString value = namedNodeMap.item( i ).toAttr().value();
map.insert( key + name, value );
};
 
QDomNodeList nlChild = domElement.childNodes();
 
// если узел является таблицей - то все дети строки
bool isTable = domElement.attribute("table", "0").toInt();
// создаем доп. элемент size равный числу детей (необходим для QSettings - beginArray)
if ( isTable )
map.insert( key + "size", nlChild.count() );
 
// проход по всем детям
for (int i = 0; i < nlChild.count(); ++i)
{
QString childName = nlChild.item(i).toElement().tagName();
if ( childName.contains("row_") )
childName = childName.right( childName.size() - 4 );
 
QString subKey = key + childName + "/";
QDomElement subElement = nlChild.item(i).toElement();
processReadKey( subKey, map, subElement );
};
}
 
 

Функции settingsPath()  и  appBaseName() - у меня реализованы в моем FileUtils - возвращают дефолтный путь к папке с настройками и базовое имя программы (без расширения)
Код
C++ (Qt)
QString settingsPath()
{
QDir dir( QCoreApplication::applicationDirPath() + "/../Settings" );
return dir.absolutePath() + "/";
}
 
QString appBaseName()
{
QString appBaseName = QCoreApplication::applicationFilePath();
QStringList sl = appBaseName.split("/");
appBaseName = sl[ sl.count()-1 ];
 
// убираем ".EXE" для виндовых файлов
appBaseName.replace( ".exe", "", Qt::CaseInsensitive );
 
return appBaseName;
}
 

Также продублировал во вложении.

Пользоваться можно через обращение к settings() - всеми возможностями самого QSettings - группы, массивы и т.д. Ограничение - нельзя использовать атрибут с именем "table" - он используется для определения таблицы (массива) при чтение настроек. Если пользоваться через прямые методы value, CXmlSettings - то в случае если значения в конфиге не было - оно создастся и заполниться значением по умолчанию (сделал чтобы программа сама создавала свой конфиг если был утерян...).
Записан
asvil
Гость
« Ответ #1 : Январь 17, 2010, 11:17 »

Помню весной пользовался этим кодом. Однако мне не понравилось наследование через мембер-указатель на родитель. Это же одна лишняя строка при использовании. Также не понравилось хранение значение в атрибутах.
Выкладываю модификацию, которую сразу можно использовать как QSettings и значения пишуться в дочерние ноды.
В свою очередь спрашиваю может кто-то реализовывал аналог QSettings для хранения настроек в sql базе?
Записан
zenden
Гость
« Ответ #2 : Январь 17, 2010, 14:14 »

Вот вы молодцы а то я уже запарился искать сохранялку в XML
Записан
break
Гипер активный житель
*****
Offline Offline

Сообщений: 846


Просмотр профиля
S
« Ответ #3 : Январь 17, 2010, 14:41 »

Цитировать
Однако мне не понравилось наследование через мембер-указатель на родитель. Это же одна лишняя строка при использовании.

Там нет такого наследования - есть только агрегирование QSettings для реализации функционала CXmlSettings - думаю это правильно, а "лишние символы при использовании" не заслуживают наследования. Руководствовался такими принципами
1) Наследование применять только там где без него нельзя обойтись
2) CXmlSettings не "реализован посредством QSettings" а лишь использует его ф-онал, он был бы реализован посредством если бы ф-ции

Код
C++ (Qt)
static bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map);
static bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map);

были виртуальными - то для их реализации нужно было бы наследоваться от QSettings

3) В случае изменения исходного кода QSettings в случае агрегирования нет(или минимум) проблем
4) Если бы ф-ции

Код
C++ (Qt)
static bool readXmlFile(QIODevice &device, QSettings::SettingsMap &map);
static bool writeXmlFile(QIODevice &device, const QSettings::SettingsMap &map);
 

получали QSettings в качестве параметра не оп ссылке, а по указателю - то вообще можно было бы не инклудить <QSettings> в заголовочнике "XmlSettings.h" - то есть фактически скрытая реализация - плюс легкая замена этой скрытой реализации в случае такой необходимости... (переход на другую версию Qt)

Но ваш пример тоже имеет право на существование. Правда на счет лишней писанины я не думаю что это актуально - т.к. все сегодняшние IDE работают с CodeComplition - я наоборот стараюсь давать более полные имена методов чтобы потом не путаться что он скрывает за функционал.

Цитировать
Также не понравилось хранение значение в атрибутах.
Может действительно нужен флажок для указаничя классу где хранить в атрибутах или во вложенных тегах. Иногда так действительно удобнее, но XML снастройками чуть побольше будет...
« Последнее редактирование: Январь 17, 2010, 14:51 от break » Записан
asvil
Гость
« Ответ #4 : Январь 17, 2010, 20:11 »

...агрегирование... - это и имел ввиду, не владею терминологией в полной мере.
XmlSettings *globalSettings = new XmlSettings() // Ну или там <componentName>Settings
Теперь природный инстинкт лени мешает завести еще одну переменную, и код продолжается в таком стиле.
globalSettings->settings()->setValue(x,1);
А внутри setValue вызыв функции, которая тоже осмысленно именована. И вот уже нужен перевод строки и кол-во закрывающих скобочек приближается к лисп-стилю. Но это я так, не для дискуссии. Может преувеличиваю.
Записан
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #5 : Январь 18, 2010, 07:23 »

наследоватся надо от QSettings, иначе смысла нет в какой-либо связи с QSettings.
Например есть в программе  метод:
void Class::setSetting(QSettings seting)

И используется оригинал QSettings, приспичило хранить в БД или XML, воткнул туда соответствующего наследника и всё.
Записан

Юра.
break
Гипер активный житель
*****
Offline Offline

Сообщений: 846


Просмотр профиля
« Ответ #6 : Январь 27, 2010, 06:39 »

наследоватся надо от QSettings, иначе смысла нет в какой-либо связи с QSettings.
Например есть в программе  метод:
void Class::setSetting(QSettings seting)

И используется оригинал QSettings, приспичило хранить в БД или XML, воткнул туда соответствующего наследника и всё.

И что? Воткнул туда

CXmlSettings xml;
xnl.settings(); ------------ указатель на QSettings и ВСЕ


Сам QSettings сделан так что его не нужно наследовать!!! - мы регистрируем формат и скармливаем его в конструктор - то есть все наши хранения настроек в БД или XML обрабатывает сам QSettings через этот формат ( фактически 2 написанные нами процедуры читания, писания настроек). Я сделал CXmlSettings только для своего удобства т.к. привычно работать с классами - можно вообще использовать 2 глобальные процедуры - регистрировать их в формат и скармливать в QSettings и все будет работать - без какого-либо наследования!

Цитировать
Теперь природный инстинкт лени мешает завести еще одну переменную, и код продолжается в таком стиле.
Смею вас уверить - наследование не для того предназначено чтобы синтаксис красивее и удобнее делать - наследование должно использоваться только на основе логического анализа класса предка и потенциального наследника - а никак не анализа длинности идентификаторов членов.
« Последнее редактирование: Январь 27, 2010, 06:43 от break » Записан
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #7 : Январь 27, 2010, 08:14 »

>>Сам QSettings сделан так что его не нужно наследовать!!!
не важно как сделан QSettings.

Простой вопрос, я могу безболезнено воткнуть твой класс в имеющийся код вместо QSettings или нет?
Т.е. там где у меня было:
QSettings seting;
obj->(seting);

можно ли сделать:
CXmlSettings seting;
obj->(seting);

Если считать, что этот самый метод obj->(seting); использует все возможности QSettings (вызывает все public-методы)?
Приведёт ли это к ожидаемому результату?
Записан

Юра.
break
Гипер активный житель
*****
Offline Offline

Сообщений: 846


Просмотр профиля
« Ответ #8 : Январь 27, 2010, 21:14 »

Цитировать
не важно как сделан QSettings.
в том то и дело что ВАЖНО - в нем нет никаких виртуальных фуекций предназначенных для замещения!
[/quote]

Цитировать
Простой вопрос, я могу безболезнено воткнуть твой класс в имеющийся код вместо QSettings или нет?
Т.е. там где у меня было:
QSettings seting;
obj->(seting);

можно ли сделать:
CXmlSettings seting;
obj->(seting);

Если считать, что этот самый метод obj->(seting); использует все возможности QSettings (вызывает все public-методы)?
Приведёт ли это к ожидаемому результату?
Я уже писал выше что надо использовать

Код
C++ (Qt)
CXmlSettings xml;
obj->( xml.settings() );

Это приведет к ожидаемому результату! Все возможности QSettings останутся в силе - и дело не в моем классе а в том что кутешники так сделали QSettings что его не нужно наследовать - достаточно зарегистрировать свой тип "НАСТРОЕК" и скормить его в конструктор QSettings (что и делает класс CXmlSettings).
Записан
break
Гипер активный житель
*****
Offline Offline

Сообщений: 846


Просмотр профиля
« Ответ #9 : Апрель 10, 2010, 00:59 »

Обнаружил такой момент - если создавать QSettings передавай туда имя файла который нельзя открыть (например имя каталога), то QSettings будет работать кешируя при этом пары, ключ-> значение где то в своей приватной части. Да так что даже создавая разные экземпляры QSettings в разных областях видимости эти пары сохраняются! Мне это кажется скорее глюком чем фичей.

Дело в том что предполагался код в котором настройки читаются в любом случае даже если сам файл настроек не указан. Это делалось для того чтобы не писать 2 раза код инициализации большого количества переменных, а инициализировать их при чтение настроек передавая значение по умолчанию (которое присвоится в случае отсутствия конкретного параметра в настройках). Ну а так как они кешируются для неправильно указанных имен файлов - то все стало путаться.

Исправлять QSettings смысла не вижу, кроме того достала проблема с его проблемой "пересортировки" ключей. Т.е. при чтение / записи произвольно менялся их порядок в файле.

Написал маленький класс кот. зависит только от QtXml и работает похоже на QSettings ( есть методы beginGroup(), endGroup() )

Оказалось пользоваться даже удобнее! Можно всегда перейти к QDomElement и работать с xml напрямую, можно по старому через beginGroup(), enfGroup()? value(),

beginArray() делать не стал т.к. чтение массивов делается легко так:


Код
C++ (Qt)
m_xml.beginGroup( "ThresholdRU_Values" );
 
int   nGearPos = 0;
float fValueI  = 0;
 
QDomElement deItem = m_xml.currentElement().firstChildElement( "item" );
while( !deItem.isNull() )
{
nGearPos = deItem.attribute( "gear_pos" ).toInt();
fValueI  = deItem.attribute( "value_i" ).toDouble();
 
m_threshold_RU_Gear_Assign.insert( nGearPos, fValueI );
 
deItem = deItem.nextSiblingElement( "item" );
};
 
m_xml.endGroup();
 



сам класс
Код
C++ (Qt)
#ifndef __XML_SETTINGS_H__
#define __XML_SETTINGS_H__
 
#include <QDomDocument>
#include <QVariant>
 
class CXml_Settings
{
QDomDocument m_doc;
QDomElement  m_rootElement;
QDomElement  m_currentElement;
QString      m_sFileName;
bool         m_bAutoSave;
 
public:
CXml_Settings();
CXml_Settings( QString sFName );
~CXml_Settings();
 
bool loadFromFile( const QString sFName );
void saveToFile( const QString sFName );
 
void beginGroup( const QString sName );
void endGroup();
QVariant value( const QString & sName, const QVariant & defaultValue = QVariant() );
void setValue ( const QString & key, const QVariant & value );
 
inline QDomElement& currentElement() { return m_currentElement; };
inline QDomElement& rootElement()    { return m_rootElement; };
inline void setAutoSave( bool bVal ) { m_bAutoSave = bVal; }
 
private:
void createRootElement();
};
 
#endif // __XML_SETTINGS_H__
 

Код
C++ (Qt)
 
#include "Xml_Settings.h"
 
#include "FileUtils.h"
#include "DebugLog.h"
 
#define ROOT_NODE_NAME "Main"
 
CXml_Settings::CXml_Settings() : m_doc("")
{
m_sFileName.clear();
 
createRootElement();
 
m_bAutoSave = true;
}
 
CXml_Settings::CXml_Settings( QString sFName ) : m_doc("")
{
m_sFileName.clear();
 
createRootElement();
 
loadFromFile( sFName );
}
 
CXml_Settings::~CXml_Settings()
{
if ( ( m_bAutoSave ) && ( !m_sFileName.isEmpty() ) )
saveToFile( m_sFileName );
}
 
bool CXml_Settings::loadFromFile( const QString sFName )
{
m_doc.clear();
 
m_sFileName = sFName;
if ( !openXMLfile( m_sFileName, QFile::ReadOnly, m_doc ) )
{
createRootElement();
return false;
}
else
{
m_rootElement = m_doc.namedItem( ROOT_NODE_NAME ).toElement();
 
bool bNormalLoad = !m_rootElement.isNull();
if ( !bNormalLoad )
ERROR_OUT << "Can't find root XML node with name =" << ROOT_NODE_NAME;
 
m_currentElement = m_rootElement;
 
return bNormalLoad;
};
}
 
void CXml_Settings::saveToFile( const QString sFName )
{
saveXMLfile( sFName, m_doc );
}
 
void CXml_Settings::beginGroup( const QString sName )
{
QDomNode node = m_currentElement.namedItem( sName );
 
if ( !node.isNull() )
m_currentElement = node.toElement();
else
{
QDomElement deNewElement = m_doc.createElement( sName );
 
m_currentElement.appendChild( deNewElement );
 
m_currentElement = deNewElement;
};
}
 
void CXml_Settings::endGroup()
{
if ( m_currentElement.nodeName() == ROOT_NODE_NAME ) return;
 
m_currentElement = m_currentElement.parentNode().toElement();
}
 
QVariant CXml_Settings::value( const QString & key, const QVariant & defaultValue )
{
if ( m_currentElement.hasAttribute( key ) )
return QVariant( m_currentElement.attribute( key ) );
else
{
m_currentElement.setAttribute( key, defaultValue.toString() );
return defaultValue;
}
}
 
void CXml_Settings::setValue ( const QString & key, const QVariant & value )
{
m_currentElement.setAttribute( key, value.toString() );
}
 
void CXml_Settings::createRootElement()
{
// QDomNode xmlNode = m_doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\"");
// m_doc.insertBefore(xmlNode, m_doc.firstChild());
 
m_rootElement    = m_doc.createElement( ROOT_NODE_NAME );
m_doc.appendChild( m_rootElement );
m_currentElement = m_rootElement;
}
 

методы из моего FileUtils
Код
C++ (Qt)
bool openXMLfile( QString fname, QFile::OpenMode mode, QDomDocument& doc )
{
QFile file( fname );
 
if ( !file.open(mode) )
{
ERROR_OUT << "Error open file: " << fname << "!!!";
return false;
};
 
if ( !doc.setContent(&file) )
{
ERROR_OUT << "Error read XML file: " << fname << "!!!";
file.close();
return false;
}
 
file.close();
return true;
}
 
bool saveXMLfile(QString fname, QDomDocument& doc)
{
QFile ofile( fname );
if (!ofile.open(QIODevice::WriteOnly))
{
ERROR_OUT << "Can't open file to write: " << fname;
return false;
};
QTextStream out(&ofile);
//doc.save(out, 4);
out.setCodec( QTextCodec::codecForName("UTF-8") );
doc.save(out, 4, QDomNode::EncodingFromTextStream );
 
ofile.close();
return true;
}
 

Записан
pastor
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 2901



Просмотр профиля WWW
« Ответ #10 : Апрель 12, 2010, 11:03 »

Продолжение http://www.prog.org.ru/topic_13144_0.html
Записан

Integrated Computer Solutions, Inc. (ICS)
http://www.ics.com/
MrLink
Гость
« Ответ #11 : Июнь 03, 2010, 16:34 »

beginArray() делать не стал т.к. чтение массивов делается легко так:
Чтение может и легко, а вот запись... каждый сам будет дописывать? Мне, вот, понадобилось. Особой проблемы, конечно, нет. Но как-то некрасиво получается.
Записан
break
Гипер активный житель
*****
Offline Offline

Сообщений: 846


Просмотр профиля
« Ответ #12 : Июнь 03, 2010, 16:56 »

Запись так же легко через QDomElement - решение с QSettings и его массивами неудобно и некрасиво
Записан
MrLink
Гость
« Ответ #13 : Июнь 04, 2010, 09:36 »

Я предполагал такой ответ - может, тогда вообще всё сделать на QDom... QSetting я не рассматриваю (пусть каждый сам решает, подходит он для его задачи или нет). Я говорю про то, что если и использовать CXml... , то в него надо еще дописать часть, что бы его можно было использовать по аналогии с чтением. Что б  потом его особенно не трогать - я его откомпилировал как статическую библиотеку...
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.357 секунд. Запросов: 22.