How to synch 3 Radio Groups (without handling 3 OnClick()S)? - delphi

I have 3 radio groups which I display on different tabsheets of a single form app, but they basically all have the same underlying functionality (and only one can be visible to the user at a time).
That is to say, if the user selects a new Radio Button of any of them, I want to programatically update the ItemIndexof the other two, to match.
BUT, I don't want to execute the OnClick() function three times.
Is there a an easy way to do this, or should I just use a single radio group and keep changing it's Parent, Top and Left as the user changes the visible tab?
The App currently has no menu, so it seems ugly to add one just for this.

Create 3 radiogroups but only one event handler and assign it to all radiogroups:
procedure TForm1.RadioGroupClick(Sender: TObject);
var Index : Integer;
begin
RadioGroup1.OnClick := nil;
RadioGroup2.OnClick := nil;
RadioGroup3.OnClick := nil;
Index := TRadioGroup(Sender).ItemIndex;
RadioGroup1.ItemIndex := Index;
RadioGroup2.ItemIndex := Index;
RadioGroup3.ItemIndex := Index;
RadioGroup1.OnClick := RadioGroupClick;
RadioGroup2.OnClick := RadioGroupClick;
RadioGroup3.OnClick := RadioGroupClick;
end;

You can use in-memory TDataset and a number of TDBRadioGroup widgets
http://lazarus-ccr.sourceforge.net/docs/lcl/dbctrls/tdbradiogroup.html
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/DBCtrls_TDBRadioGroup.html

Related

Dynamically create submenu

I have a TMainMenu with a menu item called mnuWindows. I wish to create submenu items dynamically. I thought this code might do it but it doesn't work:
var
mnuitm: TMenuItem;
mnuitm:=TMenuItem.Create(nil);
mnuitm.Text:='some text';
mnuWindows.AddObject(mnuitm);
When I click on mnuWIndows, nothing happens. Where am i going wrong?
EDIT:
The submenu was not displaying on clicking because each time I did so, the program had been freshly started and I didn't realize that under these circumstances, two clicks are necessary. The first click doesn't visibly do anything and the second click drops down the submenu. So, I concede the code snippet above works.
But I still have a difficulty. I need to create several submenu items so I tried the following loop inside the mnuWindows OnClick event handler:
for I := 0 to TabSet1.Tabs.Count - 1 do
begin
mnuitm := TMenuItem.Create(mnuWindows);
mnuitm.Text := TabSet1.Tabs[I].Text;
mnuitm.OnClick:=MenuItemClick;
if not mnuWindows.ContainsObject(mnuitm) then
mnuWindows.AddObject(mnuitm);
end;
The intent of the above code is that clicking the mnuWindows item displays a list of the tabs in a tabset. This code works up to a point. On first being clicked, it correctly lists the current tabs. But when I add a tab and click on mnuWindows again, the new tab is not shown in the list. The list is exactly as before. I wondered if the menu needed updating or refreshing somehow. I came across the following method
IFMXMenuService.UpdateMenuItem(IItemsContainer, TMenuItemChanges)
but it is poorly documented and I'm not sure how to use it or even whether it is relevant.
EDIT2:
I thought the two down votes on my post were harsh. I have searched the web extensively for an example of how to dynamically create submenus in Firemonkey and there is very little. I did find a solution from 2012, but syntax changes since then mean that it does not work in Tokyo 10.2.
Try something like this. As others have commented above, you need to provide an event that will happen when the menu item is clicked. Note also that my methods here require a bunch of parameters. It would have been cleaner if I had created a class and passed the details that way, but I wrote this a long time ago and now have many places in my code that use it in this form. Also, if I wrote this now, I would use a function to return the menu item created in case I needed to interact with it in particular cases (e.g., check it, assign a hot key, etc.)
procedure PopMenuAddItem(menu: TPopupMenu; sText: string; iID: integer;
clickEvent: TNotifyEvent; bEnabled: boolean = true);
var
NewMenuItem: TmenuItem;
begin
NewMenuItem := TmenuItem.create(menu);
with NewMenuItem do
begin
Caption := sText;
tag := iID;
Enabled := bEnabled;
OnClick := clickEvent;
end;
menu.Items.Add(NewMenuItem);
end;
procedure PopMenuAddSubItem(menuItem: TmenuItem; sText: string; iID: integer;
clickEvent: TNotifyEvent; bEnabled: boolean = true);
var
NewMenuItem: TmenuItem;
begin
NewMenuItem := TmenuItem.create(menuItem);
with NewMenuItem do begin
Caption := sText;
tag := iID;
Enabled := bEnabled;
OnClick := clickEvent;
end;
menuItem.Add(NewMenuItem);
end;
I have answered my own question.
As a reminder, what I wanted to do was to dynamically create a submenu under my top level menu item "Windows" (component name "mnuWindows"). In the submenu, I wished to list the names of the tabs in a tabset.
Attempting to create the submenu dynamically in the mnuWindows.OnClick event was a failure.
My eventual solution was to rebuild the submenu with the following method and to call this method immediately after creating a new tab, removing a tab, or renaming a tab:
procedure Form1.ReBuildWindowsMenu;
var
mnuitm: TMenuItem;
I: Integer;
begin
mnuWindows.Clear; // removes submenu items
for I := 0 to TabSet1.Tabs.Count - 1 do
begin
mnuitm := TMenuItem.Create(MainMenu1);
mnuitm.Caption:= TabSet1.Tabs[I].Text; // submenu item displays same text as associated tab
mnuitm.OnClick := MenuItemClick; // makes the associated tab active
mnuWindows.AddObject(mnuitm);
end;
end;
The OnClick handler contains the single statement
TabSet1.ActiveTabIndex:=(Sender as TMenuItem).Index;
This simple solution keeps my Windows list perfectly synched with the tabs in the tabset. I'm planning to use a similar approach to put a most recently used (MRU) file list into my File menu.

