Здравствуйте.
Вопрос о настройках.
Программа имеет много настроек, они сгруппированы по нескольким вкладкам (к примеру, WinAmp, AIMP, QIP). Настройки сохраняются в INI-файле как только пользователь нажал "Сохранить" или "Применить".
А затем, в зависимости от настройки пользователя, программа определяет, как ей себя вести.
Вопрос вот в чём: как предпочтительней определять текущие настройки перед тем, как что-то сделать?
Приведу пример настройки: при подключении отображать имя пользователя в заголовке программы. Настройка типа флаг. И как после подключения определить эту настройку, чтоб знать, писать в заголовке формы имя пользователя или нет?
Я вижу несколько вариантов:
1) Сделать функцию, которая будет считывать из INI-файла текущую настройку каждый раз, когда понадобится узнать настройку;
2) При загрузке главной формы загружать в форму настроек все настройки из INI-файла и когда понадобится, считывать настройки из формы;
3) Сделать какой-нибудь глобальный массив настроек и при загрузке главной формы считать из INI-файла все настройки в этот массив, а когда понадобится - считать настройку из массива.
Лично я сейчас предпочитаю первый вариант - каждый раз обращаться к файлу.
А как поступают гуру? И другие смертные, вроде меня? ;)
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
26-11-2008 00:29
Тут можно задуматься еще вот над чем:
Могут быть локальные настройки(для конкретного компьютера, или конкретного пользователя) и глобальные(для всей системы)
Соответственно, их надо разделить:
ini-файл с глоб.настройками положить где-то рядом с базой данных, чтобы к нему имели доступ все рабочие места.
С локальными настройками может быть 2 варианта:
1) Настройки на рабочее место. В этом случае ini-файл храниш где-то на локальном компе, например в рабочей папке программы
2) Настройки на пользователя, если пользователи могут менять рабочие места. В этом случае ini-файл локальных настроек надо хранить рядом с глобальными. В этом случае надо позаботиться об уникальности имен ini-файлов, например по имени пользователя в домене, или имени пользователя в твоей прикладной программе.
Значения настроек по умолчанию можно задать в программе, при объявлении, а потом, если есть соответствующий ini-файл, то обновить настройки из него, если его нет, то будут настройки по умолчанию.
25-11-2008 08:00 | Комментарий к предыдущим ответам
Приложение вполне может запрашивать в СУБД настройки по паре компьютер-пользователь, имитируя роаминг профайлы.
Да, при желании можно и локальные параметры запихнуть в БД, а извлекать по имени компьютера. Но на мой взгляд все-таки для этой цели отлично подходят ini-файлы и такое усложнение системы не дает никаких преимуществ.
Я имел ввиду (см. ссылку), что есть настройки, которые в БД хранить невозможно, например
Хранить в СУБД можно всё, ибо это хранилище данных, за исключением строки соединения с этой СУБД. Но есть разные способы добыть эту строку в конечном приложении. Приложение вполне может запрашивать в СУБД настройки по паре компьютер-пользователь, имитируя роаминг профайлы. При уровне доверия к пользователю стемящемуся к 0 как-то "некрасиво" давать пользователю что-то кроме исполняемого файла. Но это касается больших систем.
А в таком виде оно и не вызывает принципиальных возражений.
Но первоначально это выглядело так, что настройки надо хранить только в базе данных и только так можно обеспечить разграничение прав, а других способов вообще не существует. Причем безотносительно того, работает ли программа с БД вообще, что, кстати, совсем не просматривается в вопросе.
Но в переформулированном виде Ваше высказывание выглядит уже вполне адекватно.
И как интересно хранить настройки в такой системке ?
Я имел ввиду (см. ссылку), что есть настройки, которые в БД хранить невозможно, например:
- параметры коннекта к БД;
- размеры и положение окон программы;
- ширину столбцов в гридах;
- локальные пути для импорта/экспорта файлов;
и т.п.
Не совсем согласен. Где хранить настройки - зависит от конкретной задачи.
Я имел прежде всего свои задачи.
Конкретная задача - распределенная система на 100+ пользователей без привязки пользователя к рабочему месту + условие, что часть настроек задается только админом программы, роуминг профили отсутствуют. И как интересно хранить настройки в такой системке ?
Это отнюдь не значит, что нельзя хранить настройки в файле, но сериализация настроек через поток, на чем в основном я настаиваю, позволяет сделать систему существенно гибче, тк можно хранить настройки в любом источнике, поддерживающем потоки, а это может быть как файл, так и СУБД.
min@y™
Спасибо большое за пример. Исходники высылать не нужно, благодарю. Думаю, мне будет полезнее самому написать подобное. Таким образом я лучше в этом разберусь.
3) Третий вариант кажется наиболее подходящим. Однако, я предпочитаю использовать не массив, а переменную типа record, отражающую структуру ini-файла.
Я делаю немного сложнее, но в итоге добавлять новые настройки, хранить их в памяти, читать их оттуда, изменять и отображать на форме "Настройки" их гораздо легче. Настройки у меня хранятся в XML-файлеб но это не принципиально.
Опишу вкратце.
Каждая опция - это экземпляр класса, который является наследником вот такого общего абстрактного класса:
type
// Тип селектора
TSelectorType = (stUnknown,
stBoolean,
stInteger,
stColor,
stString,
stFont);
// --------- Общий класс-родитель настроек -------------
TCustomOption = class
private
FCaption: string; // Название настройки, отображаемое на форме
FName: string; // Имя ноды в XML-файле <Setting>
FSelectorType: TSelectorType; // Тип опции
FGroupIndex: Integer; // Идентификатор группы (для разделения)
FVisisble: Boolean; // Флаг вилимости опции. Если True, то под опцию создаётся контрол
public
constructor Create(const ACaption, AName: string); virtual;
procedure SaveToXml(AParentNode: TXMLItem); virtual; abstract; // Сохранение
procedure LoadFromXml(ANode: TXMLItem); virtual; abstract; // Загрузка
property Name: string read FName; // Имя ноды в XML-файле <Setting>
property Caption: string read FCaption; // Название настройки, отображаемое на форме
property SelectorType: TSelectorType read FSelectorType write FSelectorType; // Тип опции
property GroupIndex: Integer read FGroupIndex write FGroupIndex; // Идентификатор группы (для разделения)
property Visible: Boolean read FVisisble write FVisisble; // Флаг вилимости опции. Если True, то под опцию создаётся контрол
end;
Вот классы - потомки:
type
// ------- Класс для хранения Boolean ----------
TBooleanOption = class(TCustomOption)
private
FValue: Boolean; // Хранимое значение
public
procedure SaveToXml(AParentNode: TXMLItem); override; // Сохранение
procedure LoadFromXml(ANode: TXMLItem); override; // Загрузка
property Value: Boolean read FValue write FValue; // Хранимое значение
end;
// ------- Класс для хранения Integer ----------
TIntegerOption = class(TCustomOption)
private
FValue: Integer; // Хранимое значение
public
procedure SaveToXml(AParentNode: TXMLItem); override; // Сохранение
procedure LoadFromXml(ANode: TXMLItem); override; // Загрузка
property Value: Integer read FValue write FValue; // Хранимое значение
end;
// -------- Класс для хранения TColor -----------
TColorOption = class(TCustomOption)
private
FValue: TColor; // Хранимое значение
public
procedure SaveToXml(AParentNode: TXMLItem); override; // Сохранение
procedure LoadFromXml(ANode: TXMLItem); override; // Загрузка
property Value: TColor read FValue write FValue; // Хранимое значение
end;
// ------- Класс для хранения string -------------
TStringOption = class(TCustomOption)
private
FValue: string; // Хранимое значение
public
procedure SaveToXml(AParentNode: TXMLItem); override; // Сохранение
procedure LoadFromXml(ANode: TXMLItem); override; // Загрузка
property Value: string read FValue write FValue; // Хранимое значение
end;
// ------- Класс для хранения TFont -------------
TFontOption = class(TCustomOption)
private
FValue: TFont; // Хранимое значение
FFontEffects: Boolean; // Флаг показа свойст шрифта: Подчёркнутый, зачёркнутый, цвет
public
constructor Create(const ACaption, AName: string); override;
destructor Destroy(); override;
procedure SaveToXml(AParentNode: TXMLItem); override; // Сохранение
procedure LoadFromXml(ANode: TXMLItem); override; // Загрузка
property Value: TFont read FValue; // Хранимое значение
property FontEffects: Boolean read FFontEffects write FFontEffects;
end;
Ну и наконец - сам класс-контейнер, в котором создаются, загружаются, изменяются, сохраняются и освобождаются настройки:
// ============= Класс - Настройки программы ================
TProgramSettings = class
private
FOptions: TObjectList; // Список настроек
FFileName: string; // Имя файла для хранения настроек
function GetOptionsCount: Integer; // Количество настроек
function GetOptionByIndex(const Index: Integer): TCustomOption; // Получение опции по индексу
function GetOptionByName(const AName: string): TCustomOption; // Получение опции по имени ноды в XML-файле
function GetOptionByCaption(const ACaption: string): TCustomOption; // Получение опции по названию, отображаемому на форме
public
constructor Create();
destructor Destroy(); override;
function NewOption(const ACaption, AName: string; // Добавление опции в список
const ASelectorType: TSelectorType;
const AGroupIndex: Integer): TCustomOption;
function SaveToXml(): Boolean; // Сохранение
function LoadFromXml(): Boolean; // Загрузка
property OptionsCount: Integer read GetOptionsCount;
property OptionsByIndex[const Index: Integer]: TCustomOption read GetOptionByIndex; default; // опции по индексу
property OptionsByName[const AName: string]: TCustomOption read GetOptionByName; // опции по имени ноды в XML-файле
property OptionsByCaption[const ACaption: string]: TCustomOption read GetOptionByCaption; // опции по названию, отображаемому на форме
end;
Вот его конструктор, где вручную прописано создание списка нужных настроек:
constructor TProgramSettings.Create;
var
Index: Integer;
Option: TCustomOption;
begin
// Имя файла для хранения настроек
FFileName:= ExtractFilePath(ParamStr(0)) + 'Settings.xml';
// Создание объектов
FOptions:= TObjectList.Create(True);
// Добаляю группу "Цвета"
for Index:= 0 to High(ColorSettingsNames) do
begin
Option:= NewOption(ColorSettingsNames[Index, 0], // Название (Caption)
ColorSettingsNames[Index, 1], // Имя (Name)
stColor, // Тип селектора
ColorSettingsGroup); // Номер группы
TColorOption(Option).Value:= ColorSettingsDefaults[Index]; // Значение по умолчанию
end;
// Добавляю группу "Шрифты"
for Index:= 0 to High(FontSettingsNames) do
begin
Option:= NewOption(FontSettingsNames[Index, 0], // Название (Caption)
FontSettingsNames[Index, 1], // Имя (Name)
stFont, // Тип селектора
FontSettingsGroup); // Номер группы
// Добавляю группу "Интерфейс"
for Index:= 0 to High(InterfaceSettingsNames) do
begin
Option:= NewOption(InterfaceSettingsNames[Index, 0], // Название (Caption)
InterfaceSettingsNames[Index, 1], // Имя (Name)
stBoolean, // Тип селектора
InterfaceSettingsGroup); // Номер группы
TBooleanOption(Option).Value:= InterfaceSettingsDefaults[Index]; // Значение по умолчанию
end;
// Создание невидимых опций
NewOption('Имя последнего открытого файла',
nLastFileName,
stString,
InvisibleGroup).Visible:= False; // имя последнего открытого файла
end;
При загрузке из файла/сохранении вызывается метод LoadFromXml/SaveToXml всех опций в списке.
Список опций висит в памяти и доступен для чтения записи.
Вот такова моя концепция. Она не является наставлением, как надо писать программы, просто здесь я показал, как пишу я. Если хочешь, я вышлю файл(ы) целиком - посмотришь.
Утверждение столь же категоричное, сколь и не верное.
Хм... Может быть, но остальные варианты гораздо сложнее в реализации и администрировании (если речь идет о достаточно большой системе). Если хранить настройки в файловой системе, то необходимо обеспечить правильный доступ пользователя к файлам, что делает квалифицированый админ (обычно админ домена), поскольку это влияет на всю сеть в целом, нужна синхронизация файлов и тд и тп. С логированием действий пользователя - отдельная песня. В случае СУБД права изменения настроек определяются процедурами СУБД на основе прав записанных в таблицах СУБД (а идентификатор аутентифицированного пользователя при этом хранится в переменной пакета) и эти права применимы только к данной конкретной системе, а значит можно делегировать полномочия предоставления прав продвинутому юзеру (и логировать его действия, чтобы по башке настучать в случае чего, при этом можно однозначно утверждать, что действия выполнены сознательно именно этим пользователем). Аналогичную штуку можно реализовать в трехзвенке, но трехзвенка тяжелее в реализации и зачастую опять же использует СУБД для хранения данных. Больше вариантов мне в голову не приходит.
Можно, конечно, хранить все настройки в текстовом файле, доступном всем для записи, но инициативных дураков у нас очень много и найти потом концы кто, где и что поменял не получится, тк никто не сознается, если за руку не поймали.
Лично я тупо использую EhLib + переопределение некоторых функций (для сохранения в СУБД) + глобальные переменные для хранения глобальных настроек приложения. Что-то ближе к третьему варианту. Некоторая часть этой идеи изложена здесь http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1374
Приведу пример настройки: при подключении отображать имя пользователя в заголовке программы.
Согласно статье есть глобальный флаг при изменении которого вызовется обработчик главной формы и в нем, соответственно вызовется перерисовка заголовка, те настройка применится автоматически и немедленно, причем изменение настройки может быть произведено в одной форме, а это сразу же будет применено ко всем остальным формам, а условия применения настройки (фактически и список форм, на которые повлияет настройка) определяются конкретной формой.
Вообще, ИМХО, настройки через компонент должны сохраняться/читаться из потока, а не из файла. А вот что именно связано с потоком уже определяется приложением. Потом опять же есть настройки, которые может поменять любой пользователь, а есть настройки, которые должны задаваться администратором системы (специфика системы). Без СУБД такое разделение настроек не обеспечить, потому сразу было решено отказаться от файлов.
3) Третий вариант кажется наиболее подходящим. Однако, я предпочитаю использовать не массив, а переменную типа record, отражающую структуру ini-файла.
Я лично так и делаю. Есть структура, есть 2 процедуры - чтения/записи настроек в/из файла. Тут есть один удобный бонус. Если пользователь нажал "Применить" - настройки сохраняются в структуру, но не в файл, и применяются. Если что-то не так, можно нажать "Отмена" - старые настройки считаются из файла, всё станет как было. А вот при нажатии "Сохранить" - уже пишутся в файл, откат невозможен.
Формы с настройками вообше предпочитаю убрать из Auto-create, т.к. используются они редко. И уж ни в коем случае не хранить в них настройки.
1) Мне представляется первый вариант негибким. Он не учитывает возможности менять настройки в ходе исполнения и не сохранять эти временные настройки, а оставлять их действующими только в текущем сеансе.
2) Второй вариант тебует лишнего - зачем заводить форму только для того, чтобы там держать набор настроек?
3) Третий вариант кажется наиболее подходящим. Однако, я предпочитаю использовать не массив, а переменную типа record, отражающую структуру ini-файла.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.