Displaying TRichEdit row and column in status bar - delphi

We are adding an ASCII editor to our application, and we are using TRichEdit in Delphi XE7. We need to display the Row and Column in the status bar, but have not found any code that works with the Key down function. The code below is the current code we are using. The code below works perfect with the mouse down, but the key down is not consistent.
Row := SendMessage(asciieditor.Handle, EM_LINEFROMCHAR, asciieditor.SelStart, 0);
Col := asciieditor.SelStart - SendMessage(asciieditor.Handle, EM_LINEINDEX, Row, 0);

When you want to display the caret position in a Rich Edit control in the status bar, the usual way is to use the editor's OnSelectionChange event:
procedure TForm1.RichEdit1SelectionChange(Sender: TObject);
begin
StatusBar1.Panels[0].Text := Format('Row: %d Col: %d',
[RichEdit1.CaretPos.Y + 1, RichEdit1.CaretPos.X + 1]);
end;
This is fired every time the caret pos or selection end point is moved, which is precisely what you need.
Attempting to keep the status bar updated using mouse and keyboard events is not a good idea. For instance, what if the user is editing or typing without using the mouse or keyboard? (For instance, programmatically or using speech recognition.)
And using OnIdle is a waste of CPU cycles.

Related

Make footer bar disappear on scrolling in a Delphi / Firemonkey app

I have a simple Delphi / Firemonkey app that has a lot of data within a TListView. To maximize the available space on screen, I want to make the footer bar disappear with animation if the user scrolls down. Just the same way the LinkedIn app does (at least on Android) for example. Is there an easy way to get there? Some mechanism that is integrated in then Firemonkey framework (like TMultiView)?
I'm using Delphi 11.2.
Thank you!
In FireMonkey you can achieve this quite easily by using Animations like TFloatAnimation which you can attach to any visual FMX component.
For instance if your footer bar is a StatusBar this is how you will done it:
In Form design select your status bar component
Find TFloatAnimation component in your component palette and double click on it. This will add Float Animation to the status bar. If done correctly Float Animation will be shown as child component of your status bar.
Change the PropertyName property of the added Float Animation to point to StatusBar.Size.Height property. This tells the Float Animation that it will be updating Status bars height property.
Now write two new procedures which you can then call to fire the prepared animation when needed.
The procedures would look something like this:
procedure TForm2.HideStatusBar;
begin
//Store initial height of the status bar as start value so we can use it later
//for restoring status bar to initial size
FloatAnimation2.StartValue := StatusBar1.Height;
FloatAnimation2.StopValue := 0;
//Set inverste property to false.
FloatAnimation2.Inverse := False;
//Start the animation
FloatAnimation2.Start;
end;
procedure TForm2.ShowStatusBar;
begin
//In order to reverse animation we need to set Inverse property to True
//We don't store any StartValue or StopValue since we have done that already
//when we decided to hide the status bar
FloatAnimation2.Inverse := True;
//Start the reverse animation
FloatAnimation2.Start;
end;
The reason why I store initial height of the status bar within HideStatsBar procedure instead of setting it at design time is to allow scenarios where you may allow your users to change the size of the said status bar.
NOTE: If your footer bar is a panel with Bottom alignment be aware that animating panel breaks the alignment and other components don't update their size properly as they should. I haven't figured out why this is happening and how to fix it.
Thanks to the help of SilverWarior, I got it finally working perfect just for my needs. To apply to Tom Brunberg's questions I will provide a full solution here including SilverWarior's slightly modified code of course.
I'm using the "HeaderFooter Template" of Delphi 11.2 but had to change the footer TToolbar to a bottom-aligned TPanel. It did not work with a TToolbar on Android. I placed the TListview in the middle of the form.
procedure THeaderFooterForm.FormShow(Sender: TObject);
begin
// just add some testing data
ListView1.BeginUpdate;
for var iCnt := 0 to 4711 do
ListView1.Items.Add.Text:='ListView Item ' + iCnt.ToString;
ListView1.EndUpdate;
FScrollPos:=0;
end;
procedure THeaderFooterForm.ListView1ScrollViewChange(Sender: TObject);
begin
var iNewPos:=ListView1.ScrollViewPos;
if FScrollPos > iNewPos then
begin
HeaderLabel.Text:='OnScroll Up';
Self.ShowStatusBar;
end
else
begin
HeaderLabel.Text:='OnScroll Down';
Self.HideStatusBar;
end;
FScrollPos:=iNewPos
end;
procedure THeaderFooterForm.HideStatusBar;
begin
// hiding the footer if height is already zero makes the footer simply do
// nothing on Android (but works in Win64)
if Footer.Height < 2 then
Exit;
// store initial height of the status bar as start value so we can use it later
// for restoring status bar to initial size
FloatAnimation1.StartValue := Footer.Height;
FloatAnimation1.StopValue := 0;
// start the animation
FloatAnimation1.Start;
// just for debugging
HeaderLabel.Text:=HeaderLabel.Text + ' AnimHideStart';
end;
procedure THeaderFooterForm.ShowStatusBar;
begin
// showing the footer if already visible makes it simply do nothing (but works in Win64)
if Footer.Height > 40 then
Exit;
// using the (far more elegant) .Invers machanism did not work
FloatAnimation1.StartValue := 0;
FloatAnimation1.StopValue := 48;
// start the animation
FloatAnimation1.Start;
// just for debugging
HeaderLabel.Text:=HeaderLabel.Text + ' AnimShowStart';
end;
This minimal example code placed within the form works on Windows 11 x64 and x86 and on my Android 13 device. Would be interesting on an iOS device of course.

