Jump to content
Mike Torrettinni

User settings - split logic and UI

Recommended Posts

Hi

 

I'm slowly preparing my application to run without UI, as cmd tool. So I started with User Settings. Right now I read and write user settings directly from Registry to UI (checkboxes) and back, Logic accesses UI to decide on workflow. Now I'm thinking like this:

 

- Setup GlobalSettings record which will hold all user settings

- on application start: read settings from Registry and write into GlobalSettings

- on application exit (or on user demand - Save button on settings screen) - save settings from GlobalSettings to Registry

- when logic needs to read user settings to decide on workflow, it reads directly from GlobalSettings - no more UI access from logic

- if application is run with UI, I will have 2 methods to work with UI - UserSettingsToUI and UserSettingsFromUI which will populate UI with settings from GlobalSettings; and when user changes any settings they will be saved into GlobalSettings with UserSettingsFromUI

 

So, the new GlobalSettings record will be the main settings 'engine' and UI will not access Registry directly, anymore. Logic will never access UI anymore, so even if application is run without UI, logic will run ok based on previously saved user settings.

 

Is this the right approach?

 

Thanks!

Share this post


Link to post

Hey Mike!

 

I have some questions about your approach:

  • Are you generating two binaries (one with the UI included and one to be used from CLI only)? Or do you compile one binary to be used as a hybrid?
  • If I read between the lines I guess your UI currently is used to hold state/settings of your application.
  • So, if you derive two executable binaries, as described above: Do you want to use some or all of your logic code in both binaries (shared code usually implies easy maintenance)?
  • What does your CLI look like? Is it an interactive program? Does it use CLI dialogs, mimicking a fully graphical UI or is it text mode only? Does the user repeatedly interact with several settings?
  • You asked for the "right approach". Imho there is no "right" approach here. There are several usable approaches, each with its pros and cons. I propose to take a step back and answer one question first: What needs your tool? For example: Is your tool launched several times concurrently and will there be the chance of conflicting settings (race conditions, "syncing problem" between instances)? Are settings changed often? Does your program run for long time periods? What could be damaged, if your program crashes before it had the chance to save the settings to registry? Are you accessing settings often during the program livetime? Do you need all settings to be present in the CLI version of your program (maybe there are some settings only affecting "visual" behaviour?)?
  • Try to classify and sort your program's demands by importance. Then optimize against the top priority properties first. If a "lower" prior demand conflicts against something you already optimized before, ignore it. Maybe your tool does nothing system critical at all and it sometimes does not matter at all HOW you solve it, but only HOW QUICKLY and CLEAR/READABLE/UNDERSTANDABLE you solve the problem itself to proceed to more important matters?

Maybe this helps you and others to answer your original question/problem better...

Timelord

Edited by Timelord

Share this post


Link to post

Timelord,

 

these are some very expert questions... I just have simple single .exe application, with forms. In Project.dpr source I control if forms are created, based on parameters.

 

- So, single source, single binary. I guess hybrid since I control if forms get created depending on input.

- Simple application with Settings screen where checkboxes are read/saved directly from Registry.

 

My application processes log files and creates reports, one fo them is Action Summary report. When run without parameters - 'normal mode ' - users can load and view different log files, and based on settings, that user can control, logs are parsed differently and different information displayed. User can create reports, like Action Summary report.

When run as cmd (CLI?) parameters specify input log files, which report to run (Action Summary report) and output folder. In this case parsing and export is defined by user settings read from registry - (that user defined in 'normal mode').

 

Right now, cmd mode wants to read UI checkboxes, and since some or all forms are not created (when in cmd mode), accessing checkboxes it not working. So, I want to split logic and UI access, to avoid UI access in cmd mode.

 

When run in cmd mode, all user settings are read only. Never run concurrently, cmd and normal mode. Yes, for cmd mode only part of user settings are relevant, the rest are for normal mode.

So it's a big parsing and reporting application and I would like to automate some of the reports, than can be automated.

 

I think this answers all your questions. I hope this gives more details on my situation. 

 

Thank you for your time!

Share this post


Link to post

Okay. It seems you already made some design decisions and I try to shape the following answer while keeping those in mind. I want to emphasis that the described way is not the only way and that I still believe that there are multiple equally good, but different approaches. The following just describes how I would decide, if I had to implement it.

 

I strongly like object orientation, so I always "configure" my instances at the earliest point possible. If you store your settings globally at one single point then this implies that you either have to "pull"/"ask"/"fetch" your individual criteria for parsing each time or that you will end up duplicating your concrete setting inside by storing it inside your instance. For example: There is one parser function traversing a log file. There will be several if conditions inside the code repeatedly checking the global setting.