Shortcut triggers TAction on first created form instead of form with focus

I found (in Delphi 2010) that shortcuts always end up on first form (as owned by main form) that has that action, but not the currently focused form. My TMainFrm owns several TViewFrm. Each has a TActionManager with the same TActons.
I see some ways out, but wonder whats the best fix.. (and not a bad hack)
The forms are navigated using a tabset which calls their Hide() and Show(). I'd did not expect hidden forms to receive keypresses. Am i doing something wrong?
It seems that action shortcuts are always start at the main form, and using TCustomForm.IsShortCut() get distributed to owned forms. I see no logic there to respect hidden windows, should i override it and have it trigger the focused form first?
Disabling all TActions in TViewFrm.Hide() .. ?
Moving the TActionToolBar to TMainFrm but that is a pit of snakes and last resort.
I have found a workaround thats good enough for me; my main form now overrides TCustomForm.IsShortcut() and first checks visible windows from my list of editor tabs.
A list which i conveniently already have, so this might not work for everyone.
// Override TCustomForm and make it check the currently focused tab/window first.
function TFormMain.IsShortCut(var Message: TWMKey): Boolean;
function DispatchShortCut(const Owner: TComponent) : Boolean; // copied function unchanged
var
I: Integer;
Component: TComponent;
begin
Result := False;
{ Dispatch to all children }
for I := 0 to Owner.ComponentCount - 1 do
begin
Component := Owner.Components[I];
if Component is TCustomActionList then
begin
if TCustomActionList(Component).IsShortCut(Message) then
begin
Result := True;
Exit;
end
end
else
begin
Result := DispatchShortCut(Component);
if Result then
Break;
end
end;
end;
var
form : TForm;
begin
Result := False;
// Check my menu
Result := Result or (Menu <> nil) and (Menu.WindowHandle <> 0) and
Menu.IsShortCut(Message);
// Check currently focused form <------------------- (the fix)
for form in FEditorTabs do
if form.Visible then
begin
Result := DispatchShortCut(form);
if Result then Break;
end;
// ^ wont work using GetActiveWindow() because it always returns Self.
// Check all owned components/forms (the normal behaviour)
if not Result then
Result := inherited IsShortCut(Message);
end;
Another solution would be to change DispatchShortCut() to check for components being visible and/or enabled, but that might impact more than i'd like. I wonder whether the original code architects had a reason not to -- by design. Best would be have it called twice: first to give priority to visible+enabled components, and second call as fallback to normal behavior.

