Recent

Author Topic: Add Caret to a component  (Read 5538 times)

McClane

  • New Member
  • *
  • Posts: 41
Add Caret to a component
« on: November 17, 2021, 09:30:06 pm »
Hello, I want to make my own component and I don't know how to add a caret to it.
Can you guide me please?
I've been looking for TCaret but I couldn't find it.

Thanks in advance and have a nice day.

I want to start with this line of code:
Code: Pascal  [Select][+][-]
  1. TBEdit = class(TWinControl)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 7684
  • Debugger - SynEdit - and more
    • wiki
Re: Add Caret to a component
« Reply #1 on: November 17, 2021, 10:04:48 pm »
There is on class, but there are functions.

CreateCaret, SetCaretPos
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createcaret
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setcaretpos
and more.

The LCL offers those for all the WidgetSets.

An Example can be found in package SynEdit, in the unit SynEditPointClasses: TSynEditScreenCaretPainterSystem


There are certain rules how to handle it for paint and scroll events.

Also, there is only ONE caret for the ENTIRE application. If any other Control (including normal TEdit/TMemo) show the caret, it will disappear from your component.


You can draw your own caret. But then you must see to it, that it takes settings like blink frequency in account. Or stops blinking as the system caret would do....

McClane

  • New Member
  • *
  • Posts: 41
Re: Add Caret to a component
« Reply #2 on: November 18, 2021, 07:11:54 pm »
Hi Martin_fr, thank you so much for your reply.
I've discovered CreateCaret(handle) but the only way I can see it, it's on the paint method and it doesn't blink.
I'll keep investigating.
I have a new question and I don't know if i have to create a new post.
How can I print text on a handle.
Thanks again and have a nice day.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 7684
  • Debugger - SynEdit - and more
    • wiki
Re: Add Caret to a component
« Reply #3 on: November 18, 2021, 08:33:17 pm »
There are calls to create it, set the size, set the pos, show/hide it.....

The paint method may show/hide it. In SynEdit iirc it's created in OnFocus.

On modern Windows, the caret only blinks a few times, and if nothing is typed, then it stops blinking. On other OS that may be different.


A WinControl has a canvas, that you can print to. IIRC it has methods to do that.

There is also "TextOut" / "TextOutEx"

E.g. unit SynEditTextDrawer.


winni

  • Hero Member
  • *****
  • Posts: 2850
Re: Add Caret to a component
« Reply #4 on: November 18, 2021, 08:54:52 pm »

A WinControl has a canvas, that you can print to. IIRC it has methods to do that.

Hi!

A WinControl owns no canvas!
The reason for a lot of hacks.

Winni

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 7684
  • Debugger - SynEdit - and more
    • wiki
Re: Add Caret to a component
« Reply #5 on: November 18, 2021, 09:16:32 pm »

A WinControl has a canvas, that you can print to. IIRC it has methods to do that.

Hi!

A WinControl owns no canvas!
The reason for a lot of hacks.

Winni

Ah, yes, but
  TCustomControl = class(TWinControl)

Which should be the starting point for such endeavours.

McClane

  • New Member
  • *
  • Posts: 41
Re: Add Caret to a component
« Reply #6 on: November 23, 2021, 01:20:48 pm »
Hi again, I forget to say I'm a linux user, so maybe LCL works a bit different as MWindows.

I'm facing a problem. I'm not able to change the colors of the handle. SetBkColor and SetTextColor don't have any effect. I'm using a dark theme and the background is white and the text color is blak.

I have more doubts that I want to consult. I've been investigating the code of TCustomEdit and I couldn't find TextOut or DrawText any where. I wonder how they did it? If you have an answer please, let me know.

Martin_fr what does IIRC mean?

Thanks in advance. Have a wonderful day!

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 7684
  • Debugger - SynEdit - and more
    • wiki
Re: Add Caret to a component
« Reply #7 on: November 23, 2021, 05:00:15 pm »
"IIRC" If I Remember Correctly

TCustomEdit => this uses the default edit by the OS/WidgetSet => That is it tells Windows, QT, Cocoa or GTK to create an Edit field, and leave everything to the OS.

Edit components, that do custom drawing are
SynEdit
ATSynEdit
RichMemo ? (I believe / not sure)

They all work on all widgetsets.



