Disable/Enable Control onPaint event , but Changes not reflecting - delphi

I am trying to Enable/Disable control in OnPaint event, but Changes are not getting reflected. How ever if toggle with other application changes getting reflected(using ALT+TAB)
procedure TfrmBase.FormPaint(Sender: TObject);
var
...
...
begin
flg := False;
for i := ComponentCount - 1 downto 0 do
begin
Temp := Components[i];
if (Temp is TToolButton) then
begin
(Temp as TToolButton).Enabled := SomeFuncWhichReturnBoolean;
end
else if (Temp is TButton) then
(Temp as TButton).Enabled := SomeFuncWhichReturnBoolean ;
end;
end;
Please suggest

OnPaint is for painting, and it's not the right moment to change states. Doing this will at best trigger another paint, or at worst it won't. So either the code doesn't work, or it works inefficiently. Moreover, Paint isn't called all the time. Even when you move the form around there is no guarantee that it will be repainted. So, as a trigger, this is a very unreliable event.
Instead, toggle the control when it it added to or removed from DisableControlList. Changing Enabled of the control should trigger a repaint, so you don't have to worry about that part.
You failed to mention what kind of list this is, but maybe it has an OnChange event you can use, or you can wrap it or inherit from it to implement the toggle without making it the responsibility of the procedure that added the control to the list. The code you now have, should be in that OnChange event.

Generally spoken, there is a time to change the states and there is a time to paint the current states. Don't mix them up.
Each button represents an action that will happen when you press the button and this action is maybe allowed or not.
Delphi has a TActionList where you can manage actions. Each action has an OnExecute (what should happen) and an OnUpdate event. This OnUpdate event is a perfect place to enable or disable the action.
procedure TFoo.BarActionExecute(Sender:TObject);
begin
DoBarAction();
end;
procedure TFoo.BarActionUpdate(Sedner:TObject);
begin
(Sender as TAction).Enabled := CanDoBarAction();
end;
Just wire up all the buttons with the actions from your TActionList

Related

Remove focus from a Delphi component [duplicate]