Parts of form not getting displayed when visible set to true

My program is in a post release state so please bear with me.
Scenario
My program is based on multiple layouts for different pages of different function for an office data management system (vehicle maintenance oriented). A major category of those functions is obviously data entry. I have used different styles to suite different audiences.
Getting to the point, one of the interfaces has excel styled grids and 3 buttons for Print/Save/Reset functions. I use FastReports for the form prints.
I am developing a custom class for the grid columns to make them accommodate a predefined list of controls instead of their cells on the fly but for now i just made the required controls children of the cells in code.
The page has 3 sections (layouts);
The top one is a kind of a purpose (Add/Modify/Add Partial) selector specific to all pages and may not be visible where not required.
The Middle one is a control for receiving receipt nos of the forms to be modified, their information embedded in others etc. Its mostly on every page but not all.
The Last one has the page's content which is the grid and the 3 buttons as mentioned earlier.
Code
This is a snippet of code for displaying one of the problematic pages. It is executed when all data processing has been done with and the server OKs the transition.
Legend
AState : State Machine State Variable; Signifies the current state of the page displayed.
AMode : State Machine State Enumerator; Signifies the mode of the application as a whole, e.g. Booking (Data entry) etc. I have skipped the code involving this as it gets skipped during the transition of AState for this problem to occur.
fMode : Same as above but its the main field of the form for the purpose.
UI_CA_Controls1 : The layout which contains the booking mode's purpose selector (Combo List Box).
EV_Mode : A variable for convenience; It stores the Item Index of the purpose selector.
UI_CA_Grid : The Layout contained in UI_CA_Content and itself contains UI_CA_FieldGrid (TGrid).
fEditColumn : The second column of the grid having TEdits.
fGridDataset : The grid associated TStringList.
//
procedure TUI.SetFormState ( AState : Byte; AMode : TMode = UIM_Unselected );
var
EV_Mode, I : Byte;
begin
// ---------------------------------------------------------------------------
fFormState := AState;
// The children of the grid cells
fCalEdit1.Parent := nil; // Calender Edits
fCalEdit2.Parent := nil;
fVehicleClass.Parent := nil; // Combo List Boxes
fEmployee1.Parent := nil;
fEmployee2.Parent := nil;
fEmployee3.Parent := nil;
fEmployee4.Parent := nil;
// ---------------------------------------------------------------------------
if AState = 0 then
begin
for I := 0 to 20 do
DPCM.fGridDataset.Strings [I] := ''; // The Grid Associated TStringList
UI_CA_ReceiptNo.ReadOnly := False;
UI_CA_ReceiptNo.Text := '';
end;
// ---------------------------------------------------------------------------
UI_CA_Content.BeginUpdate;
case fMode of
// Skipped unrelated modes
UIM_Booking :
begin
UI_CA_Controls1.Visible := True;
EV_Mode := UI_CA_EV_ModeSelect.ItemIndex;
// -----------------------------------------------------------------------
if fFormState = 0 then
begin
// Skipped handling of other EV_Mode values
if EV_Mode < 7 then
begin
UI_CA_ReceiptControl.Visible := True;
UI_CA_Content.Visible := False;
end;
end
// -----------------------------------------------------------------------
else if fFormState = 1 then // The problematic area
begin
if ( EV_Mode = 3 ) or ( EV_Mode = 4 ) then
begin
UI_CA_FieldGrid.RowCount := 6;
UI_CA_Grid.Height := 160;
fCalEdit1.Parent := fEditColumn.CellControlByRow ( 0 );
fCalEdit1.Date := Date;
fCalEdit2.Parent := nil;
fVehicleClass.Parent := fEditColumn.CellControlByRow ( 2 );
fVehicleClass.ItemIndex := 0;
end;
UI_CA_Content.Visible := True;
end;
end;
// -------------------------------------------------------------------------
end;
// ---------------------------------------------------------------------------
// Workaround 1
if UI_CA_Content.Visible then
begin
UI_CA_FieldGrid.UpdateColumns;
UI_CA_Content.EndUpdate;
UI_CA_FieldGrid.SetFocus;
UI_CA_C2_Reset.SetFocus;
UI_CA_C2_Print.SetFocus;
UI_CA_C2_Save.SetFocus;
UI_CA_FieldGrid.SetFocus;
end
else UI_CA_Content.EndUpdate;
end;
The Problem
The problem is that whenever the receipt section is displayed the content section doesn't get displayed on the spot. The behavior is such that when i hover the mouse where those children controls and the 3 buttons should be they get displayed but the grid gets displayed when I click on it only.
The problem has arose by itself with no change in UI code which has baffled me for 3 days now. I have only made optimizations to protocol and data handling on the network side (Separate Data Module).
Sequence
The user wants to modify already booked vehicle's data.
The user enters the booking receipt no. ( AState = 0, AMode = UIM_Booking )
The client query's the server and the server replies with the complete dataset if exists.
The client takes the data and copies it in the strings of the Grid associated TStringlist and the children fields.
The client doesn't display the grid with the data and the 3 buttons. ( AState = 1, AMode = UIM_Booking )
What have I tried till now
Used BeginUpdate/EndUpdate which made it worse with alignment artifacts.
Used SetFocus on the grid and the buttons which resulted in random display of some of them and sometimes complete display but not every time.
Used Application.ProcessMessages with no change rather the UI thread sometimes just got stuck in it never to return. Used it in a separate thread, calling it every second with no change.
Used a separate thread for the method with even more issues.
Back tracked and restored old working code with no change (made me really angry).
Update 1: I have tried to make the grid invisible and then visible at the end of the code. Now some cells of the grid get shown randomly.
Workaround 1
The grid and buttons can be shown when the SetFocus method is called for each of them.
The order of the calls is erratic for the buttons. Like I had to call reset first and then print and save's SetFocus method otherwise only one of them got displayed.
There is a split second realignment glitch which shows the controls resizing but i think that's ignorable.
Workaround 2
Do queued repainting rather than immediate. It doesn't have any caveats but the catch is after all that there is a delay in between every repaint.
Link: https://stackoverflow.com/a/8424750/1388291
So if you people have any suggestions, I'll be really grateful.

