RichMemo is a great component, but its not finished. This page lists some work arounds for some incomplete RichMemo functions. Its important to note that the things mentioned here are NOT official characteristics of RichMemo. They are things found to work in the current release. Its likely they will change in the future, please don't assume that they will continue to work or even that they will even continue to be needed.
If you use any of these work arounds, mark your code accordingly and test carefully when you update RichMemo ! If you don't need a work around, get rid of it, some of these here are quite ugly !
- 1 GetTextAttributes()
- 2 Search() fails under Linux
- 3 OnLinkAction does not set LinkStart and LinkLen on Win10
- 4 I Cannot determine the system default or application font.
- 5 SetLink() Fails on Linux
- 6 SetLink() sometimes changes the font
- 7 GetStyleRange(..) does not count changes in background colour
- 8 Sometimes newly typed characters are not made visible, Windows
- 9 Working on a Mac, macOS
- 10 Finding your way into the RichMemo files
- 11 See Also
Status : Linux - OK; Windows - needs patch; Mac - Needs Patch.
The problem : RichMemo.GetTextAttributes() does not return false when first param is out of range (Win and macOS). With macOS using Cocoa, unpatched will cause a crash. First Param to this function points to a char in the Memo that it should report on. The function is described as returning False if the indicated char is somehow invalid. However, passing -1 or a number greater than RichMemo.GetTexLen() returns True. The function works fine under Linux.
Documented at RichMemo#GetTextAttributes
if Richmemo1.GetTextAttributes(-1, FP) then showmessage('Failed on -1'); if Richmemo1.GetTextAttributes(30000, FP) then showmessage('Failed on 30000');
The Fix An easy fix appears to be to add a test at entry into the function, insert into line 677 of RichMemo.pas -
677 Result := False; 678 if (textStart < 0) or (textStart >= GetTextLen) then exit();
Its unnecessary under linux but does no harm, possibly even returns false a touch faster under Linux ? I have tested under Win10 and (GTK2 based) Linux. However, under Cocoa, it turns out that GetTextLen() fails. So, now, need to replace that with length(Lines.Text) if GetTextLen is not fixed first. Its likely to be slower, I'm using ifdef I'm afraid, very ugly.
Search() fails under Linux
Status : Linux - Fixed early Jan 2020; Windows - OK; (Mac needs retesting - Mac(Carbon) - needs patch; Mac(Cocoa) - Fails.)
The Problem : Search():boolean fails under Linux. About line 1080 in Richmemo.pas you find Search(..):boolean implemented. (Don't confuse with Search(..):integer). This function does not set the Result at the start and always returns false on a Mac and true under Linux (even when the GTK system has set ATextStart to -1).
Documented at RichMemo#Search
The Test :
var Start, len : longint; begin RichMemo1.Clear; RichMemo1.Append('This is some text to search'); if Richmemo1.Search('xxxx', 1, 25, , Start, Len) then showmessage('found ' + inttostr(Start) + ' ' + inttostr(Len)) else showmessage('Failed to find'); end;
Tells us it found xxxx at -1 !
The Fix : Easily fixed. Has been tested under Mac (Carbon), Win10 and Linux GTK2. Add two lines to make the function Search():boolean to make it look like this -
begin Result := false; // Add this line !!!!!!!! if not HandleAllocated then HandleNeeded; if HandleAllocated then begin so.len:=Len; so.start:=Start; so.options:=SearchOpt; if not TWSCustomRichMemoClass(WidgetSetClass).isSearchEx then begin ATextStart:=TWSCustomRichMemoClass(WidgetSetClass).Search(Self, ANiddle, so); // not recommended. The text found coulbe longer than Niddle // depending on the language and search options (to be done) // mostly for Arabi and Hebrew languages ATextLength:=UTF8Length(ANiddle); if ATextStart >= 0 then Result := true; // and Add this line !!!!!!! end else begin Result:=TWSCustomRichMemoClass(WidgetSetClass).SearchEx(Self, ANiddle, so, ATextStart, ATextLength); end; end else Result:=false; end;
OnLinkAction does not set LinkStart and LinkLen on Win10
Problem : OnLinkAction does not set LinkStart and LinkLen on Win10. A call back procedure, eg Form1.RichMemo1LinkAction(..) has those two parameters to indicate where the link the user clicked is. However, on Win10 (at least) they are set randomly.
The test : On a Form, put a RichMemo and a button. Make the Button's Click event look like this -
procedure TForm1.ButtonLinkClick(Sender: TObject); begin RichMemo1.Clear; RichMemo1.Append('A Link OK ? (give it a click)'); RichMemo1.SetLink(2, 4, True); end;
Then make the RichMemo's OnLinkAction event look like -
procedure TForm1.RichMemo1LinkAction(Sender: TObject; ALinkAction: TLinkAction; const info: TLinkMouseInfo; LinkStart, LinkLen: Integer); begin Showmessage('clicked [' + RichMemo1.GetText(LinkStart, LinkLen) + '] at ' + inttostr(LinkStart) + ' ' + inttostr(LinkLen)); end;
Then run it, click the button, click the link.
The Fix : Fortunately, SelStart is set to where the user clicked, must be somewhere on the link.
procedure TEditBoxForm.RichMemo1LinkAction(Sender: TObject; ALinkAction: TLinkAction; const info: TLinkMouseInfo; LinkStart, LinkLen: Integer); var Index, Len : longint; begin Index := RichMemo1.SelStart; Len := 0; while RichMemo1.IsLink(Index) do dec(Index); inc(Index); While RichMemo1.isLink(Index + Len) do inc(Len); Showmessage('clicked [' + RichMemo1.GetText(Index, Len) + '] at ' + inttostr(Index) + ' ' + inttostr(Len)); end;
I Cannot determine the system default or application font.
The Problem : Cannot determine the system default or application font. Maybe someone else can suggest a more elegant way to find out this information, I cannot ! And it turns out that we need to know that font because a call to SetLink() changes the in use font to that one !
The Fix : Well, this is tacky but works. Make a call to SetLink() from OnShow(..), record the font and then clear it. Should work on all systems.
procedure TEditBoxForm.FormShow(Sender: TObject); var FP : TFontParams; begin RichMemo1.Clear; RichMemo1.Append('a line of text'); Richmemo1.SetLink(2, 4, true); RichMemo1.GetTextAttributes(4, FP); Showmessage('Looks like we are using ' + inttostr(FP.Size) + ' ' + FP.Name); RichMemo1.Clear; end;
SetLink() Fails on Linux
Status : Linux - needs patch; Windows - OK; Mac - Not Working.
The Problem : SetLink() Fails on Linux.
Seems that if there is an existing tag covering the spot where we want to put a link, it fails. But if we clear any tags in that area first, all good. This may not suit all applications however ! Add a line to GTK2RichMemo, #1349 in class procedure Gtk2WSCustomRichMemo.SetTextUIParams(...) just before the call to gtk_text_buffer_apply_tag_by_name(...) -
gtk_text_buffer_remove_all_tags(buffer, @istart, @iend);
This will clear all tags and so gtk_text_buffer_apply_tag_by_name(...) has no problem setting 'link'. I am afraid I do not know why this is necessary and, yes, it will obviously clear tags on that text you may want there so, I don't think its a great solution, just one that suits me and is, perhaps, better than the existing behaviour.
SetLink() sometimes changes the font
The Problem : SetLink() sometimes changes the font. This happens when, for example, your code has detected that the user has just typed in a link address, SetLink() is called but as the user continues to type the font may look different. Its because after the call, the in use font is changed to be the system or application font. But before that call, it was what ever font RichMemwas using.
Documented : Not officially. This function, eg RichMemo1.SetLink(Start, Len, True), marks an existing section of text as being a 'Link'. Start is the zero based position of the character of the link, Len is the number of characters in the link, True means make a link, False means remove an existing link. An optional forth parameter is "LinkData" (that might be returned to the event) however, I could not get that to work and did not need it anyway...
The Fix : One solution to this problem is to make sure we are using the System Font all along. See above item on this page ....
GetStyleRange(..) does not count changes in background colour
The Problem : GetStyleRange(..) does not count changes in background colour when determining range boundaries under Windows. This function is documented at RichMemo#GetStyleRange
The Fix : Its trivial to make a new version of GetStyleRange(..) to replace the one built into RichMemo as long as you have already patched RichMemo1.GetTextAttributes(Index, FPr), as detailed above.
function Form1.FPisEqual(const FP, FPRef : TFontParams) : boolean; begin Result := false; if FP.name FPRef.name then exit(); if FP.size FPRef.size then exit(); if FP.bkColor FPRef.bkcolor then exit(); if FP.style FPRef.style then exit(); if FP.HasBkClr FPRef.HasBkClr then exit(); Result := true end; function Form1.GetStyleRange(const Index : longint; out Start, Len : longint) : boolean; var FP, FPr : TFontParams; begin Result := false; Start := Index; if not RichMemo1.GetTextAttributes(Index, FPr) then exit(); repeat dec(Start); if not RichMemo1.GetTextAttributes(Start, FP) then break; until not FPisEqual(FP, FPr); inc(Start); Len := 0; repeat inc(Len); if not RichMemo1.GetTextAttributes(Start + Len, FP) then break; until not FPisEqual(FP, FPr); Result := True; end;
So, instead of calling RichMemo1.GetStyleRange(..) just use GetStyleRange(..).
Sometimes newly typed characters are not made visible, Windows
Status : Linux - OK; Windows - Needs Workaround; Mac - OK.
The Problem : Newly typed characters are not made visible, Windows. Seems to happen for exmple when you scan over the document after each keypress by hooking into the OnChange(..) event. Or you write something into the RichEdit and immediatly call (eg) GetTextAttributes(). Wiping over with the mouse or moving the control makes the newly written text visible again. Strange.
The Test : On a form, put a RichText and a button. Make the Button's Click event look like -
procedure TForm1.ButtonCheckVisibleClick(Sender: TObject); var FP : TFontParams; begin RichMemo1.Append('Can you see this line of text ?'); RichMemo1.GetTextAttributes(1, FP); end;
The Fix : (I warned you some of these are pretty ugly) I found that a call to SetLink(..) will refresh things ! Passing it 'false' says turn the link off, there is not one there anyway so all good. So make the above look like this -
procedure TForm1.ButtonCheckVisibleClick(Sender: TObject); var FP : TFontParams; begin RichMemo1.Append('Can you see this line of text ?'); RichMemo1.GetTextAttributes(1, FP); RichMemo1.SetLink(0, 1, False); end;
This is not necessary under Linux or Mac but it does no real harm, technically its a waste of cycles so you may want to use an $IFDEF
Working on a Mac, macOS
On the associated Discussion page there is a table and some notes about my experiences with RichMemo on the Mac in August/September 2017. The results show there are a bit confronting and I have chosen, at this stage, not to move them to the main page. At least until someone with some more Mac experience can confirm what I found.
In the mean time, anyone thinking of developing a cross platform application using RichMemo is advised to test their work quite early on the Mac. And, perhaps, share their experiences.
Finding your way into the RichMemo files
Here is a tip, in several cases above I suggest adding lines into Dmitry RichMemo files. The debugger is a great way to open those files and probably find the actual function we are taking about. Set a break point in your code where you call the offending RichMemo function and step into it. Editor will open the component's files and is quite happy letting you add a line or so. It will be compiled in and available in other projects using RichMemo. neat !
- RichMemo - the main page