Delphi XE5 Firemonkey TTabItem and TEdit repaint coordination - delphi

I created a TTabControl with two TTabItems. On each TTabItem there is one (or more) TImageViewers and several TEdits. When I click on the TImageViewer, a modal screen pops up, I set some values, and I want to report those values to the user through the TEdits. So on returning from the Modal screen,
I execute
editn.text := whateveritis;
I then say
editn.repaint;
Nothing happens. I say TTabItem.repaint. Nothing happens. I click the other TTabIem and then come back to the first TabItem and, voila, the Edit control contains the right information. So my editn.text := whateveritis must be working (that's the only write to the TEdit), but I can't get the blinkin' control to show the result without going off-tab. How do I get it to redisplay as soon as I change the content? Do I need to write an OnChange routine that is one line, self.repaint? Seems ugly, and I'd hope there's a more global approach. Suggestions?
In light of initial comments, let me give more details. Setup: In main screen, drop in tabcontrol, and in tabcontrol drop in 2 tabitems. In tabitem1, drop in a timageviewer and 4 tedits (plus other stuff, probably irrelevant). Image gets dropped into the imageviewer (and displays correctly). The onclick event activates the following (ellipsis cuts out irrelevant code):
procedure TSCKMain.ImageViewer1Click(Sender: TObject);
var
lochold, scrollhold : tpoint;
backfromwavform : tmodalresult;
begin
lochold.X := mouseloccallback.x;
lochold.Y := mouseloccallback.y;
scrollhold.X := round(imageviewer1.ViewportPosition.X);
scrollhold.Y := round(imageviewer1.ViewportPosition.Y);
…
repeat backfromwavform := Wavform.Showmodal until backfromwavform<>mrnone;
case backfromwavform of
mrOK : begin {blue end}
Specsingle.BlueEnd.X := lochold.X;
Specsingle.BlueEnd.y := lochold.y;
edit13.Text := inttostr(Specsingle.BlueEnd.X);
Edit14.Text := inttostr(Specsingle.BlueEnd.y);
PublicWindowFlag := 'RePlot';
end;
mrContinue : begin {red end}
Specsingle.RedEnd.X := lochold.X;
Specsingle.RedEnd.y := lochold.y;
edit15.Text := inttostr(Specsingle.RedEnd.X);
Edit16.Text := inttostr(Specsingle.RedEnd.y);
PublicWindowFlag := 'RePlot';
end;
…
end;
if PublicWindowFlag<>'Cancel' then
if PublicWindowFlag='RePlot' then
begin
specsingle.RegenImage;
end
else
showmessage('Single image semaphore error. Debug.');
Imageviewer1.scrollto(scrollhold.X-Imageviewer1.viewportposition.X, scrollhold.y-Imageviewer1.ViewportPosition.Y);
end;
The modal screen sends back either mrContinue or mrOK correctly, and the appropriate case executes. However, edit13, edit14, edit15, and edit16 do not change their content. However, if I click over to Tabitem2 and back to Tabitem1, they DO repaint and DO contain the correct characters, which they could only have gotten from the above code. Conclusion: somehow, the edits aren’t repainting independently, but it’s not clear why.

Got it. The canvas for the imageviewer, the canvas for the bitmap in the imageviewer, and the canvas for the parent form are all in play. One must be sure that the canvas is the right one. As soon as scenes got untangled between imageviewer.bitmap and everything else, the edits worked as one would expect.

Related

Displaying a disabled modal form

I'm trying to disable a TForm's descendant and showing it as a modal form.
procedure TForm1.Button1Click(Sender: TObject);
var
Frm : TMyForm;
begin
Frm := TMyForm.Create(nil);
try
Frm.Enabled := False;
Frm.ShowModal();
finally
Frm.Free;
end;
end;
At runtime, it raises the following error message:
Cannot make a visible window modal.
The OP wants to display a disabled form modally when the form should be displayed for read-only purposes.
Disabling the form is the wrong thing to do.
How do you display the information? If you are using TEdit, TMemo, or TRichEdit controls, you should simply set them to read only. Otherwise, if you have some combinations of various controls like radio buttons, you should disable each and every such control, not the form itself. I mean, surely you still want the Cancel button to be enabled?
In addition, disabling the form instead of the actual controls will make the controls look enabled, which is very confusing! That's an important point.
So what you need to do is to display the form normally (not disabled!) and then set its controls to their appropriate states when the dialog is shown.
Just to emphasise my point about disabling the form vs its controls, consider this dialog box:
If I do
procedure TCustomViewFrm.FormShow(Sender: TObject);
begin
Enabled := False;
end;
then it looks like this when shown:
As you can see, every control looks very enabled indeed, but no control responds to mouse or keyboard input. This is very confusing and a horribly bad UX.
In fact, you cannot even close the dialog box using its title-bar Close button or Alt+F4. You cannot close it using its system menu, either. In fact, you cannot close it at all, because to close a window, it must respond to user input, and a disabled window doesn't do that. (You cannot move the window, either.)
Instead, if we disable all controls (except the Cancel button),
procedure DisableControl(AControl: TWinControl);
begin
for var i := 0 to AControl.ControlCount - 1 do
begin
if
(AControl.Controls[i] is TCustomButton)
and
(TCustomButton(AControl.Controls[i]).ModalResult = mrCancel)
then
Continue;
if AControl.Controls[i] is TWinControl then
DisableControl(TWinControl(AControl.Controls[i]));
AControl.Controls[i].Enabled := False;
end;
end;
procedure TCustomViewFrm.FormShow(Sender: TObject);
begin
DisableControl(Self);
end;
you get this nice UI:
Not only is it very clear that all controls are disabled, the user can also close the dialog box without killing your application using the Task Manager.

Disabling the form still allow childs controls to receive input

I'm having a lot of headache in the last days with delphi, what im trying to do is a lot simple, block the interface at somepoint and enable after some other point.
But as simply as it sound i couldn't figure out why somethings are allowed by design, so to clarify:
1) create a project
2) in the form put a edit and a button, tab order of the edit must be first
3) configure the OnExit event of the edit and write:
Enabled := False;
4) configure the OnClick event of the button and write:
ShowMessage('this is right?');
basically this is it, now compile, the focus it will be at the edit, press tab and the form will be disabled as we demanded, so accordingly to the tab order the next control to gain focus is the button (but we disabled the form), now press space and the message should come up.
so the question is: is this right? whats the logical explanation to this behaviour?
thx in advance.
Both TButton and TEdit are TWinControl descendents - this means that they are windowed controls. When they are created they are allocated their own HWND and the operating system posts messages to them directly when they have focus. Disabling their containing form prevents the main form from receiving input messages or from receiving focus but it does not disable any other windowed control if it already has input focus.
If these controls do not have input focus, it is responsibility of the containing form to transfer input focus to them when user input (click, tab key, etc) dictates. If the form is disabled and these controls are not focused then the form will not receive the input messages that would allow it to transfer focus. If focus is transferred to a windowed control, however, then all user input goes directly to that control, even if their parent control's window is disabled - they are in fact their own separate windows.
I'm not sure the behaviour you have observed is a bug - it is perhaps not expected, but it is standard behaviour. There is generally no expectation that disabling one window will also disable others within the same application.
The problem is that there are two separate hierarchies in play. On the VCL level, the Button is a child control and has a parent (the form). On the OS level, however, both are separate windows and the (component level) parent/child relationship is not known to the OS. This would be a similar situation :
procedure TForm1.Button1Click(Sender: TObject);
var
form2 : TForm1;
begin
self.Enabled := false;
form2 := TForm1.Create(self);
try
form2.ShowModal;
finally
form2.Free;
end;
end;
Would you really expect form2 to be disabled when it was shown, simply because its TComponent owner is Form1? Surely not. Windowed controls are much the same.
Windows themselves can also have a parent/child relationship, but this is separate from component ownership (VCL parent/child) and does not necessarily behave in the same way. From MSDN:
The system passes a child window's input messages directly to the
child window; the messages are not passed through the parent window.
The only exception is if the child window has been disabled by the
EnableWindow function. In this case, the system passes any input
messages that would have gone to the child window to the parent window
instead. This permits the parent window to examine the input messages
and enable the child window, if necessary.
Emphasis mine - if you disable a child window then its messages will be routed to the parent for an opportunity to inspect and act upon them. The reverse is not true - a disabled parent will not prevent a child from receiving messages.
A rather tedious workaround could be to make your own set of TWinControls that behave like this :
TSafeButton = class(TButton)
protected
procedure WndProc(var Msg : TMessage); override;
end;
{...}
procedure TSafeButton.WndProc(var Msg : TMessage);
function ParentForm(AControl : TWinControl) : TWinControl;
begin
if Assigned(AControl) and (AControl is TForm) then
result := AControl
else
if Assigned(AControl.Parent) then
result := ParentForm(AControl.Parent)
else result := nil;
end;
begin
if Assigned(ParentForm(self)) and (not ParentForm(self).Enabled) then
Msg.Result := 0
else
inherited;
end;
This walks up the VCL parent tree until it finds a form - if it does and the form is disabled then it rejects input to the windowed control as well. Messy, and probably could be more selective (maybe some messages should not be ignored...) but it would be the start of something that could work.
Digging further, this does seem to be at odds with the documentation :
Only one window at a time can receive keyboard input; that window is
said to have the keyboard focus. If an application uses the
EnableWindow function to disable a keyboard-focus window, the window
loses the keyboard focus in addition to being disabled. EnableWindow
then sets the keyboard focus to NULL, meaning no window has the focus.
If a child window, or other descendant window, has the keyboard focus,
the descendant window loses the focus when the parent window is
disabled. For more information, see Keyboard Input.
This does not seem to happen, even explicitly setting the button's window to be a child with :
oldParent := WinAPI.Windows.SetParent(Button1.Handle, Form1.Handle);
// here, in fact, oldParent = Form1.Handle, so parent/child HWND
// relationship is correct by default.
A bit more (for repro) - same scenario Edit tabs focus to button, exit handler enables TTimer. Here the form is disabled, but the button retains focus even though this seems to confirm that Form1's HWND is indeed the parent window of the button and it should lose focus.
procedure TForm1.Timer1Timer(Sender: TObject);
var
h1, h2, h3 : cardinal;
begin
h1 := GetFocus; // h1 = Button1.Handle
h2 := GetParent(h1); // h2 = Form1.Handle
self.Enabled := false;
h3 := GetFocus; // h3 = Button1.Handle
end;
In the case where we move the button into a panel, everything seems to work (mostly) as expected. The panel is disabled and the button loses focus, but focus then moves to the parent form (WinAPI suggests it should be NULL).
procedure TForm1.Timer1Timer(Sender: TObject);
var
h1, h2, h3 : cardinal;
begin
h1 := GetFocus; // h1 = Button1.Handle
h2 := GetParent(h1); // h2 = Panel1.Handle
Panel1.Enabled := false;
h3 := GetFocus; // h3 = Form1.Handle
end;
Part of the problem seems to be here - it looks like the top form itself is taking responsibility for defocusing controls. This works except when the form itself is the one being disabled :
procedure TWinControl.CMEnabledChanged(var Message: TMessage);
begin
if not Enabled and (Parent <> nil) then RemoveFocus(False);
// ^^ False if form itself is being disabled!
if HandleAllocated and not (csDesigning in ComponentState) then
EnableWindow(WindowHandle, Enabled);
end;
procedure TWinControl.RemoveFocus(Removing: Boolean);
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.DefocusControl(Self, Removing);
end
Where
procedure TCustomForm.DefocusControl(Control: TWinControl; Removing: Boolean);
begin
if Removing and Control.ContainsControl(FFocusedControl) then
FFocusedControl := Control.Parent;
if Control.ContainsControl(FActiveControl) then SetActiveControl(nil);
end;
This partially explains the above observed behaviour - focus moves to the parent control and the active control loses focus. It still doesn't explain why the 'EnableWindow` fails to kill focus to the button's child window. This does start to seem like a WinAPI problem...

Display second form as full screen with slide animation

I have a simple application that contain 2 forms, both forms has these properties:
BorderStyle := bsnone;
WindowState := wsMaximized;
I set these properties to make both of them full screen. In the main form there is a button and when I click I want to show the second form as full screen with slide animation so I used this code:
AnimateWindow(form2.Handle, 500, AW_ACTIVATE OR AW_SLIDE OR AW_HOR_NEGATIVE);
Form2 is in auto create and visible property is set to false.
The problem when I tried this I saw odd results, the animation play but the form2 appear without any controls and not covering the full screen.
How to fix that so I can display form2 as full screen with slide animation ?
I am using XE5
Based on MSN
https://msdn.microsoft.com/en-us/library/windows/desktop/ms632669(v=vs.85).aspx
there are lots of problems reported of using this function. So I recomend you go and implement the animation yourself.
Since you are interested only in sliding information do next:
Fist change your form size to fit into the monitor size.
MyForm.Width := Screen.Width;
MyForm.Height := Screen.Height;
Then move your form to the edge of the screen you want the animation to start from. When doing so keep atleast one pixel of the form inside the visible area of the monitor.
//Strating animation from left border
MyForm.Left := 1 - MyForm.Width;
MyForm.Top := 0;
//Starting from right border
MyForm.Left := MyForm.Width - 1;
MyForm.Top := 0;
//Starting from top border
MyForm.Left := 0;
MyForm.Top := 1 - MyForm.Height;
//Starting from bottom border
MyForm.Left := 0;
MyForm.Top := MyForm.Height - 1;
Once your have positioned your form in starting possition make it visible and enable timer that will beused to update forms position multiple times (animate) until it gets into desired position
MyForm.Show;
AniTimer.Enabled;
And start animation which is basically just updating your form position by using a simple timer
//Left to right animation
procedure MyForm.AniTimerOnTimer(Sender: TObject);
//Constant used to define by how many pixels will the form be moved
//on each timer interval
const MoveStep: Integer = 5;
begin
if MyForm.Left < MoveStep then
begin
MyForm.Left := MyForm.Left + MoveStep;
end;
else
begin
MyForm.Left := 0;
AniTimer.Enabled := False;
end;
end;
Use similar approach for other directions if needed.
Instead of defining MoveStep as constant you can make it as a variable and then dynamically calculate its value so that animation is finished in N steps.
MoveStep := Screen.Width div N;
If you would like to have diagonal animation you would need two MoveStep variables. One for horizontal and one for vertical axis. And you need to make sure that both are being calculated in order to finish animation in specific number of steps
MoveStepX := Screen.Width div N;
MoveStepY := Screen.Height div N;
So now you can controll your animation speed by changing MoveStep and timer interval.
Note I don't recomend setting timer interval to small. Why?
As ypu probably know TTimer component isn't known for its acruacy so it could lead to noticable speed variation of of your animation.
Also changing form position multiple times woulrd require part of it being rerendered hwen it comes into visual rage so it could generate significant load to the CPU.
Moving fomr a few less times and with larger increments could greatly reduce the CPU load whle still performing adequate aniamtion smothness.
So do some testing to find the best combination of timer interval and move step.
Your problem is, before Form2 is first shown, the VCL does not create API windows of the windowed controls. Because it doesn't need to. Remember 'visible' is still set to false when you call AnimateWindow.
Below is a not very elegant workaround which sets 'visible' while the form has 0 width and height. It also addresses an additional problem which I don't know why you are not having. It is that I cannot animate a maximized window at all, which seems logical to me - a maximized window does not move. Anyway, to test it I suggest setting 'wsNormal' as WindowState at design time.
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if Form2.Visible then begin
Form2.WindowState := wsNormal;
AnimateWindow(Form2.Handle, 500, AW_HIDE OR AW_SLIDE OR AW_HOR_NEGATIVE);
Form2.Close;
end else begin
Form2.Width := 0;
Form2.Height := 0;
Form2.Visible := True;
ShowWindow(Form2.Handle, SW_HIDE);
Form2.WindowState := wsNormal;
Form2.Width := Form2.Monitor.Width;
Form2.Height := Form2.Monitor.Height;
AnimateWindow(form2.Handle, 500, AW_ACTIVATE or AW_SLIDE OR AW_HOR_NEGATIVE);
Form2.WindowState := wsMaximized;
end;
end;
AnimateWindow is a bit of a loner. It doesn't play nicely with the rest of the team in Delphi. Even though the MSDN doc for it says that it shows the form it actually doesn't do it properly. It only does the animation. I suppose it would do it nicely if you went all out Windows API and knew all that is required.
A few things to keep in mind:
I suppose you noticed that the slide effect doesn't show properly
with the borders enabled.
It doesn't know about the WindowState property of the form so it will not Maximize the form if you wanted it to.
It doesn't show controls after the call, only graphic controls
It knows nothing of Delphi and how Delphi handles the showing and
hiding of its forms
So the trick is:
Take away borders like you have done.
Before showing the form for the first time you must specify its size.
Seeing that you want it maximized, just set it to the screen size
where it will display and set its position to the four corners of
that screen. This can be done on Form2's OnCreate.
So upon clicking the button you first call the AnimateWindow then call normal Form2.Show
There might be other fixes but this is the one I know of.

How do I know when a control can be focused?

I have my own Treeview control derived from TCustomTreeView.
I have added some of my own procedures to the class such as adding nodes. When this procedure is called at runtime I wanted the newly added node to be selected and for the Treeview to be focused so the new node is highlighted.
Here is a extract:
procedure TMyTreeView.AddGroup(AName: string);
var
Node: TTreeNode;
Obj: TGroup;
procedure AddToTree;
begin
Obj := TGroup.Create(AName);
FGroups.Add(Obj);
Node := Items.AddObject(Node, AName, Obj);
with Node do
begin
ImageIndex := 0;
SelectedIndex := 0;
end;
Selected := Node;
SetFocus;
end;
begin
Node := nil;
AddToTree;
end;
The above works but I am facing the common error message when calling from the Forms OnCreate event:
Cannot focus a disabled or invisible window
I know you can use the OnActivate event or just don't use OnCreate at all which will not result in the error, but anyone else who may use the component may not realise this.
So I wanted to know if there is a way to determine if my Treeview (or any control) is able to receive the focus, then I could add a little checking of my own, something like:
if ControlIsFocusable then
begin
Selected := Node;
SetFocus;
end;
I know there is the Loaded procedure you can override which tells us when the control is loaded but then that would only work on first run. If the control became hidden by the user at runtime (or was not visible to begin with) the Cannot focus a disabled or invisible window error is still going to show up.
The dirty way to do it when not run in the debugger is:
try
Selected := Node;
SetFocus;
except
end;
But that defeats the purpose and I hate handling errors in this way.
So basically I wanted to know if there was a way to determine if a control can receive focus, so that we can set the focus to it?
I'm not going to answer the question that you asked, because I think you are doing this wrong.
The control should not call SetFocus on itself. I can imagine no scenario where that is the correct behaviour. The form or application or framework should determine focus. Not the control.
Imagine what happens when you have a form with two such controls? Imagine using the keyboard to focus a button, which you then press with the SPACE bar. If the action attached to the button calls your control's method which then changes the focus, you've just gone against the platform UI guidelines. You control now places a severe burden on any application that attempts to consume it.

Delphi - Maximize a form to a particular screen

Quite a simple one i would think, but i need to be able to Maximize a form to a particular screen. Cant seem to find any Delphi specific info.
I can remember the forms position over subsequent application loads. However, when i restore the position, then call WindowState := wsMaximized, the form moves to the other screen! (i do have other forms also visible on that screen - it appears its maximizing to the 'active screen')
So i need a function like so:
procedure Maximize(const aScreenIndex : Integer);
begin
if aScreenIndex < Screen.MonitorCount then
//Maximize to that screen
end;
Intercept the WM_GETMINMAXINFO message and adjust the coordinates inside its MINMAXINFO structure as needed.
Set Form.Position to poDesigned at design time
In Form.FormShow or your Maximize procedure:
procedure Maximize(const aScreenIndex : Integer);
begin
if aScreenIndex < Screen.MonitorCount then
begin
//Maximize to that screen
Myform.Left := screen.Monitors[aScreenIndex ].Left;
Myform.WindowState := wsMaximized;
end;
end;

Resources