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.
Related
I need to make report, that has summary after Details had printed, not after every each Detail.
I only know the Page Footer, but it's at page bottom not after the Detail Band.
Is there a QRBand that could go after Detail Band?
Or can you make PageFooter height resize on every page?
Summary Bands print only at the end of the report immediately after the last 'detail' (unless the AlignToBottom property is set to true).
You should add a TQRBand to the report and set the BandType property to rbSummary.
EDIT
If you need to show intermediate results on every page you could add the FooterBand and the SummaryBand.
Summary Bands prints only at the end of the report (last page) and you can use the BeforePrint event of the summary to disable the footer band.
EDIT2
You can also try with a QRGroupBand and a QRFooterBand.
In the GroupBand:
use the Expression property for separating one group of products from another (the band could also be empty)
set appropriately the FooterBand property. This is where you link the header band to the correct footer band and encapsulate your group inside. Place the header band first and then go back and place the footer band by dropping a QRBand component and immediately changing the Type from rbTitle to rbGroupFooter. Once you have done this, you can go back to the QRGroup header and select the right footer band to use.
The FooterBand is where you are printing summary totals for each product.
I was able to solve the problem with the help of quickreport events and datasource events.
In a test project, it is enough to put a tqrlabel in the pagefooter, the rest is written below.
type
TForm1 = class(TForm)
...
private
firstValue: Integer;
public
counter: Integer;
end;
var
Form1: TForm1;
PageSummery: Integer;
procedure TForm1.Button2Click(Sender: TObject);
begin
Application.CreateForm(TForm2, Form2);
adoquery.First;
counter := 0;
firstValue := adoquery.FieldByName('id').AsInteger;
PageSummery := 0;
Form2.QuickRep1.Preview;
Form2.Free;
end;
procedure TForm1.adoqueryAfterScroll(DataSet: TDataSet);
begin
if counter <= 1 then
PageSummery := firstValue + adoquery.FieldByName('id').AsInteger
else
begin
PageSummery := PageSummery + adoquery.FieldByName('id').AsInteger;
form2.QRLabel2.Caption := IntToStr(PageSummery - Form1.adoquery.FieldByName('id').AsInteger);
end;
counter := counter + 1;
end;
Pay attention to the location of the variables in the first form.
The first button is used to fill the query data.
After each increase, the data is placed in the qrlabel so that all the data is saved when the event is triggered.
The final point is in the quickreport event, which occurs before filling the print rows when creating a new page. In this event, a number of variables must be set to zero.
procedure TForm2.QuickRep1StartPage(Sender: TCustomQuickRep);
begin
if Form1.counter = 0 then
PageSummery := 0
else
PageSummery := Form1.adoquery.FieldByName('id').AsInteger;
end;
Add a GroupHeader and a GroupFooter to the report. Make the Expression on the GroupHeader = PAGENUMBER, and set Height = 0; in it's BeforePrint event handler, so that it never shows. Set the GroupHeader's FooterBand property to the GroupFooter band. Then put your page totals or whatever on the GroupFooter. This will print after the detail on every page, and the Summary will print after that on the last page!
Currently in my application the user is able to create windows (no controls on the as of yet) which are docked to a pagecontrol. Each editor has a set of toolboxes associated with them. This set is reset upon editor construction and modified as toolboxes are closed and opened. When the page control changes tab the state of the toolboxes is saved, they are closed and then the toolboxes for the new tab are restored.
My application is crashing (seemingly randomly at first) when you change tab. I believe it is when you change tab very fast (the speed of a double click) the processing of the toolboxes in the PageControl.OnChange event doesnt complete before the next PageControl.OnChange event starts and I end up with two events running in 'parallel'. My questions are:
1) Does this sound like a plausible theory? I am aware there is a GUI processing thread does delphi work by dispatching new threads to deal with events?
2) How would you suggest going about fixing this? Hold the messages until the first method finishes (it is a very quick method so there shouldnt be any use lag really)?
Here is some code incase anyone would like to look at the problem from another angle.
This is the code for the onchange event.
procedure TMainForm.PageDockingAreaChange(Sender: TObject);
var
count : integer;
// Shortcut variables
FormShortcut : TForm;
WelcomeShortcut : TWelcomePageForm;
BaseEditorShortcut : TBaseEditor;
begin
// Set nil values
FormShortcut := nil;
WelcomeShortcut := nil;
BaseEditorShortcut := nil;
// Create shortcut value
FormShortcut := Self.GetActiveForm;
if (FormShortcut is TWelcomePageForm) then WelcomeShortcut := TWelcomePageForm(FormShortcut);
if (FormShortcut is TBaseEditor) then BaseEditorShortcut := TBaseEditor(FormShortcut);
// Hide all tabs when welcome page is visible
if (WelcomeShortcut <> nil) then
begin
// Clear any existing toolboxes
Self.ToolboxClearAll;
end;
{endif}
// Try to execute toolbox setup
try
// Load toolbox state
Self.ToolboxSetup(BaseEditorShortcut);
except
// Display fatal error to the user
ShowMessage('Fatal Error : Unable to load toolboxes.');
end;
// Update last active editor
if (BaseEditorShortcut = nil) then
Self.LastActiveEditor := nil
else
Self.LastActiveEditor := BaseEditorShortcut;
{endif}
end;
This method closes all toolboxes and frees them in turn.
procedure TMainForm.ToolboxClearAll;
var
count : integer;
begin
// Save toolbox state if needed
if Assigned(Self.LastActiveEditor) then
Self.LastActiveEditor.SaveToolboxState;
// Close and free all toolboxes
for count := 0 to length(Self.ActiveToolboxes)-1 do
Self.ToolboxCloseAndFree(Self.ActiveToolboxes[count]);
// Reset array size (should already be done though)
SetLength(Self.ActiveToolboxes, 0);
end;
I am using Delphi 7 (Yes, I hear the guffaws). I have a tabbed notebook that I want certain controls to appear only in a sequence where the prior control is finished correctly. For each page in the notebook, I have a named sheet. And for the controls on that sheet, I use the tag property to determine whether they are visible at each step. Some steps result in one new control showing, some steps have as many as five controls popping into view. I thought to simply iterate through the controls on any tab sheet that's in view and turn off anything with a tag greater than the current step value. On the page in question, there appear to be 23 controls in all, some labels that are always in view, some edit fields that pop up into view and some arrow-shaped buttons for advancing when a newly popped up field gets changed. Seemed simple enough, except I kept generating Index out of range errors. The sequence would shut down with out a detailed error message for EurekaLog, not anything opened up that should have been. I finally 'resolved' the issue by plugging in a check for the NAME of the control I knew was last in the list and quitting the loop at that point. I also added the extra test for Kounter.tag <> zero to avoid leaving the Submit and Cancel buttons on in some routes. Ideas why the Kounter just kept on past 23?
procedure TFrmMain.VizToggleWTP;
var
kounter: Integer;
kontrol: TControl;
Kontrolz: Integer;
begin
Kontrolz := sheetPrintouts.ControlCount;
for Kounter := 1 to Kontrolz
do begin
// To avoid index error, check for the Cancel Button and exit at that point
if sheetPrintouts.Controls[kounter].Name = 'BtnCancelwtp'
then Break;
if (sheetPrintouts.Controls[Kounter]) is TNXEdit
then begin
kontrol := TNXEdit(sheetPrintouts.Controls[Kounter]);
kontrol.visible := (kontrol.Tag <= wtpStep);
end;
if (sheetPrintouts.Controls[Kounter]) is TJvShapedButton
then begin
kontrol := TJvShapedButton(sheetPrintouts.Controls[Kounter]);
kontrol.visible := ((kontrol.Tag <= wtpStep) and (kontrol.Tag <> 0));
end;
end;
end;
You need to replace
for Kounter := 1 to Kontrolz do
with
for Kounter := 0 to Kontrolz-1 do
since the Controls array is zero-based.
For instance, if there are three controls, they are indexed 0, 1, 2 and not 1, 2, 3.
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.
Checkbox handling in version 5.0.0 of VirtualTrees.pas appears broken when toThemeAware is enabled. Nodes that are csUncheckedNormal are drawn as checked + hot.
To correctly paint an unchecked, themed checkbox using DrawElement, the Details record must be : Element = teButton, Part = 3, and State = 5. However, VirtualTrees.pas ends up calling DrawElement with State = 1 when a node is set to csUncheckedNormal.
There seems to be a good deal of indirection and extra constants declared in VirtualTrees, so I'm not sure how best to fix this. Ideas welcomed...
(Even the minimal code to get a TVirtualStringTree on screen and filled with some data is a bit lengthy to post here. Aside from the basics, all that's needed to reproduce this is to enable toCheckSupport in TreeOptions.MiscOptions, and set Node.CheckType := ctTriStateCheckBox in the InitNode callback.)
Well, since I think the VirtualTreeView does not count with the VCL styles when porting to delphi XE2, this might light up to solve your problem. You have to get element details before you draw it, otherwise you'll get something like this (it's the simulation of how the VirtualTreeView paint check box states). Notice the different order and the artifacts; it's the result of the same code once with VCL styles disabled, second time enabled:
Quite strange I know, but I can't answer you why is this happening. I can just tell you that you should call the TThemeServices.GetElementDetails or optionally calculate the state index by your own to get the element rendering to work properly. You may try to use the following fix:
procedure TBaseVirtualTree.PaintCheckImage(Canvas: TCanvas;
const ImageInfo: TVTImageInfo; Selected: Boolean);
var
// add a new variable for calculating TThemedButton state from the input
// ImageInfo.Index; I hope ImageInfo.Index has the proper state order
State: Integer;
begin
...
case Index of
0..8: // radio buttons
begin
// get the low index of the first radio button state and increment it by
// the ImageInfo.Index and get details of the button element
State := Ord(TThemedButton(tbRadioButtonUncheckedNormal)) + Index - 1;
Details := StyleServices.GetElementDetails(TThemedButton(State));
end;
9..20: // check boxes
begin
// get the low index of the first check box state and increment it by
// the ImageInfo.Index and get details of the button element
State := Ord(TThemedButton(tbCheckBoxUncheckedNormal)) + Index - 9;
Details := StyleServices.GetElementDetails(TThemedButton(State));
end;
21..24: // buttons
begin
// get the low index of the first push button state and increment it by
// the ImageInfo.Index and get details of the button element
State := Ord(TThemedButton(tbPushButtonNormal)) + Index - 21;
Details := StyleServices.GetElementDetails(TThemedButton(State));
end;
else
Details.Part := 0;
Details.State := 0;
end;
...
end;
I've tested this for all check types and it works for me.