Jump to content
Willicious

Radio button options not remembered on re-opening app

Recommended Posts

My app uses mainly checkboxes for boolean values in the config menu. These are saved and loaded each time the app is closed and opened, no problem.

 

However, one option calls for radio buttons rather than checkboxes, because it's a choice of 3 different things. If one is chosen, then the others can't be chosen.

 

I use the same code to write the user's preference to the settings.ini file. However, when opening the app, the first radio button is always checked, regardless of what the user chose previously.

 

Does anyone know if radio buttons require different handling than checkboxes when saving/loading the user input to/from a settings file?

 

 

Share this post


Link to post
23 hours ago, KodeZwerg said:

Does it work, show and demonstrate what you try to do?

Yes, this is exactly what I'm trying to do.

 

I can't figure out where my app is going wrong. The options are dealt with across 2 pas files, I'll paste the relevant stuff here:

 

This is the actual config unit:

unit FAppConfig;

interface

uses
  GameControl;

type
  TFormAppConfig = class(TForm)
    rgChooseSound: TRadioGroup;
    rbSoundA: TRadioButton;
    rbSoundB: TRadioButton;

    procedure SetFromParams;
    procedure SaveToParams;

implementation

procedure TFormAppConfig.SetGameParams;
begin
  SetFromParams;
end;

procedure TFormAppConfig.btnApplyClick(Sender: TObject);
begin
  SaveToParams;
end;

procedure TFormAppConfig.btnOKClick(Sender: TObject);
begin
  SaveToParams;
  ModalResult := mrOK;
end;

procedure TFormAppConfig.SetFromParams;
begin
  fIsSetting := true;

    rbSoundA.Checked := GameParams.PreferSoundA;
    rbSoundB.Checked := GameParams.PreferSoundB;
end;

procedure TFormAppConfig.SaveToParams;
    GameParams.PreferSoundA := rbSoundA.Checked;
    GameParams.PreferSoundB := rbSoundB.Checked;
end;

end.

 

Then, a separate unit to deal with saving and loading:

unit GameControl;

{-------------------------------------------------------------------------------
  The gamecontrol class is in fact just a global var which is passed through as
  a parameter to all screens.
-------------------------------------------------------------------------------}

interface

type
  TMiscOption = (
    moPreferSoundA
	moPreferSoundB
  );

    property PreferSoundA: Boolean Index moPreferSoundA read GetOptionFlag write SetOptionFlag;
    property PreferSoundB: Boolean Index moPreferSoundB read GetOptionFlag write SetOptionFlag;

implementation

procedure AddUnknowns;
  begin
  	SaveBoolean('PreferSoundA', PreferSoundA);
	SaveBoolean('PreferSoundB', PreferSoundB);

procedure TDosGameParams.LoadFromIniFile;
var
  SL: TStringList;

  function LoadBoolean(aLabel: String; aDefault: Boolean): Boolean;
  begin
    PreferSoundA := LoadBoolean('PreferSoundA', PreferSoundA);
    PreferSoundB := LoadBoolean('PreferSoundB', PreferSoundB);
end;

end.

Whatever option I choose, the option is re-written as "PreferSoundA" when opening the app. This is even if SoundB is checked and written into the settings.ini file, i.e. When closing the app, the option will still say SoundB in the settings.ini.

 

Then, I open it, and SoundA is checked again.

 

P.S. This project is not mine, I'm working on an existing project which has sometimes very convoluted structure.

 

Thanks for your help!

Edited by Willicious

Share this post


Link to post
4 hours ago, Willicious said:

PreferSoundA := LoadBoolean('PreferSoundA', PreferSoundB);

PreferSoundB := LoadBoolean('PreferSoundA', PreferSoundB);

looks a bit fishy...

  • Haha 1

Share this post


Link to post

If it's possible, I'd reflect that radiobuttoned option to config according to its meaning, i.e. not a set of flags but an index from a list of choices.

  • Like 1

Share this post


Link to post

If you need options in more than one unit, I would create a record or class in a seperate unit called "uConfig" or something like that, add to your class/record everything that your app needs to be set up, add load and save methods to that class/record.

Make that class/record global available inside the "interface" part. Maybe use "initialization" to auto fill record/class with data and "finalization" to free it again if its a class.

Share this post