Delphi. How to Disable/Enable controls without triggering controls events

I have a DataSet (TZQuery), which has several boolean fields, that have TDBCheckBoxes assigned to them.
These CheckBoxes have "OnClick" events assigned to them and they are triggered whenever I change field values (which are assigned to checkboxes).
The problem is that I do not need these events triggerred, during many operations i do with the dataset.
I've tried calling DataSet.DisableControls, but then events are called right after i call DataSet.EnableControls.
So my question is - is there a way to disable triggering Data-aware controls events.
Edit (bigger picture):
If an exception happens while let's say saving data, i have to load the default values (or the values i've had before saving it). Now while loading that data, all these events (TDBCheckBoxes and other data-aware controls) are triggered, which do all sorts of operations which create lag and sometimes even unwanted changes of data, i'm looking for an universal solution of disabling them all for a short period of time.
Building on Guillem's post:
Turn off everything:
Traverse each component on the form with the for-loop, shown below, changing the properties to the desired value.
If you want to later revert back to the original property values, then you must save the original value (as OldEvent is used below.)
Edit: The code below shows the key concept being discussed. If components are being added or deleted at run-time, or if you'd like to use the absolutely least amount of memory, then use a dynamic array, and as Pieter suggests, store pointers to the components rather than indexing to them.
const
MAX_COMPONENTS_ON_PAGE = 100; // arbitrarily larger than what you'd expect. (Use a dynamic array if this worries you.
var
OldEvent: Array[0.. MAX_COMPONENTS_ON_PAGE - 1] of TNotifyEvent; // save original values here
i: Integer;
begin
for i := 0 to ComponentCount - 1 do
begin
if (Components[i] is TCheckBox) then
begin
OldEvent[i] := TCheckBox(Components[i]).OnClick; // remember old state
TCheckBox(Components[i]).OnClick := nil;
end
else if (Components[i] is TEdit) then
begin
OldEvent[i] := TEdit(Components[i]).OnClick; // remember old state
TEdit(Components[i]).OnClick := nil;
end;
end;
Revert to former values
for i := 0 to ComponentCount - 1 do
begin
if (Components[i] is TCheckBox) then
TCheckBox(Components[i]).OnClick := OldEvent[i]
else if (Components[i] is TEdit) then
TEdit(Components[i]).OnClick := OldEvent[i];
end;
There may be a way to fold all of the if-statements into one generic test that answers "Does this component have an OnClickEvent" -- but I don't know what it is.
Hopefully someone will constructively criticize my answer (rather than just down voting it.) But, hopefully what I've shown above will be workable.
One way to do this is following:
var
Event : TNotifyEvent;
begin
Event := myCheckbox.OnClick;
try
myCheckbox.OnClick := nil;
//your code here
finally
myCheckbox.OnClick := Event;
end;
end;
HTH
The internal design of the TCustomCheckBox is that it triggers the Click method every time the Checked property if changed. Be it by actually clicking it or setting it in code. And this is happening here when you call EnableControls because the control gets updated to display the value of the linked field in your dataset.
TButtonControl (which is what TCustomCheckBox inherits from) has the property ClicksDisabled. Use this instead of (or in addition to) the DisableControls/EnableControls call. Unfortunately it is protected and not made public by TCustomCheckBox but you can use a small hack to access it:
type
TButtonControlAccess = class(TButtonControl)
public
property ClicksDisabled;
end;
...
TButtonControlAccess(MyCheckBox1).ClicksDisabled := True;
// do some dataset stuff
TButtonControlAccess(MyCheckBox1).ClicksDisabled := False;
Of course you can put this into a method that checks all components and sets this property if the control inherits from TCustomCheckBox or some other criteria.

