Porting a programme using Lazarus from Windows to Mac |
IntroductionThe issues and experiences in porting an application from Windows to Mac are outlined. I (Greg Vinall) have a 'Fitness' programme written in Object Pascal using the Lazarus IDE. Having recently purchased a Macbook Air, I ported the application to the Mac. |
Installation of LazarusInstallation of Lazarus was a straight forward three-step process:
|
Preparing to codeIt was important to get one's head around the differences in the look and feel of the Mac GUI to that of the Windows GUI. I found the following couple of links informative: |
Points to note when codingThe following points are detailed further in the article.
|
Buttons are smaller and rounded on the Mac, and the default Font is a different sizeLazarus automatically makes buttons rounded if their height is 22 or less. Thus I put in conditional code to change every button on the form, when compiling on the Mac: {$IFDEF DARWIN} for i := 0 to frmMainGUI.ControlCount -1 do if frmMainGUI.Controls[i] is TButton then with frmMainGUI.Controls[i] do begin // Change the 'Height' to 22, and // Increase the width to accommodate the Apple Font Height := 22; Width := 62; end; {$ENDIF} |
There is an 'Apple' menu as the first menu, and the location and naming of menu items, such as 'Preferences...'/'Options' are differentEvery Apple application has an 'Apple' menu, with the 'Preferences...' (generally called 'Options' in the Windows environment) a submenu. Conditionally define menu controls, and create and configure them. Firstly, in the definition of your form, define the controls: {$IFDEF DARWIN} AppMenu : TMenuItem; AppAboutCmd : TMenuItem; AppSep1Cmd : TMenuItem; AppPrefCmd : TMenuItem; {$ENDIF} Secondly, create and configure them (assuming that you have a Menu control called 'MainMenu') {$IFDEF DARWIN} // Add 'Apple' Menu AppMenu := TMenuItem.Create(Self); // Application menu AppMenu.Caption := #$EF#$A3#$BF; // Unicode Apple logo char MainMenu.Items.Insert(0, AppMenu); // Add 'About' as item in application menu AppAboutCmd := TMenuItem.Create(Self); AppAboutCmd.Caption := 'About ' + BundleName; // Bundlename is the name of one of your global variables. AppAboutCmd.OnClick := @mnuAboutClick; // mnuAboutClick is the procedure to show the AboutBox. AppMenu.Add(AppAboutCmd); // Add Separator to application menu AppSep1Cmd := TMenuItem.Create(Self); AppSep1Cmd.Caption := '-'; AppMenu.Add(AppSep1Cmd); // Add 'Preferences'/'Options' to application menu AppPrefCmd := TMenuItem.Create(Self); AppPrefCmd.Caption := 'Preferences...'; AppPrefCmd.Shortcut := ShortCut(VK_OEM_COMMA, [ssMeta]); AppPrefCmd.OnClick := @OptionsCmdClick; AppMenu.Add(AppPrefCmd); {$ENDIF} Thirdly, remove the Exit submenu from the 'File' menu, and the 'About' submenu from the 'Help' menu, which is the normal location for these menus in the Windows environment, and assumes that you have these already created in your windows application, and called mnuFileExit and mnuHelpAbout: {$IFDEF DARWIN} mnuFileExit.Visible := False; mnuHelpAbout.Visible := False; {$ENDIF} |
The Menu is not part of the form, and thus alters the positioning of controlsI have controls along the bottom of my form, and in the Mac environment, with no menu as part of the form, leaves too much space at the bottom. I adjust the form's height: {$IFDEF DARWIN} frmMainGui.Height := 507; {$ENDIF} |
Shortcut keys for common functions (eg Save) differFor example, 'Save' on Windows is Ctrl-S, while on the Apple, it is Meta-S. To re-assign the keys on the Mac, use the unit LCLtype, and conditionally compile as per the example: {$IFDEF DARWIN} mnuEditCopy.Shortcut := ShortCut(VK_C, [ssMeta]); // Meta-C [ Command-C on Windows ] mnuEditPaste.Shortcut := ShortCut(VK_V, [ssMeta]); // Meta-V [ Command-V on Windows ] mnuEditUndo.Shortcut := ShortCut(VK_Z, [ssMeta]); // Meta-Z [ Command-Z on Windows ] mnuEditCut.Shortcut := ShortCut(VK_X, [ssMeta]); // Meta-X [ Command-X on Windows ] mnuEditSelectAll.Shortcut := ShortCut(VK_A, [ssMeta]); // Meta-Z [ Command-A on Windows ] mnuGoFirst.Shortcut := ShortCut(VK_UP, [ssMeta]); // Meta-Up [ Command-Up on Windows ] mnuGoPrevious.Shortcut := ShortCut(VK_UP, []); // Up mnuGoNext.Shortcut := ShortCut(VK_DOWN, []); // Down mnuGoLast.Shortcut := ShortCut(VK_DOWN, [ssMeta]); // Meta-Down [ Command-Down on Windows ] mnuFileSave.Shortcut := ShortCut(VK_S, [ssMeta]); // Meta-S [ Command-S on Windows ] mnuFilePrint.Shortcut := ShortCut(VK_P, [ssCtrl]); // Ctrl-P {$ENDIF} |
Dialog and AboutBoxes are generally not modalIn the Windows environment, make the AboutBox appear modally, while on the Mac environment, do not: {$IFDEF DARWIN} procedure TfrmMainGUI.mnuAboutClick(Sender: TObject); begin {$IFDEF DARWIN} if Assigned(frmAboutBox) then Exit; frmAboutBox := TfrmAboutBox.Create(Application); frmAboutBox.Show; {$ELSE} frmAboutBox := TfrmAboutBox.Create(Application); try frmAboutBox.ShowModal; finally frmAboutBox.Free; end; {$ENDIF} end; {$ENDIF} In the About Box Form, conditionally compile for the Mac:
procedure TfrmAboutBox.FormClose(Sender: TObject; var CloseAction: TCloseAction ); begin {$IFDEF DARWIN} CloseAction := caFree; frmAboutBox := nil; {$ENDIF} end; procedure TfrmAboutBox.FormCreate(Sender: TObject); begin // According to the Apple guidelines, About Boxes do not have a 'close' button {$IFDEF DARWIN} Button1.Visible:=False; {$ENDIF} end; |
Location of configuration and other supporting files differI have not really got a handle on the location of support files on the Mac. Two websites were helpful:
The final conclusion, although not convincingly decisive, was to store the database in:
function TfrmMainGUI.GetDBpath():string; var temp:string; begin {$ifdef Darwin} temp := '~/Library/vfitness'; {$else} temp := GetAppConfigDir(False); {$endif} Result := ExpandFileName(temp + '/vfitnessDB'); end; |
Invisible tabsheets cannot have controls created by codeControls created by code cannot have as their parent an invisible tabsheet. A workaround is to remove the controls from the tabsheet before making the tabsheet invisible: procedure TvAddEdit.RemoveParentFromAEcontrols; var i: integer; begin for i := 0 to MaxFields do AE[i].Parent:=nil; dteDate.Parent := nil; lblDate.Parent := nil; cmbCourses.Parent := nil; lblCourse.Parent := nil; btnNew.Parent := nil; btnGo.Parent := nil; end; procedure TvAddEdit.AddParentToAEcontrols; var i: integer; begin for i := 0 to MaxFields do AE[i].Parent:=TabPage; dteDate.Parent := TabPage; lblDate.Parent := TabPage; cmbCourses.Parent := TabPage; lblCourse.Parent := TabPage; btnNew.Parent := TabPage; btnGo.Parent := TabPage; end; |
TcomboBox onselect events are fired inconsistentlyOn the Mac, the onselect event in the TcomboBox is fired when setting the itemindex programmatically. It is not, in the Windows environment. The following code was added, after the itemindex is set. Note that 'cmbTableNamesSelect' is the name of my onselect procedure: {$ifdef windows} cmbTableNamesSelect(nil); {$endif} |
Source CodeThe second edition of the source code is available here, and some Data Files are available here. The Data Files need to be saved to ~/ where ~ maps to your library/vfitness on a Mac, and to your local appdata directory\vfitness v2\ on Windows. The second edition has the following changes:
You may send me comments by clicking here and making the obvious changes to the address. Please keep the suggestions constructive, and try to avoid trolling. Complements will be gladly accepted. |