Link to post
10 hours ago, Willicious said:

type TFormAppConfig = class(TForm) rgChooseSound: TRadioGroup; rbSoundA: TRadioButton; rbSoundB: TRadioButton; procedure SetFromParams; procedure SaveToParams;

You should use a TRadiogroup instead of individual TRadiobuttons. You can then save and restore the ItemIndex of the group-

  • Thanks 1

Share this post


Link to post

@Willicious

 

try some like this:

type
  TForm1 = class(TForm)
    RadioGroup1: TRadioGroup;
    Button1: TButton;
    Memo1: TMemo;
    BtnLoadRadioButtons: TButton;
    BtnSaveRadioButtons: TButton;
    procedure BtnLoadRadioButtonsClick(Sender: TObject);
    procedure BtnSaveRadioButtonsClick(Sender: TObject);
  private
    procedure MySaveRadioGroup;
    procedure MyLoadRadioGroup;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
(*
  0) Adapt your class to handle the desired components!
  1) always try actions in "Try...Except" block!
  2) in Form creating, your controls may not have been created yet!!!
*)

procedure TForm1.MySaveRadioGroup;
var
  LMemStream: TMemoryStream;
  LControl  : TControl;
begin
  // TRY... EXCEPT ... END!!!
  LMemStream := TMemoryStream.Create;
  try
    for var i: integer := 0 to (RadioGroup1.ControlCount - 1) do
      begin
        LControl := RadioGroup1.Controls[i];
        LMemStream.WriteComponent(LControl);
      end;
    //
    LMemStream.SaveToFile('MyRadioButtonsSavedOnDisk.bin');
  finally
    LMemStream.Free;
  end;
end;

procedure TForm1.MyLoadRadioGroup;
var
  LMemStream: TMemoryStream;
  LControl  : TControl;
begin
  // TRY... EXCEPT ... END!!!
  if FileExists('MyRadioButtonsSavedOnDisk.bin') then
    begin
      LMemStream := TMemoryStream.Create;
      try
        LMemStream.LoadFromFile('MyRadioButtonsSavedOnDisk.bin');
        //
        for var i: integer := 0 to (RadioGroup1.ControlCount - 1) do
          begin
            LControl := RadioGroup1.Controls[i];
            LMemStream.ReadComponent(LControl);
            //
            // TGroupButton = class(TRadioButton) on "implementation seccion"
            // TGroupButton is a "internal" type to TRadioButton usage!!!
            Memo1.Lines.Add(LControl.ClassName);
          end;
      finally
        LMemStream.Free;
      end;
    end
  else
    ShowMessage('File not found!');
end;

procedure TForm1.BtnLoadRadioButtonsClick(Sender: TObject);
begin
  MyLoadRadioGroup;
end;

procedure TForm1.BtnSaveRadioButtonsClick(Sender: TObject);
begin
  MySaveRadioGroup;
end;

end.

 

Project2_1M4RjCeSZm.gif

Edited by programmerdelphi2k
  • Like 1

Share this post


Link to post

side-effect: If a component found in the file does not exist in your form, for example, it will be created!
So check before taking any actions!

Share this post


Link to post
13 hours ago, Lars Fosdal said:

looks a bit fishy...

Fixed this, it was a typo in the post. The code is as it should be.

 

13 hours ago, Fr0sT.Brutal said:

If it's possible, I'd reflect that radiobuttoned option to config according to its meaning, i.e. not a set of flags but an index from a list of choices.

Yes, this is probably what I need to do.

 

7 hours ago, PeterBelow said:

You should use a TRadiogroup instead of individual TRadiobuttons. You can then save and restore the ItemIndex of the group-

And this, too.

 

So, it's looking like the way to handle options set by radio buttons is to group and itemindex them, like you would a dropdown list?

 

@programmerdelphi2k Thanks for your examples, very helpful 🙂 

Share this post


Link to post

OK, so I've tried doing this as a TRadioGroup instead, and I'm getting exactly the same problem:

 

rgChooseSound: TRadioGroup;

procedure TFormNXConfig.SaveToParams;
begin
	GameParams.PreferA := rgChooseSound.ItemIndex = 0;
	GameParams.PreferB := rgChooseSound.ItemIndex = 1;
end;