Opposed to that approach you also can "push" your settings implicitly into the structure of your program at the time of creation. For example: If there are multiple possible ways of formatting a line inside a log, there will be multiple (maybe configurable) classes; each implementing one of the possible formats.

 

I guess you currently use the first described approach. This is totally legit. It works, so why not. There are two different general approaches to access this "global" data, though: Either there is a "global" variable and you use this everywhere. Since your program only uses this one configuration in one run this is totally legit. If your program is very small and clear with less settings I just would implement it this way.

I tend to avoid it if there are many settings, because there will sometimes be the problem of depending on "unneeded settings": Having one global settings record implies that there will be all information in one place. If every object has access to that variable, it can potentially read all items of the record, which violates the privacy principle of some definition of object orientation: you only want to make the needed parts visible, but nothing more. The solution: Pass the settings through the constructors at the time of creation or use setters after the time of creation. This divides the information and puts it directly where its needed. Since your application uses constants (constant setting) during one run, this is easy.

 

Therefore your idea to load the settings at the start of the program looks legit to me in this case. There are several so called software patterns, for example MVC (model, view, and controller): It's good to separate and organize the structure of your program. Storing the data separate from the UI is seen as a good idea from many developers.

 

As mentioned above this is only my opinion and there are several legit ones, so do not take my proposal as law. 😉 Please wait what others have to contribute.

Oh; and CLI stands for "command line interface". It's just a term I am more used to... (I understood "cmd", though)

 

If you have some questions on some of the above, just ask 🙂

 

Kind regards,

Timelord

Edited by Timelord

Share this post


Link to post

Thank you for the detailed explanation. I will take into account 'push' setting into the logic rather then constant 'pull', but this require change that was not expected. Even thought a constant pull should not be a problem, since there will be no shared settings.

Also, this is a first step into more refactored, organized structure. Not perfect, but a good step from current stage. And of course separation of logic and UI, which is a much needed goal, right now.

 

Thanks a lot!

Share this post


Link to post

how about memorystream usage?

i mean load registry at startup into a global stream.

access that stream all the time.

in UI with writeback to Registry option.

Edited by KodeZwerg

Share this post


Link to post
Guest

@KodeZwerg

 

How should it help if you put all data of a structured store into a single bucket of bytes? You should offer more details about the benefits. Until now I only see that this is a lot more complicated.

Share this post


Link to post

source link

full german thread link

copy/pasted the idea:

uses
  Registry;

// Speichern des Headers in die Registry
procedure TForm1.Button1Click(Sender: TObject);
var
  regi : TRegistryIniFile;
  buffer : TMemoryStream;
begin
  regi := TRegistryIniFile.Create(DEINKEY);


  Buffer := TMemoryStream.Create;
  VirtualStringTree1.Header.SaveToStream(Buffer);
  Buffer.Position := 0; //hier
  Regi.WriteBinaryStream(SECTION, NAME, Buffer);
  regi.Free;

  VirtualStringTree1.Header.Columns.Clear;
end;


// Laden der Daten aus der Registry
procedure TForm1.Button2Click(Sender: TObject);
var
  regi : TRegistryIniFile;
  buffer : TMemoryStream;
  i : Integer;
  p : PChar;
begin
  regi := TRegistryIniFile.Create(DEINKEY);
  Buffer := TMemoryStream.Create;
  regi.ReadBinaryStream(SECTION, NAME, Buffer);
  Buffer.Position := 0;
  VirtualStringTree1.Header.LoadFromStream(Buffer);
  Buffer.Free;
  regi.Free;
end;

 

Share this post


Link to post
Guest

@KodeZwerg

 

Your example show how to read and write a single setting entry and it not kept inside a global stream in which you want to load registry at startup as you suggested:

2 hours ago, KodeZwerg said:

how about memorystream usage?

i mean load registry at startup into a global stream.

access that stream all the time.

in UI with writeback to Registry option.

 

Share this post


Link to post

Is this app just for you or for others as well? If latter, I'd suggest you store settings in file, instead. It is then much easier to for user to send you the settings file than do the registry extract. Then you can just copy the file and run your program locally with those settings.

And even if it's just for you, use the file. Much easier to copy the settings around :)

Share this post


Link to post

It's app for others. Now with new settings the export is extremely easy, while before it was quite a work. Usually they send screenshot of the issue, version number, so the export settings from app is not much work for them, and I think is better as it can export also details of app version, installation folder details... which usually is not stored in traditional ini file.

Share this post


Link to post

I've abstracted my app configs, using a model similar to Windows Registry, that either store into a Json file, or into Registry.