How do you add MRF list to a TRibbons dropdown button collection?

I can easly add a MRF list to A TRibbon recent items list but how do you add
the same list to a ribbon item set as a dropdownbutton? The dropdown
item is ActionBars[2].Items[1].
var
ARecentFilesList: TStringList;
ACI: TActionClientItem;
if FileExists( ARecentFilesFilename ) then
begin
ARecentFilesList.LoadFromFile( ARecentFilesFilename );
for i := 0 to ARecentFilesList.Count - 1 do
begin
// add filename to Ribbon Recent Items
Ribbon1.AddRecentItem( ARecentFilesList.Strings[ i ] );
//add the file name to dropdown button collection
//add MostRecentFiles to ActionBars[2].Items[1]
//ACI := TActionClientItem.Create( );
//ACI.Caption := ARecentFilesList.Strings[ i ];
end;
end;
Thanks,
Bill
As with much of the actionbar controls, it is not as intuitive as you'd like. The basic structure on the ribbon is like this:
Each ribbon has tabs.
Each tab has groups.
Each group has a series of controls.
Each control has a TActionClient associated with it.
Each TActionClient can have other TActionClient objects associated with it, either as ContextItems or Items. And the more you repeat this level, the deeper the nested menus.
So your strategy then, is to get your hands on the TActionClient that represents the button you'd like to add your items to. On my simple test app, I grabbed the first control on the first group - your logic may need to be more advanced.
var
ActionClient: TActionClient;
ChildItem: TActionClientItem;
begin
// Does the same as Ribbon1.AddRecentItem('C:\MyFile.txt');
ActionClient := RibbonGroup1.ActionControls[0].ActionClient;
ChildItem := ActionClient.Items.Add;
ChildItem.Action := ActionThanOpensAFile;
ChildItem.Caption := 'C:\MyFile.txt';
end;
Note that I assign the caption of my menu item after I assigned the action - this is because the action replaces the caption (and other properties too) of the client it is associated with.

Resources