procedure TFormNXConfig.SetOptions;
begin
    if GameParams.PreferA then rgChooseSound.ItemIndex = 0;
    if GameParams.PreferB then rgChooseSound.ItemIndex = 1;
end;

procedure TFormNXConfig.SetChooseSoundRadioGroup(aValue: Integer);
begin
	if rgChooseSound.ItemIndex = 0 then GameParams.PreferA;
	if rgChooseSound.ItemIndex = 1 then GameParams.PreferB;
end;

Then, in the GameControl unit:

 

property PreferA: Boolean Index moPreferA read GetOptionFlag write SetOptionFlag;
property PreferB: Boolean Index moPreferB read GetOptionFlag write SetOptionFlag;

procedure TGameParams.SaveToIniFile;
begin
  SaveBoolean('Prefer A', PreferA);
  SaveBoolean('Prefer B', PreferB);
end;

Procedure TGameParams.LoadFromIniFile;
begin
  PreferA := LoadBoolean('PreferA', PreferA);
  PreferB := LoadBoolean('PreferB', PreferB);
end;

Can anyone see where I'm going wrong, and what I need to do to fix it?

 

Using a RadioGroup and setting the options as list Items from an Index does work (i.e. it has the desired effect in-game), but the same problem of the choice not being remembered when the game is next opened occurs. It always resets to the first of the 2 options, every time. I need it to remember if the 2nd choice has been set, and load this.

 

As far as I can see, my code is at least trying to do this, and I can't see where it's failing.

Edited by Willicious

Share this post


Link to post
1 hour ago, Willicious said:

Can anyone see where I'm going wrong, and what I need to do to fix it?

Try this user friendly code with enum type  like ~TuserSettings = (spHash, spWarm, usJustRight) to line up the friendly strings.   Save  load the radiogroups item index.   Use the enumtype with a case to set any Booleans  

object RadioGroup1: TRadioGroup
  Left = 136
  Top = 232
  Width = 185
  Height = 105
  Caption = 'Settings'
  Items.Strings = (
    'Harsh'
    'Accurate'
    'Warm'
    'Fuzzy')
  TabOrder = 2
end

     

Share this post


Link to post
4 minutes ago, Pat Foley said:

Save  load the radiogroups item index.    

Yes, this is what I'm doing. The problem is that it isn't loading the setting.

Edited by Willicious

Share this post


Link to post
4 minutes ago, Pat Foley said:

Use the enumtype with a case to set any Booleans      

Can you give an example of what you mean here? Also, how does this differ from the way I've set the Booleans already?

Edited by Willicious

Share this post


Link to post

@Willicious

 

look, I see above you only "get/set" values to/from your properties and RgButtons... it's necessary know "how do you "SAVE/LOAD" this values... or be, are you saving in "disk", "Registry", etc... how do you do?

 

look, when you change the value in your Class-Game or RgButtons, this stay only on memory while you app is running, nothing more... you needs save in disk or Registry, then you will can "Load" for your RgButtons again, not in Class-Game, you got it?

 

did you try my sample above?

Edited by programmerdelphi2k

Share this post


Link to post
14 minutes ago, Willicious said:

Can you give an example of what you mean here? Also, how does this differ from the way I've set the Booleans already?

I am thinking you may still have on change events connected to the buttons.  If so set a break point in a change event to see what can happen!   

procedure TForm4.RadioGroup1Click(Sender: TObject);
begin
  var rg :=  sender as TRadioGroup;

  case TuserSound(rg.itemIndex) of
   usHarsh:
     bTreble := True;
   usWarm:
     bBose := True;
   usBoomy:
     bSubwoofers := True;
  end;
end;

 

Share this post


Link to post

I can only apologise for this...

 

It turns out it was all down to a simple typographical error 🤦‍♂️

 

I had LoadBoolean as 'Prefer A' instead of 'PreferA' - turns out that the spaces matter!

 

So sorry, and thank you to all those who replied!!

 

EDIT: A bonus outcome of this topic is that the code is now tidier and clearer; implementing this as a RadioGroup with an ItemIndex list rather than individual Radio Buttons is definitely better 👍

Edited by Willicious

Share this post


Link to post
12 hours ago, programmerdelphi2k said:

try some like this:

Why not go further and try full OS memory dump just to save a couple of bytes?

Edited by Fr0sT.Brutal

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

×