I have a modal form that displays the progress of a lengthy operation. The operation is triggered when the Form's OnActivate Event is triggered.
procedure TMyForm.FormActivate(Sender:TObject);
begin
Start;
end;
The form has a cancel button with the ModalResult property set to mrCancel and the OnClick handler sets a flag that causes the operation to end.
procedure TMyForm.CancelButtonClick(Sender: TObject);
begin
FCancel := True;
end;
When I click the cancel button it stops the operation as expected but it fails to close the form. I suspect this is because the OnActivate handler is blocking the form from closing. A second click of the button does close the form. I've tried calling Close and sending a wm_close message but nothing seems to work. Does anyone have any suggestions to get the form to respond on the first click? Perhaps another event I can use instead of OnActivate?
I know moving the operation to a thread will be suggested. That's not a possibility at this point due to a large amount of poorly written legacy code.
In the following I am going to assume that Messages are probably processed by calls to Application.ProcessMessages inside the task.
Let's take a look at the code ShowModal that is pertinent:
SendMessage(Handle, CM_ACTIVATE, 0, 0);
ModalResult := 0;
repeat
Application.HandleMessage;
if Application.Terminated then ModalResult := mrCancel else
if ModalResult <> 0 then CloseModal;
until ModalResult <> 0;
The SendMessage call results in the OnActivate event firing. In your code that then starts the task and does not return until the task is complete. By which point you've assigned to ModalResult. But wait, the next line in the excerpt above sets ModalResult back to 0 and so your setting is lost. And so the modal message loop is entered and you need to assign to ModalResult again to get the form to close.
The bottom line here is that you cannot perform the task before entering the the modal message loop. One solution is to put the long running task in a separate thread. If you cannot bring yourself to do that you can post a message to the form in the OnActivate event handler. Respond to the event by starting the task. By this point the modal message loop will be running and setting ModalResult will close the form.
Related
I have a main form with :
a table of data (list of pay)
a button calling a subform (assistant form to create easily a bound of pay for a complete year)
After closing the subform, I have to update the main form data (table view) only if the user click Generate (to avoid data update), ie only if the user generate new payroll.
Is there a way to know if the user cancel or not action in the subform?
Ok, so you NOT really talking about a form + sub form.
There are two ways to do this:
First, you can open the 2nd child form as acDialog (not modal - very big difference).
So, your main form code can do this:
Dim strF As String
strF = "frmChildForm"
DoCmd.OpenForm strF, , , , , acDialog
' CODE WILL HALT and wait for above form to be closed
' code here continues
' can even update data on this main form from that child form.
If CurrentProject.AllForms(strF).IsLoaded Then
' user hit ok - or your save button
Me.LastChildUpdate = Forms(strF)!LastUpdate
DoCmd.Close acForm, strF
Else
' code goes here - user hit cancel button, or X to close - we assume cancel
End If
Now in that other child form, you DO NOT CLOSE when your "done/save/ok" button runs. It simply sets the form's visible property to false
eg:
Me.Visible = False
So, now our calling code will WAIT until the user is done. If the form is closed, then we assume a user "canceled" that child form.
so, the user can hit X to close, OR the user might hit your close/cancel button. Your close/cancel button thus can close the form. eg this code:
docmd.Close acForm,me.name
So, now you know if the user canceled (closed) the form. Your "ok/save" button in that form ONLY sets the forms visible=False. That makes the calling code continue, and then you check if the form is open or not - and thus your code can take appropriate action.
However, you don't even have to use the "halt" code trick above.
What you can do in that child form you launch?
You can in the on-open event, or even on-load? You pick up the name of the calling form.
So, you define this in your code:
Option Compare Database
Option Explicit
Dim frmPrevious As Form
Private Sub Form_Load()
Set frmPrevious = Screen.ActiveForm
End Sub
So, now your save button on this form?
It can call/use/update ANYTHING from the main previous form caller.
So, your save button can now;
frmPrevious.Refresh
Or
frmPrevous!LastName = me.LastName
So, you can do "anything" by replacing "me" with frmPrevous, and any operation (such as your custom save button on that 2nd form) can update, call code, refresh, or do anything it wants to operation on the first main calling form.
And you can even call subs/functions in that main form.
So, you could have a public sub in the main form, and in the 2nd form, on the close event do this:
Call frmPrevous.MySubName ' tell + call code in main form.
docmd.close acForm,me.name
So the above outlines two approaches:
First idea:
You open 2nd form as "acdialog" and the code waits. You either then close the 2nd form (considered a cancel), or you save visible=false and the calling code CONTINUES.
Second idea:
You pick up the previous form in the on-load event. Now your save button can directly update values, controls etc. in "frmPrevious", and then once done, you close the 2nd form in that "ok button" routine.
So, quite much is that in that 2nd form, you can directly update controls and do things to the first calling form with the above 2nd idea.
Or, as noted, you can HALT the calling code that opens the form, wait for user response, and then use the visible=false trick to allow the calling code to continue. Once again, since the 2nd form is still open, you are free to get/grab/take values from that STILL open form, and then when done you thus as a last stop close that 2nd form. So in effect, your 2nd form does NOT close when the user is done - but only visible = false. As noted, this trick only works if you open/call the 2nd form as "acDialog". This is a great idea for say yes/no dialog prompts etc., since then you can upon user selection have the calling code continue, and examine the values in that 2nd form before closing it.
And the last but not least method:
In that 2nd form the code behind your "ok" button can simple update the first form
forms!MyFirstForm!LastName = me.LastName
docmd.close acForm,me.name
However, above has the form(s) name hard coded, and frmPrevious trick is thus somewhat better since then you don't hard code the calling forms name, and thus many forms can call + use that same 2nd form.
I'm new to working with Protractor and I was wondering in what circumstances would you need to use ExpectedConditions (example below) when using Protractor. I thought that Protractor automatically determine when an AngularJS page is fully loaded.
let EC = ExpectedConditions;
let condition = EC.presenceOf(element(by.id("something")));
browser.wait(condition, 10000);
Thanks, Eric
From my experience working with Protractor, the use of ExpectedConditions depends on the behavior of the page you are automating. It's mostly used due to failing if the condition doesn't comply in the specified time.
These conditions will also return a promise that you can handle to your liking.
I'll give you a few scenarios so you can understand where to use them.
alertIsPresent(): This condition will wait till an alert appears.
e.g.: After clicking a button, there will be an alert appearance; however, there's an API call which makes the pop-up take longer and also a small animation, so we want to wait a few seconds and no more than that.
// will click on a button
element(by.id('button')).click();
// will wait for the condition
let EC = ExpectedConditions;
browser.wait(EC.alertIsPresent(), 5000);
The following code will wait for 5 seconds after clicking the button, to see if the alert is present, else it will throw an error.
invisibilityOf(): This condition will wait till the specified element is not being displayed.
e.g.: There's a loader that appears for every single action that is triggered in the page. For this we want to wait till this loader disappears so we can continue with the automation process. By business requirements, this loader shouldn't take longer than 10 seconds.
This loader locks the whole page, so other elements are not interactable while it is up.
// trigger random action on page so loader appears
element(by.id('button2')).click();
// will wait for the condition
let EC = ExpectedConditions;
browser.wait(EC.invisibilityOf(element(by.id('loader'))), 10000);
After clicking a button, we will give a 10 seconds grace for the loader to disappear, else the condition will throw an error.
elementToBeClickable(): This condition will wait till the specified element can be clicked.
e.g.: The button to the login form is disabled by default, so it can't be clicked unless we complete the username and password textfields. The button being enabled after filling the textfields has a fast animation, either way we want to give it 1 second to complete and check if we are able to click it.
// complete both textfields required for the button to be enabled
element(by.id('username')).sendKeys('User1234');
element(by.id('password')).sendKeys('Protractor');
// will wait for the condition and then will click the button
let EC = ExpectedConditions;
browser.wait(EC.elementToBeClickable(element(by.id('loginButton'))), 1000);
element(by.id('loginButton')).click();
After completing both textfields, the condition will wait for 1 second for the element to be clickable, if it is, it will procede with the next line and click it. On the other hand, if it doesn't, an error will be thrown.
presenceOf(): In this case, the condition will check if the element is present in the DOM (Document Object Model) but it won't check if the element is visible or not.
e.g.: On a page with a radio button group containing 3 flavors: chocolate, vanilla and strawberry. Depending on which you choose, you will be shown different questions. Developers mentioned that the questions are in the page at all moments, but are hidden due to which radio button is selected at the moment. In this situation, we just want to check that all questions exist in the DOM, whether or not they will be shown by a radio button being selected.
// check all questions directly, without selecting any radio buttons
let EC = ExpectedConditions;
browser.wait(EC.presenceOf(element(by.id('question-1'))), 1000);
browser.wait(EC.presenceOf(element(by.id('question-2'))), 1000);
browser.wait(EC.presenceOf(element(by.id('question-3'))), 1000);
The time is pretty irrelevant here; nonetheless, using this conditions we will be able to check that the questions, even though hidden, exist in the DOM. If one is missing, an error will cut the test immediately.
These were a few examples I've had to deal with in the past. The use of the conditions is situational and mostly they are useful when you want to use the existing conditions since they save you the time of building them yourself.
PD: More information can be found in the Protractor API.
I'm working on Firemonkey application with a main form that contains a lot of controls on it. I want to create some more controls and send them to back using SendToBack. For some reason this does not work as expected. Controls are not being sent to full back, they stop short of 1 control.
Here's a sample setup:
Create a new TForm.
Place 3 Buttons on it, overlapping each other (Button1, Button2, Button3).
In the runtime, call Button3.SendToBack - button goes back, but only by 1 position. Button1 still remains the backmost.
Inspecting TForm source code reveals that SendToBack calls SendChildToBack, which determines backmost location as:
function TCommonCustomForm.GetBackIndex: Integer;
begin
Result := 1;
end;
should not it be 0?
Questions:
Why does SendToBack send controls to "last but one" position instead of backmost? Is there a special reason for GetBackIndex returning 1?
How do I send controls to back? Given that my form has a lot of controls and sending everything but needed controls to front with BringToFront would be undesireable.
Since I'm creating own controls,
ctrl := TSomeControl.Create(aForm);
ctrl.Parent := aForm;
ctrl.SendToBack;
can be replaced with:
ctrl := TSomeControl.Create(aForm);
aForm.InsertObject(0 {desired index}, ctrl);
is it possible to have a specific element on top of the list?
backgoround:
I have an event app, where new events are constantly added. The events are orderd by date. If an event passes, it is still on top of the page. Now that some time passed, the first elements are all over, so the first paged doesn't display any necassery data anymore. To get to events which will happen, the user has to click on the pager button.
I want this events to be displayed on top, and if the user want's to see previous events, he has to click the back button of the pager.
What have I tried:
cellTable.setVisibleRange(overElements, (overElements + totalEementCount));
this mixes up the whole pager logic, and the user can't switch from pager to page anymore.
SingleSelectionModel<JSEvents> selectionModel = new SingleSelectionModel<JSEvents>();
...
selectionModel.setSelected(nextEvent, true);
this selects the right element, and moves the pager to it, but the other elements are still visible, so if I have a few "over" events and in the worst case, only have a single "comming" event in the table. Also it is the usability is bad, since the user has to look which event is next and doesn't have it on top of the page...
If you're using a ListDataProvider, you can add the new event to the front of the list. Everything depending on that dataprovider will be notified and should redraw.
If you're using a different kind of data provider, you may have to call CellTable.redraw yourself, but I think AbstractDataProvider takes care of that for you.
As a first investigative step, you might just try calling cellTable.redraw() after your new event has been logged to see if that gets you anywhere.
found a solution:
the celltable has a method setVisibleRange. This method is used in the examples when a pager involved:
int start = display.getVisibleRange().getStart();
int end = start + display.getVisibleRange().getLength();
end = end >= data.size() ? data.size() : end;
List<JSEvents> sub = data.subList(start, end);
updateRowData(start, sub);
the updateRowData tells the celltable, which data is displayed and on which position this data starts.
Now if we want to have a speciffic element on top of the page we can use the same function. This is how I end up using it:
int end = start + PAGESIZE;
end = (end >= data.size()) ? data.size() : end;
List<JSEvents> sub = data.subList(start, end);
dataProvider.updateRowData(start, sub);
cellTable.setVisibleRange(start, sub.size());
I just discovered a weird bug in my Delphi Application. When I run the application, the Taskbar shows the icon, but I have to press it in order for the form to show. By setting a breakpoint in the OnShow event, I found that the OnShow event is being fired when I click on the Icon on the Taskbar.
Also, when I use Hide;, the form hides, but the taskbar icon does not disappear, however when I click on it, the form does not show again (which is the point, but the taskbar icon is not supposed to be there when hidden).
Here is my project file source, in case that could have something to do with it:
var
PreviousHandle : THandle;
begin
PreviousHandle := FindWindow('TfrmMain',APP_CAPTION);
if PreviousHandle = 0 then
Begin
Application.Initialize;
// So my Log and Mainform can overlap each other
Application.MainFormOnTaskbar := False;
Application.Title := 'MyApp';
Application.CreateForm(TfrmMain, frmMain);
Application.CreateForm(TfrmLog, frmLog);
Application.Run;
End else
begin
SetForegroundWindow(PreviousHandle);
end;
end.
I also tried disabling my Skinning Engine, which did not help either.
The WindowState was set to wsMinimized, and the editor does that randomly somehow. I just had to set it to wsNormal and it was all good. Both problems solved.
frm_login.hide;
Application.MainFormOnTaskbar := false;
ShowWindow(Application.Handle, SW_SHOW);
frm_login is my primary form.
frm_menu is not creating. it will creating by code after.