How to handle TListBox scrolled all the way down to last TListBoxItem in Delphi XE8?

I am trying to make a TListBox that initially loads 15 TListBoxItems in it and every time you scroll all the way to the bottom of the TListBoxItem add another 15 TListBoxItems. In a firemonkey multi-device project in Delphi XE8.
Now i can't figure out how to know if the user is scrolled all the way down in a TListBox. I've tried every event of the TListBox, but none of them seems to do it.
I think you need to come at it slightly laterally. Instead of thinking about it in terms of when the user scrolls to the bottom of the list, think about it in terms of when the last item in the list becomes visible, i.e. when the IsVisible property of the ListBox.ListItems[ ListBox.ListItems.Count - 1].IsVisible is TRUE.
This only becomes TRUE when the user has scrolled to the bottom of the list!
You could use the gesture manager or a simple timer to test the the status.
If the platform is Windows you can try something like this
var
i: Integer;
x:Double;
Begin
x := ListBox1.Height / ListBox1.ItemHeight;
i := GetScrollPos(ListBox1.Handle, SB_VERT);
if i + x >= ListBox1.Items.Count then
ShowMessage('It is at the end of scroll');
End;

String Grid using Styles in Delphi XE2 - Scroll bar doesn't update

I'm making a simple application in Delphi XE2 which uses specifically the "Carbon" style. There's a large String Grid which has thousands of rows. I have a process which loops through the records of this grid, does some work, and makes some changes in the grid. As the process loops, the row which is being currently processed gets highlighted (by setting TStringGrid.Row).
The problem is that when I apply the style to this grid, the scroll bar doesn't change position as the row is changed. The loop does properly highlight each row as it's being processed, but by the time it gets towards the end of the list, the scroll bar on the right is still all the way at the top.
How do I make the grid's scroll bar move along with it?
Here's a sample of how I'm looping:
procedure TForm1.Button1Click(Sender: TObject);
var
X: Integer;
begin
FStop:= False;
for X:= 1 to Grid.RowCount - 1 do begin
if FStop then Break; //Ability to stop loop
Grid.Row:= X; //Highlight current row
DoSomeLenghyWork;
ChangeSomethingOnGrid;
Application.ProcessMessages; //Keep program responding
end;
end;
Everything works just fine when I'm not using any styles.
If invalidate and repaint don't do anything for you, try resizing the string grid:
Grid.Width := Grid.Width - 1;
Grid.Width := Grid.Width + 1;
Try playing with the string grid options that hide and show the scrollbars. Hide them before you update and show them after. Perhaps that will force them to repaint.
Try moving the scroll position and moving it back to the original position.
This worked for me - it forces windows to repaint the border area of the StringGrid:
SetWindowPos(Grid.Handle, 0, 0, 0, Grid.Width, Grid.Height, SWP_DRAWFRAME);

Caret position on webbrowser

