[Solved] RightToLeft MessageDlg? lazarus / 31-LCL ---------------------------------------------------------------------- Avishai · 2013-10-10 05:37 I need 'MessageDlg' to show RightToLeft but I haven't found a way.  I took a look at C# and they have 'MessageBoxOptions.RtlReading'.  Does Lazarus have anything like this? I have the same problem with 'ShowMessage'. Edit:  Look at http://msdn.microsoft.com/en-us/library/k1689bxh.aspx ---------------------------------------------------------------------- Avishai · 2013-10-13 10:06 I found this on MS Developer Network but it's not for pascal: http://msdn.microsoft.com/en-us/library/ms182191.aspx?cs-save- lang=1&cs-lang=vb#code-snippet-1 ---------------------------------------------------------------------- ChrisF · 2013-10-13 12:35 It's possible in Windows, using directly the Windows API. See (MB_RIGHT and MB_RTLREADING) : http://msdn.microsoft.com/en- us/library/windows/desktop/ms645505%28v=vs.85%29.aspx Unfortunately, it seems you can't do it directly using the standard LCL components or functions. As far as I can see, the ShowMessageBox function in Dialogs.pp is interpreting all the flags; probably for compatibility reasons. If your program is running in Windows only, and if you don't care to have a message box slightly different than those of Lazarus, you can call directly the corresponding API. For instance, with a new project add the 'Windows' unit in the 'uses' clause (unit1.pas), add a pushbutton Button1 on Form1, and put this instruction in the OnClick event of the pushbutton: Code: [Select]procedure TForm1.Button1Click(Sender: TObject); begin    Windows.MessageBox(Form1.Handle,PChar('This is the first line of my text.'       +sLineBreak+'And'+sLineBreak+'this is the second line'),PChar('My Title'),MB_OK or MB_RIGHT or MB_RTLREADING); end; **EDIT** Don't know if you really need both the MB_RIGHT and the MB_RTLREADING options: use only the necessary one(s). ---------------------------------------------------------------------- Avishai · 2013-10-13 12:49 Thanks ChrisF!!!  This is GREAT! That answers almost the entire problem.  All I need now is access to the 'OK' button caption.  It also proves that the Form should be mirrored for RightToLeft applications AND that it can be done without turning Themes off.  If I could find how to do that, I might finally be able to produce a true RightToLeft application. ---------------------------------------------------------------------- Avishai · 2013-10-13 12:55 ChrisF, do you work with RightToLeft also?  If so, maybe we could share some code ---------------------------------------------------------------------- ChrisF · 2013-10-13 13:03 Quote from: Avishai on October 13, 2013, 02:55:54 pmChrisF, do you work with RightToLeft also?  If so, maybe we could share some code No, sorry. Concerning your last questions/remarks, what do you mean exactly with "All I need now is access to the 'OK' button caption" ? Do you want to modify the caption of the button ? It's impossible with standard Windows Message Box (well, it's not completely impossible, it's only more complicated). You can use any of the standard buttons of the Windows Message Box (i.e. OK, Cancel, Yes, No, Abort, Retry, Help). If not acceptable, it's probably better to code your own MessageBox function, using a custom modal form (a basic one is pretty simple to code). ---------------------------------------------------------------------- Avishai · 2013-10-13 13:11 Yes, I need to have the all the text in Hebrew (it could be any non- English text).  I have a partial answer that is working for the most part but I have a few details still to clean up. Thank you very much for your help. ---------------------------------------------------------------------- Avishai · 2013-10-13 13:40 [Solved] - This is what I have so far for making RightToLeft MessageDlg.  But you must turn Themes off to use it. Code: [Select]procedure RTLCtrl(ACtrl: TObject); { Make TWinControl RightToLeft - MSWindows ONLY! } begin   {$IfDef Windows}   if ACtrl is TWinControl then     with TWinControl(ACtrl) do begin       SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE)         or WS_EX_LAYOUTRTL or WS_EX_NOINHERITLAYOUT);   end;   {$EndIf} end; var   RtlMessageDlg: TForm; procedure TForm1.BitBtn1Click(Sender: TObject); var   RtlMessageDlg: TForm; begin   RtlMessageDlg:= CreateMessageDialog('זו הודעה שגיאה בעברית. ',mtError,[mbOK, mbCancel]);   RtlMessageDlg.Caption:= 'טעות';   RtlMessageDlg.Controls[0].Caption:= 'אישור';   RtlMessageDlg.Controls[1].Caption:= 'בטל';   for I:= 0 to RtlMessageDlg.ControlCount-1 do     RtlMessageDlg.Controls[I].BiDiMode:= bdRightToLeft;   RtlCtrl(RtlMessageDlg);   RtlMessageDlg.ShowModal;   FreeAndNil(RtlMessageDlg); end; ---------------------------------------------------------------------- Avishai · 2013-10-13 16:58 This looks like an excellent page for RightToLeft but unfortunately most of it is beyond my abilities. http://msdn.microsoft.com/en-us/goglobal/bb688119#top ---------------------------------------------------------------------- ChrisF · 2013-10-13 20:22 It's interesting (especially the 'SetProcessDefaultLayout' API). I don't have neither the environment, nor the necessary language knowledge to display and interpret the correctness of the results, but I've made a few tests (only with some English text). The results are not exactly the same: especially for the Windows part (i.e. system menu, minimize, maximize, ...). Standard LCL (I've not been able to call the 'SetProcessDefaultLayout' API with success - still have to check further) -Capture1 (LCL-Standard.png): Standard (i.e. Left to Right) -Capture2 (LCL-Inverse.png): Inverse (i.e. Right to Left), using the following instructions (inside the form create event): Code: [Select]  Application.BiDiMode := bdRightToLeft;   Form1.FlipChildren(True); Customed LCL (to check the effets of the 'SetProcessDefaultLayout' API): -Capture3 (LCLCustom-Standard.png): Standard (i.e. Left to Right) -Capture4 (LCLCustom-Inverse.png): Inverse (i.e. Right to Left), using the following instructions (before Application.CreateForm(TForm1, Form1);  inside project1.lpr): Code: [Select]// Declarations part ... const   LAYOUT_RTL = $00000001; function SetProcessDefaultLayout(dwDefaultLayout: DWORD): Longbool; stdcall; external 'user32.dll'; ... begin   RequireDerivedFormResource := True;   Application.Initialize; // Call   SetProcessDefaultLayout(LAYOUT_RTL);   Application.CreateForm(TForm1, Form1);   Application.Run; end. ---------------------------------------------------------------------- Avishai · 2013-10-13 20:39 ChrisF, *HOW* did you get 'LCLCustom-Inverse.png'!?!? I tried your code with 'SetProcessDefaultLayout' but I didn't get the R2L result. ---------------------------------------------------------------------- ChrisF · 2013-10-13 20:55 As indicated before, this code is not working with the standard LCL: probably because of some other API calls interfering with this one. So, I've used with my 'own minimal' LCL: that's why this time the visual effects of this API call are visible. ---------------------------------------------------------------------- Avishai · 2013-10-13 21:08 Well, whatever you did, I've been chasing this phantom for over 2 years looking through Lazarus and FPC code trying to get to the point that you seem to have gotten to in a few hours!  Could you share the project with me so that I can see exactly what you have done? As a side note, to check if you are getting R2L Text using Latin letters, end the string with punctuation.  If the punctuation is on the left then it is R2L Text.  Example: 'This is RightToLeft!'.  If it looks like '!This is RightToLeft' at run-time then it is R2L Text. ---------------------------------------------------------------------- ChrisF · 2013-10-14 15:03 After a few tests, I've found where the conflict occurs when WS_EX_LAYOUTRTL is used; when Themes are enabled inside the LCL, I mean. Though I've still not found how to fix the bug. The problem is relative to the double buffering process. By default double buffering is always used within form paint when themes are enabled. Of course, disabling the double buffering process gives some very a uncomfortable flickering when the form is resized/repaint; but it's just for some tests. For my tests, I've just added the WS_EX_LAYOUTRTL ExStyle for the Mainform: Code: [Select]uses   ..., LCLType, Windows; Code: [Select]  TForm1 = class(TForm) ...   public     { public declarations }     procedure CreateParams(var Params: TCreateParams); override;   end; Code: [Select]procedure TForm1.CreateParams(var Params: TCreateParams); begin   inherited;   Params.ExStyle:=Params.ExStyle or WS_EX_LAYOUTRTL; end; Now, here are a few captures with different situations: 1/ Without any modifications: problem with the double buffering (i.e. black background) 2/ "ThemesEnabled=False": seems OK, but I'm not sure because of English text only. Note that themes are still used by the Windows "rendering", but no more internally inside the LCL. Modification in "Themes.pas" Code: [Select]function TThemeServices.GetThemesEnabled: Boolean; begin //  Result := FThemesAvailable and FUseThemes and FThemedControlsEnabled;    Result:=False; end; 3/ Double buffering skipped: results identical to the former one. Modification in "win32callback.inc", in "SendPaintMessage" procedure, inside the "WindowProc" function: Code: [Select]    // check if double buffering is requested //    useDoubleBuffer := (ControlDC = 0) and (lWinControl.DoubleBuffered or ThemeServices.ThemesEnabled);     useDoubleBuffer := False; 4/ Trying to skip the "offending" part of the code: some texts are reversed. I'm not sure this is correct, though it could seems at the first glance. There is probably something wrong with regions/bitmaps buffer saving and restoring, as I'm just making a very "dirty" skip of some instructions. Modification in "win32callback.inc", in "SendPaintMessage" procedure, inside the "WindowProc" function: Code: [Select]//          if GetRandomRgn(DC, PaintRegion, SYSRGN) = 1 then if False then or just Code: [Select]//            Windows.SelectClipRgn(CurDoubleBuffer.DC, PaintRegion); I do have a question however: according to Microsoft documentation, it seems that adding WS_EX_LAYOUTRTL for the windows form (or setting SetProcessDefaultLayout(LAYOUT_RTL) for the whole program) should be sufficient to have a "correct" presentation for Hebrew and Arabic languages. Is it the case , or are some other modifications absolutely needed ? ---------------------------------------------------------------------- Avishai · 2013-10-14 16:15 Haha, WOW!  For someone that doesn't use R2L you have put a lot of work into this I found the 'GetThemesEnabled' some time ago . Code: [Select]function TThemeServices.GetThemesEnabled: Boolean; begin //  Result := FThemesAvailable and FUseThemes and FThemedControlsEnabled;    Result:=False; end; But eventually I moved to Win32Themes to do this. Code: [Select]function TWin32ThemeServices.UseThemes: Boolean; begin   //Result := UxTheme.UseThemes and (GetFileVersion(comctl32) >= ComCtlVersionIE6);   Result:= False;  // Added by Avishai end; This solves most of my problems, but it does have some effect on how Lazarus draws some components.  Like TSpeedButton, particularly if it has a glyph and it is pressed.  There are some other problems as well.  Anyway, the rest of the Lazarus community could never accept it so it has to be a local change only. QuoteI do have a question however: according to Microsoft documentation, it seems that adding WS_EX_LAYOUTRTL for the windows form (or setting SetProcessDefaultLayout(LAYOUT_RTL) for the whole program) should be sufficient to have a "correct" presentation for Hebrew and Arabic languages. Is it the case , or are some other modifications absolutely needed ? Generally speaking, the answer is 'YES', it is enough.  There are a few components that still need property BiDiMode set to bdRightToLeft but not many.  And there are still other components that simply don't work at all with mirroring. E.G. TSynEdit (no reason to do that anyway) and TTabControl.  TCustomControls will not work at all when mirroring is used. Edit: TCombBox also has some problems.  I have to put it on a TPanel, call LTRCtrl(Panelx) and then set the TComboBox BiDiMode:= bdRightToLeft. Code: [Select]procedure LTRCtrl(ACtrl: TObject); { Make TWinControl LeftToRight - MSWindows ONLY! } begin   {$IfDef Windows}   if ACtrl is TWinControl then     with ACtrl as TWinControl do begin       SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE)         and WS_EX_LAYOUTRTL and WS_EX_NOINHERITLAYOUT);   end;   {$EndIf} end; ---------------------------------------------------------------------- ChrisF · 2013-10-15 18:11 Well, a simple patch won't do the trick. In order the Windows RTL Layout is used, the whole LCL must at least "cooperate"; or eventually directly implements this feature (this is not a obligation). The problem with the TSpeedButton is not related to the themes deactivation, but the RTL Layout indeed. If the device context for its picture(s) is inversed, it's not working anymore: though, a patch is needed in this case. It's just one sample of all the many needed patches. Concerning the main problems when the RCL Layout is used inside the main form: -the region computed in the SendPaintMessage function (win32callback.inc) for the double buffering device context is incorrect. Because of the ClientToScreen API call, but not only. This is the origin of the black background for the form and its controls, -the double buffering device context is not mirrored while it should be (i.e. an image must be mirrored twice in order to get the original). That one is the reason of the inversed text display. Basically, it seems it's not really complicated to get the Windows LCL part to work with the Windows RTL Layout. But there is just a lot of basic patches to implement. For instance, something like: Code: [Select]if (GetWindowLong(ControlOrForm.Handle,GWL_EXSTYLE) and WS_EX_LAYOUTRTL)<>0 then    Do ModifiedCode else    Do CurrentCode Though it's a bit more complicated for all the graphical objects: bitmaps, icons, ... As generally mostly of them shouldn't be inversed, while some of them should be. Furthermore, the Windows RTL Layout has been implemented inside the LCL for at least one kind of controls: popup menus with Vista style. In this case, when the BidiMode property of the popup menu is different than bdLeftToRight, LAYOUT_RTL is applied to the concerned device contexts (except those of the menu icon(s), if present). That's the proof it's feasible and that the Windows part of LCL could include it. What kind of problems are you experimenting with the TComboBox control ? Do you get them only when applying the WS_EX_NOINHERITLAYOUT style to your form and its control ? As far as I've tested, using the WS_EX_NOINHERITLAYOUT style is not really recommended. I guess using it could be the reason of your problem(s) with the TComboBox control, because Windows combo boxes are in fact constituted of 2 sub-controls: edit plus listbox. ---------------------------------------------------------------------- Avishai · 2013-10-15 19:05 I don't know what to say other than THANK YOU!  My programming skills are only very basic and I would never have gotten to where you have no matter how much time I spent on it. What is so frustrating for me is that Lazarus is so very close to being a great R2L tool but these few problems are stopping me cold. To answer your question about the TComboBox, I've tried with and without WS_EX_NOINHERITLAYOUT.  I get the error either way.  I get the error three or more times at startup, click OK each time and then everything works fine after that. The workaround that I use for the TComboBox is to put it on a TPanel and make the TPanel L2R.  That seems to isolate it and I don't get the errors. ---------------------------------------------------------------------- ChrisF · 2013-10-16 21:11 I certainly don't deserve any thanks, as I've not really solved anything. Which may me hope that you don't think I intent to write all these necessary patches. Sorry, if that there has been a misunderstanding. I may eventually help, of course. But not alone, and certainly not before having some basic guidelines from the 'LCL team' concerning the way they'd wish to integrate the Windows RTL layout feature into the LCL (if they wish so). It may worth while to start a discussion about it in the concerned mailing list. Concerning your problem with the combobox, it seems to be related to the fact that you've both call FlipChildren and also set the WS_EX_LAYOUTRTL style. You should choose one only, not both. Anyway, the WS_EX_LAYOUTRTL style is supposed to do all the job, at least for the standard Windows controls: why are you using both of them ? ---------------------------------------------------------------------- Avishai · 2013-10-16 21:39 You most certainly do deserve my thanks.  You've pointed me in directions I had not gone before even though the code is way beyond my abilities.  Also I had almost given up hope of ever solving these problems, but now I have a better direction to follow. As for 'FlipChildren', I never use it.  Mirroring positions the components from the right instead of the left so that's all that is needed.  I build a LeftToRight Form and after mirroring at run-time it is RightToLeft (with a few headaches).  In MSVisual Studio you can set 'RightToLeftLayout' and the Form becomes RightToLeft in Design-Time so that you can build the Form RightToLeft.  That's my ultimate objective.  I actually butchered enough Lazarus code to try that awhile back and at first it seemed to be working.  But there were so many problems that I had to abandon that approach for now.  Some day... Anyway, just the fact that you were willing to spend some time and look at it is enough to earn my gratitude.  Thanks ---------------------------------------------------------------------- ChrisF · 2013-10-17 15:06 Well, I've not look at the concerned code in the LCL, but I guess it's related to 'something' you are doing twice. If you test this very simple code (see attached file), you'll get exactly the same error. Just removing one of these 2 instructions is sufficient to have no more an error: Code: [Select]procedure TForm1.FormCreate(Sender: TObject); begin   Form1.FlipChildren(True);     // this one Code: [Select]procedure TForm1.CreateParams(var Params: TCreateParams); begin   inherited;   Params.ExStyle:=Params.ExStyle or WS_EX_LAYOUTRTL;   // or this one end; ---------------------------------------------------------------------- Avishai · 2013-10-18 06:12 @ChrisF.  I've done a little more testing on the ComboBox issue and it seems like it has something to do with setting anchors on just about anything.  Try without the 'FlipChildren' and see that it does not produce the error.  Then add a TLabel and set it to Anchor/Left/Top/Bottom = True.  In my test I get the error again.  If I remember correctly, 'FlipChildren' sets Anchors. Edit: Set TLabel Anchors back to Anchor/Left/Top (the default) and the error disappears again. ---------------------------------------------------------------------- Avishai · 2013-10-18 07:48 @ChrisF.  I have attached a test file to demonstrate the Anchoring issue + explain another issue. ---------------------------------------------------------------------- ChrisF · 2013-10-19 17:34 It sounds logic that FlipChildren and Anchoring are both using the same mechanism, or even that the first one is calling the second one, indeed. I'll try to see if I can find something interesting into the LCL code about that. ---------------------------------------------------------------------- Avishai · 2013-10-19 18:21 I found that 'BidiFlipAnchors' is used by 'FlipChildren'.  So just to see what would happen, I tried 'Pre-reversing' the anchors at design- time so that they would end up with the default anchors at run-time.  I still got the error.  It looks like doing anything at all with Left/Right anchors triggers the error. ---------------------------------------------------------------------- engkin · 2013-10-20 01:42 If I may suggest, before calling Code: [Select]  RTLCtrl(Form1); You may want to consider clearing ComboBox1.Anchors and restoring it after that call, like: Code: [Select]var   AnchorsValue:TAnchors; begin   //Form1.FlipChildren(True);   AnchorsValue := ComboBox1.Anchors;        //Save it   Anchors.Anchors := [];                    //Clear it   RTLCtrl(Form1);   ComboBox1.Anchors := AnchorsValue;        //Restore it You can write a small procedure to do that for every TComboBox (or maybe everything) on your form. I hope it helps while looking for the real cause. ---------------------------------------------------------------------- Avishai · 2013-10-20 04:54 Thanks for the input engkin.  I gave your suggestion a try, but I get the error just clearing the anchors and then mirroring the Form. On a Form with only Label1 and ComboBox1 and Label1 anchored to ComboBox1 Code: [Select]procedure TForm1.FormCreate(Sender: TObject); begin   Label1.Anchors:= [];   ComboBox1.Anchors:= [];   RtlCtrl(Form1); end; ---------------------------------------------------------------------- engkin · 2013-10-20 13:08 Quote from: Avishai on October 20, 2013, 06:54:18 ambut I get the error just clearing the anchors and then mirroring the Form. On a Form with only Label1 and ComboBox1 and Label1 anchored to ComboBox1   You did not clear ALL of them. you still have Label1 anchored to ComboBox1. Any calculation based on the values before calling RtlCtrl is going to cause an error. Since Label1 is anchored to ComboBox1, its location is going to be calculated. ---------------------------------------------------------------------- Avishai · 2013-10-20 13:25 I have 'Label1.Anchors:= [];' in the code.  If I move ComboBox1 with code, the Label does not move with it.  So that should mean that Label1 is no longer anchored. ---------------------------------------------------------------------- engkin · 2013-10-20 13:33 You do not "anchor Label1  to ComboBox1" using Label1.Anchors. You simply right click on Label1 and choose "Anchor Editor". Right?  Whatever changes you do in that editor do not get stored in Label1.Anchors. ---------------------------------------------------------------------- Avishai · 2013-10-20 13:43 Ah.  I see where you are getting lost.  The label isn't anchored to the comboBox.  I simply place it on the form and set it's anchors akLeft/akTop/akRight:= true.  Then in code I set Label1.Anchors:= [].  That seems to be enough to generate the error. ---------------------------------------------------------------------- engkin · 2013-10-20 13:57 Are you saying that you set Label1.Anchors:= [] BEFORE calling RtlCtrl and it generated that SAME error?  Why don't you upload your sample project here? ---------------------------------------------------------------------- Avishai · 2013-10-20 14:08 This should explain it.  Start a new project with a blank Form. Code: [Select]uses Windows; procedure RTLCtrl(ACtrl: TObject); begin   if ACtrl is TWinControl then     with TWinControl(ACtrl) do begin       SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE)         or WS_EX_LAYOUTRTL);   end; end; procedure TForm1.FormCreate(Sender: TObject); var   ACombo: TComboBox;   ALabel: TLabel; begin   ACombo:= TComboBox.Create(Self);   ACombo.Parent:= Self;   ALabel:= TLabel.Create(Self);   ALabel.Parent:= Self;   ALabel.Anchors:= [];   RtlCtrl(Self); end;