As for SetBkColor... Its a long time since I looked at it. SynEdit does custom drawing, so it should contain examples how to use these (or whatever equivalent SynEdit uses).
Same for ATSynEdit (3rd party).

As I said, long time. There is a "DC" (sometimes "HDC"), which you may need.... But I would have to dive into the sources myself, or google it myself.



McClane

  • New Member
  • *
  • Posts: 41
Re: Add Caret to a component
« Reply #8 on: December 03, 2021, 03:42:46 am »
Well, I could make a caret but I still have a few more questions before change the topic to solved.
I'd like to know why if i use showCaret(handle) on SetFocus doesn't work and it works on WndProc instead.
Thanks in advance and have a nice day.

Code: Pascal  [Select][+][-]
  1. procedure THEdit.WMSetFocus(var Message: TLMSetFocus);
  2. var
  3.   s: TSize;
  4.   c: integer;
  5. begin
  6. GetTextExtentPoint(dc,pchar('a'),1,s);
  7.  CreateCaret(handle,1,1,s.cy-3);
  8.  c:= (Height - (s.cy-2) ) div 2;
  9.  CaretPos.y:= c;
  10.  CaretPos.x:= indent;
  11.  SetCaretPos(indent,CaretPos.y);
  12.  showCaret(handle); // Doesn't work
  13.   cursor:= crIBeam;
  14. end;
  15.  
  16. procedure THEdit.WndProc(var Message: TLMessage);
  17. begin
  18.   inherited WndProc(Message);
  19.  
  20.   showcaret(handle); // Works here
  21. end;
  22.  

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 7684
  • Debugger - SynEdit - and more
    • wiki
Re: Add Caret to a component
« Reply #9 on: December 03, 2021, 09:04:58 am »
Well, I could make a caret but I still have a few more questions before change the topic to solved.
I'd like to know why if i use showCaret(handle) on SetFocus doesn't work and it works on WndProc instead.

Well in the end this is a Windows function. So who knows what the team at MS thought...


However https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showcaret
Quote
ShowCaret shows the caret only if the specified window owns the caret, the caret has a shape, and the caret has not been hidden two or more times in a row. If one or more of these conditions is not met, ShowCaret does nothing and returns FALSE.

Now first of all, when you get the Focus, has the call to CreateCaret already been made? And with the correct handle?

And are you sure no other control on your form has taken the caret (there can be only one)?


For all else, its reading MS docs, and try and error.

In SynEdit I did put the following line at the start of WM_SetFocus
Code: Pascal  [Select][+][-]
  1. DestroyCaret; // Ensure recreation. On Windows only one caret exists, and it must be moved to the focused editor
I do not remember the exact reason. Could be that indeed, this is only because another component (that had focus before) may own it at that time....



Something else... At some point in the past there was a bug in Windows itself (it affected several editors not related to Lazarus).
I have no idea if that still is an issue.
Using ScrollWindowEx, could under very specific circumstances leave artefacts of the caret on the screen.

If it is still an issue....
Back then I added the following note. (you can git blame SynEdit.pas, find the commit, and see what changes where done).
Most of the conditions are such, that in SynEdit the line-text does not need to be repainted, but can be just scrolled. If the area is repainted, then the ghosts are overpainted too.
In current SynEdit it can NOT be reproduced, since the workaround is applied.

Quote
  (* * On Windows 10 there is an issue, where under certain conditions a "ghost" of the text caret remains visible.
     That is one or a series of vertical black lines remain on the screen.
     * This can be reproduced, by moving part (eg bottom) of editor off screen (not just behind the taskbar, but
     off screen). Then press caret down to scroll. Ghost carets will scroll in with the new text.
     Similar move caret to a place off-screen, unfocus editor, and refocus by clicking into the editor (move caret
     while setting focus).
     To reproduce, the editor must have a visible gutter; and must not have "current line" highlight.
     * The conditions to cause this:
     - Caret must be in part of editor that is outside the screen.
     - Carte must be destroyed (maybe only hidden?), or ScrollWindowEx must affect caret
     - Caret must be in a part of the editor for which NO call to "invalidate" was made,
       but which will be repainted.
       E.g. the gutter, but not the line area received an invalidate, and another line above/below was invalidated
       (can happen through ScrollWindowEx). -> In this case the paint message receives a rect, that contains the caret,
       even though the part containing the caret was never explicitly invalidated.
     If this happens, while the caret is on screen (even if hidden behind another window/taskbar) then all works ok.
     But if the caret was off screen, a permanent image of the caret will remain (once scrolled/moved into the screen area).
     It seem that in this case windows does not update the information, that the caret became "invisible" when paint did paint
     over it. So if the already overpainted caret, is later (by Windows) attempted to be hidden by a final "xor", then it actually
     is made permanently visible.

     As a solution, in the above conditions, the full line (actually the text part with the caret) must be invalidated too.
     Since this is hard to track, the workaround will invalidate the full line, in any case that potentially could meet the
     conditions
  *)