I'm trying to get the id of a htmlinputtextelement in webbrowser where the caret is blinking. So when I press TAB it changes.
How can I do this in delphi?
You know when you go onto a website and there are multiple inputtextelements. You can scroll through them by pressing TAB. When you are finished with box 1, TAB, fill in box 2, TAB, box 3, TAB till you have completed the form on the website. I want to do this. By knowing what the id is of the inputtextelement that the current caret is in.
You can get the mouse position with getcursorpos. can you get the caret position the same way? They do not give the same location for x and y...??
procedure TForm1.Button2Click(Sender: TObject);
var
MausPos: TPoint;
HtmlElement: IHTMLElement;
iHTMLDoc: IHtmlDocument2;
tag1:string;
id1:string;
begin
if Supports(webbrowser1.Document, IHtmlDocument2, iHTMLDoc) then
begin
if GetcaretPos(MausPos) then
begin
MausPos := webbrowser1.screentoclient(MausPos);
HtmlElement := iHTMLDoc.ElementFromPoint(MausPos.X, MausPos.Y);
The Caret is not as simple as the mouse cursor position: Each Window is free to create and display it's own caret, wherever it wants it. Here's a Using Carets link on MSDN. You'd normally expect a window to only show a Caret if it has keyboard focus, but a I don't think there's anything stopping a window from showing the Caret even if it doesn't have keyboard focus.
Since the normal behavior is to only show the caret if there's keyboard focus, you may check for that using: GetFocus. But you'll likely find out the TWebBrowser itself is holding the focus, I doubt there's an Window Handle for each HTML element.
What I assume you actually want is the active element. You can get that using:
(TWebBrowser.Document as IHTMLDocument2).activeElement
Here's a short code snippet that uses this property:
procedure TForm25.Button2Click(Sender: TObject);
begin
if (W.Document as IHTMLDocument2).activeElement <> nil then
ShowMessage((W.Document as IHTMLDocument2).activeElement.tagName);
end;

Live update of StringGrid when horizontal scroll bar is moving?

In Delphi 2010, I need to display a grid that has a horizontal scroll bar with about 15 columns x 5 rows.
I chose to use a StringGrid.
However, while the mouse button is down dragging the horizontal scroll bar, I want the grid to scroll live.
The StringGrid component, it appears, does not scroll live. It waits until the mouse button is released before updating the column and scrolling if necessary.
Also, the horizontal scroll bar button (is that what it's called) is not proportional to the number of columns. And for a down-arrow when on the bottom row to move to the top of the next column to the right...
These seem like common needs, so I was surprised not to find them in TStringGrid.
Any suggestions on a way around these two problems? I can use a DbGrid or other standard component, but my preference is to not use a commercial grid if I can avoid it. And I'm not going to use shareware or freeware...
TIA
For the first question, you can set goThumbTracking in the StringGrid's Options at design-time, or at run-time: StringGrid1.Options := StringGrid1.Options + [goThumbTracking];
For the third question, you can provide the functionality you need by using keyboard event handlers of the control. An example;
procedure TForm1.StringGrid1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
StringGrid: TStringGrid;
begin
StringGrid := Sender as TStringGrid;
case Key of
VK_DOWN:
if StringGrid.Row = StringGrid.RowCount - 1 then begin
Key := 0;
StringGrid.Row := StringGrid.FixedRows;
if StringGrid.Col = StringGrid.ColCount - 1 then
StringGrid.Col := StringGrid.FixedCols
else
StringGrid.Col := StringGrid.Col + 1;
end;
VK_UP: //...;
VK_RIGHT: //;
VK_LEFT: //;
end;
end;
For the second question, the scrolling code seems to be buried in private methods of TCustomGrid. I have no clue how to achieve that..
If noticed you are not interested in third party components - Freeware, I am not fond of these either, but we all must make sacrifices sometimes if we want to get the problems solved. This is one of these sacrifices! This component is to good to be ignored. You will not create something like it yourself if you don't have a couple of years of free time.
Either write a new component based on TStringGrid (I would not - it is not the best tool in the box to begin with)
But take some time and learn TVirtualStringTree. The component is years ahead of TStrignGrid. The source is available and there are many who uses it.
And there are events already implemented to react on scrollbar changes
OnScroll, OnShowScrollbar
http://www.delphi-gems.com/index.php?option=com_content&task=view&id=12&Itemid=38
Search on stackoverflow and you can read much more about tvirtualstringtree
Second the suggestion to use TVirtualStringTree. Working with the TStringGrid component is like stabbing yourself in the belly with a rusty scissor.

Resources