I have a DB component which DataLink.UpdateRecord is called when it receives CM_EXIT message. This message is sent when it loses focus. When I click post button, it doesn't lose focus and value is not written to datasource. How can I reach an effect of component losing focus without switching it to other one?
You could use:
procedure TCustomForm.DefocusControl(Control: TWinControl; Removing: Boolean);
We accomplish this by setting the Self.ActiveControl := nil. That causes all of the exit events to fire. In our case we wanted to also re-focus back to the control after the save took place. That required a few extra checks to ensure we had a good control that could accept focus.
procedure TSaleEditor.SaveCurrentState();
var
SavedActiveControl: TWinControl;
AlternateSavedControl: TWinControl;
begin
// Force the current control to exit and save any state.
if Self.ActiveControl <> nil then
begin
SavedActiveControl := Self.ActiveControl;
// We may have an inplace grid editor as the current control. In that case we
// will not be able to reset it as the active control. This will cause the
// Scroll box to scroll to the active control, which will be the lowest tab order
// control. Our "real" controls have names, where the dynamic inplace editor do not
// find an Alternate control to set the focus by walking up the parent list until we
// find a named control.
AlternateSavedControl := SavedActiveControl;
while (AlternateSavedControl.Name = '') and (AlternateSavedControl.Parent <> nil) do
begin
AlternateSavedControl := AlternateSavedControl.Parent;
end;
Self.ActiveControl := nil;
// If the control is a radio button then do not re-set focus
// because if you are un-selecting the radio button this will automatically
// re-select it again
if (SavedActiveControl.CanFocus = true) and
((SavedActiveControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := SavedActiveControl;
end
else if (AlternateSavedControl.CanFocus = true) and
((AlternateSavedControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := AlternateSavedControl;
end;
end;
end;
Have a look at TCustomForm.FocusControl. You can't make it lose focus without switching focus to something else, but you can switch and then immediately switch back, which would probably work.
There is a SetFocus function in windows unit. Try this:
Windows.SetFocus(0);

What is the equivalent OnScroll Event to TScrollBar for FMX?

I have not come across a solution for an Event (OnScroll) handler for a TScrollBar under Delphi 10 Seattle. I chose it because it is not a TScrollBox for which I will put any content into (like a Panel or DBGrid). It is merely going to detect the mouse action (None of the MouseUp/Down/Side-to-Side have worked either) and it is going to be used as file seek tool for which it will force a bunch of TeeCharts to update, based on the direction of the scroll.
So far I have only come across incomprehensible examples consisting of WindowsMessages (WM_HScroll) and VLC.StdCtrls.TScrollBar.OnScroll
What is the efficient way to detect the ScrollBar moving besides OnChange?
It would help to be able to manipulate the ScrollBar and have it reset itself in the middle. Much like it would if I set the value much like if I set the Min = -50 and Max = 50 (0)
To overcome the limitations of the FMX TScrollBar, you could use the following as a basis for further enhancement (partly inspired by the link you provided).
If the control(s) you will scroll with the scrollbar has (or can be equipped with) a property to store current scroll position you may use/define such, of type single. For testing I just defined it as a private property of the form, and called it sbIncremental.
I set the properties of the TScrollBar as Orientation = Vertical, SmallChange = 10, Max = 100 and Min = -100
Then, the OnChange event looks like this in my test:
procedure TForm6.ScrollBarChange(Sender: TObject);
var
sb: TScrollBar;
sbOnChange: TNotifyEvent;
begin
sb := (Sender as TScrollBar);
sbIncremental := sbIncremental + sb.Value;
Label1.Text := FloatToStr(sbIncremental); // Use the new value
sbOnChange := sb.OnChange; // Disable OnChange event
sb.OnChange := nil; // -"-
sb.Value := (sb.Min + sb.Max) / 2; // Reset position
sb.OnChange := sbOnChange; // Re-enable OnChange event
end;
To reset the ScrollBar position to the center, we need to disable the OnChange event temporarily.
Update after comments.
As I now understand, your issue is that the visual appearance of the scrollbar doesn't return to the zero position even if it's value is changed (within OnChange) to zero (with min=-100 and max=100 the zero position is in the middle). It does when clicking on the arrow buttons, but it does not if dragging the thumbgrip or clicking on either side of the thumbgrip. Visual update seems to be prevented in these two cases within the OnChange event. Also, calling ScrollBar.Repaint does not update the visual appearance. I found no way to use the OnMouseDown/OnMouseUp events. They don't appear to be linked internally?
This leaves us with following (hacky) solution: Fire a timer with a small delay, say 300 ms. When timeout occurs, the scrollbar is ready to accept new value changes and update visually. The timer also has the positive effect, that the thumbgrip moves as you click, and then moves back. Without any time between there would be no visual indication that something happens.
The OnChange event handler when using a timer
procedure TForm6.ScrollBarChange(Sender: TObject);
begin
// Use the scrollbar value
Label1.Text := FloatToStr((Sender as TScrollBar).Value);
// Enable reset timer
ScrollBarTimer.Enabled := False;
ScrollBarTimer.Enabled := True;
end;
And the OnTimer event
procedure TForm6.ScrollBarTimerTimer(Sender: TObject);
var
sbOnChange: TNotifyEvent;
begin
ScrollBarTimer.Enabled := False;
sbOnChange := ScrollBar.OnChange; // store OnChange event
ScrollBar.OnChange := nil; // disable OnChange event
ScrollBar.Value := (ScrollBar.Min + ScrollBar.Max) / 2; // reset position
ScrollBar.OnChange := sbOnChange; // re-enable OnChange event
end;

Delphi XE5 Firemonkey TTabItem and TEdit repaint coordination

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.

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.

How to remove focus from currently focused component?

I have a DB component which DataLink.UpdateRecord is called when it receives CM_EXIT message. This message is sent when it loses focus. When I click post button, it doesn't lose focus and value is not written to datasource. How can I reach an effect of component losing focus without switching it to other one?
You could use:
procedure TCustomForm.DefocusControl(Control: TWinControl; Removing: Boolean);
We accomplish this by setting the Self.ActiveControl := nil. That causes all of the exit events to fire. In our case we wanted to also re-focus back to the control after the save took place. That required a few extra checks to ensure we had a good control that could accept focus.
procedure TSaleEditor.SaveCurrentState();
var
SavedActiveControl: TWinControl;
AlternateSavedControl: TWinControl;
begin
// Force the current control to exit and save any state.
if Self.ActiveControl <> nil then
begin
SavedActiveControl := Self.ActiveControl;
// We may have an inplace grid editor as the current control. In that case we
// will not be able to reset it as the active control. This will cause the
// Scroll box to scroll to the active control, which will be the lowest tab order
// control. Our "real" controls have names, where the dynamic inplace editor do not
// find an Alternate control to set the focus by walking up the parent list until we
// find a named control.
AlternateSavedControl := SavedActiveControl;
while (AlternateSavedControl.Name = '') and (AlternateSavedControl.Parent <> nil) do
begin
AlternateSavedControl := AlternateSavedControl.Parent;
end;
Self.ActiveControl := nil;
// If the control is a radio button then do not re-set focus
// because if you are un-selecting the radio button this will automatically
// re-select it again
if (SavedActiveControl.CanFocus = true) and
((SavedActiveControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := SavedActiveControl;
end
else if (AlternateSavedControl.CanFocus = true) and
((AlternateSavedControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := AlternateSavedControl;
end;
end;
end;
Have a look at TCustomForm.FocusControl. You can't make it lose focus without switching focus to something else, but you can switch and then immediately switch back, which would probably work.
There is a SetFocus function in windows unit. Try this:
Windows.SetFocus(0);

Resources