McClane

  • New Member
  • *
  • Posts: 41
Re: Add Caret to a component
« Reply #10 on: December 03, 2021, 08:39:08 pm »
Well, it's the correct handle and there's only the textbox and a button in the form.
I tried destroycaret before create it but it doesn't help.
I think it's the correct handle because it works on WndProc.
I tried showCaret(handle), showCaret(dc), showCaret(getdc(0)) and nothings happens. It's really extrange.
Thanks for your elaborated explanation.
Have a nice day.

jamie

  • Hero Member
  • *****
  • Posts: 5129
Re: Add Caret to a component
« Reply #11 on: December 03, 2021, 08:46:47 pm »
I use those functions so I know they work,.,

first of all, before setting the caret position you must first show it in the window of your choice because if it's still showing elsewhere it will only set it there.

 After showing, then set the position

 You can also create a caret for the window at hand, too but you need to destroy it when you lose the focus.
The only true wisdom is knowing you know nothing

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 7684
  • Debugger - SynEdit - and more
    • wiki
Re: Add Caret to a component
« Reply #12 on: December 03, 2021, 08:49:42 pm »
Hi again, I forget to say I'm a linux user, so maybe LCL works a bit different as MWindows.

Ah, just looked through SynEdit again
Code: Pascal  [Select][+][-]
  1.   Result := LCLIntf.CreateCaret(Handle, 0, w, h);
  2.   SetCaretRespondToFocus(Handle, False); // Only for GTK
  3.  

So apparently on gtk, you need to tell if you want to deal with focus yourself. I have no background info. Maybe its in the docs/wiki? No idea.


McClane

  • New Member
  • *
  • Posts: 41
Re: Add Caret to a component
« Reply #13 on: December 06, 2021, 11:00:35 pm »
Hi Martin, according to this site
https://docs.microsoft.com/en-us/windows/win32/menurc/using-carets
you can set the caret position before using showCaret.

I discovered two things.
If i use showcaret in WM_SetFocus, the caret is black and doesn't blink. I'm using a dark theme and I didn't see it. But If i use show caret in WM_Paint method, the caret is white and blinks, as in WndProc.

I've attached the whole unit for you to see it. I know it is not the right way to make a TEdit but it works pretty well.
It's not finished yet and it will take me ages to finish it. It is just an attempt.

I decided to make my own TEdit because Lazarus TEdit has a problem: ctrl + backspace and ctrl + delete doesn't delete a complete word.

I read in Wind32Api that there's setcaretblinktime but it's a pity it's not available on Lazarus.
« Last Edit: December 06, 2021, 11:10:15 pm by McClane »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 7684
  • Debugger - SynEdit - and more
    • wiki
Re: Add Caret to a component
« Reply #14 on: December 06, 2021, 11:22:23 pm »
I discovered two things.
If i use showcaret in WM_SetFocus, the caret is black and doesn't blink. I'm using a dark theme and I didn't see it. But If i use show caret in WM_Paint method, the caret is white and blinks, as in WndProc.

ok, I have a vague memory of something maybe related.... I have not checked it now (no time). So, don't relay on the below without having tested it. (It may be way off).
Then again, you may not need it at all.
Also there may be diffs between WidgetSets. An some/all of the below may only apply to some WS.

IIRC, the caret is painted by inverting the pixels on the screen (I think xor $FF, but may be different). That means various things:
- if your caret overlaps partly with text, or other background color, then those parts of the caret have a diff color.
- if your background is $808080 then inverse would be $7f7f7f => hardly visible.

It also means if you paint
- while the caret is visible
- and paint an area that partly but not fully contains the caret (e.g. just the upper half)
then the two half (upper/lower) of the carets will blink at different times. When one half becomes visible, the other hides. And vice versa.

Hence, a good idea to hide the caret before painting.

 

TinyPortal © 2005-2018