From the app side, it is read at load, and write through on changes to ensure that changes are not lost.

I've used a key
  TStorageKey = (StoredForUser, StoredForAllUsers, StoredForApplication, StoredForSystem);
that maps to different locations in registry or file system to determine the scope of the config.

Share this post


Link to post

I agree with Erix A. Settings or parameters should be stored in a file, not in registry. A. in developing time maybe you will modify your settings by hand and modify registry is so trouble. B. If you release your APP, maybe user want modify settings manually not by using your APP UI, modify a file is more easy.

 

And if you want to separate your logic code and UI, you should use object. In this object your code can settings from file or save to file, and other module in your APP like UI module needs this settings data, just access this object. So this settings module is a independent module for other modules reuse it.

 

What kind of file for store your settings data? A ini file,  TIniFile can access it. Or you can  serialize your settings object to JSON or XML strings and save it into a file, etc. And you can store your settings into a SQLite database file.

 

Settings should be strings that human can read, should not be binary, cause sometimes man want to modify some parameters by hand.

 

I prefer using TStringList to read and write parameters, cause TStringList has three functions:

1. It can work as Key=Value mode.

2. It support SaveToFile and LoadFromFile.

 

Your object which maintain setting parameters and your UI can interact by DataBinding mechanism of Delphi then you do not write code to synchronize UI and parameters object, and object with file.

  • Thanks 1

Share this post


Link to post
On 11/3/2018 at 10:56 PM, Mike Torrettinni said:

Hi

 

I'm slowly preparing my application to run without UI, as cmd tool. So I started with User Settings. Right now I read and write user settings directly from Registry to UI (checkboxes) and back, Logic accesses UI to decide on workflow. Now I'm thinking like this:

 

- Setup GlobalSettings record which will hold all user settings

- on application start: read settings from Registry and write into GlobalSettings

- on application exit (or on user demand - Save button on settings screen) - save settings from GlobalSettings to Registry

- when logic needs to read user settings to decide on workflow, it reads directly from GlobalSettings - no more UI access from logic

- if application is run with UI, I will have 2 methods to work with UI - UserSettingsToUI and UserSettingsFromUI which will populate UI with settings from GlobalSettings; and when user changes any settings they will be saved into GlobalSettings with UserSettingsFromUI

 

So, the new GlobalSettings record will be the main settings 'engine' and UI will not access Registry directly, anymore. Logic will never access UI anymore, so even if application is run without UI, logic will run ok based on previously saved user settings.

 

Is this the right approach?

 

Thanks!

Yes. In my case, the settings are objects, one for each form, and together colllected in a special class. Each Settings class has public fields that are the settings and can be read globally They can be read and written by the control logic, and each can have an associated property of a control on the associated form, indicated using an attribute called AssociatedAttribute. Example:

 

type
  TAppearanceOptions = class(TSettings)
  public
    [Associated('FileNameFontEdit.TextSettings.Font.Size')]
    FileFontSize: Single;
    [Associated('FileNameFontEdit.TextSettings.Font.Family')]
    FileFontFamily: string;
    [Associated('FileNameFontEdit.TextSettings.Font.Style')]
    FileFontStyle: TFontStyles;
    [Associated('TextLineFontEdit.TextSettings.Font.Size')]
    TextFontSize: Single;
    [Associated('TextLineFontEdit.TextSettings.Font.Family')]
    TextFontFamily: string;
    [Associated('TextLineFontEdit.TextSettings.Font.Style')]
    TextFontStyle: TFontStyles;
    [Associated('BackgroundColorCombo.Color')]
    BackColor: TColor;

Each settings class can save itself to a given Storage class file (and re-load itself too).

Storage classes can write to or read from ini files, or JSON files, or XML files, or the registry.

Each settings class can set or read the properties associated by the attribute on a form given as parameter.

If the appearance options form is opened, I call GlobalSettings.AppearanceOptions.SetProperties(AppearanceForm); and when it is closed with OK, I do: GlobalSettings.AppearanceOptions.GetProperties(ApperanceForm);.

Uses quite a bit of RTTI, so it wouldn't work in old versions.

Edited by Rudy Velthuis
  • Thanks 1

Share this post


Link to post

Yes, now I have similar design, probably more basic - no RTTI. I have Store/Load Settings To/From Registry and for each Form I have LoadFromSettings(Form) in OnCreate and StoreToSettings(Form/nil = allForms) on Application close. Some reports also require on demand access, saving settings. It was a lot of work, a lot of trial and error cases, but now it works great!

Of course all the suggestinos here were very helpful in shaping the solution.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×