how can I change a selected item in listview...
here is my code sample:
for c := 0 to Form1.LV1.Items.count -1 do
begin
if (form1.lv1.Items[c].SubItems.Objects[3] as TTabSheet).TabIndex =
pgc1.ActivePageIndex then
begin
form1.lv1.Items[c].Selected:= True;
end;
end;
I created a simple application with a list view, set it into report mode, added some items, and added this OnShow event:
procedure TForm1.FormShow(Sender: TObject);
begin
ListView1.Items[1].Selected := True;
end;
The specified item was indeed selected.
The conclusion that I draw from this is that the Selected property can be used from the OnShow event. Therefore, if your program does not result in the list view selection be set, it would seem that either:
The code in the question is not running at all, or
The code is running, but the if statement condition is never True.
Your next step is to debug your program. Inspect your program as it executes using your preferred debugging technique. The interactive debugger would be a sound choice.
Related
I cannot get the accelerator keys for a TActionToolBar to work.
This is what I am doing (reproducable in D2006, XE4):
Select New -> VCL Forms Application
Add ActionManager1 to the form
Add a new action Action1 in ActionManager1, set caption of action to &Test
Add ActionToolBar1 to the form
Add an item to ActionManager.ActionBars and set ActionManager.ActionBars[0].ActionBar to ActionToolBar1
Add an item to ActionManager.ActionBars[0].Items and set Action to Action1
Set the Action1.OnExecute event to show a message
Start program --> toolbar is displayed just fine and works via mouse
Press ALT+T --> nothing happens, but a Ding sound
What step am I missing?
As the existing answer points out, action toolbars do not support this functionality.
My personal opinion is that, this has been overlooked. Toolbar buttons often showing images instead of text might be one reason to do so (at least it was for me). However, evidently, toolbar buttons have the functionality when they show their captions, so could the action toolbar buttons.
#Silver points out in a comment that action bars have the capability to find accelerated items. In fact action menus use that functionality. Same functionality could easily be integrated into TCustomForm.IsShortCut for action toolbars, which already iterates action lists to find possible shortcut targets.
We can override the method and do it ourselves. Below example gives priority to default handling so assigned shortcuts will suppress keyboard accelerators with the same character, but this logic could easily be reversed.
function TForm1.IsShortCut(var Message: TWMKey): Boolean;
var
Item: TActionClientItem;
i: Integer;
begin
Result := inherited IsShortCut(Message);
if not Result and (KeyDataToShiftState(Message.KeyData) = [ssAlt]) then begin
for i := 0 to ActionManager1.ActionBars.Count - 1 do begin
if ActionManager1.ActionBars[i].ActionBar is TActionToolBar then begin
Item := TActionToolBar(ActionManager1.ActionBars[i].ActionBar)
.FindAccelItem(Message.CharCode);
if Assigned(Item) and Item.ShowCaption and Assigned(Item.Action)
and Item.Action.Execute then begin
Result := True;
Break;
end;
end;
end;
end;
end;
It seems that accelerator keys are not implemented for TActionToolBar - so no steps missing.
The following is not a real solution but a workaround that adds shortcuts by parsing the captions of the action (thanks to the suggestion of #KenWhite). A real solution for the question you will find in the accepted answer. I'll keep that answer for reference anyway:
uses System.Actions, System.UiTypes, Vcl.Menus, Vcl.ActnMan;
procedure AddShortCutsFromActionCaption(AActionMan: TActionManager);
var
Act: TContainedAction;
AccelKey: string;
I: Integer;
begin
for I := 0 to AActionMan.ActionCount - 1 do
begin
Act := AActionMan.Actions[I];
if Act.ShortCut = 0 then
begin
AccelKey := GetHotKey(Act.Caption);
if AccelKey <> '' then
Act.ShortCut := TextToShortCut('Alt+' + AccelKey);
end;
end;
end;
AddShortCutsFromActionCaption must be run once for ActionManager1 after the localization is run. That way the different accelerator keys for different languages remain functional.
If a shortcut already exists or if the caption of the action is modified, this workaround will not work - but for my purposes this is okay.
I am using a TEdit to allow the user to enter a number, e.g. 10.
I convert the TEdit.Text to an integer and a calculation procedure is called.
In this calc procedure, a check was built-in to make sure no numbers below 10 are processed.
Currently I use the OnChange event. Suppose the user wants to change '10' into e.g.'50'. But as soon as the '10' is deleted or the '5' (to be followed by the 0) is typed, I trigger my warning that the minimum number is 10. I.e. the program won't wait until I have fully typed the 5 and 0.
I tried OnEnter, OnClick, OnExit, but I seem not to overcome this problem. The only solution is to add a separate button that will trigger the calculation with the new number. It works, but can I do without the button?
Use a timer for a delayed check, e.g.:
procedure TForm1.Edit1Change(Sender: TObject);
begin
// reset the timer
Timer1.Enabled := false;
Timer1.Enabled := true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := false;
// do your check here
end;
Setting the timer to 500 ms should be fine for most users.
And as David suggested in the comments to the question: Do not show an error dialog, use something less intrusive instead, e.g. an error message in a label near the edit or change the background color. And also: Do not prevent the focus to be moved away from that control and do not play a sound, that's also very annoying.
For our in house software we set the background of a control to yellow if there is an error and display the error message of the first such error in the status bar and also as a hint of the control. If you do that, you probably don't even need to have the delay.
Thanks, for your help. I tried the timer option, but could not get that to work. I now have this code, which works (almost - see below), but requires the used to always type a CR:
procedure Calculate;
begin
// this is my calculation procedure
ShowMessage('calculation running correctly');
end;
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
var
N : integer;
begin
if Key = #13 then
begin
N := StrtoInt(Edit1.Text);
if N<10 then ShowMessage('<10!') else
if N>100 then ShowMessage('>100!') else Calculate;
end;
end;
I used the ShowMessage() here only to see if the sample code worked. In the real program I have left that out, as you all suggested.
I also included the 'turn yellow on wrong entry' (thanks David). The only issue is that if the user runs this I get a beep from my computer. I can't see what went wrong. But what is causing the beep?
I'm making a multi-device application in Delphi XE8 which uses LiveBindings to a dataset.
There are a number of LB-specific Actions for FMX, including TFMXBindNavigateDelete. I'm trying to use this in a button-click handler like this:
Button Click Code:
procedure TForm1.Button1Click(Sender: TObject);
begin
if cdsOrdersSTATUS.Value='READY' then
begin
ShowMessage('Your Order Is Already READY/PENDING!');
end
else
begin
TAction(ActionList1.Actions[0]).Execute; //Not working,why?
end;
end;
The first (and only) item in ActionList1's Actions is my FMXBindNavigateDelete1.
The problem is, even if the code TAction(ActionList1.Actions[0]).Execute executes, the current dataset record is not deleted, so apparently
TFMXBindNavigateDelete's Action has no effect. Why is this, and how can I make it work?
Pic. ActionList1:
Actually, I think this is a good question and doesn't deserve the downvote.
I can reproduce your problem. I put two buttons on the FMX form. I set
Button1's OnClick to your Button1Click and Button2's Action to LiveBindingsBindNavigateDelete1.
Clicking Button2 pops up the standard 'Delete record?' confirmation and deletes the current record
if I answer "Yes", as expected.
However, when clicking Button1, even if your else block executes, the 'Delete record?' confirmation
does not appear, so the record has no chance of being deleted.
The reason is in the code
function TCustomAction.Execute: Boolean;
begin
Result := False;
if Supported and not Suspended then
begin
Update;
if Enabled and AutoCheck then
if (not Checked) or (Checked and (GroupIndex = 0)) then
Checked := not Checked;
if Enabled then
Result := ((ActionList <> nil) and ActionList.ExecuteAction(Self)) or
((Application <> nil) and Application.ExecuteAction(Self)) or inherited Execute or
((Application <> nil) and Application.ActionExecuteTarget(Self));
end;
end;
The Enabled property seems by default to be set to False during the call to
Update so the if Enabled then ... never executes. I haven't managed to find
a way to get Enabled set to True during the call to Update. Perhaps someone else knows how to do that.
In the case of Button2, execution then passes to TComponent.ExecuteAction and
it is the call to Action.ExecuteTarget(Self) in it which results in the
record-deletion routine executing.
So, from that, your problem seemed to me to become how to adjust the code so that
TComponent.ExecuteAction gets executed, in other words, how to associate the
Action with a component. The answer was fairly obvious.
All that's needed is this
procedure TForm1.Button1Click(Sender: TObject);
begin
if cdsOrdersSTATUS.Value='READY' then
begin
ShowMessage('Your Order Is Already READY/PENDING!');
end
else
begin
Button1.ExecuteAction(LiveBindingsBindNavigateDelete1); // <- this works
//LiveBindingsBindNavigateDelete1.Execute; //Not working,why?
end;
end;
I didn't understand exactly what you wanted to do but if you trigger the action by it's index, you can do something like this:
TAction(ActionList1.Actions[0]).Execute;
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.
I am working on the application which has two listboxes.I load the two listboxes with values and when i keep on clicking the items from the list box i get the following error while debugging.
Running the exe causes the application to close.Sometimes i get the "Access Violation" message.
so what should I do to get rid of this error from my aaplication?
EDIT
..
The main form has timer that refresh all the controls
timer_RefreshCOntrol (intervali 1).
whenver the editBox_one is modified(value)
this function is called
Procedure TStringSetting.SetValue (const AValue : String);
Begin
...
If FValueControl <> Nil then
Begin
FValueControl.OnChange := VoidNotifyEvent;
FValueControl.Text := NewValue;
FValueControl.OnChange := EditChange; //<--here the stackoverflow error comes....
end;
end;
Procedure EditChange (Sender: TObject);
Begin
Value := FValueControl.Text;
If Not EditIsValid then FValueControl.Font.Color := clRed
else If Dirty then FValueControl.Font.Color := clBlue
else FValueControl.Font.Color := clWindowText;
If #OldCustomEditChange <> Nil then OldCustomEditChange(Sender);
end;`
the EditChange (Sender: TObject); <--keeps geting called and the stackoverflow error comes
EditChange is assigned to the editbox on FormCreate
EDIT2
I am not the original developer.I just handled code sometimes back, major refactoring is not possible.
edit 3
The call stack value but what is the "???"
EDIT 4
after going through #Cosmin Prund and #david
i got the place where the infinity call start
Procedure TFloatSetting.EditChange (Sender: TObject);
Begin
SkipNextOnChange := True;
Inherited EditChange(Sender);
IfValidThenStore(FValueControl.Text);
Inherited EditChange(Sender); {<-------This is where it start}
end;
Procedure TStringSetting.EditChange (Sender: TObject);
Begin
Value := FValueControl.Text;
If Not EditIsValid then FValueControl.Font.Color := clRed
else If Dirty then FValueControl.Font.Color := clBlue
else FValueControl.Font.Color := clWindowText;
If #OldCustomEditChange <> Nil then OldCustomEditChange(Sender); {<---this keeps calling Procedure TFloatSetting.EditChange (Sender: TObject);}
end;
Based in the posted call stack it's obvious why the error is happening: TStringSetting.EditChange triggers TFloatSetting.EditChange and that in turn triggers TStringSetting.EditChange. The loop goes on like this until all stack space is exhausted.
Here are some tips on why that might happen, and tips on how to debug and fix it:
Maybe the controls involved trigger the OnChange event handler when the Value is changed progrmatically. If the two editors are supposed to display the same data in two formats and you're using the respective OnChange event handlers to keep them in sync, this might be the cause.
Maybe you're directly calling one event handler from the other.
Ways to debug this:
You should first try the breakpoint solution, as suggested by paulsm4. If the stack overflow happens every time one of the OnChange handlers is called, this solution would easily work.
Comment-out the code for one of the event handlers. Run the program, the error should no longer appear. Un-comment the code in tiny (but logical) amounts, test and repeat. When the error shows up again, you know you fund the line that's causing the error. If you can't figure it out yourself, edit the question, add the code and mark the line that you just found out it's giving you trouble.
If the controls you're using are triggering the OnChange event handler when there value is changed programatically, you should make your event handlers non-reentrant: that would stop the infinite recursive loop for sure. I almost always assume controls trigger OnChange or equivalent events when properties are changed from code and always protect myself from re-entry using something like this:
// Somewhere in the private section of your form's class:
FProcessingEventHandler: Boolean;
// This goes in your event handler
procedure TYourForm.EventHandler(Sender:TObject);
begin
if FProcessingEventHandler then Exit; // makes code non-reentrant
FProcessingEventHandler := True;
try
// old code goes here ...
finally FProcessingEventHandler := False;
end;
end;
Suggestions:
Set a breakpoint in EditChange and OldCustomEditChange to see who's calling them. Each invocation. Clearly, only EditChange should ever call OldCustomEditChange.
Look in your .dfm to make sure EditChange is only assigned to one event (not multiple events) and OldCustomEditChange isn't assigned at all.
You report a non-terminating recursive call sequence to EditChange. Looking at the code of EditChange there are two candidates for a recursive call:
OldCustomEditChange being equal to EditChange, or calling a function that in turn calls EditChange.
An event handler that responds to changes to FValueControl.Font by calling EditChange.
These are the only opportunities for the code in EditChange to call itself.
It is easy to see how both of these possibilities leads to the non-terminating recursive function call and eventually the stack overflow. Of the two candidates my bet is number 1. I would study carefully what happens when OldCustomEditChange is called.
To debug a stack overflow of this nature simply open the call stack window and look at the long sequence of calls. You will typically see a pattern with one function calling itself, possibly via one or more intermediate functions.