https://wiki.freepascal.org/api.php?action=feedcontributions&user=Eny&feedformat=atomFree Pascal wiki - User contributions [en]2024-03-28T21:26:38ZUser contributionsMediaWiki 1.35.6https://wiki.freepascal.org/index.php?title=Talk:Hungarian_notation&diff=140948Talk:Hungarian notation2020-11-03T19:23:21Z<p>Eny: Created page with "Not a good explanation of Hungarion notation. Better refer to: https://en.wikipedia.org/wiki/Hungarian_notation. I'd suggest to only mention that Hungarian notation is not so..."</p>
<hr />
<div>Not a good explanation of Hungarion notation. Better refer to: https://en.wikipedia.org/wiki/Hungarian_notation.<br />
I'd suggest to only mention that Hungarian notation is not so much used in the Pascal world, except as a naming convention for components and enumerated types (see also [[DoDont]]). The list is indeed highly arbitrary but for historic purposes could stay.</div>Enyhttps://wiki.freepascal.org/index.php?title=Free_Pascal_videos&diff=140480Free Pascal videos2020-10-13T17:51:58Z<p>Eny: Remove dead link</p>
<hr />
<div>{{Free Pascal Videos}}<br />
<br />
== Swinburne University of Technology Free Pascal Tutorials ==<br />
<br />
* Year: 2015 (videos updated 2019)<br />
* Collection: Swinburne Commons (Open)<br />
* [https://creativecommons.org/licenses/by/4.0/ Copyright Attribution 4.0 International (CC BY 4.0)]<br />
<br />
=== Introduction to programming ===<br />
<br />
* [https://commons.swinburne.edu.au/items/4f157ada-cf1e-421f-859f-22360dc9f25c/1/ 1.1 Compiling using the terminal]<br />
* [https://commons.swinburne.edu.au/items/3c1e6ac9-3621-41ba-a5d2-5a5993ceaab0/1/ 1.2 Understanding syntax rules]<br />
<br />
=== Introduction to programming in Free Pascal ===<br />
<br />
* [https://commons.swinburne.edu.au/items/10e59366-9159-4adc-aa45-459f225817dd/1/ 0.1 Introduction to programming in FPC]<br />
* [https://commons.swinburne.edu.au/items/cce8ad29-73f1-4e19-8e5e-ec21cc49b157/1/ 0.2 Installing FPC in Linux]<br />
* [https://commons.swinburne.edu.au/items/6181721e-29db-433c-8cce-c5e2a4c8a69d/1/ 0.3 Installing FPC in macOS]<br />
* [https://commons.swinburne.edu.au/items/87bc60ce-7ae6-43b8-851f-f689d040ec16/1/ 0.4 Installing FPC in Windows]<br />
* [https://commons.swinburne.edu.au/items/48211ca9-b899-45eb-9fe0-4aeb5c9b9d1a/1/ 1.1 Programs and sequence]<br />
* [https://commons.swinburne.edu.au/items/18055e35-bcd5-4251-b04d-9a9ddc88f57f/1/ 1.2 Programs and Procedures]<br />
* [https://commons.swinburne.edu.au/items/eae98114-92a9-4b1d-b31a-f6822e289305/1/ 2.1 Variables]<br />
* [https://commons.swinburne.edu.au/items/c0b1e93e-0c35-40c9-9e56-49db7e4fa5e1/1/ 2.2 Procedures with parameters]<br />
* [https://commons.swinburne.edu.au/items/d0924baf-04b9-40a1-9e99-cbe612d95724/1/ 2.3 Functions]<br />
* [https://commons.swinburne.edu.au/items/da39028b-ee53-46fe-82c9-09c0a573cc95/1/ 2.4 Hand execution]<br />
* [https://commons.swinburne.edu.au/items/5cd92d4b-0c7a-4111-9864-040193950d39/1/ 3.1 Control flow]<br />
* [https://commons.swinburne.edu.au/items/2401e11b-ea15-4bf1-a06c-744bf0b094a4/1/ 3.2 If statements]<br />
* [https://commons.swinburne.edu.au/items/c4884589-09cd-42df-8e8c-35d856199d77/1/ 3.3 Looping]<br />
* [https://commons.swinburne.edu.au/items/f6d8686b-e054-427c-ac9d-348b25f68d6c/1/ 3.4 Case statements]<br />
* [https://commons.swinburne.edu.au/items/69197033-473b-4ec5-9947-78227d7bc7c1/1/ 3.5 Modules]<br />
* [https://commons.swinburne.edu.au/items/d369f5f6-750a-42d9-be59-68ed287b7c2c/1/ 3.6 File I/O]<br />
* [https://commons.swinburne.edu.au/items/845d5925-d4b0-4958-87be-57286e620b9f/1/ 4.1 Records]<br />
* [https://commons.swinburne.edu.au/items/f81d63e1-5d5c-4dea-8b63-8e043af87ff1/1/ 4.2 Enumerations]<br />
* [https://commons.swinburne.edu.au/items/94a3d93b-2201-4a56-bb26-21455325b839/1/ 4.3 Pointers]<br />
* [https://commons.swinburne.edu.au/items/d05b86a1-d637-44fb-b37b-9422539bee3a/1/ 4.4 Function Pointers]<br />
* [https://commons.swinburne.edu.au/items/5885f774-a436-49b6-a47e-83e0f597034f/1/ 5.1 Arrays]<br />
* [https://commons.swinburne.edu.au/items/07f4ab4a-3d6e-47f4-9da6-be115e3bafad/1/ 5.2 Dynamic arrays]<br />
* [https://commons.swinburne.edu.au/items/7ead1f5f-76c0-46d3-b136-5b554572b390/1/ 6.1 Abstraction]<br />
<br />
<br />
== SchoolFreeware Free Pascal Program Tutorial ==<br />
<br />
* [http://www.youtube.com/watch?v=0VPvQ_dXMhw 1 - Getting Started - Lazarus Download Link ] <br />
* [http://www.youtube.com/watch?v=rZ_2iN-64QQ 2 - Math Examples ]<br />
* [http://www.youtube.com/watch?v=Yt-YTrabs8g 3 - Variables and Data Types ] <br />
* [http://www.youtube.com/watch?v=-ZPUlapENNU 4 - User Input ] <br />
* [http://www.youtube.com/watch?v=xp2kdOj34xE 5 - If Statements ]<br />
* [http://www.youtube.com/watch?v=41uoA9PvV1k 6 - Case Statement ] <br />
* [http://www.youtube.com/watch?v=A2ummLkXUGg 7 - While Loop ]<br />
* [http://www.youtube.com/watch?v=lRRRqb6Yg_w 8 - Repeat Until Loop ]<br />
* [http://www.youtube.com/watch?v=ofUy_ddjyhA 9 - For Loop ] <br />
* [http://www.youtube.com/watch?v=ZD05Gl1bD-Q 10 - Input Validation ]<br />
* [http://www.youtube.com/watch?v=HJSfbZXX2Ro 11 - Preventing Crashes With Val ]<br />
* [http://www.youtube.com/watch?v=fTH_GcB24Xc 12 - Menus For User Friendly Programs ]<br />
* [http://www.youtube.com/watch?v=qS4ufVXJeEg 13 - Random Numbers Code Example ]<br />
* [http://www.youtube.com/watch?v=oyCwGhqxFUg 14 - Guess My Number Game ]<br />
* [http://www.youtube.com/watch?v=39RVqGGXE-0 15 - Procedures ]<br />
* [http://www.youtube.com/watch?v=ytJiZt5WLq0 16 - Passing Values To Procedures ]<br />
* [http://www.youtube.com/watch?v=eQh37zBWEqM 17 - Function Examples ]<br />
* [http://www.youtube.com/watch?v=sv1GZnCx-PM 18 - Arrays ]<br />
* [http://www.youtube.com/watch?v=mhd-xYEat4g 19 - Gotoxy Screen Location ]<br />
* [http://www.youtube.com/watch?v=XqP7ml1f_9k 20 - Parallel Arrays ]<br />
* [http://www.youtube.com/watch?v=F7tiVi0hcZM 21 - 2 Dimensional Arrays - Matrix ]<br />
* [http://www.youtube.com/watch?v=BBB70uI1NT8 22 - Finding The Highest And Lowest Array Element ]<br />
* [http://www.youtube.com/watch?v=NauSuONa1kI 23 - Preventing Duplicate Data In Arrays ]<br />
* [http://www.youtube.com/watch?v=rqpOQtSWp-g 24 - String Manipulation ]<br />
* [http://www.youtube.com/watch?v=Vk5gye_3DD8 25 - Palindrome Strings ]<br />
* [http://www.youtube.com/watch?v=_YoAexQTD-M 26 - Separating Strings With ASCII Codes ]<br />
* [http://www.youtube.com/watch?v=TjBW3EbcFdQ 27 - Separating Strings With Ranges ]<br />
* [http://www.youtube.com/watch?v=Wa4txD7QFCU 28 - Running External Programs - IE Batch MS Paint ]<br />
* [http://www.youtube.com/watch?v=FMqXiEHxU84 29 - On The Record ]<br />
* [http://www.youtube.com/watch?v=6KZudd510C4 30 - Array Of Record ] <br />
* [http://www.youtube.com/watch?v=iXix_hJMNpc 31 - Making And Writing To Text Files ]<br />
* [http://www.youtube.com/watch?v=FdSNlRdNtbs 32 - Reading Text Files ]<br />
<br />
== Pascal Programming Tutorials ==<br />
<br />
* [http://www.youtube.com/watch?v=Kavjz_CXOTM Part 1 - Getting to know Pascal]<br />
* [http://www.youtube.com/watch?v=dVkoeoIpGlE Part 2 - Hello World!]<br />
* [http://www.youtube.com/watch?v=e4xUHtRGzHM Part 3 - Input / Output]<br />
* [http://www.youtube.com/watch?v=mE8f60w2d48 Part 4 - Variables and their roles]<br />
* [http://www.youtube.com/watch?v=rRsE7U2eaOA Part 5 - Simple Mathematics]<br />
* [http://www.youtube.com/watch?v=fhE-ctZEaa8 Part 6 - Simple Calculator] <br />
<br />
== Misc ==<br />
<br />
* [http://www.youtube.com/watch?v=o8zV0QvnpfQ Pascal 101]<br />
<br />
* [http://www.youtube.com/watch?v=weVa8IaDyMA FreePascal / code_swarm (NoMusic)]<br />
<br />
* [http://www.youtube.com/watch?v=Hm4UIPl1GqA Simple iPhone example using FreePascal and SDL ]<br />
<br />
* [http://www.youtube.com/watch?v=g_f2CUV7enU Let's Program w/ Pascal #2 : STRINGS, CONSTANTS AND ASSIGNMENTS]<br />
<br />
== See also ==<br />
<br />
* [[Lazarus videos]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Games&diff=136317Games2020-05-16T18:11:47Z<p>Eny: Petris added</p>
<hr />
<div>{{Menu_Game_Development}}<br />
<br />
==Games==<br />
<br />
Games written with Free Pascal. Currently active projects commercial, closed or open source. Some more also [[Projects using Lazarus#Games]].<br />
<br />
{| class="wikitable sortable" width="100%"<br />
! Name <br />
! Site <br />
! Open Source<br />
! Notes<br />
|- <br />
| Darkest Before the Dawn<br />
| http://castle-engine.sourceforge.net/darkest_before_dawn.php [https://play.google.com/store/apps/details?id=net.sourceforge.castleengine.darkestbeforedawn Google Play (Android version)]<br />
| {{Yes}}<br />
| Using Castle Game Engine<br />
|- <br />
| Castle<br />
| http://castle-engine.sourceforge.net/castle.php<br />
| {{Yes}}<br />
| Using Castle Game Engine<br />
|- <br />
| Diablo Roguelike<br />
| [http://diablo.chaosforge.org/ doom.chaosforge.org] [http://sourceforge.net/projects/diablorl/ Sourceforge.net]<br />
| {{Yes}}<br />
|<br />
|- <br />
| Doom Roguelike<br />
| [http://doom.chaosforge.org/ doom.chaosforge.org]<br />
| {{No}}<br />
|<br />
|- <br />
| Escape from the Universe<br />
| https://play.google.com/store/apps/details?id=com.catastrophe.games.escape.universe.space.shooter<br />
| {{No}}<br />
| Using Castle Game Engine<br />
|-<br />
| HedgeWars<br />
| [http://www.hedgewars.org/ www.hedgewars.org]<br />
| {{Yes}}<br />
| <br />
|- <br />
| Project "W"<br />
| [http://www.pascalgamedevelopment.com/showthread.php?3594-Projekt-quot-W-quot PascalGameDevelopment.com ]<br />
| {{No}}<br />
|<br />
|- <br />
| Scrabble 3D<br />
| [http://sourceforge.net/projects/scrabble/ sourceforge.net]<br />
| {{Yes}}<br />
|<br />
|- <br />
| UltraStar Deluxe<br />
| [http://sourceforge.net/projects/ultrastardx/ sourceforge.net]<br />
| {{Yes}}<br />
|<br />
|- <br />
| Minimon 3D<br />
| [http://www.minimon3d.com http://www.minimon3d.com]<br />
| {{No}}<br />
|<br />
|- <br />
| Mountains of Fire<br />
| http://castle-engine.sourceforge.net/mountains_of_fire.php<br />
| {{Yes}}<br />
| Using Castle Game Engine<br />
|- <br />
| Shu<br />
| [http://sourceforge.net/projects/rr-rr sourceforge.net]<br />
| {{Yes}}<br />
| Interesting looking RPG - in progress<br />
|- <br />
| Project Helena<br />
| [https://sourceforge.net/projects/projecthelena/ sourceforge.net]<br />
| {{Yes}}<br />
| A game by Eugene Loza<br />
|- <br />
| Asteroids vs You<br />
| [https://github.com/dimsa/ShadowEngine/ github.com], [https://play.google.com/store/apps/details?id=com.ShadowEngine.AsteroidsVsYou Google Play (Android version)]<br />
| {{Yes}}<br />
| Open Source Demo Game for SO Engine<br />
|}<br />
<br />
==Notable games written in Pascal==<br />
Here's a list of notable games made in Pascal or Object Pascal.<br />
<br />
{| class="wikitable sortable" width="100%"<br />
! Name<br />
! Site<br />
! License<br />
! Source available<br />
! Notes<br />
|-<br />
| Peg Solitaire<br />
| [[Peg Solitaire tutorial]]<br />
| Public Domain<br />
<br />
There's no license note, so I'm wondering. It may be ''Creative Commons'' as well.<br />
| {{Yes}}<br />
| A step-by-step explanation of how to build a [http://en.wikipedia.org/wiki/Peg_solitaire Peg Solitaire] using Lazarus.<br />
|-<br />
| Hedgewars<br />
| [http://www.hedgewars.org/ www.hedgewars.org]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| {{Yes}}<br />
| A well known [https://en.wikipedia.org/wiki/Worms_%28series%29 Worms] clone.<br />
|-<br />
| [https://en.wikipedia.org/wiki/List_of_minor_Apogee_Software_video_games#Beyond_the_Titanic Beyond the Titanic]<br />
| [http://legacy.3drealms.com/downloads.html#titanic Download page at 3DReams website]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| {{Yes}}<br />
| An early game by Apogee.<br />
|- <br />
| Darkest Before the Dawn<br />
|<br />
[https://play.google.com/store/apps/details?id=net.sourceforge.castleengine.darkestbeforedawn Google Play]<br />
[https://github.com/castle-engine/darkest-before-dawn GitHub]<br />
[http://castle-engine.sourceforge.net/darkest_before_dawn.php Sourceforge]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| {{Yes}}<br />
| First game using [[Castle Game Engine]] in Android.<br />
|- <br />
| Ultrastar Deluxe (USDX)<br />
| [https://github.com/UltraStar-Deluxe/USDX GitHub]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| {{Yes}}<br />
|A karaoke game, basically an OSS Singstar clone<br />
|-<br />
| Mundo<br />
| [https://sourceforge.net/projects/gamemundo/ SourceForge]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| {{Yes}}<br />
| Maybe the first [https://en.wikipedia.org/wiki/Massively_multiplayer_online_role-playing_game MMORPG] written in Pascal.<br />
|-<br />
| Petris<br />
| [[Petris]]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| {{Yes}}<br />
| A simple Lazarus/Freepascal implementation of the popular block dropping game<br />
|}<br />
<br />
==Pascal Ported Games==<br />
This list contains games written in other languages ported to Pascal. Note that most them were written for [[Delphi]] but may be they can be compiled/ported to Free Pascal too.<br />
<br />
{| class="wikitable sortable" width="100%"<br />
! Name <br />
! Site <br />
! License<br />
! Notes<br />
|-<br />
| Delphi Doom<br />
| [http://sourceforge.net/projects/delphidoom/ Sourceforge]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
|<br />
|- <br />
| Quake to Delphi<br />
| [http://sourceforge.net/projects/delphiquake/ Sourceforge] <br />
[http://www.geocities.ws/jimmyvalavanis/applications/delphiquake.html Geocities]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| Sourcefoge is the active one. The old-site appears to be quite dead now. Though the source code can still be downloaded.<br />
|-<br />
| Quake II Delphi (Quake 2 Delphi)<br />
| [http://www.sulaco.co.za/quake2/ www.sulaco.co.za]<br />
| [https://opensource.org/licenses/gpl-license GNU General Public License]<br />
| <br />
|-<br />
| C-evo<br />
| [http://www.c-evo.org/ www.c-evo.org]<br />
| Public Domain<br />
| The game is inspired by Civilization 2 game. It was spotted on [http://forum.lazarus.freepascal.org/index.php/topic,7914.0.html the forum] back in 2009. The engine used is not the original Civilization II engine. So as long as graphical resources are different, it's quite good TBS engine.<br />
|}<br />
<br />
==Game lists==<br />
You can find more games in the next links:<br />
<br />
* [[Projects using Lazarus#Games|Games]] section at page [[Projects using Lazarus]].<br />
* [http://forum.lazarus.freepascal.org/index.php/topic,39425.0.html Games from the Lazarus Graphics Contest]<br />
* [http://www.sourcecodeonline.com/sources/delphi/games.html Delphi games]<br />
* [http://www.pascalgamedevelopment.com/showthread.php?4519-The-great-list-of-Pascal-Games The great list of Pascal Games]<br />
<br />
Available [[Game Engine|game engines]] and [[Game framework|game frameworks]] also include examples and game demos.<br />
<br />
[[Category:Games]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Game_Engine&diff=124858Game Engine2019-06-14T13:11:05Z<p>Eny: Non-existing project; no info available via link and redirected page also non-existent</p>
<hr />
<div>{{Game_Engine}}<br />
{{Infobox game development}}<br />
<br />
A '''game engine''' is a software development environment designed to create [[Games|games]]. It distinguish from [[Game framework|game libraries]] in that:<br />
* Implements the ''game loop'', resource managers and other complex subsystems as networking communications, user interfaces and configuration systems.<br />
* Implements complex data structures, such as maps, particle systems and actors.<br />
* Implements tools as editors and data managers.<br />
All these subsystems would affect in gameplay aspects as movement, scoring or even genre. You can read this [http://en.wikipedia.org/wiki/Game_engine Wikipedia page] for more information.<br />
<br />
== Game Engines ==<br />
Here's a list of game engines that are Pascal/Delphi based or have Pascal binding libraries.<br />
<br />
{| class="wikitable sortable" width="100%"<br />
! Name <br />
! Site <br />
! Usage<br />
! Notes<br />
|-<br />
| [[Castle Game Engine]] <br />
| [https://castle-engine.sourceforge.io/ castle-engine.sourceforge.io] <br />
| FPC<br />
| 2D & 3D, all platforms supported<br />
|-<br />
| Quad-Engine<br />
| [http://quad-engine.com/ quad-engine.com] <br />
| Delphi/FPC/C#/C++<br />
| v0.9.0 (Diamond) 2018-01-15<br />
|-<br />
| TERRA Game Engine <br />
| [https://github.com/Relfos/TERRA-Engine github.com/Relfos/TERRA-Engine]<br />
| Delphi/FPC/Oxygene<br />
| 2D & 3D, all platforms supported<br />
|-<br />
| [[nxPascal]]<br />
| [https://github.com/Zaflis/nxpascal github.com]<br />
| FPC/Delphi<br />
| For now only OpenGL is supported. No android or other mobile support yet.<br />
|-<br />
| g2mp<br />
| [https://github.com/MrDan2345/g2mp github.com]<br />
| FPC<br />
| Ideologically replaced Dan Jet X, multiplatform, editor- and code-based development <br />
|-<br />
| Andorra 2D<br />
| [http://andorra.sourceforge.net/ sourceforge.net]<br />
| Delphi<br />
| Last update 2008<br />
|-<br />
| CAST II Game Engine<br />
| [http://www.casteng.com/ http://www.casteng.com/]<br />
| Delphi<br />
| Alive? last update 2011<br />
|-<br />
| Delphi X <br />
| [http://www.micrel.cz/Dx/ www.micrel.cz/Dx]<br />
| Delphi<br />
|<br />
|-<br />
| Afterwarp<br />
| [http://www.afterwarp.net/ www.afterwarp.net]<br />
| FPC/Delphi<br />
| <br />
|-<br />
| Brtech1<br />
| [http://www.pascalgamedevelopment.com/showthread.php?13795-Brtech1 PascalGameDevelopment.com]<br />
| FPC<br />
| Not really available as an engine or library. Many videos can be found on youtube though. <br />
|-<br />
| GameMaker: Studio<br />
| [http://www.yoyogames.com/ www.yoyogames.com] <br />
| N/A <br />
| Yes, it's not really a game engine library. But it's a game engine and studio written in Delphi. Special Pascal proud. <br />
|-<br />
| MinGRo<br />
| [https://www.sf.net/p/mingro SourceForge]<br />
| FPC<br />
| Designed for old-school style games. Still work in progress. Build with [[FPC_and_Allegro|Allegro.pas]].<br />
|-<br />
| ZGameEditor<br />
| [http://www.zgameeditor.org/ www.zgameeditor.org] <br />
| FPC/Delphi<br />
| use OpenGL for graphics and a real time synthesizer for audio<br />
|-<br />
| SO Engine<br />
| [https://github.com/dimsa/ShadowEngine github.com] <br />
| Delphi/FMX<br />
| Small Crossplatform (Win, Android, iOs) indy engine with formatters, animations, intersections and etc.<br />
|-<br />
| DGLE<br />
| [https://github.com/DGLE-HQ/DGLE github.com] <br />
| Delphi/FPC/C#/C++<br />
| Powerful free open source cross-platform game engine.<br />
|}<br />
<br />
==Physics Engines==<br />
These engines simulate the physical world (collisions, trajectories etc). Not really game engines per se, but could certainly be used in games.<br />
{| class="wikitable sortable" width="100%"<br />
! Name <br />
! Site <br />
! Usage<br />
! Notes<br />
|-<br />
| TundAx<br />
| [https://github.com/JordiCorbilla/thundax-delphi-physics-engine github.com]<br />
| FPC/Delphi<br />
| <br />
|-<br />
| Newton<br />
| [http://www.saschawillems.de/ www.saschawillems.de]<br />
| [http://www.saschawillems.de/?page_id=76 Bindings]<br />
| <br />
|-<br />
| Box2D-Delphi<br />
| [https://code.google.com/p/box2d-delphi/ code.google.com] <br />
| FPC/Delphi<br />
| This is Delphi implementation of [http://code.google.com/p/box2d/ Box2d] library<br />
|-<br />
| Kraft<br />
| [https://github.com/BeRo1985/kraft github.com]<br />
| FPC/Delphi<br />
| Pascal native physics engine by Benjamin Rosseaux. <br />
|}</div>Enyhttps://wiki.freepascal.org/index.php?title=Peg_Solitaire_tutorial&diff=115217Peg Solitaire tutorial2018-01-17T22:28:53Z<p>Eny: Proper usage of language</p>
<hr />
<div>{{Peg_Solitaire_tutorial}}<br />
<br />
This tutorial is the second Lazarus tutorial that aims at introducing the basics of Lazarus application development. It's best to start this tutorial after having finished the first one ([[Howdy World (Hello World on steroids)]]). This tutorial exlpains a bit about how to work with graphics and how to make a program modular. The final product of this tutorial is a basic but working version of the Peg Solitaire game ([http://en.wikipedia.org/wiki/Peg_solitaire]). If all goes well in the end it will look something like this:<br />
<br />
[[Image:tutpeg_solitaire.png]]<br />
<br />
==Start the project==<br />
As mentioned in the previous tutorial it's best to start with a clean, separate directory for each project. A quick recap:<br />
* Create a new directory for this game.<br />
* Start a new Application (Project/New Project... and select Application).<br />
* Save the project as PegSolitaire.<br />
* Save the main form as ufrmMain.<br />
* In the object inspector change the form's name to ''frmMain''.<br />
* Change the caption to ''Lazarus Peg Solitaire''.<br />
* In the project options insert ''bin\'' in front of the target filename (or ''bin/'' for Linuxes).<br />
<br />
And extra for this project:<br />
* Open the project options dialog (Shift-Ctrl-F11).<br />
* Select Compiler ''Options/Code Generation''.<br />
* Enable Range checking and Overflow error checking (see image belows).<br />
[[Image:tutpeg_compiler_options.png]]<br />
<br />
==First steps==<br />
It's always a good idea to seperate gui related code from data structure definitions. So the first step will be the creation of a separate unit for our Solitaire data structures.<br />
* From the menu choose ''File/New Unit''.<br />
* Save the unit as ''PegDatastructures.pas'' (and press the lowercase button that pops up).<br />
<br />
The basic elements of a Peg Solitaire board are the marbles, the board structure and the empy places. We'll simulate this by a simple matrix that has cells of a certain type (empty, occupied and not accessible). And we'll encapsulate all this in a class that handles all the data manipulation.<br />
* Add the following code to the PegDatastructures unit (after the classes in the ''uses'' section and before the ''implementation'' section):<br />
<syntaxhighlight>const<br />
C_MAX = 7; // Max board size: 7x7<br />
<br />
type<br />
TCellNums = 1..C_MAX;<br />
TCellType = (ctNoAccess, ctEmpty, ctPeg);<br />
TPegCells = array[TCellNums, TCellNums] of TCellType;<br />
<br />
TPegSolitaire = class<br />
private<br />
Size: TCellNums;<br />
PegCells: TPegCells;<br />
<br />
public<br />
constructor Create(const pSize: TCellNums);<br />
end;</syntaxhighlight><br />
<br />
It's fair to assume that other code that is going to use this class needs access to the cells contents (i.e. PegCells). The way to handle this is either by defining a set of functions to access the cells or define a so called array property. Let's go for the latter approach and add the following line to the public section of the TPegSolitaire class:<br />
<syntaxhighlight>property Cell[const pRow, pCol: TCellNums]: TCellType;</syntaxhighlight><br />
* Position the text cursor on the constructor line.<br />
* Press Ctrl-Shift-C: the IDE generates the constructor body (as we expected) but it also generates the empty bodies for the 2 methods that give us access to ''PegCells'' via the ''Cells'' property.<br />
* GetCell retrieves data from the private variable PegCells. Add the following code to the function:<br />
<syntaxhighlight>result := PegCells[pRow,pCol]</syntaxhighlight><br />
* SetCell populates the PegCells array with data. Add the following code to the procedure:<br />
<syntaxhighlight>PegCells[pRow,pCol] := pValue</syntaxhighlight><br />
* And now finalize the Create constructor. Add this code to it's body:<br />
<syntaxhighlight>var iRow,iCol: integer;<br />
begin<br />
// Store the size of the board locally<br />
Size := pSize;<br />
<br />
// Initialize all cells to 'not accessible'<br />
for iRow := 1 to C_MAX do<br />
for iCol := 1 to C_MAX do<br />
Cell[iRow,iCol] := ctNoAccess;</syntaxhighlight><br />
<br />
Now that the basic data structure is in place, let's have a look at the graphics we are going to need. There are many ways to display a solitaire board. We are going to use a paintbox. That will give us full control over the graphic features that Lazarus gives us out of the box.<br />
<br />
Our main form is going to use the data structure we defined in the PegDatastructure file. <br />
* Open the main form's sourcefile.<br />
* Add PegDatastructures it to the uses list at the top of the file:<br />
<syntaxhighlight>uses<br />
PegDatastructures,<br />
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;</syntaxhighlight><br />
* Press F12 (this will give us the form editor).<br />
* From the ''[[Standard tab|Standard]]'' palette choose a [[TButton]] and drop it on the form (in the upper left corner).<br />
* Change the caption to ''Test paint''.<br />
* On the ''[[Additional tab]]'' select [[TPaintBox]] and drop it on the form.<br />
* Change Align to ''alRight''.<br />
* Change BordSpacing.Around to ''4''.<br />
* Change Anchors.akLeft to ''true''. <br />
* Change Name to ''pbPeg''.<br />
* Resize the form so it looks something like this:<br />
[[Image:tutpeg_empty.png]]<br />
<br />
Next step is to draw the cells matrix on this paintbox by splitting it in rows and columns that will contain each cell of the board. To make it scaleable we'll calculate the width and height independantly.<br />
We'll need a couple of variables to hold the results. First the width and height of the cells, so all cells (7) fit exactly on the form.<br />
We'll develop the painting code interactively and we are going to use the button that was dropped on the form for that.<br />
* Double click the test paint button (this creates the event handler).<br />
* Add 2 variables:<br />
<syntaxhighlight>var<br />
CellWidth : integer;<br />
CellHeight: integer;</syntaxhighlight><br />
<br />
We'll need some extra variables to hold intermediate results:<br />
* Add 3 local variables: <br />
<syntaxhighlight> iRow, iCol: TCellNums;<br />
CellArea : TRect;</syntaxhighlight><br />
CellArea is used to limit the rectangular area on screen where a cell will be drawn.<br />
<br />
To process all rows and cols two straightforward for loops will do.<br />
* Add the following code to the event handler:<br />
<syntaxhighlight> // Calculate the width/height of each cell to accomodate for all cells in the paintbox<br />
CellWidth := pbPeg.Width div 7;<br />
CellHeight := pbPeg.Height div 7;<br />
<br />
// Draw boxes for all cells<br />
for iRow := 1 to 7 do<br />
for iCol := 1 to 7 do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
// And now draw the cell<br />
pbPeg.Canvas.Rectangle(CellArea);<br />
end;</syntaxhighlight><br />
<br />
A Canvas, as it's name suggests, is a control that helps us drawing things like lines, rectangles, circles etc. A paintbox control has an embedded canvas. Therefore the line ''pbPeg.Canvas.Rectangle(CellArea)'' will draw a rectangle on the paintbox, limited to the area defined in ''CellArea''. And because the paintbox is placed on the form, we can see the result there.<br />
<br />
* Compile and run the program (press F9).<br />
* Press the ''Test paint'' button.<br />
* Maximize the form (the cells disappear; don't worry we'll fix that).<br />
* Press the ''Test paint'' button again.<br />
<br />
This proves that our calculations were spot on and that we now have the means to draw whatever is necessary in the right spot. One thing needs to be done though before adding more drawing functionality. Drawing of the cells has no relation with the main form whatsoever (or any form for that matter). The only thing we need for drawing things is a Canvas and some measurements for the cells. So we are going to create a supporting class to clean up the main form.<br />
* Create a new unit (File/New Unit).<br />
* Save it as PegSolPainter.pas (File/Save and then and choose ''Rename to lowercase'').<br />
* Add a new class to the new unit file that will do all of the drawing (after the uses section, before the implementation section).<br />
<syntaxhighlight>type<br />
TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
<br />
public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
end;</syntaxhighlight><br />
<br />
Note that a TPegSolitaire variable is also added, because obviously we are going to use that class to retrieve a cells' state.<br />
* Position the text cursor in the constructor line and press Ctrl-Shift-C.<br />
* The constructor must store the 2 parms locally:<br />
<syntaxhighlight>constructor TPegSolPainter.Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
begin<br />
PegSol := pPegSol;<br />
Canvas := pCanvas;<br />
end;</syntaxhighlight><br />
<br />
Trying to compile this code will failse because we haven't added the PegDatastrucures unit to the uses section. And because we use a TCanvas we'll have to add the Graphics unit as well.<br />
* Add ''PegDatastructures'' and ''Graphics'' to the ''uses'' list.<br />
<syntaxhighlight>uses<br />
PegDatastructures,<br />
Graphics,<br />
Classes, SysUtils; </syntaxhighlight><br />
<br />
The reason we built this class was to remove all drawing and painting code from the form. So we need to create a method that will do the drawing. Before adding that method there is something that needs to be addressed: to calculate the width of a cell, we divide the paintbox width by the number of cells. In theory we could use the property Canvas.Width for this. However that property does not always give us the right width at the right time. So to be able to draw cells, we must provide our draw method with the correct values for the width and height of the canvas.<br />
<br />
Now we know this, we can add a paint method to our class.<br />
* Add procedure Repaint to the class.<br />
<syntaxhighlight> TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
<br />
public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
end;<br />
</syntaxhighlight><br />
* Generate the body of the procedure (Ctrl-Shift-C).<br />
* Copy the code from ''TfrmMain.Button1Click(Sender: TObject)'' to this newly created method:<br />
<syntaxhighlight>procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
var<br />
CellWidth : integer;<br />
CellHeight : integer;<br />
iRow, iCol : TCellNums;<br />
CellArea : TRect;<br />
begin<br />
// Calculate the width of each cell to accomodate for all cells<br />
CellWidth := pbPeg.Width div 7;<br />
CellHeight := pbPeg.Height div 7;<br />
<br />
// Draw boxes for all cells<br />
for iRow := 1 to 7 do<br />
for iCol := 1 to 7 do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
// And now draw the cell<br />
pbPeg.Canvas.Rectangle(CellArea);<br />
end;<br />
end;</syntaxhighlight><br />
<br />
Because we no longer need the paintbox pbPeg we must remove the references to it. Three changes are needed:<br />
* Change the calculation of the CellWidth and CellHeight to:<br />
<syntaxhighlight> // Calculate the width of each cell to accomodate for all cells<br />
CellWidth := pCanvasWidth div 7;<br />
CellHeight := pCanvasHeight div 7;</syntaxhighlight><br />
* Change the drawing of the rectangle to:<br />
<syntaxhighlight> // And now draw the cell<br />
Canvas.Rectangle(CellArea);</syntaxhighlight><br />
<br />
Now that we have a class that can do the fancy painting for us, it's time to make use of it. We are going to use this paint class together with the PegSolitare class to draw the cells.<br />
* In the main form source, locate the Button1Click method.<br />
* Remove all statements and variables.<br />
* Add 2 new variables: one for the game class and one for the painter class:<br />
<syntaxhighlight>procedure TfrmMain.Button1Click(Sender: TObject);<br />
var<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
begin<br />
end;</syntaxhighlight><br />
* Add ''PegSolPainter'' to the uses list.<br />
<br />
To use the paint class is fairley straightforward: create a new instance and call the repaint method like so:<br />
* Add the following code to the Button1Click method:<br />
<syntaxhighlight> // Create a new game object<br />
pegsol := TPegSolitaire.Create(7);<br />
<br />
// Create a new painter object to paint this game<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// And paint the board<br />
pegpaint.Repaint(pbPeg.Width, pbPeg.Height);<br />
<br />
// Clean up<br />
pegpaint.Free;<br />
pegsol.Free</syntaxhighlight><br />
<br />
Run and test the program to see that it works exactly as before. <br />
The result will look something like this:<br />
[[Image:tutpeg_empty_cells.png]]<br />
<br />
==It's all about events==<br />
What have we accomplished so far? There is a datastructure that holds all the data for a Peg Solitaire game, there is a class that can paint on a Canvas and we have a fairly simple form with a test button. So what's next? Events!<br />
<br />
As we have seen in the previous section, the cell matrix we painstakingly drew wasn't to last. This happens because the form doesn't know anything about our little game. As soon as the form thinks it's time to redraw itself, it does so and ignores our cell grid. What it dóes do is send a message to it's child controls that a refresh is necessary. This message is available to us as an event: a paint event.<br />
* Open the form editor (select ufrmMain in the editor and press F12).<br />
* Select the paintbox pbPeg.<br />
* Open the Events tab in the Object Inspector.<br />
* One of the events in the list is OnPaint.<br />
[[Image:tutpeg_onpaint.png]]<br />
<br />
This event is 'fired' everytime the paintbox needs to redraw it's surface. That is the place were we are going to do our drawing.<br />
<br />
* Select the OnPaint event in the Object Inspector and click on the small button with three dots. This will generate the event handler body.<br />
* Copy/Paste the exact code from the ''Button1Click'' event handler to this new method.<br />
<syntaxhighlight>procedure TfrmMain.pbPegPaint(Sender: TObject);<br />
var<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
begin<br />
// Create a new game object<br />
pegsol := TPegSolitaire.Create(7);<br />
<br />
// Create a new painter object to paint this game<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// And paint the board<br />
pegpaint.Repaint(pbPeg.Width, pbPeg.Height);<br />
<br />
// Clean up<br />
pegpaint.Free;<br />
pegsol.Free<br />
end;</syntaxhighlight><br />
* Remove all code and variables from method ''Button1Click''. Remember: the IDE will automatically remove this now empty method for us.<br />
* Remove the ''Test paint'' button from the form.<br />
* Run the program to see what happens (ignore the fact that the background color is now different).<br />
<br />
Now what is clear is that we create a game object in the OnPaint method, paint the empty cells and then destroy it. But we need to store the game object until a game has finished. The same goes for the paint object. So the OnPaint event is not the most logical place to create these objects. The form's class declaration is a better place to store them.<br />
* Find the forms's declaration.<br />
* Add the 2 variables we created in the OnPaint event:<br />
<syntaxhighlight> TfrmMain = class(TForm)<br />
pbPeg: TPaintBox;<br />
procedure pbPegPaint(Sender: TObject);<br />
private<br />
{ private declarations }<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
public<br />
{ public declarations }<br />
end; </syntaxhighlight><br />
<br />
These variables must be initialized as soon as the form opens (or anytime we want to start a new game). So let's create a procedure that does that for us and add it to the private section of the form.<br />
* Add procedure StartNewGame to the form.<br />
<syntaxhighlight> private<br />
{ private declarations }<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
<br />
procedure StartNewGame;</syntaxhighlight><br />
* Generate the body.<br />
* Add the initialzation code:<br />
<syntaxhighlight>procedure TfrmMain.StartNewGame;<br />
begin<br />
// Clean up the previous game<br />
pegpaint.Free;<br />
pegsol.Free;<br />
<br />
// Start with a new game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
end;</syntaxhighlight><br />
<br />
Now that the initialization code is created, it must be executed. A logical time to do this is as soon as the form is created (i.e. the application is started). For this another event is available to us: the FormCreate event. We can create it in two ways: find the OnFormCreate event in the Object Inspector and click on the '...' button. But another way to generate it is double clicking the form itself.<br />
* Open the form editor (press F12).<br />
* Double click somewhere in a free area on the form; do nót click on the paintbox.<br />
[[Image:tutpeg_form_create.png]]<br />
* Add the call to the StartNewGame procedure:<br />
<syntaxhighlight>procedure TfrmMain.FormCreate(Sender: TObject);<br />
begin<br />
StartNewGame<br />
end;</syntaxhighlight><br />
<br />
Now that the game and paint objects are created at program start, they are no longer required in the OnPaint method.<br />
* Locate procedure ''procedure TfrmMain.pbPegPaint(Sender: TObject);''<br />
* Remove the local variables and all code except for the line that actualle does the painting.<br />
<syntaxhighlight>procedure TfrmMain.pbPegPaint(Sender: TObject);<br />
begin<br />
// Paint the board<br />
pegpaint.Repaint(pbPeg.Width, pbPeg.Height);<br />
end;<br />
</syntaxhighlight><br />
* Run the program and see what happens.<br />
<br />
== Intermezzo==<br />
So far we have focused on building a program to play the classic solitaire game in the 7x7 grid. Is it now possible to create smaller or bigger grids for other type games? Let's test this.<br />
* In the main form locate the StartNewGame method.<br />
* Change the line ''pegsol := TPegSolitaire.Create(7);'' to ''pegsol := TPegSolitaire.Create(5);''. So a smaller board with 5x5 squares is created.<br />
* Run the program and see what happens.<br />
<br />
As we can see on screen we still see a 7x7 matrix! This is the result of using magic numbers, which we should have avoided (see http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Unnamed_numerical_constants).<br />
<br />
Using magic numbers equals disaster: it's a matter of ''when'', not ''if'' a program will fail.<br />
* Open PegSolPainter.<br />
* Locate the procedure ''TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);''.<br />
In there we see the number of 7 a couple of times. This the magic number that plays tricks on us. In the calculation of the cell width and height, we need the size of the board as stored in the pegsol game variable. And the same goes for the iRow and iCol loops. So let's fix this once and for all:<br />
<syntaxhighlight> // Calculate the width of each cell to accomodate for all cells<br />
CellWidth := pCanvasWidth div pegsol.Size;<br />
CellHeight := pCanvasHeight div pegsol.Size;<br />
<br />
// Draw boxes for all cells<br />
for iRow := 1 to pegsol.Size do<br />
for iCol := 1 to pegsol.Size do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
// And now draw the cell<br />
Canvas.Rectangle(CellArea);<br />
end;</syntaxhighlight><br />
<br />
There's a caveat here: pegsol doesn't have a publicly accessible Size variable. And this is how it should be: all variables in a class should be private. The way to access those private '''''values''''' is via functions or properties (the interface of the class). For this simple value we will use a property.<br />
* Open PegDatastructures.<br />
* Rename the private variable in TPegSolitaire to ''FSize''.<br />
* Add a public read only property to the class: property Size: TCellNums read FSize;<br />
The class will now look like this:<br />
<syntaxhighlight> TPegSolitaire = class<br />
private<br />
FSize: TCellNums;<br />
PegCells: TPegCells;<br />
function GetCell(const pRow, pCol: TCellNums): TCellType;<br />
procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);<br />
<br />
public<br />
constructor Create(const pSize: TCellNums);<br />
property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;<br />
property Size: TCellNums read FSize;<br />
end;<br />
</syntaxhighlight><br />
It's common practice to prefix variables that are accessed via properties with a letter ''F''. We've made the public access to the size property read-only, because it should never be changed, once the game is started. The only place where the private variable should get it's final value is in the Create constructor.<br />
* Locate the constructor.<br />
* Change ''Size := pSize;'' to ''FSize := pSize;'' (if you don't you'll get a compilation error).<br />
* And while we're at it, the initialization needs a minor fix as well. There is no need to initialize cells we are not going to use. So the constructor should look like this (''C_MAX'' is replaced with ''Size''):<br />
<syntaxhighlight>constructor TPegSolitaire.Create(const pSize: TCellNums);<br />
var iRow,iCol: integer;<br />
begin<br />
FSize := pSize;<br />
for iRow := 1 to Size do<br />
for iCol := 1 to Size do<br />
Cell[iRow,iCol] := ctNoAccess;<br />
end;</syntaxhighlight><br />
<br />
We are now ready to test the program and see if it now draws a nice 5x5 matrix.<br />
* Run the program and see what happens (a 5x5 matrix is drawn).<br />
<br />
Now that the basics of the game are in place, it's time to flesh out the gui.<br />
<br />
==Let's get artistic==<br />
So far so good. We now have a game class, a supporting paint class and an unimpressive checker board on our form (well, sort of). In our solitaire game a cell can have 3 states: not accessible, empty or occupied. For each cell type we want a different graphic representation. Let's get to it.<br />
* In the editor open PegSolPainter (the unit with the paint class for our board).<br />
* Locate the Repaint method.<br />
* Locate the line where the cell is drawn: ''Canvas.Rectangle(CellArea);''.<br />
Obviously we need to make a change here. It depends on the cell state what needs to be drawn (not accessible cell, empty cell or peg cell). The case... operation comes to the rescue.<br />
* Change the drawing of cells like so:<br />
<syntaxhighlight> // Draw boxes for all cells<br />
for iRow := 1 to pegsol.Size do<br />
for iCol := 1 to pegsol.Size do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
<br />
// And now draw the cell based on the cell's contents<br />
case pegsol.Cell[iRow,iCol] of<br />
<br />
ctNoAccess: // Draw cells that are not accessible<br />
begin<br />
Canvas.Brush.Color := clGray;<br />
Canvas.Rectangle(CellArea);<br />
end;<br />
<br />
ctEmpty: // Draw cells that are currently empty<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea);<br />
end;<br />
<br />
ctPeg: // Draw cells that are occupied<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea); // Erase the background first<br />
Canvas.Brush.Color := clGreen; <br />
Canvas.Ellipse(CellArea); // Draw the pegs as green circles<br />
end;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
We could run the program at this stage (just try it), but it will only display a boring 5x5 grayish grid. This is because we didn't define any game setup yet. Let's fix this first.<br />
<br />
* Open the main form source.<br />
* Locate the StartNewGame method.<br />
* Create a 7x7 game instead of 5x5.<br />
* Initialize a couple of cells. For example:<br />
<syntaxhighlight>procedure TfrmMain.StartNewGame;<br />
begin<br />
// Clean up the previous game<br />
pegpaint.Free;<br />
pegsol.Free;<br />
<br />
// Start with a new 7x7 game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// Initialize some cells<br />
pegsol.Cell[3,4] := ctEmpty;<br />
pegsol.Cell[4,2] := ctPeg;<br />
pegsol.Cell[4,3] := ctPeg;<br />
pegsol.Cell[4,4] := ctEmpty;<br />
pegsol.Cell[4,5] := ctPeg;<br />
pegsol.Cell[4,6] := ctPeg;<br />
pegsol.Cell[5,4] := ctEmpty;<br />
end;</syntaxhighlight><br />
* Run the program (it'll look something like the image below).<br />
[[Image:tutpeg_first_pegs.png]]<br />
<br />
==Populate the board==<br />
As we know a classic solitaire board should look something like this:<br />
<br />
[[Image:tutpeg_classic.png]]<br />
<br />
And if we were to populate all cells individually, that would result in a lot of code. What if we could intialize the game by just passing it some text that would symbolically describe the board? Something like this:<br />
<syntaxhighlight> // Initialize the cells to the classic game<br />
pegsol.InitializeBoard( ' ooo ' + LineEnding +<br />
' ooo ' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
'ooo.ooo' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
' ooo ' + LineEnding +<br />
' ooo ' );</syntaxhighlight><br />
#'''o''' is an occupied cell.<br />
#'''.''' is an empty but playable cell.<br />
#The spaces indicate cells that are not accessible.<br />
<br />
Let's assume this is going to work and create a method in the TPegSelitaire class that can handle this.<br />
* Let's be optimistic (also called 'Top down design') and add the above code to TfrmMain.StartNewGame:<br />
<syntaxhighlight>procedure TfrmMain.StartNewGame;<br />
begin<br />
// Clean up the previous game<br />
pegpaint.Free;<br />
pegsol.Free;<br />
<br />
// Start with a new 7x7 game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// Initialize the cells to the classic game<br />
pegsol.InitializeBoard( ' ooo ' + LineEnding +<br />
' ooo ' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
'ooo.ooo' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
' ooo ' + LineEnding +<br />
' ooo ' );<br />
end;</syntaxhighlight><br />
* Open the PegDatastructures sourcefile.<br />
* Add this procedure to the public section of the class: ''InitializeBoard(const pBoard: ansistring);''<br />
<syntaxhighlight> public<br />
constructor Create(const pSize: TCellNums);<br />
procedure InitializeBoard(const pBoard: ansistring);<br />
<br />
property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;<br />
property Size: TCellNums read FSize;</syntaxhighlight><br />
* Generate the body of InitializeBoard (Ctrl-Shift-C...).<br />
<br />
Now what does this procedure actually need to do? It must split the textstring into seperate lines and then process those lines, Because we used ''LineEnding'' tokens to separate the lines, we can use a ''TStringList'' class to split them.<br />
* Code ''InitalizeBoard'' like so:<br />
<syntaxhighlight>procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);<br />
var lst : TStringList;<br />
iRow,iCol: integer;<br />
s : string;<br />
begin<br />
// Create a list with the board text in it. This will split all lines<br />
// into individual lines, because of the LineEnding 'splitter'.<br />
lst := TStringList.Create;<br />
lst.Text := pBoard;<br />
<br />
// Process all lines one at a time<br />
for iRow := 0 to lst.Count-1 do<br />
if iRow < Size then // Make sure there is no overflow in the rows<br />
begin<br />
// Process a single line of text<br />
s := lst[iRow];<br />
for iCol := 1 to length(s) do<br />
if iCol <= Size then // Make sure there is no overflow in the columns<br />
case s[iCol] of<br />
' ': Cell[iRow+1,iCol] := ctNoAccess;<br />
'.': Cell[iRow+1,iCol] := ctEmpty;<br />
'o': Cell[iRow+1,iCol] := ctPeg;<br />
end;<br />
end;<br />
<br />
// Clean up the list<br />
lst.Free;<br />
end;</syntaxhighlight><br />
#a TStringList is used as a buffer. This works because we used LineEnding as a separator between all lines.<br />
#There are ''Count'' number of lines, but they are numbered 0..Count-1. Our cells are numbered starting with 1. That's why you see ''iRow+1'' in the cell assignments.<br />
<br />
The above procedure contains a lot of extra variables and shouldn't be that difficult to understand. It's possible to reduce the procedure to the bare minimum like so:<br />
<syntaxhighlight>procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);<br />
var iRow,iCol: integer;<br />
begin<br />
with TStringList.Create do<br />
begin<br />
Text := pBoard;<br />
for iRow := 0 to Min(Count-1, Size-1) do<br />
for iCol := 1 to Min(length(Strings[iRow]),Size) do<br />
case Strings[iRow][iCol] of<br />
' ': Cell[iRow+1,iCol] := ctNoAccess;<br />
'.': Cell[iRow+1,iCol] := ctEmpty;<br />
'o': Cell[iRow+1,iCol] := ctPeg;<br />
end;<br />
Free;<br />
end;<br />
end;</syntaxhighlight><br />
This procedure does exactly the same but it uses the handy feature that you can use the With statement together with dynamically created objects. For this procedure to work add the Math unit to the uses section.<br />
<br />
* Run the program. All cells are now populated like in the classic Peg Solitaire game.<br />
<br />
==Events revisited==<br />
It doesn't look too impressive yet, but we have done quite a lot. The main thing missing at the moment is being able to leap cells to remove them from the board. And for that we are going to use the mouse. But how? Let's investigate the possibilities.<br />
<br />
* Drop a [[TMemo]] on the form, to the left of the paint box pbPeg (it's on the ''Standard'' tab of the component palette).<br />
* In the Object Inspector change Align to ''alClient''. It will occupy all remaining space that is left of the paintbox.<br />
* Change BorderSpacing.Around to ''4''.<br />
* Clear the Lines property (click on the 3-dotted button and erase the text). The result should look like this:<br />
[[Image:tutpeg_with_memo.png]]<br />
* Select the paintbox pbPeg.<br />
* In the Object Inspector select the Events tab.<br />
There are 2 events that are interesting to us: OnMouseDown and OnMouseUp. Let's see what happens whith the OnMouseDown event.<br />
* In the Object Inspector select the OnMouseDown event.<br />
* Click on the 3-dotted button (this generates the body of the event procedure).<br />
<br />
The header of the generated procedure contains a number of variables. Two of them are X and Y. They contain the position on the paintbox where the mouse button was pressed. Let's make them visible by adding some information to the memo component we've placed on the form.<br />
* Add the following statement to the procedure; it adds a formatted line to the memo displaying the position where the mouse button was pressed:<br />
<syntaxhighlight>Memo1.Append( format('Mouse down @ %dx%d',[X,Y]) )</syntaxhighlight><br />
* Run the program (press F9).<br />
* Click on the game board at several different locations. The memo box will display where the mouse button was pressed.<br />
* End the program.<br />
<br />
Obviously we cannot use the 'raw' X and Y coordinates; they are too high for our 7x7 game board. They must be translated to the cell's row and column coordinates. For that we need a function that translates a X/Y coordinate to a cell coordinate. For that we need the width and height of the paintbox, the number of cells in the grid and the width/height of an individual cell. The one place where all this comes together is... the paint class. So that is the obvious place to do this calculation.<br />
* Open the PegDatastructures file.<br />
* Add the ''TCellPosition'' record type to store cell coordinates:<br />
<syntaxhighlight>type<br />
TCellNums = 1..C_MAX;<br />
TCellType = (ctNoAccess, ctEmpty, ctPeg);<br />
TPegCells = array[TCellNums, TCellNums] of TCellType;<br />
<br />
TCellPosition = record<br />
Row: TCellNums;<br />
Col: TCellNums;<br />
end;</syntaxhighlight><br />
<br />
* Open the PegSolPainter class file.<br />
* Add the public function CanvasXYtoCell(...) to the class:<br />
<syntaxhighlight> public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
<br />
procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
function CanvasXYtoCell(const pX, pY: integer): TCellPosition;</syntaxhighlight><br />
* Generate the bode (Ctrl-Shift-C).<br />
* Add the following code:<br />
<syntaxhighlight>function TPegSolPainter.CanvasXYtoCell(const pX, pY: integer): TCellPosition;<br />
begin<br />
result.Col := (pX div CellWidth) + 1;<br />
result.Row := (pY div CellHeight) + 1;<br />
end;</syntaxhighlight><br />
The above code maps the large X/Y coordinate on the actual cell's row/col numbers. There is one problem though: the CellWidth and CellHeight are not available. They are intermediate results in the Repaint procedure and not stored. Let's fix that as well.<br />
* Add ''CellWidth'' and ''CellHeight'' as private variables to the ''TPegSolPainter'' class:<br />
<syntaxhighlight> TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
CellWidth : integer;<br />
CellHeight : integer;</syntaxhighlight><br />
* Remove the variables ''CellWidth'' and ''CellHeight'' from the variable list in the Repaint procedure:<br />
<syntaxhighlight>procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
var<br />
iRow, iCol : TCellNums;<br />
CellArea : TRect;<br />
begin</syntaxhighlight><br />
<br />
Now we can modify the procedure where the mouse down coordinate was processed. With the new function the X/Y position can now be translated to an actuel cell coordinate.<br />
* Open the main form sourcefile.<br />
* Locate the ''pbPegMouseDown'' procedure.<br />
* Change the code to:<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
var CellRC: TCellPosition;<br />
begin<br />
CellRC := pegpaint.CanvasXYtoCell(X,Y);<br />
Memo1.Append( format('Mouse down @ %dx%d',[CellRC.Row, CellRC.Col]) )<br />
end;</syntaxhighlight><br />
<br />
Let's do the same for the OnMouseUp event:<br />
* Generate the procedure body for the OnMouseUp event.<br />
* Add this code:<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
with pegpaint.CanvasXYtoCell(X,Y) do<br />
Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) )<br />
end;</syntaxhighlight><br />
Note that this method does exactly the same as the OnMouseDown, but using the ''with'' construct saves us the trouble of declaring a local variable (less is more...).<br />
<br />
Let's test this:<br />
* Run the program.<br />
* Click on the upper left most cell and hold the mouse down.<br />
* Move the mouse cursor to another cell.<br />
* Release the mouse button.<br />
Every time you press the mouse button and release it, the exact cell where the mouse button was pressed or released is displayed in the memo box.<br />
<br />
==Gluing things together==<br />
This program works a bit like the calculator program:<br />
#The user selects a peg to move by pressing the mouse button.<br />
#The user moves the peg over another peg to an empty cell (leaps).<br />
#The user release the mouse button to execute the leap.<br />
Here we do not remember a number but a cell (-position). So as soon as the user presses the mouse button on a cell, we must store that position somewhere.<br />
* Open the main form's sourcefile.<br />
* Add a variable ''FromCell'' the the form's private variables.<br />
<syntaxhighlight> private<br />
{ private declarations }<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
FromCell: TCellPosition; // The peg that is going to leap</syntaxhighlight><br />
* Locate the OnMouseDown event handler.<br />
* Add the following line to that procedure:<br />
<syntaxhighlight>FromCell := pegpaint.CanvasXYtoCell(X,Y);</syntaxhighlight><br />
<br />
Now as soon as the user releases the mouse button, we must execute the leap (verify if it's ok and update the board). Let's be optimistic (remember the Top Down remark?) and assume there is a procedure that does that for us: Leap(<FromCell>, <ToCell>).<br />
* Locate the OnMouseUp event handler and add the following line:<br />
<syntaxhighlight>pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));</syntaxhighlight><br />
''FromCell'' we already populated when the mouse button was pressed. The target cell is simply calculated by calling pegpaint.CanvasXYToCell(...). We could introduce a local variable for that, but it's not really necessary.<br />
* Add the following line to the procedure as well:<br />
<syntaxhighlight>pbPeg.Repaint;</syntaxhighlight><br />
This will force the paintbox to repaint itself, so all updated cells show the correct contents.<br />
<br />
The complete OnMouseDown method now looks like this:<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
with pegpaint.CanvasXYtoCell(X,Y) do<br />
Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) );<br />
pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));<br />
pbPeg.Repaint;<br />
end;</syntaxhighlight><br />
<br />
Before going into the Leap procedure, a bit of extra functionality is introduced. Remember that we can retrieve the state of a cell (empty, peg or no access) via the Cell property of TPegSolitaire? For example ''pegsol.Cell[2,2]'' would give us the state of the cell in row 2 and column 2. But in the mean time we have introduced a new datastructure: TCellPosition. So for example if we want to get the state of the ''FromCell'' we would need the following awkward statement:<br />
<syntaxhighlight>state := pegsol.Cell[ FromCell.Row, FramCell.Col ]</syntaxhighlight><br />
It would be nice if we could write something like:<br />
<syntaxhighlight>state := pegsol.GetCell(FromCell)</syntaxhighlight><br />
Let's add this to our game class.<br />
* Open sourcefile PegDatastructures.<br />
* Add the private function: ''GetCell(const pPosition: TCellPosition): TCellType;''<br />
<syntaxhighlight> TPegSolitaire = class<br />
private<br />
FSize: TCellNums;<br />
PegCells: TPegCells;<br />
<br />
function GetCell(const pRow, pCol: TCellNums): TCellType;<br />
procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);<br />
function GetCell(const pPosition: TCellPosition): TCellType;</syntaxhighlight><br />
There now are two functions with the name GetCell, but the parameter list is different. So Lazarus knows exactly when to call which function.<br />
* Generate the body of the function (Ctrl-Shift-C).<br />
* Insert the following code:<br />
<syntaxhighlight>result := Cell[pPosition.Row, pPosition.Col]</syntaxhighlight><br />
<br />
Back to the main storyline. We have introduced a procedure ''Leap(...)'', but it doesn't exist yet. It is a method that changes the game board, so the place to put it is the TPegSolitaire class.<br />
* Open sourcefile PegDatastructures.<br />
* Add procedure Leap to the public section of the TPegSolitaire class.<br />
<syntaxhighlight> public<br />
constructor Create(const pSize: TCellNums);<br />
procedure InitializeBoard(const pBoard: ansistring);<br />
procedure Leap(const pFromCell, pToCell: TCellPosition);<br />
</syntaxhighlight><br />
* Generate the procedure body (Ctrl-Shift-C).<br />
<br />
Now this procedure does all the hard work: check if the cells are valid and if the leap is at all possible. This is not difficult, but the number of checks is quite extensive for something that looks simple. The code is fairly self-explanatory and if not, hopefully the comments explain what is going on. Note that the procedure uses the GetCell(<cellposition>) function we created earlier.<br />
<syntaxhighlight>procedure TPegSolitaire.Leap(const pFromCell, pToCell: TCellPosition);<br />
var dx, dy: integer;<br />
JumpedCell: TCellPosition;<br />
begin<br />
// Verify that the start cell is occupied and the target cell is empty<br />
// If not, leave the procedure via the EXIT.<br />
if (GetCell(pFromCell) <> ctPeg) or<br />
(GetCell(pToCell) <> ctEmpty) then<br />
EXIT;<br />
<br />
// Calculate the horizontal and vertical distance between the cells<br />
dx := abs(pFromCell.Col - pToCell.Col);<br />
dy := abs(pFromCell.Row - pToCell.Row);<br />
<br />
// A valid move has one direction equal to zero and the other equal to 2<br />
if ((dx = 2) and (dy = 0))<br />
or ((dx = 0) and (dy = 2)) then<br />
begin<br />
// Determine the position of the jumped cell; it's in the middle<br />
JumpedCell.Col := (pFromCell.Col + pToCell.Col) div 2;<br />
JumpedCell.Row := (pFromCell.Row + pToCell.Row) div 2;<br />
<br />
// Final check: is there a peg in the jumped cell?<br />
if GetCell(JumpedCell) = ctPeg then<br />
begin<br />
// Jump: clear the FromCell, empty the jumped cell and populate the ToCell<br />
Cell[pFromCell.Row, pFromCell.Col] := ctEmpty;<br />
Cell[JumpedCell.Row, JumpedCell.Col] := ctEmpty;<br />
Cell[pToCell.Row, pToCell.Col] := ctPeg;<br />
end;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
Believe it or not but we now have a working Peg Solitaire game program!<br />
* Run the program.<br />
* Press the mouse button on one peg cell.<br />
* Leap over another peg cell to an empty cell.<br />
* Release the mouse button.<br />
* Repeat...<br />
<br />
==Cleaning up==<br />
On the form we still have the memo component that displays mouse clicks. We don't really need it anymore.<br />
* Delete the memo from the main form.<br />
* Delete the memo updates from the MouseDown and MouseUp events.<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
FromCell := pegpaint.CanvasXYtoCell(X,Y);<br />
end;<br />
<br />
procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y) );<br />
pbPeg.Repaint;<br />
end;</syntaxhighlight><br />
<br />
* Open the form editor (F12).<br />
* Select the paintbox pbPeg<br />
* In the Object Inspector change property Align to ''alClient'' (look on the Properties tab).<br />
* Remove the unit ''stdctrls'' from the uses list. The compiler would othwerwise give a hint in the Messages window that it isn't used (it was automatically added when we dropped the memo component on the form).<br />
<br />
===What's next?===<br />
To make it a fully functional game we need a way to change the board without recompiling the application. And the graphics could be improved as well. Let's address the first issue first.<br />
<br />
==Creating more board layouts==<br />
It would be nice if we could add more boards to our game without recompiling the application. The obvious place would be to store the board layouts in external textfiles. The user can then start a new game by opening one of the textfiles. The fun thing is, it's easy to add because we've already done most of the hard work.<br />
First let's make two board layouts.<br />
* From the menu choose ''File/New...''; this opens a pop up dialog.<br />
* Choose the option Module/Text and click OK.<br />
* Type the following text (it should look familiar :-) ):<br />
<syntaxhighlight> ooo<br />
ooo<br />
ooooooo<br />
ooo.ooo<br />
ooooooo<br />
ooo<br />
ooo</syntaxhighlight><br />
* From the menu choose File/Save.<br />
* Save the file in the projects' bin folder as ''classic.txt''.<br />
<br />
Add another file:<br />
* From the menu choose ''File/New...''.<br />
* Choose the option Module/Text and click OK.<br />
* Type the following text:<br />
<syntaxhighlight> ...<br />
.o.<br />
..ooo..<br />
.ooooo.<br />
ooooooo<br />
...<br />
...</syntaxhighlight><br />
* From the menu choose File/Save.<br />
* Save the file in the bin folder as ''triangle.txt''.<br />
<br />
There are many ways to create and open new boards. In this tutorial we'll use the menu driven approach (a thorough description of the menu is something for another tutorial).<br />
<br />
* Open the main form's editor.<br />
* Select the [[TMainMenu]] control from the Standard components palette.<br />
* Click on the form (it doesn't matter if you 'hit' the paintbox). An icon is displayed showing that the menu is now availabe.<br />
* Double click on the icon with label MainMenu1. This opens the menu editor with one item aldready available, labeled ''New Item1''.<br />
* In the Object Inspector change Caption to ''&File'' (include the ampersand; it's used to draw the underscore symbol when you press Alt to access the main menu).<br />
* Right click on the File item in the Menu Editor and select ''Create Submenu'' from the pop up window.<br />
* Change the Caption to ''E&xit'' (this adds a familiar application exit button).<br />
* Right click on the File item.<br />
* Select ''Insert New Item (after)'' from the pop up menu.<br />
* Change the caption of that new menu to ''&Game''.<br />
* Right click on the Game menu and select ''Create Submenu'' from the pop up..<br />
* Change the caption to ''Load from file...''.<br />
The menu will now look something like this:<br />
<br />
[[Image:tutpeg_mainmenu.png]]<br />
<br />
The menu is also visible at the top of the main form.<br />
<br />
* Click on the ''File'' menu item; this opens the submenu and ''Exit'' is visible.<br />
* Double click on ''Exit''. As you might have guessed this creates an event handler. In other words the procedure that gets executed as soon as the user clicks on this menu item.<br />
* Add the following not too complex code to the generated procedure:<br />
<syntaxhighlight>Close</syntaxhighlight><br />
This will close the main form. And because it is the only form of our application it will end the application.<br />
<br />
Okay now it's time to look at how to open the board files. There are standard components and menu's available to do this, but let's do it the manual way.<br />
* Open the main form's editor.<br />
* In the component palette select the ''[[Dialogs tab]]''.<br />
* Select the [[TOpenDialog]] component (click on it).<br />
* Click on the main form (anywhere). A OpenDialog1 component is now visible. This component provides us with a kind of explorer pop up with which we can select files. The main form now looks like this:<br />
<br />
[[Image:tutpeg_mainform.png]]<br />
<br />
* In the Object Inspector click on the Filter property and then on the 3-dotted button. This pops up the filter dialog. This is the place where we tell the file dialog what files we allow the user to select.<br />
* We only want to see text files so add this filter line: ''Peg Solitaire textfiles (*.txt) | *.txt'' (see image below).<br />
* Press OK.<br />
<br />
[[Image:tutpeg_filterdialog.png]]<br />
<br />
* In the Object Inspector change Options.ofFileMustExist to ''true''. This forces the dialog to only allow existing files to be selected.<br />
<br />
So far so good. Now to actually open a file when the menu item is selected:<br />
* On the main form click on the Game menu item. This opens the drop down menu.<br />
* Click on ''Load from file...''. As expected this generates an.... event handler.<br />
* Add the following code to the Event handler:<br />
<syntaxhighlight> if OpenDialog1.Execute then<br />
ShowMessage(OpenDialog1.FileName);</syntaxhighlight><br />
<br />
Remember: small steps. We have now added a menu to the form to exit the application and to load a game board set up from disk. We haven't actually coded the loading part, but let's first try the code we have so far.<br />
* Run the application.<br />
* From the menu choose ''Game/Load from file...''.<br />
* Select ''triangle.txt'' and press Open.<br />
* A window pops up that shows us the selected filename.<br />
* End the program.<br />
<br />
Now let's finish the board loading code. The code is pretty self-explanatory and reuses some of the procedures we have created so far.<br />
* Open the main form's editor.<br />
* From the menu choose ''Game/Load from file...''. This will open the event handler.<br />
* Replace the code with this:<br />
<syntaxhighlight>procedure TfrmMain.MenuItem4Click(Sender: TObject);<br />
begin<br />
// Open the pop up dialog<br />
if OpenDialog1.Execute then<br />
begin<br />
// Start with a new empty board<br />
StartNewGame;<br />
<br />
// Dynamically create a stringlist to load the board layout<br />
with TStringList.Create do<br />
begin<br />
// Load the board layout from the textfile<br />
LoadFromFile(OpenDialog1.FileName);<br />
<br />
// Initialize the board with the file's contents<br />
pegsol.InitializeBoard(Text);<br />
<br />
// Clean up the stringlist<br />
Free<br />
end;<br />
<br />
// After loading the new board update the paintbox<br />
pbPeg.Repaint;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
And that's all there is to it!<br />
<br />
Run the program, open a board set up file and play the game!<br />
<br />
==Eye candy==<br />
We now have a working Peg Solitaire application. There's still plenty of room for improvent, but the game itself is working nicely. The last part of this tutorial is about improving the looks of the game. Sure the game works, but a bit more detail in the graphics would be nice. And that's pretty easy to add. All we need are three images for the cell types, update the painter class and in the main form, tell the painter to use the images.<br />
So let's add the three images to the painter class:<br />
* Open the PegSolPainter sourcefile.<br />
* Add the 3 images to the private variables section:<br />
<syntaxhighlight> TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
CellWidth : integer;<br />
CellHeight : integer;<br />
<br />
ImgNoAccess: TPicture;<br />
ImgEmpty : TPicture;<br />
ImgPeg : TPicture;</syntaxhighlight><br />
* Add a procedure to the public section for loading the images.<br />
<syntaxhighlight> public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
function CanvasXYtoCell(const pX, pY: integer): TCellPosition;<br />
procedure LoadImage(const pCellType: TCellType; const pFilename: string);</syntaxhighlight><br />
* Generate the body for the procedure.<br />
* Add the code:<br />
<syntaxhighlight>procedure TPegSolPainter.LoadImage(const pCellType: TCellType; const pFilename: string);<br />
<br />
procedure UpdateImage(var pImg: TPicture; const pNewImage: TPicture);<br />
begin<br />
pImg.Free;<br />
pImg := pNewImage<br />
end;<br />
<br />
var pic: TPicture;<br />
begin<br />
// Make sure the file exists<br />
if FileExists(pFilename) then<br />
begin<br />
// First load the picture in a local variable<br />
pic := TPicture.Create;<br />
pic.LoadFromFile(pFilename);<br />
<br />
// Now update the required image based on the pCelltype parm<br />
case pCelltype of<br />
ctNoAccess: UpdateImage(ImgNoAccess, pic);<br />
ctEmpty : UpdateImage(ImgEmpty, pic);<br />
ctPeg : UpdateImage(ImgPeg, pic);<br />
end;<br />
end;<br />
end;</syntaxhighlight><br />
The code again is self-explanatory. Note that we use a short local procedure to update an image. If we wouldn't do that then the case statement (''case pCellType of'') would be more complex (i.e. contain more redundant code). Now each cell type only needs one line to update the right image.<br />
<br />
We've added the pictures to the class. Now we must use them and that of course happens in the Repaint procedure. For each celltype we check whether an image is available. And if so, use that image instead of the plain drawing.<br />
* Locate the Repaint method.<br />
* In the case statement, change the ctNoAccess, ctEmpty and ctPeg sections to:<br />
<syntaxhighlight> // And now draw the cell based on the cell's contents<br />
case pegsol.Cell[iRow,iCol] of<br />
<br />
ctNoAccess: // Draw cells that are not accessible<br />
if not assigned(ImgNoAccess) then<br />
begin<br />
Canvas.Brush.Color := clGray;<br />
Canvas.Rectangle(CellArea);<br />
end<br />
else with ImgNoAccess do<br />
Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));<br />
<br />
<br />
ctEmpty: // Draw cells that are currently empty<br />
if not assigned(ImgEmpty) then<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea);<br />
end<br />
else with ImgEmpty do<br />
Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));<br />
<br />
<br />
ctPeg: // Draw cells that are occupied<br />
if not assigned(ImgPeg) then<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea); // Erase the background first<br />
Canvas.Brush.Color := clGreen;<br />
Canvas.Ellipse(CellArea);<br />
end<br />
else with ImgPeg do<br />
Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));<br />
<br />
end;</syntaxhighlight><br />
<br />
In the main form locate the StartNewGame method and add the image loading code to the painter class.<br />
* Open the main form editor.<br />
* Locate method ''StartNewGame''.<br />
* Add LoadImage statements after the creation of the ''pegpaint'' object:<br />
<syntaxhighlight> // Start with a new 7x7 game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
pegpaint.LoadImage(ctNoAccess, 'tutpeg_cellforbidden.jpg');<br />
pegpaint.LoadImage(ctEmpty, 'tutpeg_cellempty.jpg');<br />
pegpaint.LoadImage(ctPeg, 'tutpeg_celloccupied.jpg');</syntaxhighlight><br />
<br />
Make sure that the above images are downloaded from below and put in the program's ''bin'' folder, or better yet, create your own images.<br />
Note: to download these images, click on them first thén download them.<br />
<br />
[[Image:tutpeg_cellforbidden.jpg]]<br />
[[Image:tutpeg_cellempty.jpg]]<br />
[[Image:tutpeg_celloccupied.jpg]]<br />
<br />
* Now test the solitaire game and hopefully it looks something like the image at the beginning of this tutorial...<br />
<br />
==Final tweaks==<br />
<br />
Most programs have their own icons that are displayed in menu's and in explorer windows. It's easy to add your own custom icon to an application.<br />
* From the menu choose ''Project/Project Options...''.<br />
* Click on ''Project Options'' in the pop up dialog (right at the top). This will display the window with the application icon. <br />
* Click on ''Load Icon'' and load your icon of choice; or download the marble that comes with this tutorial:<br />
[[Image:tutpeg_marble.png]]<br />
* Press OK.<br />
The application will now have a nice marble icon in the form's title bar and in explorer windows.<br />
<br />
[[Category:Tutorials]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Petris&diff=114879Petris2018-01-04T21:03:20Z<p>Eny: Page creation for Petris</p>
<hr />
<div>{{Petris}}<br />
<br />
Petris is yet another Tetris clone.<br />
<br />
==Description==<br />
<br />
Petris is a (long overdue) basic implementation of Tetris. Provided for demonstration purposes only.<br />
The game is Free Software, and all the artwork are under GPL compatible creative-commons licenses.<br />
<br />
The game contains two components: an engine that holds all the game logic. And a front end built with out-of-the box LCL components. So it will be quite easy to build another front end (e.g. with SDL) and keep using the same engine.<br />
<br />
The game/GUI (not the engine) reguires Lazarus 1.8.0 (or higher).<br />
<br />
==Screenshots==<br />
<br />
[[Image:petris.png]]<br />
<br />
==Download==<br />
<br />
The sources for Petris can be downloaded from https://github.com/eny-fpc/petris.<br />
<br />
[[Category:Games]]</div>Enyhttps://wiki.freepascal.org/index.php?title=File:petris.png&diff=114878File:petris.png2018-01-04T20:40:12Z<p>Eny: Example of Petris - yet another (simple) Tetris clone</p>
<hr />
<div>Example of Petris - yet another (simple) Tetris clone</div>Enyhttps://wiki.freepascal.org/index.php?title=Peg_Solitaire_tutorial&diff=106653Peg Solitaire tutorial2016-12-30T15:14:07Z<p>Eny: Typo</p>
<hr />
<div>This tutorial is the second Lazarus tutorial that aims at introducing the basics of Lazarus application development. It's best to start this tutorial after having finished the first one ([[Howdy World (Hello World on steroids)]]). This tutorial exlpains a bit about how to work with graphics and how to make a program modular. The final product of this tutorial is a basic but working version of the Peg Solitaire game ([http://en.wikipedia.org/wiki/Peg_solitaire]). If all goes well in the end it will look something like this:<br />
<br />
[[Image:tutpeg_solitaire.png]]<br />
<br />
==Start the project==<br />
As mentioned in the previous tutorial it's best to start with a clean, separate directory for each project. A quick recap:<br />
* Create a new directory for this game.<br />
* Start a new Application (Project/New Project... and select Application).<br />
* Save the project as PegSolitaire.<br />
* Save the main form as ufrmMain.<br />
* In the object inspector change the form's name to ''frmMain''.<br />
* Change the caption to ''Lazarus Peg Solitaire''.<br />
* In the project options insert ''bin\'' in front of the target filename (or ''bin/'' for Linuxes).<br />
<br />
And extra for this project:<br />
* Open the project options dialog (Shift-Ctrl-F11).<br />
* Select Compiler ''Options/Code Generation''.<br />
* Enable Range checking and Overflow error checking (see image belows).<br />
[[Image:tutpeg_compiler_options.png]]<br />
<br />
==First steps==<br />
It's always a good idea to seperate gui related code from data structure definitions. So the first step will be the creation of a separate unit for our Solitaire data structures.<br />
* From the menu choose ''File/New Unit''.<br />
* Save the unit as ''PegDatastructures.pas'' (and press the lowercase button that pops up).<br />
<br />
The basic elements of a Peg Solitaire board are the marbles, the board structure and the empy places. We'll simulate this by a simple matrix that has cells of a certain type (empty, occupied and not accessible). And we'll encapsulate all this in a class that handles all the data manipulation.<br />
* Add the following code to the PegDatastructures unit (after the classes in the ''uses'' section and before the ''implementation'' section):<br />
<syntaxhighlight>const<br />
C_MAX = 7; // Max board size: 7x7<br />
<br />
type<br />
TCellNums = 1..C_MAX;<br />
TCellType = (ctNoAccess, ctEmpty, ctPeg);<br />
TPegCells = array[TCellNums, TCellNums] of TCellType;<br />
<br />
TPegSolitaire = class<br />
private<br />
Size: TCellNums;<br />
PegCells: TPegCells;<br />
<br />
public<br />
constructor Create(const pSize: TCellNums);<br />
end;</syntaxhighlight><br />
<br />
It's fair to assume that other code that is going to use this class needs access to the cells contents (i.e. PegCells). The way to handle this is either by defining a set of functions to access the cells or define a so called array property. Let's go for the latter approach and add the following line to the public section of the TPegSolitaire class:<br />
<syntaxhighlight>property Cell[const pRow, pCol: TCellNums]: TCellType;</syntaxhighlight><br />
* Position the text cursor on the constructor line.<br />
* Press Ctrl-Shift-C: the IDE generates the constructor body (as we expected) but it also generates the empty bodies for the 2 methods that give us access to ''PegCells'' via the ''Cells'' property.<br />
* GetCell retrieves data from the private variable PegCells. Add the following code to the function:<br />
<syntaxhighlight>result := PegCells[pRow,pCol]</syntaxhighlight><br />
* SetCell populates the PegCells array with data. Add the following code to the procedure:<br />
<syntaxhighlight>PegCells[pRow,pCol] := pValue</syntaxhighlight><br />
* And now finalize the Create constructor. Add this code to it's body:<br />
<syntaxhighlight>var iRow,iCol: integer;<br />
begin<br />
// Store the size of the board locally<br />
Size := pSize;<br />
<br />
// Initialize all cells to 'not accessible'<br />
for iRow := 1 to C_MAX do<br />
for iCol := 1 to C_MAX do<br />
Cell[iRow,iCol] := ctNoAccess;</syntaxhighlight><br />
<br />
Now that the basic data structure is in place, let's have a look at the graphics we are going to need. There are many ways to display a solitaire board. We are going to use a paintbox. That will give us full control over the graphic features that Lazarus gives us out of the box.<br />
<br />
Our main form is going to use the data structure we defined in the PegDatastructure file. <br />
* Open the main form's sourcefile.<br />
* Add PegDatastructures it to the uses list at the top of the file:<br />
<syntaxhighlight>uses<br />
PegDatastructures,<br />
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;</syntaxhighlight><br />
* Press F12 (this will give us the form editor).<br />
* From the ''Standard'' palette choose a TButton and drop it on the form (in the upper left corner).<br />
* Change the caption to ''Test paint''.<br />
* On the ''Additional'' tab select TPaintbox and drop it on the form.<br />
* Change Align to ''alRight''.<br />
* Change BordSpacing.Around to ''4''.<br />
* Change Anchors.akLeft to ''true''. <br />
* Change Name to ''pbPeg''.<br />
* Resize the form so it looks something like this:<br />
[[Image:tutpeg_empty.png]]<br />
<br />
Next step is to draw the cells matrix on this paintbox by splitting it in rows and columns that will contain each cell of the board. To make it scaleable we'll calculate the width and height independantly.<br />
We'll need a couple of variables to hold the results. First the width and height of the cells, so all cells (7) fit exactly on the form.<br />
We'll develop the painting code interactively and we are going to use the button that was dropped on the form for that.<br />
* Double click the test paint button (this creates the event handler).<br />
* Add 2 variables:<br />
<syntaxhighlight>var<br />
CellWidth : integer;<br />
CellHeight: integer;</syntaxhighlight><br />
<br />
We'll need some extra variables to hold intermediate results:<br />
* Add 3 local variables: <br />
<syntaxhighlight> iRow, iCol: TCellNums;<br />
CellArea : TRect;</syntaxhighlight><br />
CellArea is used to limit the rectangular area on screen where a cell will be drawn.<br />
<br />
To process all rows and cols two straightforward for loops will do.<br />
* Add the following code to the event handler:<br />
<syntaxhighlight> // Calculate the width/height of each cell to accomodate for all cells in the paintbox<br />
CellWidth := pbPeg.Width div 7;<br />
CellHeight := pbPeg.Height div 7;<br />
<br />
// Draw boxes for all cells<br />
for iRow := 1 to 7 do<br />
for iCol := 1 to 7 do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
// And now draw the cell<br />
pbPeg.Canvas.Rectangle(CellArea);<br />
end;</syntaxhighlight><br />
<br />
A Canvas, as it's name suggests, is a control that helps us drawing things like lines, rectangles, circles etc. A paintbox control has an embedded canvas. Therefore the line ''pbPeg.Canvas.Rectangle(CellArea)'' will draw a rectangle on the paintbox, limited to the area defined in ''CellArea''. And because the paintbox is placed on the form, we can see the result there.<br />
<br />
* Compile and run the program (press F9).<br />
* Press the ''Test paint'' button.<br />
* Maximize the form (the cells disappear; don't worry we'll fix that).<br />
* Press the ''Test paint'' button again.<br />
<br />
This proves that our calculations were spot on and that we now have the means to draw whatever is necessary in the right spot. One thing needs to be done though before adding more drawing functionality. Drawing of the cells has no relation with the main form whatsoever (or any form for that matter). The only thing we need for drawing things is a Canvas and some measurements for the cells. So we are going to create a supporting class to clean up the main form.<br />
* Create a new unit (File/New Unit).<br />
* Save it as PegSolPainter.pas (File/Save and then and choose ''Rename to lowercase'').<br />
* Add a new class to the new unit file that will do all of the drawing (after the uses section, before the implementation section).<br />
<syntaxhighlight>type<br />
TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
<br />
public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
end;</syntaxhighlight><br />
<br />
Note that a TPegSolitaire variable is also added, because obviously we are going to use that class to retrieve a cells' state.<br />
* Position the text cursor in the constructor line and press Ctrl-Shift-C.<br />
* The constructor must store the 2 parms locally:<br />
<syntaxhighlight>constructor TPegSolPainter.Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
begin<br />
PegSol := pPegSol;<br />
Canvas := pCanvas;<br />
end;</syntaxhighlight><br />
<br />
Trying to compile this code will failse because we haven't added the PegDatastrucures unit to the uses section. And because we use a TCanvas we'll have to add the Graphics unit as well.<br />
* Add ''PegDatastructures'' and ''Graphics'' to the ''uses'' list.<br />
<syntaxhighlight>uses<br />
PegDatastructures,<br />
Graphics,<br />
Classes, SysUtils; </syntaxhighlight><br />
<br />
The reason we built this class was to remove all drawing and painting code from the form. So we need to create a method that will do the drawing. Before adding that method there is something that needs to be addressed: to calculate the width of a cell, we divide the paintbox width by the number of cells. In theory we could use the property Canvas.Width for this. However that property does not always give us the right width at the right time. So to be able to draw cells, we must provide our draw method with the correct values for the width and height of the canvas.<br />
<br />
Now we know this, we can add a paint method to our class.<br />
* Add procedure Repaint to the class.<br />
<syntaxhighlight> TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
<br />
public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
end;<br />
</syntaxhighlight><br />
* Generate the body of the procedure (Ctrl-Shift-C).<br />
* Copy the code from ''TfrmMain.Button1Click(Sender: TObject)'' to this newly created method:<br />
<syntaxhighlight>procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
var<br />
CellWidth : integer;<br />
CellHeight : integer;<br />
iRow, iCol : TCellNums;<br />
CellArea : TRect;<br />
begin<br />
// Calculate the width of each cell to accomodate for all cells<br />
CellWidth := pbPeg.Width div 7;<br />
CellHeight := pbPeg.Height div 7;<br />
<br />
// Draw boxes for all cells<br />
for iRow := 1 to 7 do<br />
for iCol := 1 to 7 do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
// And now draw the cell<br />
pbPeg.Canvas.Rectangle(CellArea);<br />
end;<br />
end;</syntaxhighlight><br />
<br />
Because we no longer need the paintbox pbPeg we must remove the references to it. Three changes are needed:<br />
* Change the calculation of the CellWidth and CellHeight to:<br />
<syntaxhighlight> // Calculate the width of each cell to accomodate for all cells<br />
CellWidth := pCanvasWidth div 7;<br />
CellHeight := pCanvasHeight div 7;</syntaxhighlight><br />
* Change the drawing of the rectangle to:<br />
<syntaxhighlight> // And now draw the cell<br />
Canvas.Rectangle(CellArea);</syntaxhighlight><br />
<br />
Now that we have a class that can do the fancy painting for us, it's time to make use of it. We are going to use this paint class together with the PegSolitare class to draw the cells.<br />
* In the main form source, locate the Button1Click method.<br />
* Remove all statements and variables.<br />
* Add 2 new variables: one for the game class and one for the painter class:<br />
<syntaxhighlight>procedure TfrmMain.Button1Click(Sender: TObject);<br />
var<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
begin<br />
end;</syntaxhighlight><br />
* Add ''PegSolPainter'' to the uses list.<br />
<br />
To use the paint class is fairley straightforward: create a new instance and call the repaint method like so:<br />
* Add the following code to the Button1Click method:<br />
<syntaxhighlight> // Create a new game object<br />
pegsol := TPegSolitaire.Create(7);<br />
<br />
// Create a new painter object to paint this game<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// And paint the board<br />
pegpaint.Repaint(pbPeg.Width, pbPeg.Height);<br />
<br />
// Clean up<br />
pegpaint.Free;<br />
pegsol.Free</syntaxhighlight><br />
<br />
Run and test the program to see that it works exactly as before. <br />
The result will look something like this:<br />
[[Image:tutpeg_empty_cells.png]]<br />
<br />
==It's all about events==<br />
What have we accomplished so far? There is a datastructure that holds all the data for a Peg Solitaire game, there is a class that can paint on a Canvas and we have a fairly simple form with a test button. So what's next? Events!<br />
<br />
As we have seen in the previous section, the cell matrix we painstakingly drew wasn't to last. This happens because the form doesn't know anything about our little game. As soon as the form thinks it's time to redraw itself, it does so and ignores our cell grid. What it dóes do is send a message to it's child controls that a refresh is necessary. This message is available to us as an event: a paint event.<br />
* Open the form editor (select ufrmMain in the editor and press F12).<br />
* Select the paintbox pbPeg.<br />
* Open the Events tab in the Object Inspector.<br />
* One of the events in the list is OnPaint.<br />
[[Image:tutpeg_onpaint.png]]<br />
<br />
This event is 'fired' everytime the paintbox needs to redraw it's surface. That is the place were we are going to do our drawing.<br />
<br />
* Select the OnPaint event in the Object Inspector and click on the small button with three dots. This will generate the event handler body.<br />
* Copy/Paste the exact code from the ''Button1Click'' event handler to this new method.<br />
<syntaxhighlight>procedure TfrmMain.pbPegPaint(Sender: TObject);<br />
var<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
begin<br />
// Create a new game object<br />
pegsol := TPegSolitaire.Create(7);<br />
<br />
// Create a new painter object to paint this game<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// And paint the board<br />
pegpaint.Repaint(pbPeg.Width, pbPeg.Height);<br />
<br />
// Clean up<br />
pegpaint.Free;<br />
pegsol.Free<br />
end;</syntaxhighlight><br />
* Remove all code and variables from method ''Button1Click''. Remember: the IDE will automatically remove this now empty method for us.<br />
* Remove the ''Test paint'' button from the form.<br />
* Run the program to see what happens (ignore the fact that the background color is now different).<br />
<br />
Now what is clear is that we create a game object in the OnPaint method, paint the empty cells and then destroy it. But we need to store the game object until a game has finished. The same goes for the paint object. So the OnPaint event is not the most logical place to create these objects. The form's class declaration is a better place to store them.<br />
* Find the forms's declaration.<br />
* Add the 2 variables we created in the OnPaint event:<br />
<syntaxhighlight> TfrmMain = class(TForm)<br />
pbPeg: TPaintBox;<br />
procedure pbPegPaint(Sender: TObject);<br />
private<br />
{ private declarations }<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
public<br />
{ public declarations }<br />
end; </syntaxhighlight><br />
<br />
These variables must be initialized as soon as the form opens (or anytime we want to start a new game). So let's create a procedure that does that for us and add it to the private section of the form.<br />
* Add procedure StartNewGame to the form.<br />
<syntaxhighlight> private<br />
{ private declarations }<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
<br />
procedure StartNewGame;</syntaxhighlight><br />
* Generate the body.<br />
* Add the initialzation code:<br />
<syntaxhighlight>procedure TfrmMain.StartNewGame;<br />
begin<br />
// Clean up the previous game<br />
pegpaint.Free;<br />
pegsol.Free;<br />
<br />
// Start with a new game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
end;</syntaxhighlight><br />
<br />
Now that the initialization code is created, it must be executed. A logical time to do this is as soon as the form is created (i.e. the application is started). For this another event is available to us: the FormCreate event. We can create it in two ways: find the OnFormCreate event in the Object Inspector and click on the '...' button. But another way to generate it is double clicking the form itself.<br />
* Open the form editor (press F12).<br />
* Double click somewhere in a free area on the form; do nót click on the paintbox.<br />
[[Image:tutpeg_form_create.png]]<br />
* Add the call to the StartNewGame procedure:<br />
<syntaxhighlight>procedure TfrmMain.FormCreate(Sender: TObject);<br />
begin<br />
StartNewGame<br />
end;</syntaxhighlight><br />
<br />
Now that the game and paint objects are created at program start, they are no longer required in the OnPaint method.<br />
* Locate procedure ''procedure TfrmMain.pbPegPaint(Sender: TObject);''<br />
* Remove the local variables and all code except for the line that actualle does the painting.<br />
<syntaxhighlight>procedure TfrmMain.pbPegPaint(Sender: TObject);<br />
begin<br />
// Paint the board<br />
pegpaint.Repaint(pbPeg.Width, pbPeg.Height);<br />
end;<br />
</syntaxhighlight><br />
* Run the program and see what happens.<br />
<br />
== Intermezzo==<br />
So far we have focused on building a program to play the classic solitaire game in the 7x7 grid. Is it now possible to create smaller or bigger grids for other type games? Let's test this.<br />
* In the main form locate the StartNewGame method.<br />
* Change the line ''pegsol := TPegSolitaire.Create(7);'' to ''pegsol := TPegSolitaire.Create(5);''. So a smaller board with 5x5 squares is created.<br />
* Run the program and see what happens.<br />
<br />
As we can see on screen we still see a 7x7 matrix! This is the result of using magic numbers, which we should have avoided (see http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Unnamed_numerical_constants).<br />
<br />
Using magic numbers equals disaster: it's a matter of ''when'', not ''if'' a program will fail.<br />
* Open PegSolPainter.<br />
* Locate the procedure ''TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);''.<br />
In there we see the number of 7 a couple of times. This the magic number that plays tricks on us. In the calculation of the cell width and height, we need the size of the board as stored in the pegsol game variable. And the same goes for the iRow and iCol loops. So let's fix this once and for all:<br />
<syntaxhighlight> // Calculate the width of each cell to accomodate for all cells<br />
CellWidth := pCanvasWidth div pegsol.Size;<br />
CellHeight := pCanvasHeight div pegsol.Size;<br />
<br />
// Draw boxes for all cells<br />
for iRow := 1 to pegsol.Size do<br />
for iCol := 1 to pegsol.Size do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
// And now draw the cell<br />
Canvas.Rectangle(CellArea);<br />
end;</syntaxhighlight><br />
<br />
There's a caveat here: pegsol doesn't have a publicly accessible Size variable. And this is how it should be: all variables in a class should be private. The way to access those private '''''values''''' is via functions or properties (the interface of the class). For this simple value we will use a property.<br />
* Open PegDatastructures.<br />
* Rename the private variable in TPegSolitaire to ''FSize''.<br />
* Add a public read only property to the class: property Size: TCellNums read FSize;<br />
The class will now look like this:<br />
<syntaxhighlight> TPegSolitaire = class<br />
private<br />
FSize: TCellNums;<br />
PegCells: TPegCells;<br />
function GetCell(const pRow, pCol: TCellNums): TCellType;<br />
procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);<br />
<br />
public<br />
constructor Create(const pSize: TCellNums);<br />
property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;<br />
property Size: TCellNums read FSize;<br />
end;<br />
</syntaxhighlight><br />
It's common practice to prefix variables that are accessed via properties with a letter ''F''. We've made the public access to the size property read-only, because it should never be changed, once the game is started. The only place where the private variable should get it's final value is in the Create constructor.<br />
* Locate the constructor.<br />
* Change ''Size := pSize;'' to ''FSize := pSize;'' (if you don't you'll get a compilation error).<br />
* And while we're at it, the initialization needs a minor fix as well. There is no need to initialize cells we are not going to use. So the constructor should look like this (''C_MAX'' is replaced with ''Size''):<br />
<syntaxhighlight>constructor TPegSolitaire.Create(const pSize: TCellNums);<br />
var iRow,iCol: integer;<br />
begin<br />
FSize := pSize;<br />
for iRow := 1 to Size do<br />
for iCol := 1 to Size do<br />
Cell[iRow,iCol] := ctNoAccess;<br />
end;</syntaxhighlight><br />
<br />
We are now ready to test the program and see if it now draws a nice 5x5 matrix.<br />
* Run the program and see what happens (a 5x5 matrix is drawn).<br />
<br />
Now that the basics of the game are in place, it's time to flesh out the gui.<br />
<br />
==Let's get artistic==<br />
So far so good. We now have a game class, a supporting paint class and an unimpressive checker board on our form (well, sort of). In our solitaire game a cell can have 3 states: not accessible, empty or occupied. For each cell type we want a different graphic representation. Let's get to it.<br />
* In the editor open PegSolPainter (the unit with the paint class for our board).<br />
* Locate the Repaint method.<br />
* Locate the line where the cell is drawn: ''Canvas.Rectangle(CellArea);''.<br />
Obviously we need to make a change here. It depends on the cell state what needs to be drawn (not accessible cell, empty cell or peg cell). The case... operation comes to the rescue.<br />
* Change the drawing of cells like so:<br />
<syntaxhighlight> // Draw boxes for all cells<br />
for iRow := 1 to pegsol.Size do<br />
for iCol := 1 to pegsol.Size do<br />
begin<br />
// Calculate the position of the cell in the paintbox<br />
CellArea.Top := (iRow-1) * CellHeight;<br />
CellArea.Left := (iCol-1) * CellWidth;<br />
CellArea.Right := CellArea.Left + CellWidth;<br />
CellArea.Bottom := CellArea.Top + CellHeight;<br />
<br />
// And now draw the cell based on the cell's contents<br />
case pegsol.Cell[iRow,iCol] of<br />
<br />
ctNoAccess: // Draw cells that are not accessible<br />
begin<br />
Canvas.Brush.Color := clGray;<br />
Canvas.Rectangle(CellArea);<br />
end;<br />
<br />
ctEmpty: // Draw cells that are currently empty<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea);<br />
end;<br />
<br />
ctPeg: // Draw cells that are occupied<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea); // Erase the background first<br />
Canvas.Brush.Color := clGreen; <br />
Canvas.Ellipse(CellArea); // Draw the pegs as green circles<br />
end;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
We could run the program at this stage (just try it), but it will only display a boring 5x5 grayish grid. This is because we didn't define any game setup yet. Let's fix this first.<br />
<br />
* Open the main form source.<br />
* Locate the StartNewGame method.<br />
* Create a 7x7 game instead of 5x5.<br />
* Initialize a couple of cells. For example:<br />
<syntaxhighlight>procedure TfrmMain.StartNewGame;<br />
begin<br />
// Clean up the previous game<br />
pegpaint.Free;<br />
pegsol.Free;<br />
<br />
// Start with a new 7x7 game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// Initialize some cells<br />
pegsol.Cell[3,4] := ctEmpty;<br />
pegsol.Cell[4,2] := ctPeg;<br />
pegsol.Cell[4,3] := ctPeg;<br />
pegsol.Cell[4,4] := ctEmpty;<br />
pegsol.Cell[4,5] := ctPeg;<br />
pegsol.Cell[4,6] := ctPeg;<br />
pegsol.Cell[5,4] := ctEmpty;<br />
end;</syntaxhighlight><br />
* Run the program (it'll look something like the image below).<br />
[[Image:tutpeg_first_pegs.png]]<br />
<br />
==Populate the board==<br />
As we know a classic solitaire board should look something like this:<br />
<br />
[[Image:tutpeg_classic.png]]<br />
<br />
And if we were to populate all cells individually, that would result in a lot of code. What if we could intialize the game by just passing it some text that would symbolically describe the board? Something like this:<br />
<syntaxhighlight> // Initialize the cells to the classic game<br />
pegsol.InitializeBoard( ' ooo ' + LineEnding +<br />
' ooo ' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
'ooo.ooo' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
' ooo ' + LineEnding +<br />
' ooo ' );</syntaxhighlight><br />
#'''o''' is an occupied cell.<br />
#'''.''' is an empty but playable cell.<br />
#The spaces indicate cells that are not accessible.<br />
<br />
Let's assume this is going to work and create a method in the TPegSelitaire class that can handle this.<br />
* Let's be optimistic (also called 'Top down design') and add the above code to TfrmMain.StartNewGame:<br />
<syntaxhighlight>procedure TfrmMain.StartNewGame;<br />
begin<br />
// Clean up the previous game<br />
pegpaint.Free;<br />
pegsol.Free;<br />
<br />
// Start with a new 7x7 game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
// Initialize the cells to the classic game<br />
pegsol.InitializeBoard( ' ooo ' + LineEnding +<br />
' ooo ' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
'ooo.ooo' + LineEnding +<br />
'ooooooo' + LineEnding +<br />
' ooo ' + LineEnding +<br />
' ooo ' );<br />
end;</syntaxhighlight><br />
* Open the PegDatastructures sourcefile.<br />
* Add this procedure to the public section of the class: ''InitializeBoard(const pBoard: ansistring);''<br />
<syntaxhighlight> public<br />
constructor Create(const pSize: TCellNums);<br />
procedure InitializeBoard(const pBoard: ansistring);<br />
<br />
property Cell[const pRow, pCol: TCellNums]: TCellType read GetCell write SetCell;<br />
property Size: TCellNums read FSize;</syntaxhighlight><br />
* Generate the body of InitializeBoard (Ctrl-Shift-C...).<br />
<br />
Now what does this procedure actually need to do? It must split the textstring into seperate lines and then process those lines, Because we used ''LineEnding'' tokens to separate the lines, we can use a ''TStringList'' class to split them.<br />
* Code ''InitalizeBoard'' like so:<br />
<syntaxhighlight>procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);<br />
var lst : TStringList;<br />
iRow,iCol: integer;<br />
s : string;<br />
begin<br />
// Create a list with the board text in it. This will split all lines<br />
// into individual lines, because of the LineEnding 'splitter'.<br />
lst := TStringList.Create;<br />
lst.Text := pBoard;<br />
<br />
// Process all lines one at a time<br />
for iRow := 0 to lst.Count-1 do<br />
if iRow < Size then // Make sure there is no overflow in the rows<br />
begin<br />
// Process a single line of text<br />
s := lst[iRow];<br />
for iCol := 1 to length(s) do<br />
if iCol <= Size then // Make sure there is no overflow in the columns<br />
case s[iCol] of<br />
' ': Cell[iRow+1,iCol] := ctNoAccess;<br />
'.': Cell[iRow+1,iCol] := ctEmpty;<br />
'o': Cell[iRow+1,iCol] := ctPeg;<br />
end;<br />
end;<br />
<br />
// Clean up the list<br />
lst.Free;<br />
end;</syntaxhighlight><br />
#a TStringList is used as a buffer. This works because we used LineEnding as a separator between all lines.<br />
#There are ''Count'' number of lines, but they are numbered 0..Count-1. Our cells are numbered starting with 1. That's why you see ''iRow+1'' in the cell assignments.<br />
<br />
The above procedure contains a lot of extra variables and shouldn't be that difficult to understand. It's possible to reduce the procedure to the bare minimum like so:<br />
<syntaxhighlight>procedure TPegSolitaire.InitializeBoard(const pBoard: ansistring);<br />
var iRow,iCol: integer;<br />
begin<br />
with TStringList.Create do<br />
begin<br />
Text := pBoard;<br />
for iRow := 0 to Min(Count-1, Size-1) do<br />
for iCol := 1 to Min(length(Strings[iRow]),Size) do<br />
case Strings[iRow][iCol] of<br />
' ': Cell[iRow+1,iCol] := ctNoAccess;<br />
'.': Cell[iRow+1,iCol] := ctEmpty;<br />
'o': Cell[iRow+1,iCol] := ctPeg;<br />
end;<br />
Free;<br />
end;<br />
end;</syntaxhighlight><br />
This procedure does exactly the same but it uses the handy feature that you can use the With statement together with dynamically created objects. For this procedure to work add the Math unit to the uses section.<br />
<br />
* Run the program. All cells are now populated like in the classic Peg Solitaire game.<br />
<br />
==Events revisited==<br />
It doesn't look too impressive yet, but we have done quite a lot. The main thing missing at the moment is being able to leap cells to remove them from the board. And for that we are going to use the mouse. But how? Let's investigate the possibilities.<br />
<br />
* Drop a TMemo on the form, to the left of the paint box pbPeg (it's on the ''Standard'' tab of the component palette).<br />
* In the Object Inspector change Align to ''alClient''. It will occupy all remaining space that is left of the paintbox.<br />
* Change BorderSpacing.Around to ''4''.<br />
* Clear the Lines property (click on the 3-dotted button and erase the text). The result should look like this:<br />
[[Image:tutpeg_with_memo.png]]<br />
* Select the paintbox pbPeg.<br />
* In the Object Inspector select the Events tab.<br />
There are 2 events that are interesting to us: OnMouseDown and OnMouseUp. Let's see what happens whith the OnMouseDown event.<br />
* In the Object Inspector select the OnMouseDown event.<br />
* Click on the 3-dotted button (this generates the body of the event procedure).<br />
<br />
The header of the generated procedure contains a number of variables. Two of them are X and Y. They contain the position on the paintbox where the mouse button was pressed. Let's make them visible by adding some information to the memo component we've placed on the form.<br />
* Add the following statement to the procedure; it adds a formatted line to the memo displaying the position where the mouse button was pressed:<br />
<syntaxhighlight>Memo1.Append( format('Mouse down @ %dx%d',[X,Y]) )</syntaxhighlight><br />
* Run the program (press F9).<br />
* Click on the game board at several different locations. The memo box will display where the mouse button was pressed.<br />
* End the program.<br />
<br />
Obviously we cannot use the 'raw' X and Y coordinates; they are too high for our 7x7 game board. They must be translated to the cell's row and column coordinates. For that we need a function that translates a X/Y coordinate to a cell coordinate. For that we need the width and height of the paintbox, the number of cells in the grid and the width/height of an individual cell. The one place where all this comes together is... the paint class. So that is the obvious place to do this calculation.<br />
* Open the PegDatastructures file.<br />
* Add the ''TCellPosition'' record type to store cell coordinates:<br />
<syntaxhighlight>type<br />
TCellNums = 1..C_MAX;<br />
TCellType = (ctNoAccess, ctEmpty, ctPeg);<br />
TPegCells = array[TCellNums, TCellNums] of TCellType;<br />
<br />
TCellPosition = record<br />
Row: TCellNums;<br />
Col: TCellNums;<br />
end;</syntaxhighlight><br />
<br />
* Open the PegSolPainter class file.<br />
* Add the public function CanvasXYtoCell(...) to the class:<br />
<syntaxhighlight> public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
<br />
procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
function CanvasXYtoCell(const pX, pY: integer): TCellPosition;</syntaxhighlight><br />
* Generate the bode (Ctrl-Shift-C).<br />
* Add the following code:<br />
<syntaxhighlight>function TPegSolPainter.CanvasXYtoCell(const pX, pY: integer): TCellPosition;<br />
begin<br />
result.Col := (pX div CellWidth) + 1;<br />
result.Row := (pY div CellHeight) + 1;<br />
end;</syntaxhighlight><br />
The above code maps the large X/Y coordinate on the actual cell's row/col numbers. There is one problem though: the CellWidth and CellHeight are not available. They are intermediate results in the Repaint procedure and not stored. Let's fix that as well.<br />
* Add ''CellWidth'' and ''CellHeight'' as private variables to the ''TPegSolPainter'' class:<br />
<syntaxhighlight> TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
CellWidth : integer;<br />
CellHeight : integer;</syntaxhighlight><br />
* Remove the variables ''CellWidth'' and ''CellHeight'' from the variable list in the Repaint procedure:<br />
<syntaxhighlight>procedure TPegSolPainter.Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
var<br />
iRow, iCol : TCellNums;<br />
CellArea : TRect;<br />
begin</syntaxhighlight><br />
<br />
Now we can modify the procedure where the mouse down coordinate was processed. With the new function the X/Y position can now be translated to an actuel cell coordinate.<br />
* Open the main form sourcefile.<br />
* Locate the ''pbPegMouseDown'' procedure.<br />
* Change the code to:<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
var CellRC: TCellPosition;<br />
begin<br />
CellRC := pegpaint.CanvasXYtoCell(X,Y);<br />
Memo1.Append( format('Mouse down @ %dx%d',[CellRC.Row, CellRC.Col]) )<br />
end;</syntaxhighlight><br />
<br />
Let's do the same for the OnMouseUp event:<br />
* Generate the procedure body for the OnMouseUp event.<br />
* Add this code:<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
with pegpaint.CanvasXYtoCell(X,Y) do<br />
Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) )<br />
end;</syntaxhighlight><br />
Note that this method does exactly the same as the OnMouseDown, but using the ''with'' construct saves us the trouble of declaring a local variable (less is more...).<br />
<br />
Let's test this:<br />
* Run the program.<br />
* Click on the upper left most cell and hold the mouse down.<br />
* Move the mouse cursor to another cell.<br />
* Release the mouse button.<br />
Every time you press the mouse button and release it, the exact cell where the mouse button was pressed or released is displayed in the memo box.<br />
<br />
==Gluing things together==<br />
This program works a bit like the calculator program:<br />
#The user selects a peg to move by pressing the mouse button.<br />
#The user moves the peg over another peg to an empty cell (leaps).<br />
#The user release the mouse button to execute the leap.<br />
Here we do not remember a number but a cell (-position). So as soon as the user presses the mouse button on a cell, we must store that position somewhere.<br />
* Open the main form's sourcefile.<br />
* Add a variable ''FromCell'' the the form's private variables.<br />
<syntaxhighlight> private<br />
{ private declarations }<br />
pegsol : TPegSolitaire; // The game data<br />
pegpaint: TPegSolPainter; // The paint class for the game<br />
FromCell: TCellPosition; // The peg that is going to leap</syntaxhighlight><br />
* Locate the OnMouseDown event handler.<br />
* Add the following line to that procedure:<br />
<syntaxhighlight>FromCell := pegpaint.CanvasXYtoCell(X,Y);</syntaxhighlight><br />
<br />
Now as soon as the user releases the mouse button, we must execute the leap (verify if it's ok and update the board). Let's be optimistic (remember the Top Down remark?) and assume there is a procedure that does that for us: Leap(<FromCell>, <ToCell>).<br />
* Locate the OnMouseUp event handler and add the following line:<br />
<syntaxhighlight>pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));</syntaxhighlight><br />
''FromCell'' we already populated when the mouse button was pressed. The target cell is simply calculated by calling pegpaint.CanvasXYToCell(...). We could introduce a local variable for that, but it's not really necessary.<br />
* Add the following line to the procedure as well:<br />
<syntaxhighlight>pbPeg.Repaint;</syntaxhighlight><br />
This will force the paintbox to repaint itself, so all updated cells show the correct contents.<br />
<br />
The complete OnMouseDown method now looks like this:<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
with pegpaint.CanvasXYtoCell(X,Y) do<br />
Memo1.Append( format('Mouse up @ %dx%d',[Row, Col]) );<br />
pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y));<br />
pbPeg.Repaint;<br />
end;</syntaxhighlight><br />
<br />
Before going into the Leap procedure, a bit of extra functionality is introduced. Remember that we can retrieve the state of a cell (empty, peg or no access) via the Cell property of TPegSolitaire? For example ''pegsol.Cell[2,2]'' would give us the state of the cell in row 2 and column 2. But in the mean time we have introduced a new datastructure: TCellPosition. So for example if we want to get the state of the ''FromCell'' we would need the following awkward statement:<br />
<syntaxhighlight>state := pegsol.Cell[ FromCell.Row, FramCell.Col ]</syntaxhighlight><br />
It would be nice if we could write something like:<br />
<syntaxhighlight>state := pegsol.GetCell(FromCell)</syntaxhighlight><br />
Let's add this to our game class.<br />
* Open sourcefile PegDatastructures.<br />
* Add the private function: ''GetCell(const pPosition: TCellPosition): TCellType;''<br />
<syntaxhighlight> TPegSolitaire = class<br />
private<br />
FSize: TCellNums;<br />
PegCells: TPegCells;<br />
<br />
function GetCell(const pRow, pCol: TCellNums): TCellType;<br />
procedure SetCell(const pRow, pCol: TCellNums; const pValue: TCellType);<br />
function GetCell(const pPosition: TCellPosition): TCellType;</syntaxhighlight><br />
There now are two functions with the name GetCell, but the parameter list is different. So Lazarus knows exactly when to call which function.<br />
* Generate the body of the function (Ctrl-Shift-C).<br />
* Insert the following code:<br />
<syntaxhighlight>result := Cell[pPosition.Row, pPosition.Col]</syntaxhighlight><br />
<br />
Back to the main storyline. We have introduced a procedure ''Leap(...)'', but it doesn't exist yet. It is a method that changes the game board, so the place to put it is the TPegSolitaire class.<br />
* Open sourcefile PegDatastructures.<br />
* Add procedure Leap to the public section of the TPegSolitaire class.<br />
<syntaxhighlight> public<br />
constructor Create(const pSize: TCellNums);<br />
procedure InitializeBoard(const pBoard: ansistring);<br />
procedure Leap(const pFromCell, pToCell: TCellPosition);<br />
</syntaxhighlight><br />
* Generate the procedure body (Ctrl-Shift-C).<br />
<br />
Now this procedure does all the hard work: check if the cells are valid and if the leap is at all possible. This is not difficult, but the number of checks is quite extensive for something that looks simple. The code is fairly self-explanatory and if not, hopefully the comments explain what is going on. Note that the procedure uses the GetCell(<cellposition>) function we created earlier.<br />
<syntaxhighlight>procedure TPegSolitaire.Leap(const pFromCell, pToCell: TCellPosition);<br />
var dx, dy: integer;<br />
JumpedCell: TCellPosition;<br />
begin<br />
// Verify that the start cell is occupied and the target cell is empty<br />
// If not, leave the procedure via the EXIT.<br />
if (GetCell(pFromCell) <> ctPeg) or<br />
(GetCell(pToCell) <> ctEmpty) then<br />
EXIT;<br />
<br />
// Calculate the horizontal and vertical distance between the cells<br />
dx := abs(pFromCell.Col - pToCell.Col);<br />
dy := abs(pFromCell.Row - pToCell.Row);<br />
<br />
// A valid move has one direction equal to zero and the other equal to 2<br />
if ((dx = 2) and (dy = 0))<br />
or ((dx = 0) and (dy = 2)) then<br />
begin<br />
// Determine the position of the jumped cell; it's in the middle<br />
JumpedCell.Col := (pFromCell.Col + pToCell.Col) div 2;<br />
JumpedCell.Row := (pFromCell.Row + pToCell.Row) div 2;<br />
<br />
// Final check: is there a peg in the jumped cell?<br />
if GetCell(JumpedCell) = ctPeg then<br />
begin<br />
// Jump: clear the FromCell, empty the jumped cell and populate the ToCell<br />
Cell[pFromCell.Row, pFromCell.Col] := ctEmpty;<br />
Cell[JumpedCell.Row, JumpedCell.Col] := ctEmpty;<br />
Cell[pToCell.Row, pToCell.Col] := ctPeg;<br />
end;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
Believe it or not but we now have a working Peg Solitaire game program!<br />
* Run the program.<br />
* Press the mouse button on one peg cell.<br />
* Leap over another peg cell to an empty cell.<br />
* Release the mouse button.<br />
* Repeat...<br />
<br />
==Cleaning up==<br />
On the form we still have the memo component that displays mouse clicks. We don't really need it anymore.<br />
* Delete the memo from the main form.<br />
* Delete the memo updates from the MouseDown and MouseUp events.<br />
<syntaxhighlight>procedure TfrmMain.pbPegMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
FromCell := pegpaint.CanvasXYtoCell(X,Y);<br />
end;<br />
<br />
procedure TfrmMain.pbPegMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);<br />
begin<br />
pegsol.Leap(FromCell, pegpaint.CanvasXYtoCell(X,Y) );<br />
pbPeg.Repaint;<br />
end;</syntaxhighlight><br />
<br />
* Open the form editor (F12).<br />
* Select the paintbox pbPeg<br />
* In the Object Inspector change property Align to ''alClient'' (look on the Properties tab).<br />
* Remove the unit ''stdctrls'' from the uses list. The compiler would othwerwise give a hint in the Messages window that it isn't used (it was automatically added when we dropped the memo component on the form).<br />
<br />
===What's next?===<br />
To make it a fully functional game we need a way to change the board without recompiling the application. And the graphics could be improved as well. Let's address the first issue first.<br />
<br />
==Creating more board layouts==<br />
It would be nice if we could add more boards to our game without recompiling the application. The obvious place would be to store the board layouts in external textfiles. The user can then start a new game by opening one of the textfiles. The fun thing is, it's easy to add because we've already done most of the hard work.<br />
First let's make two board layouts.<br />
* From the menu choose ''File/New...''; this opens a pop up dialog.<br />
* Choose the option Module/Text and click OK.<br />
* Type the following text (it should look familiar :-) ):<br />
<syntaxhighlight> ooo<br />
ooo<br />
ooooooo<br />
ooo.ooo<br />
ooooooo<br />
ooo<br />
ooo</syntaxhighlight><br />
* From the menu choose File/Save.<br />
* Save the file in the projects' bin folder as ''classic.txt''.<br />
<br />
Add another file:<br />
* From the menu choose ''File/New...''.<br />
* Choose the option Module/Text and click OK.<br />
* Type the following text:<br />
<syntaxhighlight> ...<br />
.o.<br />
..ooo..<br />
.ooooo.<br />
ooooooo<br />
...<br />
...</syntaxhighlight><br />
* From the menu choose File/Save.<br />
* Save the file in the bin folder as ''triangle.txt''.<br />
<br />
There are many ways to create and open new boards. In this tutorial we'll use the menu driven approach (a thorough description of the menu is something for another tutorial).<br />
<br />
* Open the main form's editor.<br />
* Select the TMainMenu control from the Standard components palette.<br />
* Click on the form (it doesn't matter if you 'hit' the paintbox). An icon is displayed showing that the menu is now availabe.<br />
* Double click on the icon with label MainMenu1. This opens the menu editor with one item aldready available, labeled ''New Item1''.<br />
* In the Object Inspector change Caption to ''&File'' (include the ampersand; it's used to draw the underscore symbol when you press Alt to access the main menu).<br />
* Right click on the File item in the Menu Editor and select ''Create Submenu'' from the pop up window.<br />
* Change the Caption to ''E&xit'' (this adds a familiar application exit button).<br />
* Right click on the File item.<br />
* Select ''Insert New Item (after)'' from the pop up menu.<br />
* Change the caption of that new menu to ''&Game''.<br />
* Right click on the Game menu and select ''Create Submenu'' from the pop up..<br />
* Change the caption to ''Load from file...''.<br />
The menu will now look something like this:<br />
<br />
[[Image:tutpeg_mainmenu.png]]<br />
<br />
The menu is also visible at the top of the main form.<br />
<br />
* Click on the ''File'' menu item; this opens the submenu and ''Exit'' is visible.<br />
* Double click on ''Exit''. As you might have guessed this creates an event handler. In other words the procedure that gets executed as soon as the user clicks on this menu item.<br />
* Add the following not too complex code to the generated procedure:<br />
<syntaxhighlight>Close</syntaxhighlight><br />
This will close the main form. And because it is the only form of our application it will end the application.<br />
<br />
Okay now it's time to look at how to open the board files. There are standard components and menu's available to do this, but let's do it the manual way.<br />
* Open the main form's editor.<br />
* In the component palette select the ''Dialogs'' tab.<br />
* Select the TOpenDialog component (click on it).<br />
* Click on the main form (anywhere). A OpenDialog1 component is now visible. This component provides us with a kind of explorer pop up with which we can select files. The main form now looks like this:<br />
<br />
[[Image:tutpeg_mainform.png]]<br />
<br />
* In the Object Inspector click on the Filter property and then on the 3-dotted button. This pops up the filter dialog. This is the place where we tell the file dialog what files we allow the user to select.<br />
* We only want to see text files so add this filter line: ''Peg Solitaire textfiles (*.txt) | *.txt'' (see image below).<br />
* Press OK.<br />
<br />
[[Image:tutpeg_filterdialog.png]]<br />
<br />
* In the Object Inspector change Options.ofFileMustExist to ''true''. This forces the dialog to only allow existing files to be selected.<br />
<br />
So far so good. Now to actually open a file when the menu item is selected:<br />
* On the main form click on the Game menu item. This opens the drop down menu.<br />
* Click on ''Load from file...''. As expected this generates an.... event handler.<br />
* Add the following code to the Event handler:<br />
<syntaxhighlight> if OpenDialog1.Execute then<br />
ShowMessage(OpenDialog1.FileName);</syntaxhighlight><br />
<br />
Remember: small steps. We have now added a menu to the form to exit the application and to load a game board set up from disk. We haven't actually coded the loading part, but let's first try the code we have so far.<br />
* Run the application.<br />
* From the menu choose ''Game/Load from file...''.<br />
* Select ''triangle.txt'' and press Open.<br />
* A window pops up that shows us the selected filename.<br />
* End the program.<br />
<br />
Now let's finish the board loading code. The code is pretty self-explanatory and reuses some of the procedures we have created so far.<br />
* Open the main form's editor.<br />
* From the menu choose ''Game/Load from file...''. This will open the event handler.<br />
* Replace the code with this:<br />
<syntaxhighlight>procedure TfrmMain.MenuItem4Click(Sender: TObject);<br />
begin<br />
// Open the pop up dialog<br />
if OpenDialog1.Execute then<br />
begin<br />
// Start with a new empty board<br />
StartNewGame;<br />
<br />
// Dynamically create a stringlist to load the board layout<br />
with TStringList.Create do<br />
begin<br />
// Load the board layout from the textfile<br />
LoadFromFile(OpenDialog1.FileName);<br />
<br />
// Initialize the board with the file's contents<br />
pegsol.InitializeBoard(Text);<br />
<br />
// Clean up the stringlist<br />
Free<br />
end;<br />
<br />
// After loading the new board update the paintbox<br />
pbPeg.Repaint;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
And that's all there is to it!<br />
<br />
Run the program, open a board set up file and play the game!<br />
<br />
==Eye candy==<br />
We now have a working Peg Solitaire application. There's still plenty of room for improvent, but the game itself is working nicely. The last part of this tutorial is about improving the looks of the game. Sure the game works, but a bit more detail in the graphics would be nice. And that's pretty easy to add. All we need are three images for the cell types, update the painter class and in the main form, tell the painter to use the images.<br />
So let's add the three images to the painter class:<br />
* Open the PegSolPainter sourcefile.<br />
* Add the 3 images to the private variables section:<br />
<syntaxhighlight> TPegSolPainter = class<br />
private<br />
PegSol : TPegSolitaire;<br />
Canvas : TCanvas;<br />
CellWidth : integer;<br />
CellHeight : integer;<br />
<br />
ImgNoAccess: TPicture;<br />
ImgEmpty : TPicture;<br />
ImgPeg : TPicture;</syntaxhighlight><br />
* Add a procedure to the public section for loading the images.<br />
<syntaxhighlight> public<br />
constructor Create(pPegSol: TPegSolitaire; pCanvas: TCanvas);<br />
procedure Repaint(const pCanvasWidth, pCanvasHeight: integer);<br />
function CanvasXYtoCell(const pX, pY: integer): TCellPosition;<br />
procedure LoadImage(const pCellType: TCellType; const pFilename: string);</syntaxhighlight><br />
* Generate the body for the procedure.<br />
* Add the code:<br />
<syntaxhighlight>procedure TPegSolPainter.LoadImage(const pCellType: TCellType; const pFilename: string);<br />
<br />
procedure UpdateImage(var pImg: TPicture; const pNewImage: TPicture);<br />
begin<br />
pImg.Free;<br />
pImg := pNewImage<br />
end;<br />
<br />
var pic: TPicture;<br />
begin<br />
// Make sure the file exists<br />
if FileExists(pFilename) then<br />
begin<br />
// First load the picture in a local variable<br />
pic := TPicture.Create;<br />
pic.LoadFromFile(pFilename);<br />
<br />
// Now update the required image based on the pCelltype parm<br />
case pCelltype of<br />
ctNoAccess: UpdateImage(ImgNoAccess, pic);<br />
ctEmpty : UpdateImage(ImgEmpty, pic);<br />
ctPeg : UpdateImage(ImgPeg, pic);<br />
end;<br />
end;<br />
end;</syntaxhighlight><br />
The code again is self-explanatory. Note that we use a short local procedure to update an image. If we wouldn't do that then the case statement (''case pCellType of'') would be more complex (i.e. contain more redundant code). Now each cell type only needs one line to update the right image.<br />
<br />
We've added the pictures to the class. Now we must use them and that of course happens in the Repaint procedure. For each celltype we check whether an image is available. And if so, use that image instead of the plain drawing.<br />
* Locate the Repaint method.<br />
* In the case statement, change the ctNoAccess, ctEmpty and ctPeg sections to:<br />
<syntaxhighlight> // And now draw the cell based on the cell's contents<br />
case pegsol.Cell[iRow,iCol] of<br />
<br />
ctNoAccess: // Draw cells that are not accessible<br />
if not assigned(ImgNoAccess) then<br />
begin<br />
Canvas.Brush.Color := clGray;<br />
Canvas.Rectangle(CellArea);<br />
end<br />
else with ImgNoAccess do<br />
Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));<br />
<br />
<br />
ctEmpty: // Draw cells that are currently empty<br />
if not assigned(ImgEmpty) then<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea);<br />
end<br />
else with ImgEmpty do<br />
Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));<br />
<br />
<br />
ctPeg: // Draw cells that are occupied<br />
if not assigned(ImgPeg) then<br />
begin<br />
Canvas.Brush.Color := clBlue;<br />
Canvas.Rectangle(CellArea); // Erase the background first<br />
Canvas.Brush.Color := clGreen;<br />
Canvas.Ellipse(CellArea);<br />
end<br />
else with ImgPeg do<br />
Canvas.CopyRect(CellArea, Bitmap.Canvas, Rect(0,0,Width,Height));<br />
<br />
end;</syntaxhighlight><br />
<br />
In the main form locate the StartNewGame method and add the image loading code to the painter class.<br />
* Open the main form editor.<br />
* Locate method ''StartNewGame''.<br />
* Add LoadImage statements after the creation of the ''pegpaint'' object:<br />
<syntaxhighlight> // Start with a new 7x7 game<br />
pegsol := TPegSolitaire.Create(7);<br />
pegpaint := TPegSolPainter.Create(pegsol, pbPeg.Canvas);<br />
<br />
pegpaint.LoadImage(ctNoAccess, 'tutpeg_cellforbidden.jpg');<br />
pegpaint.LoadImage(ctEmpty, 'tutpeg_cellempty.jpg');<br />
pegpaint.LoadImage(ctPeg, 'tutpeg_celloccupied.jpg');</syntaxhighlight><br />
<br />
Make sure that the above images are downloaded from below and put in the program's ''bin'' folder, or better yet, create your own images.<br />
Note: to download these images, click on them first thén download them.<br />
<br />
[[Image:tutpeg_cellforbidden.jpg]]<br />
[[Image:tutpeg_cellempty.jpg]]<br />
[[Image:tutpeg_celloccupied.jpg]]<br />
<br />
* Now test the solitaire game and hopefully it looks something like the image at the beginning of this tutorial...<br />
<br />
==Final tweaks==<br />
<br />
Most programs have their own icons that are displayed in menu's and in explorer windows. It's easy to add your own custom icon to an application.<br />
* From the menu choose ''Project/Project Options...''.<br />
* Click on ''Project Options'' in the pop up dialog (right at the top). This will display the window with the application icon. <br />
* Click on ''Load Icon'' and load your icon of choice; or download the marble that comes with this tutorial:<br />
[[Image:tutpeg_marble.png]]<br />
* Press OK.<br />
The application will now have a nice marble icon in the form's title bar and in explorer windows.<br />
<br />
[[Category:Tutorials]]</div>Enyhttps://wiki.freepascal.org/index.php?title=TappyTux&diff=106652TappyTux2016-12-30T15:02:22Z<p>Eny: Remove broken link.</p>
<hr />
<div>{{TappyTux}}<br />
<br />
TappyTux is a children's educational games suite.<br />
<br />
==Description==<br />
<br />
TappyTux is a children's educational games suite. TappyTux originally started as a childrens typing tutor, but it has evolved a modular architecture which allows many types of educational games to be played on the same engine. It has been highly optimized for use on remote displays.<br />
<br />
The game is Free Software, and all the artwork and sound are under GPL compatible creative-commons licenses.<br />
<br />
It is fun and addictive, and although it is meant for children, many adults have told me they Love it too. Version 2 takes it to the next level however, the interface and game code had a massive overhaul, sound and music support was improved and the game was modularised to allow many different game types to be played on the same engine.<br />
<br />
The current version ships with two modules, tappywords (a typing tutor) and tappymath (an algebra game). Writing new modules is really easy so even a beginner programmer could add more.<br />
<br />
The game is designed to work well in a thin-client lab, and as of this release, is based on GTK2, improving not just the look and feel but also the internationalisation support.<br />
<br />
==Screenshots==<br />
<br />
[[Image:TappyTux_1.png]]<br />
<br />
[[Image:TappyTux_2.jpg]]<br />
<br />
==Download==<br />
<br />
New version: Under development<br />
<br />
==SVN==<br />
<br />
svn co https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/applications/tappytux/ tappytux<br />
<br />
==Developers==<br />
<br />
New authors:<br />
<br />
*Felipe Monteiro de Carvalho<br />
*Dennis T. S. C.<br />
*Ana Claudia M. V.<br />
<br />
Original author: A J Venter<br />
<br/></div>Enyhttps://wiki.freepascal.org/index.php?title=Lazarus_Mazes_demo&diff=106651Lazarus Mazes demo2016-12-30T14:57:52Z<p>Eny: Fix the download/location link</p>
<hr />
<div>== About ==<br />
The program below gives a demonstration of how a maze can be created with Lazarus. The maze generation is based on a depth-first backtracking implementation[http://en.wikipedia.org/wiki/Maze_generation_algorithm#Depth-first_search]. As a little bonus a simple solver is implemented that shows a route from the start of the maze to the exit.<br />
<br />
Changing maze metrics:<br />
<br />
[[Image:mazedemo1.png]]<br />
<br />
<br />
The solver in action:<br />
<br />
[[Image:mazedemo2.png]]<br />
<br />
== Download ==<br />
The demo program can be downloaded from [https://github.com/eny-fpc/lazes github].<br />
<br />
[[Category:Example programs]]</div>Enyhttps://wiki.freepascal.org/index.php?title=GameCube&diff=106650GameCube2016-12-30T14:40:12Z<p>Eny: Remove reference to example programs; there is no working program available.</p>
<hr />
<div>''This is only a stub for a work in progress porting. At this time there is a generic PowerPC compiler and a stripped down RTL. It lacks the prt0 and the linker script; no download available yet.''<br />
<br />
== GameCube specs ==<br />
*'''CPU''': IBM PowerPC '[http://en.wikipedia.org/wiki/Gekko_%28microprocessor%29 Gekko]' @486 MHz (based on [http://en.wikipedia.org/wiki/PowerPC_750CXe PowerPC 750CXe])<br />
*'''GPU''': ATI 'Flipper' graphics @162 MHz<br />
*'''Disc Format''': 8cm GameCube Optical Disc (1.5 GB)<br />
* '''Internal Storage''':<br />
** 24 MB of main [http://en.wikipedia.org/wiki/1T-SRAM 1T-SRAM]<br />
** 16 MB of auxiliary DRAM <br />
*'''External Storage''': 2 GameCube Memory Cards<br />
*'''External Ports''':<br />
** 4 GameCube Controller Ports<br />
** 1 High-Speed Data Port<br />
** 2 Serial Ports <br />
*'''Max Resolution''': 480p (DOL-001 models only)<br />
*'''Internet''':<br />
** 10/100 Ethernet Adapter (optional) <br />
** Modem Adapter (optional) <br />
<br />
== Status ==<br />
*PowerPC CPU is supported.<br />
*A stripped down RTL is running<br />
*Some small tests are working fine on emulator<br />
<br />
== Screenshots ==<br />
''These screenshots are grabbed from gcube emulator running on win32''<br />
<br />
A simple static screen: <br />
<br />
[[Image:fpc4gamecube.png|A static example]]<br />
<br />
<br />
A bouncing square over a scrolling background:<br />
<br />
[[Image:Pageflip.png|Dynamic page flipping example]]<br />
<br />
== Useful links ==</div>Enyhttps://wiki.freepascal.org/index.php?title=Sudoku&diff=106648Sudoku2016-12-30T14:34:55Z<p>Eny: /* Download */</p>
<hr />
<div>{{Sudoku}}<br />
===About===<br />
There is very little to tell about this program. It is just an attempt to make a program which solves Sudoku puzzles.<br />
<br />
===License===<br />
See the sources. I just hope it is a useful demo. Useful as in you can learn something from it.<br />
<br />
===Author===<br />
[[User:Matthijs|Matthijs Willemstein]]<br />
<br />
===Download===<br />
The application can be found on the [https://sourceforge.net/projects/lazarus-ccr/files/Demos%20and%20examples/ Lazarus CCR Files page].<br />
<br/>Note that the application only compiles with old versions of Lazarus/FPC (FPC version <= 2.4.x).<br />
<br />
===Usage===<br />
If you run the program you will see a grid, and two buttons. If you press the "Fill" button, you are able to edit the grid and place the numbers in the grid according to your puzzle.<br><br />
When you are finished entering the digits press "Solve" and the grid will be filled with the solution.<br />
<br />
===Description===<br />
The programs solves all Sudoku puzzles that can be solved by logic. It will not solve the puzzle where you need to guess for a solution.<br />
<br />
So it follows these rules (taken from [http://www.websudoku.com/ WebSudoku]):<br />
<br />
The rules of Sudoku are simple. Enter digits from 1 to 9 into the blank spaces. Every row must contain one of each digit. So must every column, as must every 3x3 square.<br />
Each Sudoku has a unique solution that can be reached logically without guessing.<br />
<br />
===Related Sites===<br />
[http://sudokusolver.info/ Sudoku Solver] Online Sudoku Solver Released Under The GNU public license.<br />
<br />
[[category:Example programs]]<br />
[[category:Game Development]]<br />
<br />
=Translations=<br />
* [[Main Page/de | Deutsch (German)]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Sudoku&diff=106647Sudoku2016-12-30T14:23:23Z<p>Eny: Fix the download/location link</p>
<hr />
<div>{{Sudoku}}<br />
===About===<br />
There is very little to tell about this program. It is just an attempt to make a program which solves Sudoku puzzles.<br />
<br />
===License===<br />
See the sources. I just hope it is a useful demo. Useful as in you can learn something from it.<br />
<br />
===Author===<br />
[[User:Matthijs|Matthijs Willemstein]]<br />
<br />
===Download===<br />
The application can be found on the [https://sourceforge.net/projects/lazarus-ccr/files/Demos%20and%20examples/ Lazarus CCR Files page].<br />
<br />
===Usage===<br />
If you run the program you will see a grid, and two buttons. If you press the "Fill" button, you are able to edit the grid and place the numbers in the grid according to your puzzle.<br><br />
When you are finished entering the digits press "Solve" and the grid will be filled with the solution.<br />
<br />
===Description===<br />
The programs solves all Sudoku puzzles that can be solved by logic. It will not solve the puzzle where you need to guess for a solution.<br />
<br />
So it follows these rules (taken from [http://www.websudoku.com/ WebSudoku]):<br />
<br />
The rules of Sudoku are simple. Enter digits from 1 to 9 into the blank spaces. Every row must contain one of each digit. So must every column, as must every 3x3 square.<br />
Each Sudoku has a unique solution that can be reached logically without guessing.<br />
<br />
===Related Sites===<br />
[http://sudokusolver.info/ Sudoku Solver] Online Sudoku Solver Released Under The GNU public license.<br />
<br />
[[category:Example programs]]<br />
[[category:Game Development]]<br />
<br />
=Translations=<br />
* [[Main Page/de | Deutsch (German)]]</div>Enyhttps://wiki.freepascal.org/index.php?title=GameCube&diff=106646GameCube2016-12-30T14:16:09Z<p>Eny: Remove non-working links</p>
<hr />
<div>''This is only a stub for a work in progress porting. At this time there is a generic PowerPC compiler and a stripped down RTL. It lacks the prt0 and the linker script; no download available yet.''<br />
<br />
== GameCube specs ==<br />
*'''CPU''': IBM PowerPC '[http://en.wikipedia.org/wiki/Gekko_%28microprocessor%29 Gekko]' @486 MHz (based on [http://en.wikipedia.org/wiki/PowerPC_750CXe PowerPC 750CXe])<br />
*'''GPU''': ATI 'Flipper' graphics @162 MHz<br />
*'''Disc Format''': 8cm GameCube Optical Disc (1.5 GB)<br />
* '''Internal Storage''':<br />
** 24 MB of main [http://en.wikipedia.org/wiki/1T-SRAM 1T-SRAM]<br />
** 16 MB of auxiliary DRAM <br />
*'''External Storage''': 2 GameCube Memory Cards<br />
*'''External Ports''':<br />
** 4 GameCube Controller Ports<br />
** 1 High-Speed Data Port<br />
** 2 Serial Ports <br />
*'''Max Resolution''': 480p (DOL-001 models only)<br />
*'''Internet''':<br />
** 10/100 Ethernet Adapter (optional) <br />
** Modem Adapter (optional) <br />
<br />
== Status ==<br />
*PowerPC CPU is supported.<br />
*A stripped down RTL is running<br />
*Some small tests are working fine on emulator<br />
<br />
== Screenshots ==<br />
''These screenshots are grabbed from gcube emulator running on win32''<br />
<br />
A simple static screen: <br />
<br />
[[Image:fpc4gamecube.png|A static example]]<br />
<br />
<br />
A bouncing square over a scrolling background:<br />
<br />
[[Image:Pageflip.png|Dynamic page flipping example]]<br />
<br />
== Useful links ==<br />
[[Category:Example programs]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Hilbert_curve_demo&diff=106645Hilbert curve demo2016-12-30T13:47:42Z<p>Eny: Download link updated</p>
<hr />
<div>== About ==<br />
The program below gives a demonstration of how Hilbert curves [http://en.wikipedia.org/wiki/Hilbert_curve] can be drawn. It also demonstrates how threads can be used to prevent the application from locking up while a drawing is constructed.<br />
The two screenshots below are from the program running on Windows XP and Ubuntu 10.10.<br />
<br />
Hilbert Curve program running on Ubuntu 10.10.<br />
<br />
[[Image:Hc_ub.png]]<br />
<br />
Hilbert Curve program running on Windows XP.<br />
<br />
[[Image:hc_winxp.png]]<br />
<br />
== Download ==<br />
The demo program can be downloaded from github [https://github.com/eny-fpc/HilbertCurve]<br />
<br />
[[Category:Example programs]]<br />
[[Category:Chaos Theory]]</div>Enyhttps://wiki.freepascal.org/index.php?title=How_To_Use_TFPExpressionParser&diff=106642How To Use TFPExpressionParser2016-12-30T12:19:55Z<p>Eny: Simplify and add comments</p>
<hr />
<div>TFPExpressionParser allows to analyze and calculate expressions such as <code>sin(x)*cos(2*x)</code> for any value of the variable <code>x</code>. Besides mathematical expressions it can also handle boolean, string formulas, date/time values etc. Even user-provided functions can be linked in. <br />
<br />
It belongs to FPC Free Component Library (FCL) and is implemented in the unit <code>fpexprpars.pp</code>, folder <code>(fpc_source_dir)/packages/fcl-base/src</code>. Just add <code>fpexprpars</code> to the uses clauses to get access to its functionality. See the file "fpexprpars.txt" (in <code>(fpc_source_dir)/packages/fcl-base/examples</code>) for a short documenation.<br />
<br />
== Creating the parser ==<br />
You apply the parser by creating an instance like this:<br />
<source><br />
uses<br />
fpexprpars;<br />
<br />
var<br />
FParser: TFPExpressionParser;<br />
begin<br />
FParser := TFpExpressionParser.Create(self);<br />
// ... do something (see below)<br />
</source><br />
<br />
If this is called from a method of a form, "self" points to the form. Since the parser inherits from <code>TComponent</code>, there is no need to destroy it explicitly since its owner, the form, will do it. On the other hand, it is also possible to create the parser from anywhere in a program without a form or even class being involved; in this case use <code>nil</code> as the owner of the parser, but don't forget to <code>.Free</code> the parser after its usage:<br />
<br />
<source><br />
uses<br />
fpexprpars;<br />
<br />
var<br />
FParser: TFPExpressionParser;<br />
begin<br />
FParser := TFPExpressionParser.Create(nil);<br />
try<br />
// ... do something (see below)<br />
finally<br />
FParser.Free;<br />
end;<br />
end;<br />
</source><br />
<br />
== Built-in categories ==<br />
The parser is designed in a very flexible way, but the default parser is quite dumb. You have to specify which kind of expressions it will accept. This is done by adding the corresponding identifier to the set of built-in categories. They are accessible by the parser's property <code>BuiltIns</code>:<br />
<br />
<source><br />
type<br />
TBuiltInCategory = (bcStrings, bcDateTime, bcMath, bcBoolean, bcConversion, bcData, bcVaria, bcUser);<br />
TBuiltInCategories = set of TBuiltInCategory;<br />
</source><br />
<br />
Here is a collection of the built-in symbols which can be used by adding categories to the parser's <code>BuiltIns</code> - it should be clear to anybody who "speaks" Pascal what these symbols mean...<br />
* '''bcStrings''': <code>Length</code>, <code>Copy</code>, <code>Delete</code>, <code>Pos</code>, <code>Lowercase</code>, <code>Uppercase</code>, <code>StringReplace</code>, <code>CompareText</code><br />
* '''bcDateTime''': <code>Date</code>, <code>Time</code>, <code>Now</code>, <code>DayOfWeek</code>, <code>ExtractYear</code>, <code>ExtractMonth</code>, <code>ExtractDay</code>, <code>ExtractHour</code>, <code>ExtractMin</code>, <code>ExtractSec</code>, <code>Extractmsec</code>, <code>EncodeDate</code>, <code>EncodeTime</code>, <code>ShortDayName</code>, <code>ShortMonthName</code>, <code>LongDayName</code>, <code>LongMonthName</code><br />
* '''bcMath''': <code>cos</code>, <code>sin</code>, <code>arctan</code>, <code>abs</code>, <code>sqr</code>, <code>sqrt</code>, <code>exp</code>, <code>ln</code>, <code>log</code>, <code>frac</code>, <code>int</code>, <code>round</code>, <code>trunc</code>,<br />
* '''bcBoolean''': <code>shl</code>, <code>shr</code>, <code>IFS</code>, <code>IFF</code>, <code>IFD</code>, <code>IFI</code> (The <code>IFxxx</code> symbols have the same effect as fpc's <code>IfThen</code> for string (<code>IFS</code>), floating point (<code>IFF</code>), date/time (<code>IFD</code>), or integer (<code>IFI</code>) variables)<br />
* '''bcConversion''': <code>IntToStr</code>, <code>StrToInt</code>, <code>StrToIntDef</code>, <code>FloatToStr</code>, <code>StrToFloat</code>, <code>StrToFloatDef</code>, <code>BoolToStr</code>, <code>StrToBool</code>, <code>StrToBoolDef</code>, <code>DateToStr</code>, <code>TimeToStr</code>, <code>StrToDate</code>, <code>StrToDateDef</code>, <code>StrToTime</code>, <code>StrToTimeDef</code>, <code>StrToDateTime</code>, <code>StrToDateTimeDef</code><br />
<br />
<code>bcData</code>, <code>bcVaria</code>, and <code>bcUser</code> are not used anywhere within fpexprpars. <br />
<br />
{{Note|These symbols are not case-sensitive.}}<br />
<br />
In order to use a mathematical expression the option <code>bcMath</code> has to be added to the parser's <code>Builtins</code>:<br />
<br />
<source><br />
FParser.Builtins := [bcMath]; // or FParser.Builtins := FParser.Builtins + [bcMath];<br />
</source><br />
<br />
== Expressions ==<br />
=== An expression with constants ===<br />
As a first example we have the parser calculate a very simple expression <code>1+1</code>.<br />
<br />
The first step is to tell the parser which expression is to be calculated. There is a property <code>Expression</code> for this purpose:<br />
<br />
<source><br />
FParser.Expression := '1+1';<br />
</source><br />
<br />
The next step is to calculate the expression: just call <code>Evaluate</code> or <code>EvaluateExpression</code> - the former is is a function while the latter one is a procedure which passes the result as a parameter. <br />
<br />
<source><br />
var<br />
parserResult: TFPExpressionResult;<br />
begin<br />
....<br />
parserResult := FParser.Evaluate; // or: FParser.EvaluateExpression(parserResult);<br />
</source><br />
<br />
What is that mysterious <code>TFPExpressionResult</code>? Since the parser is very flexible and can deal with numbers, strings, date/times or booleans there must be a more complex data type which returns a calculation result:<br />
<br />
<source><br />
type<br />
TResultType = (rtBoolean, rtInteger, rtFloat, tDateTime, rtString);<br />
<br />
TFPExpressionResult = record<br />
ResString : String;<br />
Case ResultType : TResultType of<br />
rtBoolean : (ResBoolean : Boolean);<br />
rtInteger : (ResInteger : Int64);<br />
rtFloat : (ResFloat : TExprFloat);<br />
rtDateTime : (ResDateTime : TDatetime);<br />
rtString : ();<br />
end;<br />
</source><br />
<br />
The member <code>ResultType</code> signals which one of the data fields is valid. It is important to understand this since the expression parser is very strict on data types.<br />
<br />
In our example, we are adding two integers, therefore the result is an integer as well. If, on the other had, we had used the expression <code>"1.0 + 1"</code>, the first number would have been a floating point value, and the result would have been a float! Therefore, always have a look at the member <code>ResultType</code> of the <code>TFPExpressionResult</code> before picking the result. To simplify the usage of the expression result data type, <code>fpexprpars</code> exposes a function <code>ArgToFloat</code> which gets the entire expression result record as a parameter and selects the right component if a floating point result is expected:<br />
<br />
<source><br />
var<br />
parserResult: TFPExpressionResult;<br />
resultValue: Double;<br />
...<br />
parserResult := FParser.Evaluate; // or: FParser.EvaluateExpression(parserResult);<br />
resultValue := ArgToFloat(parserResult);<br />
</source><br />
<br />
{{Note|Floating point constants in expressions must have a point as decimal separator, not a comma as used in some European countries. If your expression string comes from user input and contains decimal commas you have to replace the commas by points first before assigning it to the parsers's <code>Expression</code>. }}<br />
<br />
=== An expression with a variable ===<br />
In this example, we calculate the value of <code>sin(x)*cos(2*x)</code> for <code>x = 0.5</code>.<br />
<br />
==== Defining variables ====<br />
At first we have to define the variables. We have only one, <code>x</code>. The parser has a method <code>AddFloatVariable</code> to declare a floating point variable; there are also methods<br />
<br />
* <code>AddBooleanVariable</code><br />
* <code>AddStringVariable</code><br />
* <code>AddDateTimeVariable</code><br />
<br />
for boolean, string and date/time variables, respectively.<br />
<br />
Each one of these methods expects the name of the variable along with its default value. For the sample function <code>sin(x)*cos(2*x)</code> we just call:<br />
<source><br />
FParser.AddFloatVariable('x', 0.5);<br />
</source><br />
<br />
<code>0.5</code> is entered here as a default value because that is the argument at which we want to calculate the expression (it will be shown [[#Changing_variables|below]] how to modify a variable). From now on, the parser will use this number whenever it finds the variable <code>x</code> in the expression.<br />
<br />
Of course, you can add other names, e.g. constants like <code>e</code>, etc. (The number <code>pi</code> is already built-in).<br />
<br />
==== Defining the expression ====<br />
In the next step, the expression string has to be passed to the parser: <br />
<br />
<source><br />
FParser.Expression := 'sin(x)*cos(2*x)';<br />
</source><br />
<br />
It is essential to call this after setting up of the variables because the parser needs to know the variables for analyzing the expression.<br />
<br />
==== Calculating the expression ====<br />
This is done as before with the constant expression - here is a complete procedure which shows the equation and its result in a message box:<br />
<br />
<source><br />
var<br />
FParser: TFPExpressionParser;<br />
resultValue: Double;<br />
begin<br />
FParser := TFPExpressionParser.Create(nil);<br />
try<br />
FParser.BuiltIns := [bcMath];<br />
FParser.Identifiers.AddFloatVariable('x', 0.5);<br />
FParser.Expression := 'sin(x)*cos(2*x)';<br />
resultValue := FParser.Evaluate.ResFloat; // or: resultValue := ArgToFloat(FParser.Evaluate);<br />
ShowMessage(FParser.Expression + ' = ' + FloatToStr(resultValue));<br />
finally<br />
FParser.Free;<br />
end;<br />
end; <br />
</source><br />
<br />
==== Changing variables ====<br />
So far, <code>x</code> always has the value 0.5 - it behaves like a constant, we could have used the expression <code>"sin(0.5)*cos(2*0.5)"</code> as well.<br />
<br />
To make it behave more like a "variable", we now calculate the test function for the <code>x</code> values between -10 and 10 at integer steps.<br />
<br />
The main question is: How to replace the value assigned to a variable? There are several possibilities - all of them require the internal variable <code>Identifier</code> (type <code>TFPExprIdentifierDef</code>) which exposes various ways to access variables and their properties:<br />
* Use the return value of the <code>AddFloatVariable</code> function.<br />
* Seek an identifier by calling <code>FindIdentifierByName</code> with the variable name as a parameter.<br />
* Access the identifier from the <code>Identifiers</code> collection of the parser by using the known index of the variable: We had added <code>x</code> as the only variable, therefore, it must be at index 0.<br />
<br />
Once the <code>Identifier</code> is known, the value of the variable can be changed by accessing the property <code>AsFloat</code> (or <code>AsDateTime</code> etc. accordingly):<br />
<br />
<source><br />
var<br />
FParser: TFPExpressionParser;<br />
argument: TFPExprIdentifierDef;<br />
s: string;<br />
x: integer;<br />
f: double;<br />
begin<br />
s:='';<br />
FParser := TFPExpressionParser.Create(nil);<br />
try<br />
// Enable the use of mathematical expressions<br />
FParser.BuiltIns := [bcMath];<br />
<br />
// Add the function argument<br />
argument := FParser.Identifiers.AddFloatVariable('x', 0.0);<br />
<br />
// Define the function, using the argument name x as defined above<br />
FParser.Expression := 'sin(x)*cos(2*x)';<br />
<br />
// Calculate some function values<br />
for x := -10 to 10 do<br />
begin<br />
argument.AsFloat := x; // Set the function argument value<br />
f := FParser.Evaluate.ResFloat; // Calculate the function value<br />
s := s + format('x[%d]:[%g]' + LineEnding, [x,f]); // Demo output to display the result<br />
end;<br />
<br />
// Show the result<br />
ShowMessage(s);<br />
<br />
finally<br />
FParser.Free;<br />
end;<br />
end; <br />
</source><br />
<br />
== Adding user-defined functions ==<br />
The default parser only knows the built-in functions mentioned above. One of the strengths of of the expression parser is that it is very easy to extend to include other functions. This can be done by calling the method <code>Identifiers.AddFunction</code>, e.g.<br />
<source><br />
FParser.AddFunction('tan', 'F', 'F', @ExprTan);<br />
</source><br />
<br />
In this example, we add the function tan(x) by specifying its name as it will be called in the function expressions (first parameter), the type of the result values (second parameter, "F" = float, "I" = integer, "D" = date/time, "S" = string, or "B" = boolean) and the type of the input values (third parameter, same logic). If a function accepts several input parameters the type of each one must be specified, e.g. by 'FF' for two floating point values, or 'FI' for a first float and a second integer parameter. The last parameter points to the function which is called whenever "tan" is found in the expression string. Since this function has a particular syntax we have to implement it in our own source code:<br />
<br />
<source><br />
procedure ExprTan(var Result: TFPExpressionResult; Const Args: TExprParameterArray);<br />
var<br />
x: Double;<br />
begin<br />
x := ArgToFloat(Args[0]);<br />
Result.resFloat := tan(x);<br />
end;<br />
</source><br />
<br />
The result of the calculation is returned as parameter <code>"Result"</code> which is a <code>TFPExpressionResult</code> that we met above. The arguments for the calculation are passed by <code>Args</code> which is just an array of TFPExpressionResult values - again because parameters can have several data types. The term TFPExpression''Results'' is maybe a bit misleading here, because this array holds all the *input* parameters as specified by the input types of the AddFunction method.<br />
<br />
<br />
[[Category:FCL]]<br />
[[Category:Tutorials]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Using_INI_Files&diff=98643Using INI Files2016-01-01T23:23:51Z<p>Eny: Extended the example</p>
<hr />
<div>{{INI Files}}<br />
==INI Files==<br />
<br />
===Basic Information===<br />
INI files are text files that store key/value pairs that are grouped in sections.<br />
<br />
INI files can be used to save user settings easily. With the [http://www.freepascal.org/docs-html/fcl/inifiles/index.html IniFiles unit] and the [http://www.freepascal.org/docs-html/fcl/inifiles/tinifile.html TINIFile class] you can easily work with them. The IniFiles unit is part of the FCL.<br />
<br />
Currently only INI files compliant with the more restrictive Windows ini-file format are supported. I.e. using [http://en.wikipedia.org/wiki/INI_file#Sections sections] is mandatory and only the semicolon can be used for comments.<br />
For more general information about ini-files read [http://en.wikipedia.org/wiki/INI_file this].<br />
<br />
===INI Files===<br />
INI Files use brackets to create and mark '''Sections''', which contain '''keys''' and key '''values'''.<br />
A Key and its corresponding Value are separated with an equals sign (Key=Value).<br />
<br />
Section names are put inside square brackets ([Section]).<br />
<br />
Comments are permitted and are marked with a semicolon (;) at the beginning of a line.<br />
<br />
An example ini file:<br />
<br />
<syntaxhighlight lang="ini"><br />
; Comment. Beginning of INI file<br />
<br />
; empty lines are ignored<br />
<br />
[General]<br />
; This starts a General section<br />
Compiler=FreePascal<br />
; Key Compiler and value FreePascal<br />
</syntaxhighlight><br />
<br />
===Ini file reading example===<br />
<br />
The console application below shows how to read ini files. To test it, create an ini file with the name "DB.ini" and the contents below, and save it in the same folder as the executable program.<br />
<syntaxhighlight lang="ini"><br />
[DB-INFO]<br />
Author=Adam<br />
Pass=secret<br />
MaxAttempts=5<br />
DBFile=C:\Money.dat<br />
</syntaxhighlight><br />
<br />
The code to read this ini file:<br />
<syntaxhighlight><br />
Program IniReadExample;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
classes, sysutils, IniFiles;<br />
<br />
const<br />
C_DB_SECTION = 'DB-INFO';<br />
<br />
var<br />
INI: TINIFile;<br />
Author, Pass, DBFile: String;<br />
MaxAttempts: integer;<br />
PassEnter: String;<br />
TryCount: integer;<br />
begin<br />
// Create the object, specifying the the ini file that contains the settings<br />
INI := TINIFile.Create('DB.ini');<br />
<br />
// Put reading the INI file inside a try/finally block to prevent memory leaks<br />
try<br />
// Demonstrates reading values from the INI file.<br />
Author := INI.ReadString(C_DB_SECTION,'Author','');<br />
Pass := INI.ReadString(C_DB_SECTION,'Pass','');<br />
DBFile := INI.ReadString(C_DB_SECTION,'DBFile','');<br />
MaxAttempts := INI.ReadInteger(C_DB_SECTION,'MaxAttempts',1);<br />
<br />
// Do something with the values read; e.g. verify the password<br />
if Pass <> '' then<br />
begin<br />
// Ask for the password, max attempts = MaxAttempts<br />
TryCount := MaxAttempts;<br />
repeat<br />
write('Please enter password; ', TryCount, ' attempt(s) left: ');<br />
readln(PassEnter);<br />
dec(TryCount);<br />
if (PassEnter <> Pass) and (TryCount > 0) then<br />
writeln('Wrong Password, please try again');<br />
until(PassEnter = Pass) or (TryCount = 0);<br />
<br />
// Correct password given?<br />
if PassEnter = Pass then<br />
writeln('Correct Password.')<br />
else<br />
writeln('Invalid password, but maxiumm number of password attempts reached.');<br />
end;<br />
<br />
writeln('Author : ', Author);<br />
writeln('File : ', DBFile);<br />
writeln('Password : ', Pass);<br />
writeln('Max password attempts: ', MaxAttempts);<br />
writeln;<br />
write('Press Enter to close...');<br />
Readln;<br />
<br />
finally<br />
// After the ini file was used it must be freed to prevent memory leaks.<br />
INI.Free;<br />
end;<br />
end.<br />
</syntaxhighlight><br />
<br />
===Objects to know===<br />
In the TINIFile class there are many different properties, procedures and functions that can be used.<br />
<br />
'''CaseSensitive''' - This property allows you to say if the keys and sections are case sensitive or not. By default they aren't.<br />
<br />
'''ReadString''' - Has 3 constant statements. The first one is the section to search in. The second one is the key to look for. The third one is a default string in case the key and/or section searched for is not found.<br />
<br />
'''WriteString''' - has three constant statements, too. The first is the section. The second is the key and the last is the value that you want to write. If the key and section exist already, the key will be overwritten with the new value..<br />
<br />
'''ReadSections''' - Will allow you to to take the sections from the INI file and put them in a TStrings class (or TStringList with the AS operator).<br />
<br />
'''DeleteKey''' - Remove an existing key from a specific section.<br />
<br />
'''EraseSection''' - Remove a section and all its data.<br />
<br />
There are more procedures and functions but this is enough to get you started.<br />
<br />
===Reference Documentation===<br />
Here: [http://lazarus-ccr.sourceforge.net/docs/fcl/inifiles/index.html Free Pascal documentation on INI files]<br />
<br />
== See also ==<br />
* [[HistoryFiles]]<br />
* [[XML Tutorial]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:FPC]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Using_INI_Files&diff=98642Using INI Files2016-01-01T22:56:51Z<p>Eny: Fix errors in ini-file (make FCL compliant)</p>
<hr />
<div>{{INI Files}}<br />
==INI Files==<br />
<br />
===Basic Information===<br />
INI files are text files that store key/value pairs that are grouped in sections.<br />
<br />
INI files can be used to save user settings easily. With the [http://www.freepascal.org/docs-html/fcl/inifiles/index.html IniFiles unit] and the [http://www.freepascal.org/docs-html/fcl/inifiles/tinifile.html TINIFile class] you can easily work with them. The IniFiles unit is part of the FCL.<br />
<br />
Currently only INI files compliant with the more restrictive Windows ini-file format are supported. I.e. using [http://en.wikipedia.org/wiki/INI_file#Sections sections] is mandatory and only the semicolon can be used for comments.<br />
For more general information about ini-files read [http://en.wikipedia.org/wiki/INI_file this].<br />
<br />
===INI Files===<br />
INI Files use brackets to create and mark '''Sections''', which contain '''keys''' and key '''values'''.<br />
A Key and its corresponding Value are separated with an equals sign (Key=Value).<br />
<br />
Section names are put inside square brackets ([Section]).<br />
<br />
Comments are permitted and are marked with a semicolon (;) at the beginning of a line.<br />
<br />
An example ini file:<br />
<br />
<syntaxhighlight lang="ini"><br />
; Comment. Beginning of INI file<br />
<br />
; empty lines are ignored<br />
<br />
[General]<br />
; This starts a General section<br />
Compiler=FreePascal<br />
; Key Compiler and value FreePascal<br />
</syntaxhighlight><br />
<br />
===Ini file reading example===<br />
<br />
The console application below shows how to read ini files. To test it one should create the following ini file with the name "C:\DB.ini". Edit it to so it contains a section called INIDB and the following keys and values:<br />
<br />
<syntaxhighlight lang="ini"><br />
[INIDB]<br />
; Save as C:\DB.ini<br />
Author=Adam<br />
Pass=<br />
DBFile=C:\Money.dat<br />
</syntaxhighlight><br />
<br />
Now let's move on the the code..<br />
<syntaxhighlight><br />
Program IniSample;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
Uses<br />
Classes,SysUtils,INIFiles;<br />
<br />
Var<br />
INI:TINIFile;<br />
Author,Pass,DBFile:String;<br />
PassEnter:String;<br />
<br />
begin<br />
// Create the object, specifying the place where it can find the ini file:<br />
INI := TINIFile.Create('C:\DB.ini');<br />
// Demonstrates reading strings from the INI file.<br />
// You can also read booleans etc.<br />
Author := INI.ReadString('INIDB','Author','');<br />
Pass := INI.ReadString('INIDB','Pass','');<br />
DBFile := INI.ReadString('INIDB','DBFile','');<br />
if Pass <> '' then<br />
begin<br />
Writeln('Password Required');<br />
Repeat<br />
Readln(PassEnter);<br />
if not (PassEnter = Pass) then Writeln('Wrong Password');<br />
until(PassEnter = Pass);<br />
Writeln('Correct Password');<br />
end;<br />
Writeln('Author : '+Author);<br />
Writeln('File : '+DBFile);<br />
Writeln('Password : '+Pass);<br />
Readln;<br />
// After we used ini file, we must call the Free method of object<br />
// ... although this really should be wrapped in a try..finally block<br />
// so that the object will be freed regardless of any errors above.<br />
Ini.Free; <br />
end. <br />
</syntaxhighlight><br />
<br />
===Objects to know===<br />
In the TINIFile class there are many different properties, procedures and functions that can be used.<br />
<br />
'''CaseSensitive''' - This property allows you to say if the keys and sections are case sensitive or not. By default they aren't.<br />
<br />
'''ReadString''' - Has 3 constant statements. The first one is the section to search in. The second one is the key to look for. The third one is a default string in case the key and/or section searched for is not found.<br />
<br />
'''WriteString''' - has three constant statements, too. The first is the section. The second is the key and the last is the value that you want to write. If the key and section exist already, the key will be overwritten with the new value..<br />
<br />
'''ReadSections''' - Will allow you to to take the sections from the INI file and put them in a TStrings class (or TStringList with the AS operator).<br />
<br />
'''DeleteKey''' - Remove an existing key from a specific section.<br />
<br />
'''EraseSection''' - Remove a section and all its data.<br />
<br />
There are more procedures and functions but this is enough to get you started.<br />
<br />
===Reference Documentation===<br />
Here: [http://lazarus-ccr.sourceforge.net/docs/fcl/inifiles/index.html Free Pascal documentation on INI files]<br />
<br />
== See also ==<br />
* [[HistoryFiles]]<br />
* [[XML Tutorial]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:FPC]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Using_INI_Files&diff=98641Using INI Files2016-01-01T22:53:59Z<p>Eny: Consistency in key/name</p>
<hr />
<div>{{INI Files}}<br />
==INI Files==<br />
<br />
===Basic Information===<br />
INI files are text files that store key/value pairs that are grouped in sections.<br />
<br />
INI files can be used to save user settings easily. With the [http://www.freepascal.org/docs-html/fcl/inifiles/index.html IniFiles unit] and the [http://www.freepascal.org/docs-html/fcl/inifiles/tinifile.html TINIFile class] you can easily work with them. The IniFiles unit is part of the FCL.<br />
<br />
Currently only INI files compliant with the more restrictive Windows ini-file format are supported. I.e. using [http://en.wikipedia.org/wiki/INI_file#Sections sections] is mandatory and only the semicolon can be used for comments.<br />
For more general information about ini-files read [http://en.wikipedia.org/wiki/INI_file this].<br />
<br />
===INI Files===<br />
INI Files use brackets to create and mark '''Sections''', which contain '''keys''' and key '''values'''.<br />
A Key and its corresponding Value are separated with an equals sign (Key=Value).<br />
<br />
Section names are put inside square brackets ([Section]).<br />
<br />
Comments are usually permitted and are marked with a semicolon (;) at the beginning of a line. As INI files are not totally standardized, other characters are also used, such as #<br />
<br />
Nowadays, XML files are often used for string storage instead of INI files, because INI files don't handle large strings very well. However, ini(-like) files remain a dominant way of configuring Linux/Unix settings.<br />
<br />
An example ini file:<br />
<br />
<syntaxhighlight lang="ini"><br />
; Comment. Beginning of INI file<br />
<br />
; empty lines are ignored<br />
<br />
; note that no section has been defined.<br />
Compiler=Delphi<br />
; Key: Compiler<br />
; Value: Delphi<br />
<br />
[General]<br />
; This starts a General section<br />
Compiler=FreePascal<br />
; Key Compiler and value FreePascal<br />
</syntaxhighlight><br />
<br />
===Ini file reading example===<br />
<br />
The console application below shows how to read ini files. To test it one should create the following ini file with the name "C:\DB.ini". Edit it to so it contains a section called INIDB and the following keys and values:<br />
<br />
<syntaxhighlight lang="ini"><br />
[INIDB]<br />
; Save as C:\DB.ini<br />
Author=Adam<br />
Pass=<br />
DBFile=C:\Money.dat<br />
</syntaxhighlight><br />
<br />
Now let's move on the the code..<br />
<syntaxhighlight><br />
Program IniSample;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
Uses<br />
Classes,SysUtils,INIFiles;<br />
<br />
Var<br />
INI:TINIFile;<br />
Author,Pass,DBFile:String;<br />
PassEnter:String;<br />
<br />
begin<br />
// Create the object, specifying the place where it can find the ini file:<br />
INI := TINIFile.Create('C:\DB.ini');<br />
// Demonstrates reading strings from the INI file.<br />
// You can also read booleans etc.<br />
Author := INI.ReadString('INIDB','Author','');<br />
Pass := INI.ReadString('INIDB','Pass','');<br />
DBFile := INI.ReadString('INIDB','DBFile','');<br />
if Pass <> '' then<br />
begin<br />
Writeln('Password Required');<br />
Repeat<br />
Readln(PassEnter);<br />
if not (PassEnter = Pass) then Writeln('Wrong Password');<br />
until(PassEnter = Pass);<br />
Writeln('Correct Password');<br />
end;<br />
Writeln('Author : '+Author);<br />
Writeln('File : '+DBFile);<br />
Writeln('Password : '+Pass);<br />
Readln;<br />
// After we used ini file, we must call the Free method of object<br />
// ... although this really should be wrapped in a try..finally block<br />
// so that the object will be freed regardless of any errors above.<br />
Ini.Free; <br />
end. <br />
</syntaxhighlight><br />
<br />
===Objects to know===<br />
In the TINIFile class there are many different properties, procedures and functions that can be used.<br />
<br />
'''CaseSensitive''' - This property allows you to say if the keys and sections are case sensitive or not. By default they aren't.<br />
<br />
'''ReadString''' - Has 3 constant statements. The first one is the section to search in. The second one is the key to look for. The third one is a default string in case the key and/or section searched for is not found.<br />
<br />
'''WriteString''' - has three constant statements, too. The first is the section. The second is the key and the last is the value that you want to write. If the key and section exist already, the key will be overwritten with the new value..<br />
<br />
'''ReadSections''' - Will allow you to to take the sections from the INI file and put them in a TStrings class (or TStringList with the AS operator).<br />
<br />
'''DeleteKey''' - Remove an existing key from a specific section.<br />
<br />
'''EraseSection''' - Remove a section and all its data.<br />
<br />
There are more procedures and functions but this is enough to get you started.<br />
<br />
===Reference Documentation===<br />
Here: [http://lazarus-ccr.sourceforge.net/docs/fcl/inifiles/index.html Free Pascal documentation on INI files]<br />
<br />
== See also ==<br />
* [[HistoryFiles]]<br />
* [[XML Tutorial]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:FPC]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Using_INI_Files&diff=98640Using INI Files2016-01-01T22:52:33Z<p>Eny: Partial rewrite to remove invalid text and add url's to documentation.</p>
<hr />
<div>{{INI Files}}<br />
==INI Files==<br />
<br />
===Basic Information===<br />
INI files are text files that store name/value pairs that are grouped in sections.<br />
<br />
INI files can be used to save user settings easily. With the [http://www.freepascal.org/docs-html/fcl/inifiles/index.html IniFiles unit] and the [http://www.freepascal.org/docs-html/fcl/inifiles/tinifile.html TINIFile class] you can easily work with them. The IniFiles unit is part of the FCL.<br />
<br />
Currently only INI files compliant with the more restrictive Windows ini-file format are supported. I.e. using [http://en.wikipedia.org/wiki/INI_file#Sections sections] is mandatory and only the semicolon can be used for comments.<br />
For more general information about ini-files read [http://en.wikipedia.org/wiki/INI_file this].<br />
<br />
===INI Files===<br />
INI Files use brackets to create and mark '''Sections''', which contain '''keys''' and key '''values'''.<br />
A Key and its corresponding Value are separated with an equals sign (Key=Value).<br />
<br />
Section names are put inside square brackets ([Section]).<br />
<br />
Comments are usually permitted and are marked with a semicolon (;) at the beginning of a line. As INI files are not totally standardized, other characters are also used, such as #<br />
<br />
Nowadays, XML files are often used for string storage instead of INI files, because INI files don't handle large strings very well. However, ini(-like) files remain a dominant way of configuring Linux/Unix settings.<br />
<br />
An example ini file:<br />
<br />
<syntaxhighlight lang="ini"><br />
; Comment. Beginning of INI file<br />
<br />
; empty lines are ignored<br />
<br />
; note that no section has been defined.<br />
Compiler=Delphi<br />
; Key: Compiler<br />
; Value: Delphi<br />
<br />
[General]<br />
; This starts a General section<br />
Compiler=FreePascal<br />
; Key Compiler and value FreePascal<br />
</syntaxhighlight><br />
<br />
===Ini file reading example===<br />
<br />
The console application below shows how to read ini files. To test it one should create the following ini file with the name "C:\DB.ini". Edit it to so it contains a section called INIDB and the following keys and values:<br />
<br />
<syntaxhighlight lang="ini"><br />
[INIDB]<br />
; Save as C:\DB.ini<br />
Author=Adam<br />
Pass=<br />
DBFile=C:\Money.dat<br />
</syntaxhighlight><br />
<br />
Now let's move on the the code..<br />
<syntaxhighlight><br />
Program IniSample;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
Uses<br />
Classes,SysUtils,INIFiles;<br />
<br />
Var<br />
INI:TINIFile;<br />
Author,Pass,DBFile:String;<br />
PassEnter:String;<br />
<br />
begin<br />
// Create the object, specifying the place where it can find the ini file:<br />
INI := TINIFile.Create('C:\DB.ini');<br />
// Demonstrates reading strings from the INI file.<br />
// You can also read booleans etc.<br />
Author := INI.ReadString('INIDB','Author','');<br />
Pass := INI.ReadString('INIDB','Pass','');<br />
DBFile := INI.ReadString('INIDB','DBFile','');<br />
if Pass <> '' then<br />
begin<br />
Writeln('Password Required');<br />
Repeat<br />
Readln(PassEnter);<br />
if not (PassEnter = Pass) then Writeln('Wrong Password');<br />
until(PassEnter = Pass);<br />
Writeln('Correct Password');<br />
end;<br />
Writeln('Author : '+Author);<br />
Writeln('File : '+DBFile);<br />
Writeln('Password : '+Pass);<br />
Readln;<br />
// After we used ini file, we must call the Free method of object<br />
// ... although this really should be wrapped in a try..finally block<br />
// so that the object will be freed regardless of any errors above.<br />
Ini.Free; <br />
end. <br />
</syntaxhighlight><br />
<br />
===Objects to know===<br />
In the TINIFile class there are many different properties, procedures and functions that can be used.<br />
<br />
'''CaseSensitive''' - This property allows you to say if the keys and sections are case sensitive or not. By default they aren't.<br />
<br />
'''ReadString''' - Has 3 constant statements. The first one is the section to search in. The second one is the key to look for. The third one is a default string in case the key and/or section searched for is not found.<br />
<br />
'''WriteString''' - has three constant statements, too. The first is the section. The second is the key and the last is the value that you want to write. If the key and section exist already, the key will be overwritten with the new value..<br />
<br />
'''ReadSections''' - Will allow you to to take the sections from the INI file and put them in a TStrings class (or TStringList with the AS operator).<br />
<br />
'''DeleteKey''' - Remove an existing key from a specific section.<br />
<br />
'''EraseSection''' - Remove a section and all its data.<br />
<br />
There are more procedures and functions but this is enough to get you started.<br />
<br />
===Reference Documentation===<br />
Here: [http://lazarus-ccr.sourceforge.net/docs/fcl/inifiles/index.html Free Pascal documentation on INI files]<br />
<br />
== See also ==<br />
* [[HistoryFiles]]<br />
* [[XML Tutorial]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:FPC]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Talk:Everything_else_about_translations&diff=95304Talk:Everything else about translations2015-09-07T13:47:22Z<p>Eny: Making a maze of the wiki. This page should be merged (back??) with the page about internationalization and localization</p>
<hr />
<div>OMG... why was this page created? Why not keep the i18n together???</div>Enyhttps://wiki.freepascal.org/index.php?title=Executing_External_Programs&diff=95187Executing External Programs2015-09-04T23:02:32Z<p>Eny: Remove irrelvant comments</p>
<hr />
<div>{{Executing External Programs}}<br />
<br />
== Overview : Comparison ==<br />
{| class="wikitable"<br />
!Method<br />
!Platforms<br />
!Single Line<br />
!Features<br />
|-<br />
|SysUtils.ExecuteProcess<br />
|Cross-Platform<br />
|Yes<br />
|Very limited, synchronous<br />
|-<br />
|(ShellApi) ShellExecute<br />
|MS Windows only<br />
|Yes<br />
|Many. Can start programs with elevation/admin permissions.<br />
|-<br />
|Unix fpsystem, fpexecve<br />
|Unix only<br />
|<br />
|<br />
|-<br />
|TProcess <br />
|Cross-Platform<br />
|No<br />
|Full<br />
|-<br />
|RunCommand(InDir)<br />
|Cross-Platform '''Requires FPC 2.6.2+'''<br />
|Yes<br />
|Covers common TProcess usage<br />
|-<br />
|(LCLIntf) OpenDocument<br />
|Cross-Platform<br />
|Yes<br />
|Only open document<br />
|}<br />
<br />
==(Process.)RunCommand==<br />
In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the [[Projects using Lazarus#fpcup|fpcup]] project.<br />
These functions are meant for basic and intermediate use and can capture output to a single string and fully support the ''large output'' case. <br />
<br />
A simple example is<br />
<syntaxhighlight><br />
uses Process;<br />
...<br />
var s : ansistring;<br />
...<br />
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then<br />
writeln(s); <br />
</syntaxhighlight> <br />
An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets p.CurrentDirectory):<br />
<syntaxhighlight><br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;<br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
</syntaxhighlight><br />
<br />
== SysUtils.ExecuteProcess ==<br />
(Cross-platform)<BR><br />
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :<br />
<br />
<syntaxhighlight>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight><br />
<br />
The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the next section about the prefered cross-platform '''TProcess''', or if you only wish to target Windows you may use '''ShellExecute'''.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess reference]<br />
<br />
== MS Windows : CreateProcess, ShellExecute and WinExec ==<br />
<br />
{{Note|While FPC/Lazarus has support for '''CreateProcess''', '''ShellExecute''' and/or '''WinExec''', this support is only in Win32/64. If your program is cross-platform, consider using '''RunCommand''' or '''TProcess'''.}}<br />
{{Note|WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.}}<br />
<br />
'''ShellExecute''' is a standard MS Windows function (ShellApi.h) with good [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx documentation on MSDN] (note their remarks about initialising COM if you find the function unreliable).<br />
<br />
<syntaxhighlight><br />
uses ..., ShellApi;<br />
<br />
// Simple one-liner (ignoring error returns) :<br />
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;<br />
<br />
// Execute a Batch File :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;<br />
<br />
// Open a command window in a given folder :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;<br />
<br />
// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;<br />
<br />
// or a useful procedure:<br />
procedure RunShellExecute(const prog,params:string);<br />
begin<br />
// ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' isn't always needed <br />
// path+prog, params, working folder,<br />
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...<br />
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success<br />
// return values 0..32 are errors<br />
end;<br />
</syntaxhighlight><br />
<br />
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.<br />
<br />
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.<br />
<br />
If in Delphi you used ShellExecute for '''documents''' like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).<br />
<br />
=== Using ShellExecuteEx for elevation/administrator permissions ===<br />
If you need to execute external program with administrator/elevated privileges, you can use the '''runas''' method with the alternative ShellExecuteEx function:<br />
<syntaxhighlight><br />
uses ShellApi, ...;<br />
<br />
function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;<br />
var<br />
sei: TShellExecuteInfoA;<br />
begin<br />
FillChar(sei, SizeOf(sei), 0);<br />
sei.cbSize := SizeOf(sei);<br />
sei.Wnd := Handle;<br />
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;<br />
sei.lpVerb := 'runas';<br />
sei.lpFile := PAnsiChar(Path);<br />
sei.lpParameters := PAnsiChar(Params);<br />
sei.nShow := SW_SHOWNORMAL;<br />
Result := ShellExecuteExA(@sei);<br />
end;<br />
<br />
procedure TFormMain.RunAddOrRemoveApplication;<br />
begin<br />
// Example that uses elevated rundll to open the Control Panel to Programs and features<br />
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');<br />
end;<br />
</syntaxhighlight><br />
<br />
== Unix fpsystem, fpexecve and shell ==<br />
<br />
These functions are platform dependent.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem reference]<br />
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve reference]<br />
* [http://www.freepascal.org/docs-html/rtl/unix/shell.html shell reference]<br />
<br />
Note that the 1.0.x '''Unix.Shell'' has been deprecated for a while, and is removed in trunk. Use fpsystem.<br />
<br />
== TProcess ==<br />
<br />
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:<br />
<br />
* Platform Independent<br />
* Capable of reading from stdout and writing to stdin.<br />
* Possible to wait for a command to finish or let it run while your program moves on.<br />
<br />
Important notes:<br />
* TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..<br />
* Presumably on Linux/Unix: you '''must''' specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.<br />
* On Windows, if the command is in the path, you don't need to specify the full path.<br />
* [[doc:fcl/process/tprocess.html|TProcess reference]]<br />
<br />
=== The Simplest Example ===<br />
<br />
A lot of typical cases have been prepared in the [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]] functions. Before you start copy and paste the examples below, check them out first.<br />
<br />
=== A Simple Example ===<br />
This example ('''that shouldn't be used in production, see Large Output or, better, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''') just shows you how to run an external program, nothing more:<br />
<syntaxhighlight><br />
// This is a demo program that shows<br />
// how to launch an external program.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This defines the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
var <br />
AProcess: TProcess;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler (i386 version that is)<br />
AProcess.Executable:= 'ppc386';<br />
<br />
// Pass -h together with ppc386 so actually 'ppc386 -h' is executed:<br />
AProcess.Parameters.Add('-h');<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. vvvvvvvvvvvvvv<br />
AProcess.Options := AProcess.Options + [poWaitOnExit];<br />
<br />
// Now let AProcess run the program<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
That's it! You have just learned to run an external program from inside your own program.<br />
<br />
=== An improved example (but not correct yet)===<br />
That's nice, but how do I read the Output of a program that I have run?<br />
<br />
Well, let's expand our example a little and do just that:<br />
'''This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in [[#Reading large output]].'''<br />
<br />
<syntaxhighlight><br />
// This is a <br />
// FLAWED<br />
// demo program that shows<br />
// how to launch an external program<br />
// and read from its output.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This is defining the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
// Also now we are adding a TStringList to store the <br />
// data read from the programs output.<br />
var <br />
AProcess: TProcess;<br />
AStringList: TStringList;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
AProcess.Executable := '/usr/bin/ppc386'; <br />
AProcess.Parameters.Add('-h'); <br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. Also now we will tell it that<br />
// we want to read the output of the file.<br />
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];<br />
<br />
// Now that AProcess knows what the commandline is it can be run.<br />
AProcess.Execute;<br />
<br />
// After AProcess has finished, the rest of the program will be executed.<br />
<br />
// Now read the output of the program we just ran into a TStringList.<br />
AStringList := TStringList.Create;<br />
AStringList.LoadFromStream(AProcess.Output);<br />
<br />
// Save the output to a file and clean up the TStringList.<br />
AStringList.SaveToFile('output.txt');<br />
AStringList.Free;<br />
<br />
// Now that the output from the process is processed, it can be freed.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
<br />
=== Reading large output ===<br />
In the previous example we waited until the program exited. Then we read what the program has written to its output.<br />
<br />
Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from. <br />
<br />
But the calling program doesn't read from it until the called program has ended. A deadlock occurs.<br />
<br />
The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.<br />
<br />
If you want to read output from an external process, this is the code you should adapt for production use.<br />
<syntaxhighlight>program LargeOutputDemo;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes, SysUtils, Process; // Process is the unit that holds TProcess<br />
<br />
const<br />
BUF_SIZE = 2048; // Buffer size for reading the output in chunks<br />
<br />
var<br />
AProcess : TProcess;<br />
OutputStream : TStream;<br />
BytesRead : longint;<br />
Buffer : array[1..BUF_SIZE] of byte;<br />
<br />
begin<br />
// Set up the process; as an example a recursive directory search is used<br />
// because that will usually result in a lot of data.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// The commands for Windows and *nix are different hence the $IFDEFs<br />
{$IFDEF Windows}<br />
// In Windows the dir command cannot be used directly because it's a build-in<br />
// shell command. Therefore cmd.exe and the extra parameters are needed.<br />
AProcess.Executable := 'c:\windows\system32\cmd.exe';<br />
AProcess.Parameters.Add('/c');<br />
AProcess.Parameters.Add('dir /s c:\windows');<br />
{$ENDIF Windows}<br />
<br />
{$IFDEF Unix}<br />
AProcess.Executable := '/bin/ls';<br />
AProcess.Parameters.Add('--recursive');<br />
AProcess.Parameters.Add('--all');<br />
AProcess.Parameters.Add('-l');<br />
{$ENDIF Unix}<br />
<br />
// Process option poUsePipes has to be used so the output can be captured.<br />
// Process option poWaitOnExit can not be used because that would block<br />
// this program, preventing it from reading the output data of the process.<br />
AProcess.Options := [poUsePipes];<br />
<br />
// Start the process (run the dir/ls command)<br />
AProcess.Execute;<br />
<br />
// Create a stream object to store the generated output in. This could<br />
// also be a file stream to directly save the output to disk.<br />
OutputStream := TMemoryStream.Create;<br />
<br />
// All generated output from AProcess is read in a loop until no more data is available<br />
repeat<br />
// Get the new data from the process to a maximum of the buffer size that was allocated.<br />
// Note that all read(...) calls will block except for the last one, which returns 0 (zero).<br />
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);<br />
<br />
// Add the bytes that were read to the stream for later usage<br />
OutputStream.Write(Buffer, BytesRead)<br />
<br />
until BytesRead = 0; // Stop if no more data is available<br />
<br />
// The process has finished so it can be cleaned up<br />
AProcess.Free;<br />
<br />
// Now that all data has been read it can be used; for example to save it to a file on disk<br />
with TFileStream.Create('output.txt', fmCreate) do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
CopyFrom(OutputStream, OutputStream.Size);<br />
Free<br />
end;<br />
<br />
// Or the data can be shown on screen<br />
with TStringList.Create do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
LoadFromStream(OutputStream);<br />
writeln(Text);<br />
writeln('--- Number of lines = ', Count, '----');<br />
Free<br />
end;<br />
<br />
// Clean up<br />
OutputStream.Free;<br />
end.</syntaxhighlight><br />
Note that the above could also be accomplished by using RunCommand:<br />
<syntaxhighlight>var s: string;<br />
...<br />
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight><br />
<br />
=== Using input and output of a TProcess ===<br />
See processdemo example in the [https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].<br />
<br />
=== Hints on the use of TProcess ===<br />
When creating a cross-platform program, the OS-specific executable name can be set using directives "{$IFDEF}" and "{$ENDIF}".<br />
<br />
Example:<br />
<syntaxhighlight>{...}<br />
AProcess := TProcess.Create(nil)<br />
<br />
{$IFDEF WIN32}<br />
AProcess.Executable := 'calc.exe'; <br />
{$ENDIF}<br />
<br />
{$IFDEF LINUX}<br />
AProcess.Executable := 'kcalc'; <br />
{$ENDIF}<br />
<br />
AProcess.Execute;<br />
{...}</syntaxhighlight><br />
<br />
=== OS X show application bundle in foreground ===<br />
<br />
You can start an '''application bundle''' via TProcess by starting the executable within the bundle. For example:<br />
<syntaxhighlight><br />
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';<br />
</syntaxhighlight><br />
<br />
This will start the ''Calendar'', but the window will be behind the current application.<br />
To get the application in the foreground you can use the '''open''' utility with the '''-n''' parameter:<br />
<syntaxhighlight><br />
AProcess.Executable:='/usr/bin/open';<br />
AProcess.Parameters.Add('-n');<br />
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole<br />
AProcess.Parameters.Add('/Application/iCal.app');<br />
</syntaxhighlight><br />
<br />
If your application needs parameters, you can pass '''open''' the '''--args''' parameter, after which all parameters are passed to the application:<br />
<syntaxhighlight><br />
AProcess.Parameters.Add('--args');<br />
AProcess.Parameters.Add('argument1');<br />
AProcess.Parameters.Add('argument2');<br />
</syntaxhighlight><br />
<br />
=== Run detached program ===<br />
<br />
Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:<br />
<br />
<syntaxhighlight><br />
var<br />
Process: TProcess;<br />
I: Integer;<br />
begin<br />
Process := TProcess.Create(nil);<br />
try<br />
Process.InheritHandles := False;<br />
Process.Options := [];<br />
Process.ShowWindow := swoShow;<br />
<br />
// Copy default environment variables including DISPLAY variable for GUI application to work<br />
for I := 1 to GetEnvironmentVariableCount do<br />
Process.Environment.Add(GetEnvironmentString(I));<br />
<br />
Process.Executable := '/usr/bin/gedit'; <br />
Process.Execute;<br />
finally<br />
Process.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== Example of "talking" with aspell process ===<br />
<br />
Inside [http://pasdoc.sourceforge.net/ pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.<br />
<br />
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.<br />
<br />
=== Replacing shell operators like "| < >" ===<br />
<br />
Sometimes you want to run a more complicated command that pipes its data to another command or to a file.<br />
Something like <br />
<syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight><br />
or<br />
<syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight><br />
<br />
Executing this with TProcess will not work. i.e:<br />
<br />
<syntaxhighlight>// this won't work<br />
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; <br />
Process.Execute;</syntaxhighlight><br />
<br />
==== Why using special operators to redirect output doesn't work ====<br />
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.<br />
It is possible to redirect output however just the way you wanted. See the [[Executing_External_Programs#How_to_redirect_output_with_TProcess |next section]].<br />
<br />
=== How to redirect output with TProcess ===<br />
<br />
You can redirect the output of a command to another command by using a TProcess instance for '''each''' command.<br />
<br />
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]<br />
<br />
Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.<br />
<br />
<syntaxhighlight>program Project1;<br />
<br />
uses<br />
Classes, sysutils, process;<br />
<br />
var<br />
FirstProcess,<br />
SecondProcess: TProcess;<br />
Buffer: array[0..127] of char;<br />
ReadCount: Integer;<br />
ReadSize: Integer;<br />
begin<br />
FirstProcess := TProcess.Create(nil);<br />
SecondProcess := TProcess.Create(nil);<br />
<br />
FirstProcess.Options := [poUsePipes]; <br />
FirstProcess.Executable := 'pwd'; <br />
<br />
SecondProcess.Options := [poUsePipes,poStderrToOutPut];<br />
SecondProcess.Executable := 'grep'; <br />
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); <br />
// this would be the same as "pwd | grep / -"<br />
<br />
FirstProcess.Execute;<br />
SecondProcess.Execute;<br />
<br />
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do<br />
begin<br />
if FirstProcess.Output.NumBytesAvailable > 0 then<br />
begin<br />
// make sure that we don't read more data than we have allocated<br />
// in the buffer<br />
ReadSize := FirstProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
// now read the output into the buffer<br />
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);<br />
// and write the buffer to the second process<br />
SecondProcess.Input.Write(Buffer[0], ReadCount);<br />
<br />
// if SecondProcess writes much data to it's Output then <br />
// we should read that data here to prevent a deadlock<br />
// see the previous example "Reading Large Output"<br />
end;<br />
end;<br />
// Close the input on the SecondProcess<br />
// so it finishes processing it's data<br />
SecondProcess.CloseInput;<br />
<br />
// and wait for it to complete<br />
// be carefull what command you run because it may not exit when<br />
// it's input is closed and the following line may loop forever<br />
while SecondProcess.Running do<br />
Sleep(1);<br />
// that's it! the rest of the program is just so the example<br />
// is a little 'useful'<br />
<br />
// we will reuse Buffer to output the SecondProcess's<br />
// output to *this* programs stdout<br />
WriteLn('Grep output Start:');<br />
ReadSize := SecondProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
if ReadSize > 0 then<br />
begin<br />
ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);<br />
WriteLn(Copy(Buffer,0, ReadCount));<br />
end<br />
else<br />
WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);<br />
WriteLn('Grep output Finish:');<br />
<br />
// free our process objects<br />
FirstProcess.Free;<br />
SecondProcess.Free;<br />
end.</syntaxhighlight><br />
<br />
That's it. Now you can redirect output from one program to another.<br />
<br />
==== Notes ====<br />
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:<br />
<syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight><br />
<br />
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.<br />
<br />
===Redirecting input and output and running under root===<br />
A common problem on Unixes (OSX) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ''ping'' command.<br />
<br />
If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). This sample runs <code>ls</code> on the <code>/root</code> directory, but can of course be adapted.<br />
<br />
A '''better way''' to do this is to use the policykit package, which should be available on all recent Linuxes. [http://lazarus.freepascal.org/index.php/topic,14479.0.html See the forum thread for details.]<br />
<br />
Large parts of this code are similar to the earlier example, but it also shows how to redirect stdout and stderr of the process being called separately to stdout and stderr of our own code.<br />
<br />
<syntaxhighlight><br />
program rootls;<br />
<br />
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,<br />
calling sudo on Linux/OSX, and supplying input on stdin}<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes,<br />
Math, {for min}<br />
Process;<br />
<br />
procedure RunsLsRoot;<br />
var<br />
Proc: TProcess;<br />
CharBuffer: array [0..511] of char;<br />
ReadCount: integer;<br />
ExitCode: integer;<br />
SudoPassword: string;<br />
begin<br />
WriteLn('Please enter the sudo password:');<br />
Readln(SudoPassword);<br />
ExitCode := -1; //Start out with failure, let's see later if it works<br />
Proc := TProcess.Create(nil); //Create a new process<br />
try<br />
Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr<br />
Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo<br />
// -S causes sudo to read the password from stdin.<br />
Proc.Execute; //start it. sudo will now probably ask for a password<br />
<br />
// write the password to stdin of the sudo program:<br />
SudoPassword := SudoPassword + LineEnding;<br />
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));<br />
SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof<br />
SudoPassword := ''; // and make the program a bit safer from snooping?!?<br />
<br />
// main loop to read output from stdout and stderr of sudo<br />
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or<br />
(Proc.Stderr.NumBytesAvailable > 0) do<br />
begin<br />
// read stdout and write to our stdout<br />
while Proc.Output.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Output.Read(CharBuffer, ReadCount);<br />
Write(StdOut, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
// read stderr and write to our stderr<br />
while Proc.Stderr.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Stderr.Read(CharBuffer, ReadCount);<br />
Write(StdErr, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
end;<br />
ExitCode := Proc.ExitStatus;<br />
finally<br />
Proc.Free;<br />
Halt(ExitCode);<br />
end;<br />
end;<br />
<br />
begin<br />
RunsLsRoot;<br />
end.<br />
</syntaxhighlight><br />
<br />
Other thoughts:<br />
It would no doubt be advisable to see if sudo actually prompts for a password. This can be checked consistently by setting the environment variable SUDO_PROMPT to something we watch for while reading the stdout of TProcess avoiding the problem of the prompt being different for different locales. Setting an environment variable causes the default values to be cleared(inherited from our process) so we have to copy the environment from our program if needed.<br />
<br />
=== Using fdisk with sudo on Linux ===<br />
The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.<br />
<br />
<syntaxhighlight><br />
program getpartitioninfo;<br />
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.<br />
Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}<br />
<br />
Uses<br />
Classes, SysUtils, FileUtil, Process;<br />
<br />
var<br />
hprocess: TProcess;<br />
sPass: String;<br />
OutputLines: TStringList;<br />
<br />
begin <br />
sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password<br />
OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure <br />
// OutputLines is freed... Same for hProcess.<br />
<br />
// The following example will open fdisk in the background and give us partition information<br />
// Since fdisk requires elevated priviledges we need to <br />
// pass our password as a parameter to sudo using the -S<br />
// option, so it will will wait till our program sends our password to the sudo application<br />
hProcess := TProcess.Create(nil);<br />
// On Linux/Unix/OSX, we need specify full path to our executable:<br />
hProcess.Executable := '/bin/sh';<br />
// Now we add all the parameters on the command line:<br />
hprocess.Parameters.Add('-c');<br />
// Here we pipe the password to the sudo command which then executes fdisk -l: <br />
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');<br />
// Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe<br />
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];<br />
// Now run:<br />
hProcess.Execute;<br />
<br />
// hProcess should have now run the external executable (because we use poWaitOnExit).<br />
// Now you can process the process output (standard output and standard error), eg:<br />
OutputLines.Add('stdout:');<br />
OutputLines.LoadFromStream(hprocess.Output);<br />
OutputLines.Add('stderr:');<br />
OutputLines.LoadFromStream(hProcess.Stderr);<br />
// Show output on screen:<br />
writeln(OutputLines.Text);<br />
<br />
// Clean up to avoid memory leaks:<br />
hProcess.Free;<br />
OutputLines.Free;<br />
<br />
//Below are some examples as you see we can pass illegal characters just as if done from terminal <br />
//Even though you have read elsewhere that you can not I assure with this method you can :)<br />
<br />
//hprocess.Parameters.Add('ping -c 1 www.google.com');<br />
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');<br />
<br />
//Using QuotedStr() is not a requirement though it makes for cleaner code;<br />
//you can use double quote and have the same effect.<br />
<br />
//hprocess.Parameters.Add('glxinfo | grep direct'); <br />
<br />
// This method can also be used for installing applications from your repository:<br />
<br />
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); <br />
<br />
end. <br />
</syntaxhighlight><br />
<br />
<br />
===Parameters which contain spaces (Replacing Shell Quotes)===<br />
<br />
In the Linux shell it is possible to write quoted arguments like this:<br />
<br />
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram<br />
<br />
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):<br />
#--batch<br />
#--eval-command=info symbol 0x0000DDDD<br />
#the full path to myprogram<br />
<br />
TProcess can also pass parameters containing spaces, but it uses a different quoting style. Instead of only quoting part of the parameter, quote all of it. Like this:<br />
<br />
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';<br />
<br />
And also remember to only pass full paths.<br />
<br />
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446<br />
<br />
==LCLIntf Alternatives==<br />
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.<br />
<br />
===Open document in default application===<br />
<br />
In some situations you need to open some document/file using default associated application rather than execute a particular program. <br />
This depends on running operating system. Lazarus provides a platform independent procedure '''OpenDocument''' which will handle it for you. Your application will continue running without waiting for the document process to close.<br />
<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenDocument('manual.pdf'); <br />
...<br />
</syntaxhighlight><br />
<br />
* [[opendocument|OpenDocument reference]]<br />
<br />
===Open web page in default web browser===<br />
<br />
Just pass the URL required, the leading http:// appears to be optional under certain circumstances. <br />
Also, passing a filename appears to give the same results as OpenDocument()<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenURL('www.lazarus.freepascal.org/');<br />
</syntaxhighlight><br />
<br />
See also:<br />
* [[openurl|OpenURL reference]]<br />
<br />
Or, you could use '''TProcess''' like this:<br />
<syntaxhighlight><br />
uses Process;<br />
<br />
procedure OpenWebPage(URL: string);<br />
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"<br />
var<br />
Browser, Params: string;<br />
begin<br />
FindDefaultBrowser(Browser, Params);<br />
with TProcess.Create(nil) do<br />
try<br />
Executable := Browser;<br />
Params:=Format(Params, [URL]);<br />
Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself<br />
Parameters.Add(Params);<br />
Options := [poNoConsole];<br />
Execute;<br />
finally<br />
Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]<br />
* [[opendocument]]<br />
* [[openurl]]<br />
* [[TProcessUTF8]]<br />
* [[TXMLPropStorage]]<br />
* [[Webbrowser]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Parallel programming]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Inter-process communication]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Everything_else_about_translations&diff=95101Everything else about translations2015-09-01T13:56:28Z<p>Eny: /* Lazarus Resources */</p>
<hr />
<div>===Introduction===<br />
<br />
'''Note:''' This stuff was before in [[Translations_/_i18n_/_localizations_for_programs]]. If you think that something is really important move that stuff back. Please read both articles carefully in order to avoid duplicate stuff.<br />
<br />
===.po Files===<br />
<br />
There are many free graphical tools to edit .po files, which are simple text like the .rst files, but with some more options, like a header providing fields for author, encoding, language and date. Every FPC installation provides the tool '''rstconv''' (windows: rstconv.exe). This tool can be used to convert a .rst file into a .po file. The IDE can do this automatically.<br />
Some free tools: kbabel, po-auto-translator, poedit, virtaal.<br />
<br />
Virtaal has a translation memory containing source-target language pairs for items that you already translated once, and a translation suggestion function that shows already translated terms in various open source software packages. These function may save you a lot of work and improve consistency.<br />
<br />
Example of using rstconv directly:<br />
rstconv -i unit1.rst -o unit1.po<br />
<br />
===Translating===<br />
<br />
For every language the .po file must be copied and translated. The LCL translation unit uses the common language codes (en=english, de=german, it=italian, ...) to search. For example the German translation of unit1.po would be unit1.de.po. To achieve this, copy the unit1.po file to unit1.de.po, unit1.it.po, and whatever language you want to support and then the translators can edit their specific .po file.<br />
<br />
{{Note|For Brazilians/Portuguese: Lazarus IDE and LCL only have a Brazilian Portuguese translation and these files have 'pt_BR.po' extensions}}<br />
<br />
===IDE options for automatic updates of .po files===<br />
<br />
*The unit containing the resource strings must be added to the package or project.<br />
*You must provide a .po path, this means a separate directory. For example: create a sub directory ''language'' in the package / project directory. For projects go to the Project > Project Options. For packages go to Options > IDE integration.<br />
<br />
When this options are enabled, the IDE generates or updates the base .po file using the information contained in .rst and .lrt files (rstconv tool is then not necesary). The update process begins by collecting all existing entries found in base .po file and in .rst and .lrt files and then applying the following features it finds and brings up to date any translated .xx.po file. <br />
<br />
====Removal of Obsolete entries====<br />
<br />
Entries in the base .po file that are not found in .rst and .lrt files are removed. Subsequently, all entries found in translated .xx.po files not found in the base .po file are also removed. This way, .po files are not cluttered with obsolete entries and translators don't have to translate entries that are not used.<br />
<br />
====Duplicate entries====<br />
<br />
Duplicate entries occur when for some reason the same text is used for different resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:<br />
<syntaxhighlight> dlfMouseSimpleGutterSect = 'Gutter';<br />
dlgMouseOptNodeGutter = 'Gutter';<br />
dlgGutter = 'Gutter';<br />
dlgAddHiAttrGroupGutter = 'Gutter'; <br />
</syntaxhighlight><br />
A converted .rst file for this resource strings would look similar to this in a .po file:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.<br />
<br />
The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.<br />
<br />
====Fuzzy entries====<br />
<br />
Changes in resource strings affect translations, for example if initially a resource string was defined like:<br />
<syntaxhighlight>dlgEdColor = 'Syntax highlight';</syntaxhighlight><br />
<br />
this would produce a .po entry similar to this<br />
<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr ""<br />
which if translated to Spanish (this sample was taken from lazarus history), may result in<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr "Color"<br />
Suppose then that at a later time, the resource string has been changed to<br />
<syntaxhighlight><br />
dlgEdColor = 'Colors';<br />
</syntaxhighlight><br />
the resulting .po entry may become<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Colors"<br />
msgstr ""<br />
Note that while the ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors'. As the string was already translated the old translation may not match the new meaning. Indeed, for the new string probably 'Colores' may be a better translation. <br />
The automatic update tool notices this situation and produces an entry like this:<br />
#: lazarusidestrconsts.dlgedcolor<br />
#, fuzzy<br />
#| msgid "Syntax highlight"<br />
msgctxt "lazarusidestrconsts.dlgedcolor"<br />
msgid "Colors"<br />
msgstr "Color"<br />
In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present a special GUI to the translator user for this item. In this case, the flag would mean that the translation in its current state is doubtful and needs to be reviewed more carefully by translator. The "#|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.<br />
<br />
===Translating Forms, Datamodules and Frames===<br />
<br />
When the i18n option is enabled for the project / package then the IDE automatically creates .lrt files for every form. It creates the .lrt file on saving a unit. So, if you enable the option for the first time, you must open every form once, move it a little bit, so that it is modified, and save the form. For example if you save a form ''unit1.pas'' the IDE creates a ''unit1.lrt''. And on compile the IDE gathers all strings of all .lrt files and all .rst file into a single .po file (projectname.po or packagename.po) in the i18n directory.<br />
<br />
For the forms to be actually translated at runtime, you have to assign a translator to LRSTranslator (defined in LResources) in the initialization section to one of your units<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
LResources;<br />
...<br />
...<br />
initialization<br />
LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');</syntaxhighlight><br />
<br />
<s>However there's no TPoTranslator class (i.e a class that translates using .po files) available in the LCL. This is a possible implementation (partly lifted from DefaultTranslator.pas in the LCL):</s> The following code isn't needed anymore if you use recent Lazarus 0.9.29 snapshots. Simply include DefaultTranslator in Uses clause.<br />
<br />
<syntaxhighlight>unit PoTranslator;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
interface<br />
<br />
uses<br />
Classes, SysUtils, LResources, typinfo, Translations;<br />
<br />
type<br />
<br />
{ TPoTranslator }<br />
<br />
TPoTranslator=class(TAbstractTranslator)<br />
private<br />
FPOFile:TPOFile;<br />
public<br />
constructor Create(POFileName:string);<br />
destructor Destroy;override;<br />
procedure TranslateStringProperty(Sender:TObject; <br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;<br />
end;<br />
<br />
implementation<br />
<br />
{ TPoTranslator }<br />
<br />
constructor TPoTranslator.Create(POFileName: string);<br />
begin<br />
inherited Create;<br />
FPOFile:=TPOFile.Create(POFileName);<br />
end;<br />
<br />
destructor TPoTranslator.Destroy;<br />
begin<br />
FPOFile.Free;<br />
inherited Destroy;<br />
end;<br />
<br />
procedure TPoTranslator.TranslateStringProperty(Sender: TObject;<br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);<br />
var<br />
s: String;<br />
begin<br />
if not Assigned(FPOFile) then exit;<br />
if not Assigned(PropInfo) then exit;<br />
{DO we really need this?}<br />
if Instance is TComponent then<br />
if csDesigning in (Instance as TComponent).ComponentState then exit;<br />
{End DO :)}<br />
if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;<br />
s:=FPOFile.Translate(Content, Content);<br />
if s<>'' then Content:=s;<br />
end;<br />
<br />
end.</syntaxhighlight><br />
<br />
Alternatively you can transform the .po file into .mo using msgfmt (isn't needed anymore if you use recent 0.9.29 snapshot) and simply use the DefaultTranslator unit<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
DefaultTranslator;</syntaxhighlight><br />
<br />
which will automatically look in several standard places for a .po file (higher precedence) or .mo file <s>(the disadvantage is that you'll have to keep around both the .mo files for the DefaultTranslator unit and the .po files for TranslateUnitResourceStrings)</s>.<br />
If you use DefaultTranslator, it will try to automatically detect the language based on the LANG environment variable (overridable using the --lang command line switch), then look in these places for the translation (LANG stands for the desired language, ext can be either po or mo):<br />
<br />
* <Application Directory>/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/languages/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/LC_MESSAGES/<LANG/><Application Filename>.<ext><br />
<br />
under unix-like systems it will also look in<br />
<br />
* /usr/share/locale/<LANG>/LC_MESSAGES/<Application Filename>.<ext><br />
<br />
as well as using the short part of the language (e.g. if it is "es_ES" or "es_ES.UTF-8" and it doesn't exist it will also try "es")<br />
<br />
===Translating at start of program===<br />
<br />
For every .po file, you must call TranslateUnitResourceStrings. The LCL po file is lclstrconsts. For example you do this in FormCreate of your MainForm:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., gettext, translations;<br />
<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
PODirectory, Lang, FallbackLang: String;<br />
begin<br />
PODirectory := '/path/to/lazarus/lcl/languages/';<br />
GetLanguageIDs(Lang, FallbackLang);<br />
Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);<br />
<br />
// the following dialog now shows translated buttons:<br />
MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);<br />
end;<br />
</syntaxhighlight><br />
<br />
===Compiling po files into the executable===<br />
<br />
If you don't want to install the .po files, but put all files of the application into the executable, use one the following methods.<br />
<br />
====FPC Resources (Recommended)====<br />
Normal resources are now recommended for current FPC (including all recent Lazarus versions) [[Lazarus_Resources]]<br />
<br />
*Add the resources (.po files) to executable with the Lazarus IDE (Project Options > Resources) as RCDATA.<br />
<br />
<syntaxhighlight><br />
uses<br />
LCLType<br />
<br />
function Translate(Language: string): boolean;<br />
var<br />
Res: TResourceStream;<br />
PoStringStream: TStringStream;<br />
PoFile: TPOFile;<br />
begin<br />
Res := TResourceStream.Create(HInstance, 'project1.' + Language, RT_RCDATA);<br />
PoStringStream := TStringStream.Create('');<br />
Res.SaveToStream(PoStringStream);<br />
Res.Free;<br />
<br />
PoFile := TPOFile.Create(False);<br />
PoFile.ReadPOText(PoStringStream.DataString);<br />
PoStringStream.Free;<br />
<br />
Result := TranslateResourceStrings(PoFile);<br />
PoFile.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
====Lazarus Resources====<br />
<br />
*Create a new unit (not a form!).<br />
*Convert the .po file(s) to .lrs using tools/lazres:<br />
<pre><br />
./lazres unit1.lrs unit1.de.po<br />
</pre><br />
<br />
This will create an include file unit1.lrs beginning with<br />
<syntaxhighlight>LazarusResources.Add('unit1.de','PO',[<br />
...</syntaxhighlight><br />
<br />
*Add the code:<br />
<syntaxhighlight>uses LResources, Translations;<br />
<br />
resourcestring<br />
MyCaption = 'Caption';<br />
<br />
function TranslateUnitResourceStrings: boolean;<br />
var<br />
r: TLResource;<br />
POFile: TPOFile;<br />
begin<br />
r:=LazarusResources.Find('unit1.de','PO');<br />
POFile:=TPOFile.Create(False); //if Full=True then you can get a crash (Issue #0026021)<br />
try<br />
POFile.ReadPOText(r.Value);<br />
Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);<br />
finally<br />
POFile.Free;<br />
end;<br />
end;<br />
<br />
initialization<br />
{$I unit1.lrs}</syntaxhighlight><br />
<br />
* Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.<br />
<br />
Unfortunately this code will not compile with Lazarus 1.2.2 and earlier.<br />
<br />
For these Lazarus versions you can use something like this:<br />
<syntaxhighlight><br />
type<br />
TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);<br />
<br />
function TranslateFromResource(AResourceName, ALanguage : String): TTranslateFromResourceResult;<br />
var<br />
LRes : TLResource;<br />
POFile : TPOFile = nil;<br />
SStream : TStringStream = nil;<br />
begin<br />
Result := trResourceNotFound;<br />
LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');<br />
if LRes <> nil then<br />
try<br />
SStream := TStringStream.Create(LRes.Value); <br />
POFile := TPoFile.Create(SStream, False); // WARNING: This example relies on the bug in TStringStream.Create(...) that the<br />
// position in the stream is incorrectly set to 0, even though the stream<br />
// is initialized with LRes.Value i.e. assumed to be ready for *appending*!!!<br />
try<br />
if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess<br />
else Result := trTranslationError;<br />
except<br />
Result := trTranslationError;<br />
end;<br />
finally<br />
if Assigned(SStream) then SStream.Free;<br />
if Assigned(POFile) then POFile.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
Usage example:<br />
<br />
<syntaxhighlight><br />
initialization<br />
{$I lclstrconsts.de.lrs}<br />
TranslateFromResource('lclstrconsts', 'de');<br />
end.<br />
</syntaxhighlight><br />
<br />
===Cross-platform method to determine system language===<br />
<br />
The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.<br />
<br />
<syntaxhighlight><br />
uses<br />
Classes, SysUtils {add additional units that may be needed by your code here}<br />
{$IFDEF win32}<br />
, Windows<br />
{$ELSE}<br />
, Unix<br />
{$IFDEF LCLCarbon}<br />
, MacOSAll<br />
{$ENDIF}<br />
{$ENDIF}<br />
;<br />
</syntaxhighlight><br />
<br />
<syntaxhighlight><br />
function GetOSLanguage: string;<br />
{platform-independent method to read the language of the user interface}<br />
var<br />
l, fbl: string;<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef: CFLocaleRef;<br />
locale: CFStringRef;<br />
buffer: StringPtr;<br />
bufferSize: CFIndex;<br />
encoding: CFStringEncoding;<br />
success: boolean;<br />
{$ENDIF}<br />
begin<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef := CFLocaleCopyCurrent;<br />
locale := CFLocaleGetIdentifier(theLocaleRef);<br />
encoding := 0;<br />
bufferSize := 256;<br />
buffer := new(StringPtr);<br />
success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);<br />
if success then<br />
l := string(buffer^)<br />
else<br />
l := '';<br />
fbl := Copy(l, 1, 2);<br />
dispose(buffer);<br />
{$ELSE}<br />
{$IFDEF LINUX}<br />
fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);<br />
{$ELSE}<br />
GetLanguageIDs(l, fbl);<br />
{$ENDIF}<br />
{$ENDIF}<br />
Result := fbl;<br />
end;<br />
</syntaxhighlight><br />
<br />
===Translating the IDE===<br />
<br />
====Files====<br />
The .po files of the IDE are in the lazarus source directory:<br />
*lazarus/languages strings for the IDE<br />
*lazarus/lcl/languages/ strings for the LCL<br />
*lazarus/components/ideintf/languages/ strings for the IDE interface<br />
<br />
====Translators====<br />
* The German translation is maintained by Swen Heinig.<br />
* The Finnish translation is maintained by Seppo Suurtarla<br />
* The Russian translation is maintained by Maxim Ganetsky<br />
* The French translation is maintained by Gilles Vasseur<br />
<br />
When you want to start a new translation, ask on the mailing if someone is already working on that.<br />
<br />
Please read carefully: [[Lazarus_Documentation#Translating.2FInternationalization.2FLocalization|Translating/Internationalization/Localization]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Everything_else_about_translations&diff=95100Everything else about translations2015-09-01T13:55:45Z<p>Eny: Warn for the bug in TStringStream.Create(...)</p>
<hr />
<div>===Introduction===<br />
<br />
'''Note:''' This stuff was before in [[Translations_/_i18n_/_localizations_for_programs]]. If you think that something is really important move that stuff back. Please read both articles carefully in order to avoid duplicate stuff.<br />
<br />
===.po Files===<br />
<br />
There are many free graphical tools to edit .po files, which are simple text like the .rst files, but with some more options, like a header providing fields for author, encoding, language and date. Every FPC installation provides the tool '''rstconv''' (windows: rstconv.exe). This tool can be used to convert a .rst file into a .po file. The IDE can do this automatically.<br />
Some free tools: kbabel, po-auto-translator, poedit, virtaal.<br />
<br />
Virtaal has a translation memory containing source-target language pairs for items that you already translated once, and a translation suggestion function that shows already translated terms in various open source software packages. These function may save you a lot of work and improve consistency.<br />
<br />
Example of using rstconv directly:<br />
rstconv -i unit1.rst -o unit1.po<br />
<br />
===Translating===<br />
<br />
For every language the .po file must be copied and translated. The LCL translation unit uses the common language codes (en=english, de=german, it=italian, ...) to search. For example the German translation of unit1.po would be unit1.de.po. To achieve this, copy the unit1.po file to unit1.de.po, unit1.it.po, and whatever language you want to support and then the translators can edit their specific .po file.<br />
<br />
{{Note|For Brazilians/Portuguese: Lazarus IDE and LCL only have a Brazilian Portuguese translation and these files have 'pt_BR.po' extensions}}<br />
<br />
===IDE options for automatic updates of .po files===<br />
<br />
*The unit containing the resource strings must be added to the package or project.<br />
*You must provide a .po path, this means a separate directory. For example: create a sub directory ''language'' in the package / project directory. For projects go to the Project > Project Options. For packages go to Options > IDE integration.<br />
<br />
When this options are enabled, the IDE generates or updates the base .po file using the information contained in .rst and .lrt files (rstconv tool is then not necesary). The update process begins by collecting all existing entries found in base .po file and in .rst and .lrt files and then applying the following features it finds and brings up to date any translated .xx.po file. <br />
<br />
====Removal of Obsolete entries====<br />
<br />
Entries in the base .po file that are not found in .rst and .lrt files are removed. Subsequently, all entries found in translated .xx.po files not found in the base .po file are also removed. This way, .po files are not cluttered with obsolete entries and translators don't have to translate entries that are not used.<br />
<br />
====Duplicate entries====<br />
<br />
Duplicate entries occur when for some reason the same text is used for different resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:<br />
<syntaxhighlight> dlfMouseSimpleGutterSect = 'Gutter';<br />
dlgMouseOptNodeGutter = 'Gutter';<br />
dlgGutter = 'Gutter';<br />
dlgAddHiAttrGroupGutter = 'Gutter'; <br />
</syntaxhighlight><br />
A converted .rst file for this resource strings would look similar to this in a .po file:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.<br />
<br />
The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.<br />
<br />
====Fuzzy entries====<br />
<br />
Changes in resource strings affect translations, for example if initially a resource string was defined like:<br />
<syntaxhighlight>dlgEdColor = 'Syntax highlight';</syntaxhighlight><br />
<br />
this would produce a .po entry similar to this<br />
<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr ""<br />
which if translated to Spanish (this sample was taken from lazarus history), may result in<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr "Color"<br />
Suppose then that at a later time, the resource string has been changed to<br />
<syntaxhighlight><br />
dlgEdColor = 'Colors';<br />
</syntaxhighlight><br />
the resulting .po entry may become<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Colors"<br />
msgstr ""<br />
Note that while the ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors'. As the string was already translated the old translation may not match the new meaning. Indeed, for the new string probably 'Colores' may be a better translation. <br />
The automatic update tool notices this situation and produces an entry like this:<br />
#: lazarusidestrconsts.dlgedcolor<br />
#, fuzzy<br />
#| msgid "Syntax highlight"<br />
msgctxt "lazarusidestrconsts.dlgedcolor"<br />
msgid "Colors"<br />
msgstr "Color"<br />
In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present a special GUI to the translator user for this item. In this case, the flag would mean that the translation in its current state is doubtful and needs to be reviewed more carefully by translator. The "#|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.<br />
<br />
===Translating Forms, Datamodules and Frames===<br />
<br />
When the i18n option is enabled for the project / package then the IDE automatically creates .lrt files for every form. It creates the .lrt file on saving a unit. So, if you enable the option for the first time, you must open every form once, move it a little bit, so that it is modified, and save the form. For example if you save a form ''unit1.pas'' the IDE creates a ''unit1.lrt''. And on compile the IDE gathers all strings of all .lrt files and all .rst file into a single .po file (projectname.po or packagename.po) in the i18n directory.<br />
<br />
For the forms to be actually translated at runtime, you have to assign a translator to LRSTranslator (defined in LResources) in the initialization section to one of your units<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
LResources;<br />
...<br />
...<br />
initialization<br />
LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');</syntaxhighlight><br />
<br />
<s>However there's no TPoTranslator class (i.e a class that translates using .po files) available in the LCL. This is a possible implementation (partly lifted from DefaultTranslator.pas in the LCL):</s> The following code isn't needed anymore if you use recent Lazarus 0.9.29 snapshots. Simply include DefaultTranslator in Uses clause.<br />
<br />
<syntaxhighlight>unit PoTranslator;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
interface<br />
<br />
uses<br />
Classes, SysUtils, LResources, typinfo, Translations;<br />
<br />
type<br />
<br />
{ TPoTranslator }<br />
<br />
TPoTranslator=class(TAbstractTranslator)<br />
private<br />
FPOFile:TPOFile;<br />
public<br />
constructor Create(POFileName:string);<br />
destructor Destroy;override;<br />
procedure TranslateStringProperty(Sender:TObject; <br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;<br />
end;<br />
<br />
implementation<br />
<br />
{ TPoTranslator }<br />
<br />
constructor TPoTranslator.Create(POFileName: string);<br />
begin<br />
inherited Create;<br />
FPOFile:=TPOFile.Create(POFileName);<br />
end;<br />
<br />
destructor TPoTranslator.Destroy;<br />
begin<br />
FPOFile.Free;<br />
inherited Destroy;<br />
end;<br />
<br />
procedure TPoTranslator.TranslateStringProperty(Sender: TObject;<br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);<br />
var<br />
s: String;<br />
begin<br />
if not Assigned(FPOFile) then exit;<br />
if not Assigned(PropInfo) then exit;<br />
{DO we really need this?}<br />
if Instance is TComponent then<br />
if csDesigning in (Instance as TComponent).ComponentState then exit;<br />
{End DO :)}<br />
if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;<br />
s:=FPOFile.Translate(Content, Content);<br />
if s<>'' then Content:=s;<br />
end;<br />
<br />
end.</syntaxhighlight><br />
<br />
Alternatively you can transform the .po file into .mo using msgfmt (isn't needed anymore if you use recent 0.9.29 snapshot) and simply use the DefaultTranslator unit<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
DefaultTranslator;</syntaxhighlight><br />
<br />
which will automatically look in several standard places for a .po file (higher precedence) or .mo file <s>(the disadvantage is that you'll have to keep around both the .mo files for the DefaultTranslator unit and the .po files for TranslateUnitResourceStrings)</s>.<br />
If you use DefaultTranslator, it will try to automatically detect the language based on the LANG environment variable (overridable using the --lang command line switch), then look in these places for the translation (LANG stands for the desired language, ext can be either po or mo):<br />
<br />
* <Application Directory>/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/languages/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/LC_MESSAGES/<LANG/><Application Filename>.<ext><br />
<br />
under unix-like systems it will also look in<br />
<br />
* /usr/share/locale/<LANG>/LC_MESSAGES/<Application Filename>.<ext><br />
<br />
as well as using the short part of the language (e.g. if it is "es_ES" or "es_ES.UTF-8" and it doesn't exist it will also try "es")<br />
<br />
===Translating at start of program===<br />
<br />
For every .po file, you must call TranslateUnitResourceStrings. The LCL po file is lclstrconsts. For example you do this in FormCreate of your MainForm:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., gettext, translations;<br />
<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
PODirectory, Lang, FallbackLang: String;<br />
begin<br />
PODirectory := '/path/to/lazarus/lcl/languages/';<br />
GetLanguageIDs(Lang, FallbackLang);<br />
Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);<br />
<br />
// the following dialog now shows translated buttons:<br />
MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);<br />
end;<br />
</syntaxhighlight><br />
<br />
===Compiling po files into the executable===<br />
<br />
If you don't want to install the .po files, but put all files of the application into the executable, use one the following methods.<br />
<br />
====FPC Resources (Recommended)====<br />
Normal resources are now recommended for current FPC (including all recent Lazarus versions) [[Lazarus_Resources]]<br />
<br />
*Add the resources (.po files) to executable with the Lazarus IDE (Project Options > Resources) as RCDATA.<br />
<br />
<syntaxhighlight><br />
uses<br />
LCLType<br />
<br />
function Translate(Language: string): boolean;<br />
var<br />
Res: TResourceStream;<br />
PoStringStream: TStringStream;<br />
PoFile: TPOFile;<br />
begin<br />
Res := TResourceStream.Create(HInstance, 'project1.' + Language, RT_RCDATA);<br />
PoStringStream := TStringStream.Create('');<br />
Res.SaveToStream(PoStringStream);<br />
Res.Free;<br />
<br />
PoFile := TPOFile.Create(False);<br />
PoFile.ReadPOText(PoStringStream.DataString);<br />
PoStringStream.Free;<br />
<br />
Result := TranslateResourceStrings(PoFile);<br />
PoFile.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
====Lazarus Resources====<br />
<br />
*Create a new unit (not a form!).<br />
*Convert the .po file(s) to .lrs using tools/lazres:<br />
<pre><br />
./lazres unit1.lrs unit1.de.po<br />
</pre><br />
<br />
This will create an include file unit1.lrs beginning with<br />
<syntaxhighlight>LazarusResources.Add('unit1.de','PO',[<br />
...</syntaxhighlight><br />
<br />
*Add the code:<br />
<syntaxhighlight>uses LResources, Translations;<br />
<br />
resourcestring<br />
MyCaption = 'Caption';<br />
<br />
function TranslateUnitResourceStrings: boolean;<br />
var<br />
r: TLResource;<br />
POFile: TPOFile;<br />
begin<br />
r:=LazarusResources.Find('unit1.de','PO');<br />
POFile:=TPOFile.Create(False); //if Full=True then you can get a crash (Issue #0026021)<br />
try<br />
POFile.ReadPOText(r.Value);<br />
Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);<br />
finally<br />
POFile.Free;<br />
end;<br />
end;<br />
<br />
initialization<br />
{$I unit1.lrs}</syntaxhighlight><br />
<br />
* Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.<br />
<br />
Unfortunately this code will not compile with Lazarus 1.2.2 and earlier.<br />
<br />
For these Lazarus versions you can use something like this:<br />
<syntaxhighlight><br />
type<br />
TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);<br />
<br />
function TranslateFromResource(AResourceName, ALanguage : String): TTranslateFromResourceResult;<br />
var<br />
LRes : TLResource;<br />
POFile : TPOFile = nil;<br />
SStream : TStringStream = nil;<br />
begin<br />
Result := trResourceNotFound;<br />
LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');<br />
if LRes <> nil then<br />
try<br />
SStream := TStringStream.Create(LRes.Value); <br />
POFile := TPoFile.Create(SStream, False); // WARNING: This example relies on the bug in TStringStream.Create(...) that the<br />
// position in the stream is incorrectly set to 0, even though the stream<br />
// is initialized with LRes.Value i.e. assumed to be ready to be *appended*! <br />
try<br />
if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess<br />
else Result := trTranslationError;<br />
except<br />
Result := trTranslationError;<br />
end;<br />
finally<br />
if Assigned(SStream) then SStream.Free;<br />
if Assigned(POFile) then POFile.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
Usage example:<br />
<br />
<syntaxhighlight><br />
initialization<br />
{$I lclstrconsts.de.lrs}<br />
TranslateFromResource('lclstrconsts', 'de');<br />
end.<br />
</syntaxhighlight><br />
<br />
===Cross-platform method to determine system language===<br />
<br />
The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.<br />
<br />
<syntaxhighlight><br />
uses<br />
Classes, SysUtils {add additional units that may be needed by your code here}<br />
{$IFDEF win32}<br />
, Windows<br />
{$ELSE}<br />
, Unix<br />
{$IFDEF LCLCarbon}<br />
, MacOSAll<br />
{$ENDIF}<br />
{$ENDIF}<br />
;<br />
</syntaxhighlight><br />
<br />
<syntaxhighlight><br />
function GetOSLanguage: string;<br />
{platform-independent method to read the language of the user interface}<br />
var<br />
l, fbl: string;<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef: CFLocaleRef;<br />
locale: CFStringRef;<br />
buffer: StringPtr;<br />
bufferSize: CFIndex;<br />
encoding: CFStringEncoding;<br />
success: boolean;<br />
{$ENDIF}<br />
begin<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef := CFLocaleCopyCurrent;<br />
locale := CFLocaleGetIdentifier(theLocaleRef);<br />
encoding := 0;<br />
bufferSize := 256;<br />
buffer := new(StringPtr);<br />
success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);<br />
if success then<br />
l := string(buffer^)<br />
else<br />
l := '';<br />
fbl := Copy(l, 1, 2);<br />
dispose(buffer);<br />
{$ELSE}<br />
{$IFDEF LINUX}<br />
fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);<br />
{$ELSE}<br />
GetLanguageIDs(l, fbl);<br />
{$ENDIF}<br />
{$ENDIF}<br />
Result := fbl;<br />
end;<br />
</syntaxhighlight><br />
<br />
===Translating the IDE===<br />
<br />
====Files====<br />
The .po files of the IDE are in the lazarus source directory:<br />
*lazarus/languages strings for the IDE<br />
*lazarus/lcl/languages/ strings for the LCL<br />
*lazarus/components/ideintf/languages/ strings for the IDE interface<br />
<br />
====Translators====<br />
* The German translation is maintained by Swen Heinig.<br />
* The Finnish translation is maintained by Seppo Suurtarla<br />
* The Russian translation is maintained by Maxim Ganetsky<br />
* The French translation is maintained by Gilles Vasseur<br />
<br />
When you want to start a new translation, ask on the mailing if someone is already working on that.<br />
<br />
Please read carefully: [[Lazarus_Documentation#Translating.2FInternationalization.2FLocalization|Translating/Internationalization/Localization]]</div>Enyhttps://wiki.freepascal.org/index.php?title=RSS&diff=95099RSS2015-09-01T13:45:47Z<p>Eny: Fix TStringStream usage (a bug in the implementation results in incorrect code like here)</p>
<hr />
<div>==Overview==<br />
This is a small unit I created to read RSS feeds. Maybe it is useful.<br />
<br />
==RSS unit code==<br />
<syntaxhighlight><br />
unit rss;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
interface<br />
<br />
uses<br />
Classes, SysUtils,<br />
lNetComponents, lNet, lhttp, lHTTPUtil, DOM, XMLRead, db;<br />
<br />
const<br />
RSSXML_RSS = 'rss';<br />
RSSXML_CHANNEL = 'channel';<br />
RSSXML_ITEM = 'item';<br />
RSSXML_TITLE = 'title';<br />
RSSXML_LINK = 'link';<br />
RSSXML_DESCRIPTION = 'description';<br />
RSSXML_PUBDATE = 'pubDate';<br />
RSSXML_COMMENTS = 'comments';<br />
<br />
type<br />
<br />
{ TFeedItem }<br />
<br />
TFeedItem = class(TObject)<br />
private<br />
fTitle, fLink, fComments : String;<br />
fDescription: TStringList;<br />
fPubDate: TDateTime;<br />
public<br />
constructor create();<br />
destructor destroy(); override;<br />
class function CreateFromNode(aNode: TDOMNode): TFeedItem;<br />
property Title : String read fTitle write fTitle;<br />
property Link : String read fLink write fLink;<br />
property Comments : String read fComments write fComments;<br />
property Description : TStringList read fDescription write fDescription;<br />
end;<br />
<br />
TFeedReadState = (frReady, frRequest, frParse, frSave, frDone, frError);<br />
<br />
// Callback for result report<br />
TRSSResultEvent = procedure(const aFeedID: string) of object;<br />
<br />
{ TRssFeed }<br />
<br />
TRssFeed = class(TObject)<br />
private<br />
fTitle, fLink, fLanguage, fError, fFeedID: String;<br />
fDescription: TStringList;<br />
fItems: TList;<br />
fXmlString: TStringList;<br />
fReadState: TFeedReadState;<br />
HTTPClient: TLHTTPClientComponent;<br />
HTTPBuffer: string;<br />
fDoc : TXMLDocument;<br />
fOnResult: TRSSResultEvent;<br />
fDatabase: TDatabase;<br />
procedure AddItem(aItem : TFeedItem);<br />
procedure ClearItems;<br />
procedure HTTPClientError(const msg: string; aSocket: TLSocket);<br />
function HTTPClientInput(ASocket: TLHTTPClientSocket; ABuffer: pchar; ASize: integer): integer;<br />
procedure HTTPClientDisconnect(aSocket: TLSocket);<br />
public<br />
constructor create(aLink: String; aFeedID: string; aDatabase: TDatabase);<br />
destructor destroy(); override;<br />
procedure RssRead;<br />
property Title: String read fTitle write fTitle;<br />
property Language: String read fLanguage write fLanguage;<br />
property Link: String read fLink write fLink;<br />
property Description: TStringList read fDescription write fDescription;<br />
property Items: TList read fItems write fItems;<br />
property ReadState: TFeedReadState read fReadState;<br />
property OnResult: TRSSResultEvent read fOnResult write fOnResult;<br />
end;<br />
<br />
<br />
implementation<br />
<br />
{ TFeedItem }<br />
<br />
constructor TFeedItem.create();<br />
begin<br />
fDescription := TStringList.Create;<br />
end;<br />
<br />
destructor TFeedItem.destroy;<br />
begin<br />
FreeAndNil(fDescription);<br />
inherited destroy;<br />
end;<br />
<br />
class function TFeedItem.CreateFromNode(aNode: TDOMNode): TFeedItem;<br />
var<br />
propertynode: TDOMNode;<br />
begin<br />
Result := TFeedItem.Create();<br />
propertynode := aNode.FindNode(RSSXML_TITLE);<br />
if propertynode <> nil then<br />
Result.fTitle := propertynode.TextContent;<br />
propertynode := aNode.FindNode(RSSXML_LINK);<br />
if propertynode <> nil then<br />
Result.fLink := propertynode.TextContent;<br />
propertynode := aNode.FindNode(RSSXML_DESCRIPTION);<br />
if propertynode <> nil then<br />
Result.fDescription.Text := propertynode.TextContent;<br />
propertynode := aNode.FindNode(RSSXML_COMMENTS);<br />
if propertynode <> nil then<br />
Result.fComments := propertynode.TextContent;<br />
end;<br />
<br />
{ TRssFeed }<br />
<br />
procedure TRssFeed.AddItem(aItem: TFeedItem);<br />
begin<br />
fItems.Add(aItem);<br />
end;<br />
<br />
procedure TRssFeed.ClearItems;<br />
var<br />
i: Integer;<br />
Item: TFeedItem;<br />
begin<br />
for i := 0 to fItems.Count - 1 do<br />
begin<br />
Item := TFeedItem(fItems[0]);<br />
FreeAndNil(Item);<br />
end;<br />
fItems.Clear;<br />
end;<br />
<br />
procedure TRssFeed.HTTPClientError(const msg: string; aSocket: TLSocket);<br />
begin<br />
fError := msg;<br />
fReadState := frError;<br />
end;<br />
<br />
function TRssFeed.HTTPClientInput(ASocket: TLHTTPClientSocket; ABuffer: pchar;<br />
ASize: integer): integer;<br />
var<br />
oldLength: dword;<br />
begin<br />
oldLength := Length(HTTPBuffer);<br />
setlength(HTTPBuffer,oldLength + ASize);<br />
move(ABuffer^,HTTPBuffer[oldLength + 1], ASize);<br />
Result := aSize; // tell the http buffer we read it all<br />
end;<br />
<br />
procedure TRssFeed.HTTPClientDisconnect(aSocket: TLSocket);<br />
var<br />
S: TStringStream;<br />
channel, propertynode: TDOMNode;<br />
newsitem: TFeedItem;<br />
begin<br />
fReadState:= frParse;<br />
try<br />
S := TStringStream.Create('');<br />
try<br />
ReadXMLFile(fDoc, S);<br />
finally<br />
S.Free;<br />
end;<br />
except<br />
on E : Exception do<br />
begin<br />
fReadState:= frError;<br />
fError := E.Message;<br />
Exit;<br />
end;<br />
end;<br />
if ((fDoc.documentElement.nodeName = RSSXML_RSS) and<br />
(fDoc.documentElement.hasChildNodes)) then<br />
begin<br />
channel := fDoc.DocumentElement.FindNode(RSSXML_CHANNEL);<br />
if channel <> nil then<br />
begin<br />
propertynode := channel.FindNode(RSSXML_TITLE);<br />
if propertynode <> nil then<br />
fTitle := propertynode.TextContent;<br />
propertynode := channel.FindNode(RSSXML_LINK);<br />
if propertynode <> nil then<br />
fLink := propertynode.TextContent;<br />
propertynode := channel.FindNode(RSSXML_DESCRIPTION);<br />
if propertynode <> nil then<br />
fDescription.Text := propertynode.TextContent;<br />
propertynode := channel.FindNode(RSSXML_ITEM);<br />
while propertynode <> nil do<br />
begin<br />
newsitem:= TFeedItem.CreateFromNode(propertynode);<br />
AddItem(newsitem);<br />
propertynode := propertynode.NextSibling;<br />
end;<br />
end;<br />
end;<br />
fReadState:= frSave;<br />
end;<br />
<br />
constructor TRssFeed.create(aLink: String; aFeedID: string; aDatabase: TDatabase);<br />
begin<br />
fFeedID := aFeedID;<br />
fLink := aLink;<br />
fDatabase := aDatabase;<br />
fReadState := frReady;<br />
fDescription := TStringList.Create;<br />
fItems := TList.Create;<br />
HTTPClient := TLHTTPClientComponent.Create(nil);<br />
fDoc := TXMLDocument.create;<br />
end;<br />
<br />
destructor TRssFeed.destroy;<br />
begin<br />
ClearItems;<br />
FreeAndNil(fDescription);<br />
FreeAndNil(HTTPClient);<br />
FreeAndNil(fDoc);<br />
inherited destroy;<br />
end;<br />
<br />
procedure TRssFeed.RssRead;<br />
var<br />
aHost, aURI: String;<br />
aPort: Word;<br />
begin<br />
DecomposeURL(fLink, aHost, aURI, aPort);<br />
HTTPClient.Host:= aHost;<br />
HTTPClient.Port:= aPort;<br />
HTTPClient.URI:= aURI;<br />
HTTPClient.OnError:= @HTTPClientError;<br />
HTTPClient.OnInput:= @HTTPClientInput;<br />
HTTPClient.OnDisconnect:= @HTTPClientDisconnect;<br />
HTTPClient.SendRequest;<br />
fReadState:= frRequest;<br />
end;<br />
<br />
end.<br />
</syntaxhighlight><br />
<br />
[[Category:FPC]]<br />
[[Category:Networking]]</div>Enyhttps://wiki.freepascal.org/index.php?title=XML_Tutorial&diff=95098XML Tutorial2015-09-01T13:44:50Z<p>Eny: Fix TStringStream usage (a bug in the implementation results in incorrect code like here)</p>
<hr />
<div>{{XML Tutorial}}<br />
<br />
The Extensible Markup Language (XML) is a World Wide Web Consortium (or "[http://www.w3.org/ W3C]") recommended language created to interchange information between different systems.<br />
<br />
It is a text based way to store information as opposed to storing the information in a binary format. <br />
<br />
Modern data interchange languages such as XHTML, as well as most WebServices technologies, are based on XML. This wiki can only really give a thumbnail overview of XML, with the primary focus being the parsing and use of XML files in Free Pascal applications. If you are interested in a more comprehensive explanation of XML and how it is used, see http://en.wikipedia.org/wiki/XML.<br />
<br />
== Introduction ==<br />
<br />
Currently there is a set of units that provides support for XML on Free Pascal. These units are called "XMLRead", "XMLWrite" and "DOM" and they are part of the Free Component Library (FCL) from the Free Pascal Compiler. The FCL is already on the default search path for the compiler on Lazarus, so you only need to add the units to your uses clause in order to get XML support. The FCL is not documented currently (October / 2005), so this short tutorial aims at introducing XML access using those units.<br />
<br />
The XML DOM (Document Object Model) is a set of standardized objects that provide a similar interface for using XML on different languages and systems. The standard only specifies the methods, properties and other interface parts of the object, leaving the implementation free for different languages. The FCL currently fully supports the [http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ XML DOM 2.0] and a subset of [http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/ XML DOM 3.0] listed [[dom|here]].<br />
<br />
== Usage Examples ==<br />
<br />
Below there is a list of XML data manipulation examples with growing complexity.<br />
<br />
=== Units in the uses clause: Unicode or Ansi ===<br />
<br />
FPC comes with XML units which utilize ANSI-encoded routines and therefore in each platform the encoding might be different and might not be Unicode. Lazarus comes with another separate set of XML units in the package LazUtils which fully support UTF-8 Unicode in all platforms. The units are compatible and one can change from to the other just by changing the uses clause.<br />
<br />
The units for using the FPC XML support which uses strings encoded in the system encoding are:<br />
* DOM<br />
* XMLRead<br />
* XMLWrite<br />
* XMLCfg<br />
* XMLUtils<br />
* XMLStreaming<br />
<br />
The units for using the Lazarus XML support which has full UTF-8 Unicode support are:<br />
* laz2_DOM<br />
* laz2_XMLRead<br />
* laz2_XMLWrite<br />
* laz2_XMLCfg<br />
* laz2_XMLUtils<br />
* laz_XMLStreaming. <br />
<br />
Not all of them are needed in every example, though. You will need DOM as it defines several types including TXMLDocument.<br />
<br />
=== Reading a text node ===<br />
<br />
For Delphi Programmers:<br />
Note that when working with TXMLDocument, the text within a Node is considered a separate TEXT Node. As a result, you must access a node's text value as a separate node. Alternatively, the '''TextContent''' property may be used to retrieve content of all text nodes beneath the given one, concatenated together.<br />
<br />
The '''ReadXMLFile''' procedure always creates a new '''TXMLDocument''', so you don't have to create it beforehand. However, be sure to destroy the document by calling '''Free''' when you are done.<br />
<br />
For instance, consider the following XML:<br />
<br />
<syntaxhighlight lang="xml"><?xml version="1.0"?><br />
<request><br />
<request_type>PUT_FILE</request_type><br />
<username>123</username><br />
<password>abc</password><br />
</request></syntaxhighlight><br />
<br />
The following code example shows both the correct and the incorrect ways of getting the value of the text node (add the units '''laz2_XMLRead''' and '''laz2_DOM''' to the used units list):<br />
<br />
<source>var<br />
PassNode: TDOMNode;<br />
Doc: TXMLDocument;<br />
begin<br />
try<br />
// Read in xml file from disk<br />
ReadXMLFile(Doc, 'test.xml');<br />
// Retrieve the "password" node<br />
PassNode := Doc.DocumentElement.FindNode('password');<br />
// Write out value of the selected node<br />
WriteLn(PassNode.NodeValue); // will be blank<br />
// The text of the node is actually a separate child node<br />
WriteLn(PassNode.FirstChild.NodeValue); // correctly prints "abc"<br />
// alternatively<br />
WriteLn(PassNode.TextContent);<br />
finally<br />
// finally, free the document<br />
Doc.Free;<br />
end;<br />
end;</source><br />
<br />
Note that ReadXMLFile(...) ignores all leading whitespace characters when parsing a document. The section [[#Whitespace_characters|whitespace characters]] describes how to keep them.<br />
<br />
=== Printing the names of nodes and attributes ===<br />
<br />
If you want to navigate the DOM tree: when you need to access nodes in sequence, it is best to use '''FirstChild''' and '''NextSibling''' properties (to iterate forward), or '''LastChild''' and '''PreviousSibling''' (to iterate backward).<br />
<br />
For random access it is possible to use '''ChildNodes''' or '''GetElementsByTagName''' methods, but these will create a TDOMNodeList object which eventually must be freed. This differs from other DOM implementations like MSXML, because the FCL implementation is object-based, not interface-based.<br />
<br />
The following example shows how to print the names of nodes to a TMemo placed on a form.<br />
<br />
Below is the XML file called 'test.xml':<br />
<br />
<syntaxhighlight lang="xml"><?xml version="1.0"?><br />
<images directory="mydir"><br />
<imageNode URL="graphic.jpg" title=""><br />
<Peca DestinoX="0" DestinoY="0">Pecacastelo.jpg1.swf</Peca><br />
<Peca DestinoX="0" DestinoY="86">Pecacastelo.jpg2.swf</Peca><br />
</imageNode><br />
</images></syntaxhighlight><br />
<br />
And here the Pascal code to execute the task:<br />
<br />
<source>var<br />
Doc: TXMLDocument;<br />
Child: TDOMNode;<br />
j: Integer;<br />
begin<br />
try<br />
ReadXMLFile(Doc, 'test.xml');<br />
Memo.Lines.Clear;<br />
// using FirstChild and NextSibling properties<br />
Child := Doc.DocumentElement.FirstChild;<br />
while Assigned(Child) do<br />
begin<br />
Memo.Lines.Add(Child.NodeName + ' ' + Child.Attributes.Item[0].NodeValue);<br />
// using ChildNodes method<br />
with Child.ChildNodes do<br />
try<br />
for j := 0 to (Count - 1) do<br />
Memo.Lines.Add(format('%s %s (%s=%s; %s=%s)',<br />
[<br />
Item[j].NodeName,<br />
Item[j].FirstChild.NodeValue,<br />
Item[j].Attributes.Item[0].NodeName, // 1st attribute details<br />
Item[j].Attributes.Item[0].NodeValue,<br />
Item[j].Attributes.Item[1].NodeName, // 2nd attribute details<br />
Item[j].Attributes.Item[1].NodeValue<br />
]));<br />
finally<br />
Free;<br />
end;<br />
Child := Child.NextSibling;<br />
end;<br />
finally<br />
Doc.Free;<br />
end;<br />
end;</source><br />
<br />
This will print:<br />
<br />
<pre>imageNode graphic.jpg<br />
Peca Pecacastelo.jpg1.swf (DestinoX=0; DestinoY=0)<br />
Peca Pecacastelo.jpg2.swf (DestinoX=0; DestinoY=86)</pre><br />
<br />
=== Populating a TreeView with XML ===<br />
<br />
One common use of XML files is to parse them and show their contents in a tree like format. You can find the TTreeView component on the "Common Controls" tab on Lazarus.<br />
<br />
The function below will take a XML document previously loaded from a file or generated on code, and will populate a TreeView with it´s contents. The caption of each node will be the content of the first attribute of each node.<br />
<br />
<source>procedure TForm1.XML2Tree(tree: TTreeView; XMLDoc: TXMLDocument);<br />
var<br />
iNode: TDOMNode;<br />
<br />
procedure ProcessNode(Node: TDOMNode; TreeNode: TTreeNode);<br />
var<br />
cNode: TDOMNode;<br />
s: string;<br />
begin<br />
if Node = nil then Exit; // Stops if reached a leaf<br />
<br />
// Adds a node to the tree<br />
if Node.HasAttributes and (Node.Attributes.Length>0) then<br />
s := Node.Attributes[0].NodeValue<br />
else<br />
s := ''; <br />
TreeNode := tree.Items.AddChild(TreeNode, s);<br />
<br />
// Goes to the child node<br />
cNode := Node.FirstChild;<br />
<br />
// Processes all child nodes<br />
while cNode <> nil do<br />
begin<br />
ProcessNode(cNode, TreeNode);<br />
cNode := cNode.NextSibling;<br />
end;<br />
end;<br />
<br />
begin<br />
iNode := XMLDoc.DocumentElement.FirstChild;<br />
while iNode <> nil do<br />
begin<br />
ProcessNode(iNode, nil); // Recursive<br />
iNode := iNode.NextSibling;<br />
end;<br />
end;</source><br />
<br />
Another example that displays the complete XML structure including all attribute values (note: the long line referencing TreeView has been split so it will word wrap for this wiki; when writing it in code you do not have to break the line unless you like the formatting) :<br />
<source>procedure XML2Tree(XMLDoc:TXMLDocument; TreeView:TTreeView);<br />
<br />
// Local function that outputs all node attributes as a string<br />
function GetNodeAttributesAsString(pNode: TDOMNode):string;<br />
var i: integer;<br />
begin<br />
Result:='';<br />
if pNode.HasAttributes then<br />
for i := 0 to pNode.Attributes.Length -1 do<br />
with pNode.Attributes[i] do<br />
Result := Result + format(' %s="%s"', [NodeName, NodeValue]);<br />
<br />
// Remove leading and trailing spaces<br />
Result:=Trim(Result);<br />
end;<br />
<br />
// Recursive function to process a node and all its child nodes <br />
<br />
procedure ParseXML(Node:TDOMNode; TreeNode: TTreeNode);<br />
begin<br />
// Exit procedure if no more nodes to process<br />
if Node = nil then Exit;<br />
<br />
// Add node to TreeView<br />
TreeNode := TreeView.Items.AddChild(TreeNode, <br />
Trim(Node.NodeName+' '+ <br />
GetNodeAttributesAsString(Node)+ <br />
Node.NodeValue)<br />
);<br />
<br />
// Process all child nodes<br />
Node := Node.FirstChild;<br />
while Node <> Nil do<br />
begin<br />
ParseXML(Node, TreeNode);<br />
Node := Node.NextSibling;<br />
end;<br />
end;<br />
<br />
begin<br />
TreeView.Items.Clear;<br />
ParseXML(XMLDoc.DocumentElement,nil);<br />
end;<br />
</source><br />
<br />
=== Modifying a XML document ===<br />
<br />
The first thing to remember is that TDOMDocument is the "handle" to the DOM. You can get an instance of this class by creating one or by loading a XML document.<br />
<br />
Nodes on the other hand cannot be created like a normal object. You *must* use the methods provided by TDOMDocument to create them, and later use other methods to put them in the correct place in the tree. This is because nodes must be "owned" by a specific document in DOM.<br />
<br />
Below are some common methods from TDOMDocument:<br />
<br />
<source>function CreateElement(const tagName: DOMString): TDOMElement; virtual;<br />
function CreateTextNode(const data: DOMString): TDOMText;<br />
function CreateCDATASection(const data: DOMString): TDOMCDATASection; virtual;<br />
function CreateAttribute(const name: DOMString): TDOMAttr; virtual;</source><br />
<br />
<tt>CreateElement</tt> creates a new element.<br />
<br />
<tt>CreateTextNode</tt> creates a text node.<br />
<br />
<tt>CreateAttribute</tt> creates an attribute node.<br />
<br />
<tt>CreateCDATASection</tt> creates a CDATA section: regular XML markup characters such as <> are not interpreted within the CDATA section. See [https://secure.wikimedia.org/wikipedia/en/wiki/CDATA Wikipedia article on CDATA]<br />
<br />
A more convenient method to manipulate attributes is to use <tt>TDOMElement.SetAttribute</tt> method, which is also represented as the default property of <tt>TDOMElement</tt>:<br />
<br />
<syntaxhighlight><br />
// these two statements are equivalent<br />
Element.SetAttribute('name', 'value');<br />
Element['name'] := 'value';<br />
</syntaxhighlight><br />
<br />
And here an example method that will locate the selected item on a TTreeView and then insert a child node to the XML document it represents. The TreeView must be previously filled with the contents of an XML file using the [[Networking#Populating a TreeView with XML|XML2Tree function]].<br />
<br />
<source>procedure TForm1.actAddChildNode(Sender: TObject);<br />
var<br />
position: Integer;<br />
NovoNo: TDomNode;<br />
begin<br />
{*******************************************************************<br />
* Detects the selected element<br />
*******************************************************************}<br />
if TreeView1.Selected = nil then Exit;<br />
<br />
if TreeView1.Selected.Level = 0 then<br />
begin<br />
position := TreeView1.Selected.Index;<br />
<br />
NovoNo := XMLDoc.CreateElement('item');<br />
TDOMElement(NovoNo).SetAttribute('nome', 'Item');<br />
TDOMElement(NovoNo).SetAttribute('arquivo', 'Arquivo');<br />
with XMLDoc.DocumentElement.ChildNodes do<br />
begin<br />
Item[position].AppendChild(NovoNo);<br />
Free;<br />
end;<br />
<br />
{*******************************************************************<br />
* Updates the TreeView<br />
*******************************************************************}<br />
TreeView1.Items.Clear;<br />
XML2Tree(TreeView1, XMLDoc);<br />
end<br />
else if TreeView1.Selected.Level >= 1 then<br />
begin<br />
{*******************************************************************<br />
* This function only works on the first level of the tree,<br />
* but can easily be modified to work for any number of levels<br />
*******************************************************************}<br />
end;<br />
end;</source><br />
<br />
=== Create a TXMLDocument from a string ===<br />
<br />
Given an XML document in string variable ''MyXmlString'', the following code will create it's DOM:<br />
<br />
<source>var<br />
S: TStringStream;<br />
XML: TXMLDocument;<br />
begin<br />
S := TStringStream.Create('');<br />
try<br />
// Read complete XML document<br />
ReadXMLFile(XML, S); <br />
// Alternatively: read only an XML Fragment<br />
ReadXMLFragment(AParentNode, S); <br />
finally<br />
S.Free;<br />
end;<br />
end;</source><br />
<br />
=== Validating a document ===<br />
<br />
Since March 2007, DTD validation facility has been added to the FCL XML parser. Validation is checking that logical structure of the document conforms to the predefined rules, called ''Document Type Definition'' (DTD).<br />
<br />
Here is an example of XML document with a DTD:<br />
<br />
<syntaxhighlight lang="xml"><?xml version='1.0'?><br />
<!DOCTYPE root [<br />
<!ELEMENT root (child)+ ><br />
<!ELEMENT child (#PCDATA)><br />
]><br />
<root><br />
<child>This is a first child.</child><br />
<child>And this is the second one.</child><br />
</root></syntaxhighlight><br />
<br />
This DTD specifies that 'root' element must have one or more 'child' elements, and that 'child' elements may have only character data inside. If parser detects any violations from these rules, it will report them.<br />
<br />
Loading such document is slightly more complicated. Let's assume we have XML data in a TStream object:<br />
<br />
<source>procedure TMyObject.DOMFromStream(AStream: TStream);<br />
var<br />
Parser: TDOMParser;<br />
Src: TXMLInputSource;<br />
TheDoc: TXMLDocument;<br />
begin<br />
try<br />
// create a parser object<br />
Parser := TDOMParser.Create;<br />
// and the input source<br />
Src := TXMLInputSource.Create(AStream);<br />
// we want validation<br />
Parser.Options.Validate := True;<br />
// assign a error handler which will receive notifications<br />
Parser.OnError := @ErrorHandler;<br />
// now do the job<br />
Parser.Parse(Src, TheDoc);<br />
// ...and cleanup<br />
finally<br />
Src.Free;<br />
Parser.Free;<br />
end;<br />
end;<br />
<br />
procedure TMyObject.ErrorHandler(E: EXMLReadError);<br />
begin<br />
if E.Severity = esError then // we are interested in validation errors only<br />
writeln(E.Message);<br />
end;</source><br />
<br />
=== Whitespace characters ===<br />
If you want to preserve leading whitespace characters in node texts, the above method is the way to load your XML document. Leading whitespace characters are ignored by default. That is the reason why the ReadXML(...) function never returns any leading whitespace characters in node texts.<br />
Before calling ''Parser.Parse(Src, TheDoc)'' insert the line <br />
<br />
<source>Parser.Options.PreserveWhitespace := True;</source><br />
<br />
This will force the parser to return all whitespace characters. This includes all the newline characters that exist in an XML document to make it more readable!<br />
<br />
=== Streamed reading ===<br />
<br />
DOM-based processing requires the entire document loaded into memory. This may be not desirable, or not possible if document is huge. FCL provides functionality to read XML data one node at a time, using TXMLReader class and its descendants. This is similar to SAX, but works without callbacks. TXMLReader closely resembles .NET XmlReader class.<br />
A basic example follows:<br />
<br />
<source><br />
uses<br />
Classes,xmlreader,xmltextreader,xmlutils;<br />
<br />
procedure readme(AStream: TStream);<br />
var<br />
xtr: TXmlReader;<br />
settings: TXMLReaderSettings;<br />
inp: TXMLInputSource;<br />
begin<br />
settings := TXMLReaderSettings.Create;<br />
try<br />
settings.PreserveWhiteSpace := True;<br />
settings.Namespaces := True;<br />
inp := TXMLInputSource.Create(AStream);<br />
try<br />
xtr := TXmlTextReader.Create(inp,settings);<br />
try<br />
// Here the reading starts<br />
while xtr.Read do<br />
begin<br />
write(xtr.NodeType:25);<br />
if xtr.name<>'' then<br />
write(xtr.Name:9)<br />
else<br />
write('*no name* ');<br />
write(xtr.Value);<br />
writeln;<br />
if xtr.NodeType=ntElement then<br />
begin<br />
// print attributes<br />
if xtr.MoveToFirstAttribute then<br />
begin<br />
repeat<br />
writeln('---',xtr.NodeType:21,xtr.Name:10,xtr.Value:10);<br />
until not xtr.MoveToNextAttribute;<br />
xtr.MoveToContent;<br />
end; <br />
end;<br />
end;<br />
// Cleanup follows <br />
finally<br />
xtr.Free;<br />
end;<br />
finally<br />
inp.Free;<br />
end;<br />
finally<br />
settings.Free;<br />
end;<br />
end;<br />
</source><br />
<br />
=== Generating a XML file ===<br />
<br />
Below is the complete code to write a XML file.<br />
(This was taken from a tutorial in the DeveLazarus blog)<br />
Please, remember to include the DOM and XMLWrite units in your uses clause.<br />
<br />
<source>unit Unit1;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
interface<br />
<br />
uses<br />
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls,<br />
DOM, XMLWrite;<br />
<br />
type<br />
{ TForm1 }<br />
TForm1 = class(TForm)<br />
Button1: TButton;<br />
Label1: TLabel;<br />
Label2: TLabel;<br />
procedure Button1Click(Sender: TObject);<br />
private<br />
{ private declarations }<br />
public<br />
{ public declarations }<br />
end;<br />
<br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{ TForm1 }<br />
<br />
procedure TForm1.Button1Click(Sender: TObject);<br />
var<br />
Doc: TXMLDocument; // variable to document<br />
RootNode, parentNode, nofilho: TDOMNode; // variable to nodes<br />
begin<br />
try<br />
// Create a document<br />
Doc := TXMLDocument.Create;<br />
<br />
// Create a root node<br />
RootNode := Doc.CreateElement('register');<br />
Doc.Appendchild(RootNode); // save root node<br />
<br />
// Create a parent node<br />
RootNode:= Doc.DocumentElement;<br />
parentNode := Doc.CreateElement('usuario');<br />
TDOMElement(parentNode).SetAttribute('id', '001'); // create atributes to parent node<br />
RootNode.Appendchild(parentNode); // save parent node<br />
<br />
// Create a child node<br />
parentNode := Doc.CreateElement('nome'); // create a child node<br />
// TDOMElement(parentNode).SetAttribute('sexo', 'M'); // create atributes<br />
nofilho := Doc.CreateTextNode('Fernando'); // insert a value to node<br />
parentNode.Appendchild(nofilho); // save node<br />
RootNode.ChildNodes.Item[0].AppendChild(parentNode); // insert child node in respective parent node<br />
<br />
// Create a child node<br />
parentNode := Doc.CreateElement('idade'); // create a child node<br />
// TDOMElement(parentNode).SetAttribute('ano', '1976'); // create atributes<br />
nofilho := Doc.CreateTextNode('32'); // insert a value to node<br />
parentNode.Appendchild(nofilho); // save node<br />
RootNode.ChildNodes.Item[0].AppendChild(parentNode); // insert a childnode in respective parent node<br />
<br />
writeXMLFile(Doc, 'test.xml'); // write to XML<br />
finally<br />
Doc.Free; // free memory<br />
end;<br />
end;<br />
<br />
initialization<br />
{$I unit1.lrs}<br />
<br />
end.</source><br />
<br />
The result will be the XML file below:<br />
<syntaxhighlight lang="xml"><?xml version="1.0"?><br />
<register><br />
<usuario id="001"><br />
<nome>Fernando</nome><br />
<idade>32</idade><br />
</usuario><br />
</register></syntaxhighlight><br />
<br />
An example where you don't need to reference an item by index.<br />
<br />
<source><br />
procedure TForm1.Button2Click(Sender: TObject);<br />
var<br />
Doc: TXMLDocument;<br />
RootNode, ElementNode,ItemNode,TextNode: TDOMNode;<br />
i: integer;<br />
begin<br />
try<br />
// Create a document<br />
Doc := TXMLDocument.Create;<br />
// Create a root node<br />
RootNode := Doc.CreateElement('Root');<br />
Doc.Appendchild(RootNode);<br />
RootNode:= Doc.DocumentElement;<br />
// Create nodes<br />
for i := 1 to 20 do<br />
begin<br />
ElementNode:=Doc.CreateElement('Element');<br />
TDOMElement(ElementNode).SetAttribute('id', IntToStr(i));<br />
<br />
ItemNode:=Doc.CreateElement('Item1');<br />
TDOMElement(ItemNode).SetAttribute('Attr1', IntToStr(i));<br />
TDOMElement(ItemNode).SetAttribute('Attr2', IntToStr(i));<br />
TextNode:=Doc.CreateTextNode('Item1Value is '+IntToStr(i));<br />
ItemNode.AppendChild(TextNode);<br />
ElementNode.AppendChild(ItemNode);<br />
<br />
ItemNode:=Doc.CreateElement('Item2');<br />
TDOMElement(ItemNode).SetAttribute('Attr1', IntToStr(i));<br />
TDOMElement(ItemNode).SetAttribute('Attr2', IntToStr(i));<br />
TextNode:=Doc.CreateTextNode('Item2Value is '+IntToStr(i));<br />
ItemNode.AppendChild(TextNode);<br />
ElementNode.AppendChild(ItemNode);<br />
<br />
RootNode.AppendChild(ElementNode);<br />
end;<br />
// Save XML<br />
WriteXMLFile(Doc,'TestXML_v2.xml');<br />
finally<br />
Doc.Free;<br />
end;<br />
</source><br />
<br />
Generated XML:<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<Root><br />
<Element id="1"><br />
<Item1 Attr1="1" Attr2="1">Item1Value is 1</Item1><br />
<Item2 Attr1="1" Attr2="1">Item2Value is 1</Item2><br />
</Element><br />
<Element id="2"><br />
<Item1 Attr1="2" Attr2="2">Item1Value is 2</Item1><br />
<Item2 Attr1="2" Attr2="2">Item2Value is 2</Item2><br />
</Element><br />
<Element id="3"><br />
<Item1 Attr1="3" Attr2="3">Item1Value is 3</Item1><br />
<Item2 Attr1="3" Attr2="3">Item2Value is 3</Item2><br />
</Element><br />
</Root><br />
</syntaxhighlight><br />
<br />
=== Encoding ===<br />
<br />
Starting from FPC version 2.4, the XML reader is able to process data in any encoding by using external decoders. See [[XML_Decoders]] for more details.<br />
<br />
According to the XML standard, the encoding attribute in the first line of the XML is optional in case the actual encoding is UTF-8 ('''without''' BOM - Byte Order Marker) or UTF-16 (UTF-16 BOM).<br />
<br />
TXMLDocument has an encoding property since FPC 2.4. It is ignored as WriteXMLFile always uses UTF-8.<br />
* FPC 2.4 doesn´t generate an encoding attribute in the first line of the XML file<br />
* FPC 2.6.0 and later explicitly write an UTF8 encoding attribute, as this is needed for some programs that cannot handle the XML without it.<br />
<br />
== See also ==<br />
<br />
* [[XML Decoders]]<br />
* [[Using INI Files]]<br />
* [[fcl-xml]]<br />
* [[Internet Tools]], for XPath 2 / XQuery processing<br />
<br />
== External Links ==<br />
<br />
* [http://www.w3schools.com/xml/default.asp W3Schools] Xml Tutorial<br />
<br />
* [http://www.thomas-zastrow.de/texte/fpcxml/index.php Thomas Zastrow article] [http://web.archive.org/web/20080802150722/http://www.thomas-zastrow.de/texte/fpcxml/index.php Alternate link] FPC and XML<br />
<br />
[[Category:Free Component Library]]<br />
[[Category:Tutorials]]<br />
[[Category:XML]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Executing_External_Programs&diff=94729Executing External Programs2015-08-11T12:38:30Z<p>Eny: Place commands in the right order</p>
<hr />
<div>{{Executing External Programs}}<br />
<br />
== Overview : Comparison ==<br />
{| class="wikitable"<br />
!Method<br />
!Platforms<br />
!Single Line<br />
!Features<br />
|-<br />
|SysUtils.ExecuteProcess<br />
|Cross-Platform<br />
|Yes<br />
|Very limited, synchronous<br />
|-<br />
|(ShellApi) ShellExecute<br />
|MS Windows only<br />
|Yes<br />
|Many. Can start programs with elevation/admin permissions.<br />
|-<br />
|Unix fpsystem, fpexecve<br />
|Unix only<br />
|<br />
|<br />
|-<br />
|TProcess <br />
|Cross-Platform<br />
|No<br />
|Full<br />
|-<br />
|RunCommand(InDir)<br />
|Cross-Platform '''Requires FPC 2.6.2+'''<br />
|Yes<br />
|Covers common TProcess usage<br />
|-<br />
|(LCLIntf) OpenDocument<br />
|Cross-Platform<br />
|Yes<br />
|Only open document<br />
|}<br />
<br />
==(Process.)RunCommand==<br />
In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the [[Projects using Lazarus#fpcup|fpcup]] project.<br />
These functions are meant for basic and intermediate use and can capture output to a single string and fully support the ''large output'' case. <br />
<br />
A simple example is<br />
<syntaxhighlight><br />
uses Process;<br />
...<br />
var s : ansistring;<br />
...<br />
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then<br />
writeln(s); <br />
</syntaxhighlight> <br />
An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets p.CurrentDirectory):<br />
<syntaxhighlight><br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;<br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
</syntaxhighlight><br />
<br />
== SysUtils.ExecuteProcess ==<br />
(Cross-platform)<BR><br />
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :<br />
<br />
<syntaxhighlight>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight><br />
<br />
The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the next section about the prefered cross-platform '''TProcess''', or if you only wish to target Windows you may use '''ShellExecute'''.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess reference]<br />
<br />
== MS Windows : CreateProcess, ShellExecute and WinExec ==<br />
<br />
{{Note|While FPC/Lazarus has support for '''CreateProcess''', '''ShellExecute''' and/or '''WinExec''', this support is only in Win32/64. If your program is cross-platform, consider using '''RunCommand''' or '''TProcess'''.}}<br />
{{Note|WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.}}<br />
<br />
'''ShellExecute''' is a standard MS Windows function (ShellApi.h) with good [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx documentation on MSDN] (note their remarks about initialising COM if you find the function unreliable).<br />
<br />
<syntaxhighlight><br />
uses ..., ShellApi;<br />
<br />
// Simple one-liner (ignoring error returns) :<br />
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;<br />
<br />
// Execute a Batch File :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;<br />
<br />
// Open a command window in a given folder :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;<br />
<br />
// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;<br />
<br />
// or a useful procedure:<br />
procedure RunShellExecute(const prog,params:string);<br />
begin<br />
// ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' isn't always needed <br />
// path+prog, params, working folder,<br />
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...<br />
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success<br />
// return values 0..32 are errors<br />
end;<br />
</syntaxhighlight><br />
<br />
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.<br />
<br />
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.<br />
<br />
If in Delphi you used ShellExecute for '''documents''' like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).<br />
<br />
=== Using ShellExecuteEx for elevation/administrator permissions ===<br />
If you need to execute external program with administrator/elevated privileges, you can use the '''runas''' method with the alternative ShellExecuteEx function:<br />
<syntaxhighlight><br />
uses ShellApi, ...;<br />
<br />
function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;<br />
var<br />
sei: TShellExecuteInfoA;<br />
begin<br />
FillChar(sei, SizeOf(sei), 0);<br />
sei.cbSize := SizeOf(sei);<br />
sei.Wnd := Handle;<br />
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;<br />
sei.lpVerb := 'runas';<br />
sei.lpFile := PAnsiChar(Path);<br />
sei.lpParameters := PAnsiChar(Params);<br />
sei.nShow := SW_SHOWNORMAL;<br />
Result := ShellExecuteExA(@sei);<br />
end;<br />
<br />
procedure TFormMain.RunAddOrRemoveApplication;<br />
begin<br />
// Example that uses elevated rundll to open the Control Panel to Programs and features<br />
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');<br />
end;<br />
</syntaxhighlight><br />
<br />
== Unix fpsystem, fpexecve and shell ==<br />
<br />
These functions are platform dependent.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem reference]<br />
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve reference]<br />
* [http://www.freepascal.org/docs-html/rtl/unix/shell.html shell reference]<br />
<br />
Note that the 1.0.x '''Unix.Shell'' has been deprecated for a while, and is removed in trunk. Use fpsystem.<br />
<br />
== TProcess ==<br />
<br />
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:<br />
<br />
* Platform Independent<br />
* Capable of reading from stdout and writing to stdin.<br />
* Possible to wait for a command to finish or let it run while your program moves on.<br />
<br />
Important notes:<br />
* TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..<br />
* Presumably on Linux/Unix: you '''must''' specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.<br />
* On Windows, if the command is in the path, you don't need to specify the full path.<br />
* [[doc:fcl/process/tprocess.html|TProcess reference]]<br />
<br />
=== The Simplest Example ===<br />
<br />
A lot of typical cases have been prepared in the [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]] functions. Before you start copy and paste the examples below, check them out first.<br />
<br />
=== A Simple Example ===<br />
This example ('''that shouldn't be used in production, see Large Output or, better, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''') just shows you how to run an external program, nothing more:<br />
<syntaxhighlight><br />
// This is a demo program that shows<br />
// how to launch an external program.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This defines the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
var <br />
AProcess: TProcess;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler (i386 version that is)<br />
AProcess.Executable:= 'ppc386';<br />
<br />
// Pass -h together with ppc386 so actually 'ppc386 -h' is executed:<br />
AProcess.Parameters.Add('-h');<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. vvvvvvvvvvvvvv<br />
AProcess.Options := AProcess.Options + [poWaitOnExit];<br />
<br />
// Now let AProcess run the program<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
That's it! You have just learned to run an external program from inside your own program.<br />
<br />
=== An improved example (but not correct yet)===<br />
That's nice, but how do I read the Output of a program that I have run?<br />
<br />
Well, let's expand our example a little and do just that:<br />
'''This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in [[#Reading large output]].'''<br />
<br />
<syntaxhighlight><br />
// This is a <br />
// FLAWED<br />
// demo program that shows<br />
// how to launch an external program<br />
// and read from its output.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This is defining the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
// Also now we are adding a TStringList to store the <br />
// data read from the programs output.<br />
var <br />
AProcess: TProcess;<br />
AStringList: TStringList;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
AProcess.Executable := '/usr/bin/ppc386'; <br />
AProcess.Parameters.Add('-h'); <br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. Also now we will tell it that<br />
// we want to read the output of the file.<br />
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];<br />
<br />
// Now that AProcess knows what the commandline is it can be run.<br />
AProcess.Execute;<br />
<br />
// After AProcess has finished, the rest of the program will be executed.<br />
<br />
// Now read the output of the program we just ran into a TStringList.<br />
AStringList := TStringList.Create;<br />
AStringList.LoadFromStream(AProcess.Output);<br />
<br />
// Save the output to a file and clean up the TStringList.<br />
AStringList.SaveToFile('output.txt');<br />
AStringList.Free;<br />
<br />
// Now that the output from the process is processed, it can be freed.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
<br />
=== Reading large output ===<br />
In the previous example we waited until the program exited. Then we read what the program has written to its output.<br />
<br />
Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from. <br />
<br />
But the calling program doesn't read from it until the called program has ended. A deadlock occurs.<br />
<br />
The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.<br />
<br />
If you want to read output from an external process, this is the code you should adapt for production use.<br />
<syntaxhighlight>program LargeOutputDemo;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes, SysUtils, Process; // Process is the unit that holds TProcess<br />
<br />
const<br />
BUF_SIZE = 2048; // Buffer size for reading the output in chunks<br />
<br />
var<br />
AProcess : TProcess;<br />
OutputStream : TStream;<br />
BytesRead : longint;<br />
Buffer : array[1..BUF_SIZE] of byte;<br />
<br />
begin<br />
// Set up the process; as an example a recursive directory search is used<br />
// because that will usually result in a lot of data.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// The commands for Windows and *nix are different hence the $IFDEFs<br />
{$IFDEF Windows}<br />
// In Windows the dir command cannot be used directly because it's a build-in<br />
// shell command. Therefore cmd.exe and the extra parameters are needed.<br />
AProcess.Executable := 'c:\windows\system32\cmd.exe';<br />
AProcess.Parameters.Add('/c');<br />
AProcess.Parameters.Add('dir /s c:\windows');<br />
{$ENDIF Windows}<br />
<br />
{$IFDEF Unix}<br />
AProcess.Executable := '/bin/ls';<br />
AProcess.Parameters.Add('--recursive');<br />
AProcess.Parameters.Add('--all');<br />
AProcess.Parameters.Add('-l');<br />
{$ENDIF Unix}<br />
<br />
// Process option poUsePipes has to be used so the output can be captured.<br />
// Process option poWaitOnExit can not be used because that would block<br />
// this program, preventing it from reading the output data of the process.<br />
AProcess.Options := [poUsePipes];<br />
<br />
// Start the process (run the dir/ls command)<br />
AProcess.Execute;<br />
<br />
// Create a stream object to store the generated output in. This could<br />
// also be a file stream to directly save the output to disk.<br />
OutputStream := TMemoryStream.Create;<br />
<br />
// All generated output from AProcess is read in a loop until no more data is available<br />
repeat<br />
// Get the new data from the process to a maximum of the buffer size that was allocated.<br />
// Note that all read(...) calls will block except for the last one, which returns 0 (zero).<br />
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);<br />
<br />
// Add the bytes that were read to the stream for later usage<br />
OutputStream.Write(Buffer, BytesRead)<br />
<br />
until BytesRead = 0; // Stop if no more data is available<br />
<br />
// The process has finished so it can be cleaned up<br />
AProcess.Free;<br />
<br />
// Now that all data has been read it can be used; for example to save it to a file on disk<br />
with TFileStream.Create('output.txt', fmCreate) do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
CopyFrom(OutputStream, OutputStream.Size);<br />
Free<br />
end;<br />
<br />
// Or the data can be shown on screen<br />
with TStringList.Create do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
LoadFromStream(OutputStream);<br />
writeln(Text);<br />
writeln('--- Number of lines = ', Count, '----');<br />
Free<br />
end;<br />
<br />
// Clean up<br />
OutputStream.Free;<br />
end.</syntaxhighlight><br />
Note that the above could also be accomplished by using RunCommand:<br />
<syntaxhighlight>var s: string;<br />
...<br />
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight><br />
<br />
=== Using input and output of a TProcess ===<br />
See processdemo example in the [https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].<br />
<br />
=== Hints on the use of TProcess ===<br />
When creating a cross-platform program, the OS-specific executable name can be set using directives "{$IFDEF}" and "{$ENDIF}".<br />
<br />
Example:<br />
<syntaxhighlight>{...}<br />
AProcess := TProcess.Create(nil)<br />
<br />
{$IFDEF WIN32}<br />
AProcess.Executable := 'calc.exe'; <br />
{$ENDIF}<br />
<br />
{$IFDEF LINUX}<br />
AProcess.Executable := 'kcalc'; <br />
{$ENDIF}<br />
<br />
AProcess.Execute;<br />
{...}</syntaxhighlight><br />
<br />
=== OS X show application bundle in foreground ===<br />
<br />
You can start an '''application bundle''' via TProcess by starting the executable within the bundle. For example:<br />
<syntaxhighlight><br />
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';<br />
</syntaxhighlight><br />
<br />
This will start the ''Calendar'', but the window will be behind the current application.<br />
To get the application in the foreground you can use the '''open''' utility with the '''-n''' parameter:<br />
<syntaxhighlight><br />
AProcess.Executable:='/usr/bin/open';<br />
AProcess.Parameters.Add('-n');<br />
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole<br />
AProcess.Parameters.Add('/Application/iCal.app');<br />
</syntaxhighlight><br />
<br />
If your application needs parameters, you can pass '''open''' the '''--args''' parameter, after which all parameters are passed to the application:<br />
<syntaxhighlight><br />
AProcess.Parameters.Add('--args');<br />
AProcess.Parameters.Add('argument1');<br />
AProcess.Parameters.Add('argument2');<br />
</syntaxhighlight><br />
<br />
=== Run detached program ===<br />
<br />
Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:<br />
<br />
<syntaxhighlight><br />
var<br />
Process: TProcess;<br />
I: Integer;<br />
begin<br />
Process := TProcess.Create(nil);<br />
try<br />
Process.InheritHandles := False;<br />
Process.Options := [];<br />
Process.ShowWindow := swoShow;<br />
<br />
// Copy default environment variables including DISPLAY variable for GUI application to work<br />
for I := 1 to GetEnvironmentVariableCount do<br />
Process.Environment.Add(GetEnvironmentString(I));<br />
<br />
Process.Executable := '/usr/bin/gedit'; <br />
Process.Execute;<br />
finally<br />
Process.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== Example of "talking" with aspell process ===<br />
<br />
Inside [http://pasdoc.sourceforge.net/ pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.<br />
<br />
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.<br />
<br />
=== Replacing shell operators like "| < >" ===<br />
<br />
Sometimes you want to run a more complicated command that pipes its data to another command or to a file.<br />
Something like <br />
<syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight><br />
or<br />
<syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight><br />
<br />
Executing this with TProcess will not work. i.e:<br />
<br />
<syntaxhighlight>// this won't work<br />
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; <br />
Process.Execute;</syntaxhighlight><br />
<br />
==== Why using special operators to redirect output doesn't work ====<br />
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.<br />
It is possible to redirect output however just the way you wanted. See the [[Executing_External_Programs#How_to_redirect_output_with_TProcess |next section]].<br />
<br />
=== How to redirect output with TProcess ===<br />
<br />
You can redirect the output of a command to another command by using a TProcess instance for '''each''' command.<br />
<br />
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]<br />
<br />
Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.<br />
<br />
<syntaxhighlight>program Project1;<br />
<br />
uses<br />
Classes, sysutils, process;<br />
<br />
var<br />
FirstProcess,<br />
SecondProcess: TProcess;<br />
Buffer: array[0..127] of char;<br />
ReadCount: Integer;<br />
ReadSize: Integer;<br />
begin<br />
FirstProcess := TProcess.Create(nil);<br />
SecondProcess := TProcess.Create(nil);<br />
<br />
FirstProcess.Options := [poUsePipes];<br />
SecondProcess.Options := [poUsePipes,poStderrToOutPut];<br />
<br />
// FirstProcess.CommandLine := 'pwd'; -----> CommandLine is deprecated, USE:<br />
FirstProcess.Executable := 'pwd'; <br />
<br />
// SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -'; -----> CommandLine is deprecated, USE:<br />
SecondProcess.Executable := 'grep'; <br />
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); <br />
// this would be the same as "pwd | grep / -"<br />
<br />
FirstProcess.Execute;<br />
SecondProcess.Execute;<br />
<br />
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do<br />
begin<br />
if FirstProcess.Output.NumBytesAvailable > 0 then<br />
begin<br />
// make sure that we don't read more data than we have allocated<br />
// in the buffer<br />
ReadSize := FirstProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
// now read the output into the buffer<br />
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);<br />
// and write the buffer to the second process<br />
SecondProcess.Input.Write(Buffer[0], ReadCount);<br />
<br />
// if SecondProcess writes much data to it's Output then <br />
// we should read that data here to prevent a deadlock<br />
// see the previous example "Reading Large Output"<br />
end;<br />
end;<br />
// Close the input on the SecondProcess<br />
// so it finishes processing it's data<br />
SecondProcess.CloseInput;<br />
<br />
// and wait for it to complete<br />
// be carefull what command you run because it may not exit when<br />
// it's input is closed and the following line may loop forever<br />
while SecondProcess.Running do<br />
Sleep(1);<br />
// that's it! the rest of the program is just so the example<br />
// is a little 'useful'<br />
<br />
// we will reuse Buffer to output the SecondProcess's<br />
// output to *this* programs stdout<br />
WriteLn('Grep output Start:');<br />
ReadSize := SecondProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
if ReadSize > 0 then<br />
begin<br />
ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);<br />
WriteLn(Copy(Buffer,0, ReadCount));<br />
end<br />
else<br />
WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);<br />
WriteLn('Grep output Finish:');<br />
<br />
// free our process objects<br />
FirstProcess.Free;<br />
SecondProcess.Free;<br />
end.</syntaxhighlight><br />
<br />
That's it. Now you can redirect output from one program to another.<br />
<br />
==== Notes ====<br />
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:<br />
<syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight><br />
<br />
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.<br />
<br />
===Redirecting input and output and running under root===<br />
A common problem on Unixes (OSX) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ''ping'' command.<br />
<br />
If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). This sample runs <code>ls</code> on the <code>/root</code> directory, but can of course be adapted.<br />
<br />
A '''better way''' to do this is to use the policykit package, which should be available on all recent Linuxes. [http://lazarus.freepascal.org/index.php/topic,14479.0.html See the forum thread for details.]<br />
<br />
Large parts of this code are similar to the earlier example, but it also shows how to redirect stdout and stderr of the process being called separately to stdout and stderr of our own code.<br />
<br />
<syntaxhighlight><br />
program rootls;<br />
<br />
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,<br />
calling sudo on Linux/OSX, and supplying input on stdin}<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes,<br />
Math, {for min}<br />
Process;<br />
<br />
procedure RunsLsRoot;<br />
var<br />
Proc: TProcess;<br />
CharBuffer: array [0..511] of char;<br />
ReadCount: integer;<br />
ExitCode: integer;<br />
SudoPassword: string;<br />
begin<br />
WriteLn('Please enter the sudo password:');<br />
Readln(SudoPassword);<br />
ExitCode := -1; //Start out with failure, let's see later if it works<br />
Proc := TProcess.Create(nil); //Create a new process<br />
try<br />
Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr<br />
Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo<br />
// -S causes sudo to read the password from stdin.<br />
Proc.Execute; //start it. sudo will now probably ask for a password<br />
<br />
// write the password to stdin of the sudo program:<br />
SudoPassword := SudoPassword + LineEnding;<br />
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));<br />
SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof<br />
SudoPassword := ''; // and make the program a bit safer from snooping?!?<br />
<br />
// main loop to read output from stdout and stderr of sudo<br />
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or<br />
(Proc.Stderr.NumBytesAvailable > 0) do<br />
begin<br />
// read stdout and write to our stdout<br />
while Proc.Output.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Output.Read(CharBuffer, ReadCount);<br />
Write(StdOut, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
// read stderr and write to our stderr<br />
while Proc.Stderr.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Stderr.Read(CharBuffer, ReadCount);<br />
Write(StdErr, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
end;<br />
ExitCode := Proc.ExitStatus;<br />
finally<br />
Proc.Free;<br />
Halt(ExitCode);<br />
end;<br />
end;<br />
<br />
begin<br />
RunsLsRoot;<br />
end.<br />
</syntaxhighlight><br />
<br />
Other thoughts:<br />
It would no doubt be advisable to see if sudo actually prompts for a password. This can be checked consistently by setting the environment variable SUDO_PROMPT to something we watch for while reading the stdout of TProcess avoiding the problem of the prompt being different for different locales. Setting an environment variable causes the default values to be cleared(inherited from our process) so we have to copy the environment from our program if needed.<br />
<br />
=== Using fdisk with sudo on Linux ===<br />
The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.<br />
<br />
<syntaxhighlight><br />
program getpartitioninfo;<br />
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.<br />
Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}<br />
<br />
Uses<br />
Classes, SysUtils, FileUtil, Process;<br />
<br />
var<br />
hprocess: TProcess;<br />
sPass: String;<br />
OutputLines: TStringList;<br />
<br />
begin <br />
sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password<br />
OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure <br />
// OutputLines is freed... Same for hProcess.<br />
<br />
// The following example will open fdisk in the background and give us partition information<br />
// Since fdisk requires elevated priviledges we need to <br />
// pass our password as a parameter to sudo using the -S<br />
// option, so it will will wait till our program sends our password to the sudo application<br />
hProcess := TProcess.Create(nil);<br />
// On Linux/Unix/OSX, we need specify full path to our executable:<br />
hProcess.Executable := '/bin/sh';<br />
// Now we add all the parameters on the command line:<br />
hprocess.Parameters.Add('-c');<br />
// Here we pipe the password to the sudo command which then executes fdisk -l: <br />
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');<br />
// Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe<br />
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];<br />
// Now run:<br />
hProcess.Execute;<br />
<br />
// hProcess should have now run the external executable (because we use poWaitOnExit).<br />
// Now you can process the process output (standard output and standard error), eg:<br />
OutputLines.Add('stdout:');<br />
OutputLines.LoadFromStream(hprocess.Output);<br />
OutputLines.Add('stderr:');<br />
OutputLines.LoadFromStream(hProcess.Stderr);<br />
// Show output on screen:<br />
writeln(OutputLines.Text);<br />
<br />
// Clean up to avoid memory leaks:<br />
hProcess.Free;<br />
OutputLines.Free;<br />
<br />
//Below are some examples as you see we can pass illegal characters just as if done from terminal <br />
//Even though you have read elsewhere that you can not I assure with this method you can :)<br />
<br />
//hprocess.Parameters.Add('ping -c 1 www.google.com');<br />
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');<br />
<br />
//Using QuotedStr() is not a requirement though it makes for cleaner code;<br />
//you can use double quote and have the same effect.<br />
<br />
//hprocess.Parameters.Add('glxinfo | grep direct'); <br />
<br />
// This method can also be used for installing applications from your repository:<br />
<br />
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); <br />
<br />
end. <br />
</syntaxhighlight><br />
<br />
<br />
===Parameters which contain spaces (Replacing Shell Quotes)===<br />
<br />
In the Linux shell it is possible to write quoted arguments like this:<br />
<br />
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram<br />
<br />
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):<br />
#--batch<br />
#--eval-command=info symbol 0x0000DDDD<br />
#the full path to myprogram<br />
<br />
TProcess can also pass parameters containing spaces, but it uses a different quoting style. Instead of only quoting part of the parameter, quote all of it. Like this:<br />
<br />
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';<br />
<br />
And also remember to only pass full paths.<br />
<br />
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446<br />
<br />
==LCLIntf Alternatives==<br />
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.<br />
<br />
===Open document in default application===<br />
<br />
In some situations you need to open some document/file using default associated application rather than execute a particular program. <br />
This depends on running operating system. Lazarus provides a platform independent procedure '''OpenDocument''' which will handle it for you. Your application will continue running without waiting for the document process to close.<br />
<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenDocument('manual.pdf'); <br />
...<br />
</syntaxhighlight><br />
<br />
* [[opendocument|OpenDocument reference]]<br />
<br />
===Open web page in default web browser===<br />
<br />
Just pass the URL required, the leading http:// appears to be optional under certain circumstances. <br />
Also, passing a filename appears to give the same results as OpenDocument()<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenURL('www.lazarus.freepascal.org/');<br />
</syntaxhighlight><br />
<br />
See also:<br />
* [[openurl|OpenURL reference]]<br />
<br />
Or, you could use '''TProcess''' like this:<br />
<syntaxhighlight><br />
uses Process;<br />
<br />
procedure OpenWebPage(URL: string);<br />
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"<br />
var<br />
Browser, Params: string;<br />
begin<br />
FindDefaultBrowser(Browser, Params);<br />
with TProcess.Create(nil) do<br />
try<br />
Executable := Browser;<br />
Params:=Format(Params, [URL]);<br />
Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself<br />
Parameters.Add(Params);<br />
Options := [poNoConsole];<br />
Execute;<br />
finally<br />
Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]<br />
* [[opendocument]]<br />
* [[openurl]]<br />
* [[TProcessUTF8]]<br />
* [[TXMLPropStorage]]<br />
* [[Webbrowser]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Parallel programming]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Inter-process communication]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Executing_External_Programs&diff=94728Executing External Programs2015-08-11T12:32:51Z<p>Eny: Remove reference to Commandline</p>
<hr />
<div>{{Executing External Programs}}<br />
<br />
== Overview : Comparison ==<br />
{| class="wikitable"<br />
!Method<br />
!Platforms<br />
!Single Line<br />
!Features<br />
|-<br />
|SysUtils.ExecuteProcess<br />
|Cross-Platform<br />
|Yes<br />
|Very limited, synchronous<br />
|-<br />
|(ShellApi) ShellExecute<br />
|MS Windows only<br />
|Yes<br />
|Many. Can start programs with elevation/admin permissions.<br />
|-<br />
|Unix fpsystem, fpexecve<br />
|Unix only<br />
|<br />
|<br />
|-<br />
|TProcess <br />
|Cross-Platform<br />
|No<br />
|Full<br />
|-<br />
|RunCommand(InDir)<br />
|Cross-Platform '''Requires FPC 2.6.2+'''<br />
|Yes<br />
|Covers common TProcess usage<br />
|-<br />
|(LCLIntf) OpenDocument<br />
|Cross-Platform<br />
|Yes<br />
|Only open document<br />
|}<br />
<br />
==(Process.)RunCommand==<br />
In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the [[Projects using Lazarus#fpcup|fpcup]] project.<br />
These functions are meant for basic and intermediate use and can capture output to a single string and fully support the ''large output'' case. <br />
<br />
A simple example is<br />
<syntaxhighlight><br />
uses Process;<br />
...<br />
var s : ansistring;<br />
...<br />
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then<br />
writeln(s); <br />
</syntaxhighlight> <br />
An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets p.CurrentDirectory):<br />
<syntaxhighlight><br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;<br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
</syntaxhighlight><br />
<br />
== SysUtils.ExecuteProcess ==<br />
(Cross-platform)<BR><br />
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :<br />
<br />
<syntaxhighlight>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight><br />
<br />
The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the next section about the prefered cross-platform '''TProcess''', or if you only wish to target Windows you may use '''ShellExecute'''.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess reference]<br />
<br />
== MS Windows : CreateProcess, ShellExecute and WinExec ==<br />
<br />
{{Note|While FPC/Lazarus has support for '''CreateProcess''', '''ShellExecute''' and/or '''WinExec''', this support is only in Win32/64. If your program is cross-platform, consider using '''RunCommand''' or '''TProcess'''.}}<br />
{{Note|WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.}}<br />
<br />
'''ShellExecute''' is a standard MS Windows function (ShellApi.h) with good [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx documentation on MSDN] (note their remarks about initialising COM if you find the function unreliable).<br />
<br />
<syntaxhighlight><br />
uses ..., ShellApi;<br />
<br />
// Simple one-liner (ignoring error returns) :<br />
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;<br />
<br />
// Execute a Batch File :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;<br />
<br />
// Open a command window in a given folder :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;<br />
<br />
// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;<br />
<br />
// or a useful procedure:<br />
procedure RunShellExecute(const prog,params:string);<br />
begin<br />
// ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' isn't always needed <br />
// path+prog, params, working folder,<br />
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...<br />
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success<br />
// return values 0..32 are errors<br />
end;<br />
</syntaxhighlight><br />
<br />
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.<br />
<br />
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.<br />
<br />
If in Delphi you used ShellExecute for '''documents''' like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).<br />
<br />
=== Using ShellExecuteEx for elevation/administrator permissions ===<br />
If you need to execute external program with administrator/elevated privileges, you can use the '''runas''' method with the alternative ShellExecuteEx function:<br />
<syntaxhighlight><br />
uses ShellApi, ...;<br />
<br />
function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;<br />
var<br />
sei: TShellExecuteInfoA;<br />
begin<br />
FillChar(sei, SizeOf(sei), 0);<br />
sei.cbSize := SizeOf(sei);<br />
sei.Wnd := Handle;<br />
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;<br />
sei.lpVerb := 'runas';<br />
sei.lpFile := PAnsiChar(Path);<br />
sei.lpParameters := PAnsiChar(Params);<br />
sei.nShow := SW_SHOWNORMAL;<br />
Result := ShellExecuteExA(@sei);<br />
end;<br />
<br />
procedure TFormMain.RunAddOrRemoveApplication;<br />
begin<br />
// Example that uses elevated rundll to open the Control Panel to Programs and features<br />
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');<br />
end;<br />
</syntaxhighlight><br />
<br />
== Unix fpsystem, fpexecve and shell ==<br />
<br />
These functions are platform dependent.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem reference]<br />
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve reference]<br />
* [http://www.freepascal.org/docs-html/rtl/unix/shell.html shell reference]<br />
<br />
Note that the 1.0.x '''Unix.Shell'' has been deprecated for a while, and is removed in trunk. Use fpsystem.<br />
<br />
== TProcess ==<br />
<br />
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:<br />
<br />
* Platform Independent<br />
* Capable of reading from stdout and writing to stdin.<br />
* Possible to wait for a command to finish or let it run while your program moves on.<br />
<br />
Important notes:<br />
* TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..<br />
* Presumably on Linux/Unix: you '''must''' specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.<br />
* On Windows, if the command is in the path, you don't need to specify the full path.<br />
* [[doc:fcl/process/tprocess.html|TProcess reference]]<br />
<br />
=== The Simplest Example ===<br />
<br />
A lot of typical cases have been prepared in the [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]] functions. Before you start copy and paste the examples below, check them out first.<br />
<br />
=== A Simple Example ===<br />
This example ('''that shouldn't be used in production, see Large Output or, better, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''') just shows you how to run an external program, nothing more:<br />
<syntaxhighlight><br />
// This is a demo program that shows<br />
// how to launch an external program.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This defines the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
var <br />
AProcess: TProcess;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler (i386 version that is)<br />
AProcess.Executable:= 'ppc386';<br />
<br />
// Pass -h together with ppc386 so actually 'ppc386 -h' is executed:<br />
AProcess.Parameters.Add('-h');<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. vvvvvvvvvvvvvv<br />
AProcess.Options := AProcess.Options + [poWaitOnExit];<br />
<br />
// Now let AProcess run the program<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
That's it! You have just learned to run an external program from inside your own program.<br />
<br />
=== An improved example (but not correct yet)===<br />
That's nice, but how do I read the Output of a program that I have run?<br />
<br />
Well, let's expand our example a little and do just that:<br />
'''This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in [[#Reading large output]].'''<br />
<br />
<syntaxhighlight><br />
// This is a <br />
// FLAWED<br />
// demo program that shows<br />
// how to launch an external program<br />
// and read from its output.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This is defining the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
// Also now we are adding a TStringList to store the <br />
// data read from the programs output.<br />
var <br />
AProcess: TProcess;<br />
AStringList: TStringList;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Create the TStringList object.<br />
AStringList := TStringList.Create;<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler<br />
// AProcess.CommandLine := '/usr/bin/ppc386 -h'; -----> CommandLine is deprecated, <br />
<br />
// -----> CommandLine is deprecated, use:<br />
AProcess.Executable := '/usr/bin/ppc386'; <br />
AProcess.Parameters.Add('-h'); <br />
<br />
// Previous versions of FPC did not yet have .Executable and .Parameters and<br />
// used the .CommandLine property. While this is still available, it is deprecated<br />
// so it should not be used.<br />
<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. Also now we will tell it that<br />
// we want to read the output of the file.<br />
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];<br />
<br />
// Now that AProcess knows what the commandline is <br />
// we will run it.<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
<br />
// Now read the output of the program we just ran<br />
// into the TStringList.<br />
AStringList.LoadFromStream(AProcess.Output);<br />
<br />
// Save the output to a file.<br />
AStringList.SaveToFile('output.txt');<br />
<br />
// Now that the file is saved we can free the <br />
// TStringList and the TProcess.<br />
AStringList.Free;<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
<br />
=== Reading large output ===<br />
In the previous example we waited until the program exited. Then we read what the program has written to its output.<br />
<br />
Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from. <br />
<br />
But the calling program doesn't read from it until the called program has ended. A deadlock occurs.<br />
<br />
The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.<br />
<br />
If you want to read output from an external process, this is the code you should adapt for production use.<br />
<syntaxhighlight>program LargeOutputDemo;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes, SysUtils, Process; // Process is the unit that holds TProcess<br />
<br />
const<br />
BUF_SIZE = 2048; // Buffer size for reading the output in chunks<br />
<br />
var<br />
AProcess : TProcess;<br />
OutputStream : TStream;<br />
BytesRead : longint;<br />
Buffer : array[1..BUF_SIZE] of byte;<br />
<br />
begin<br />
// Set up the process; as an example a recursive directory search is used<br />
// because that will usually result in a lot of data.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// The commands for Windows and *nix are different hence the $IFDEFs<br />
{$IFDEF Windows}<br />
// In Windows the dir command cannot be used directly because it's a build-in<br />
// shell command. Therefore cmd.exe and the extra parameters are needed.<br />
AProcess.Executable := 'c:\windows\system32\cmd.exe';<br />
AProcess.Parameters.Add('/c');<br />
AProcess.Parameters.Add('dir /s c:\windows');<br />
{$ENDIF Windows}<br />
<br />
{$IFDEF Unix}<br />
AProcess.Executable := '/bin/ls';<br />
AProcess.Parameters.Add('--recursive');<br />
AProcess.Parameters.Add('--all');<br />
AProcess.Parameters.Add('-l');<br />
{$ENDIF Unix}<br />
<br />
// Process option poUsePipes has to be used so the output can be captured.<br />
// Process option poWaitOnExit can not be used because that would block<br />
// this program, preventing it from reading the output data of the process.<br />
AProcess.Options := [poUsePipes];<br />
<br />
// Start the process (run the dir/ls command)<br />
AProcess.Execute;<br />
<br />
// Create a stream object to store the generated output in. This could<br />
// also be a file stream to directly save the output to disk.<br />
OutputStream := TMemoryStream.Create;<br />
<br />
// All generated output from AProcess is read in a loop until no more data is available<br />
repeat<br />
// Get the new data from the process to a maximum of the buffer size that was allocated.<br />
// Note that all read(...) calls will block except for the last one, which returns 0 (zero).<br />
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);<br />
<br />
// Add the bytes that were read to the stream for later usage<br />
OutputStream.Write(Buffer, BytesRead)<br />
<br />
until BytesRead = 0; // Stop if no more data is available<br />
<br />
// The process has finished so it can be cleaned up<br />
AProcess.Free;<br />
<br />
// Now that all data has been read it can be used; for example to save it to a file on disk<br />
with TFileStream.Create('output.txt', fmCreate) do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
CopyFrom(OutputStream, OutputStream.Size);<br />
Free<br />
end;<br />
<br />
// Or the data can be shown on screen<br />
with TStringList.Create do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
LoadFromStream(OutputStream);<br />
writeln(Text);<br />
writeln('--- Number of lines = ', Count, '----');<br />
Free<br />
end;<br />
<br />
// Clean up<br />
OutputStream.Free;<br />
end.</syntaxhighlight><br />
Note that the above could also be accomplished by using RunCommand:<br />
<syntaxhighlight>var s: string;<br />
...<br />
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight><br />
<br />
=== Using input and output of a TProcess ===<br />
See processdemo example in the [https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].<br />
<br />
=== Hints on the use of TProcess ===<br />
When creating a cross-platform program, the OS-specific executable name can be set using directives "{$IFDEF}" and "{$ENDIF}".<br />
<br />
Example:<br />
<syntaxhighlight>{...}<br />
AProcess := TProcess.Create(nil)<br />
<br />
{$IFDEF WIN32}<br />
AProcess.Executable := 'calc.exe'; <br />
{$ENDIF}<br />
<br />
{$IFDEF LINUX}<br />
AProcess.Executable := 'kcalc'; <br />
{$ENDIF}<br />
<br />
AProcess.Execute;<br />
{...}</syntaxhighlight><br />
<br />
=== OS X show application bundle in foreground ===<br />
<br />
You can start an '''application bundle''' via TProcess by starting the executable within the bundle. For example:<br />
<syntaxhighlight><br />
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';<br />
</syntaxhighlight><br />
<br />
This will start the ''Calendar'', but the window will be behind the current application.<br />
To get the application in the foreground you can use the '''open''' utility with the '''-n''' parameter:<br />
<syntaxhighlight><br />
AProcess.Executable:='/usr/bin/open';<br />
AProcess.Parameters.Add('-n');<br />
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole<br />
AProcess.Parameters.Add('/Application/iCal.app');<br />
</syntaxhighlight><br />
<br />
If your application needs parameters, you can pass '''open''' the '''--args''' parameter, after which all parameters are passed to the application:<br />
<syntaxhighlight><br />
AProcess.Parameters.Add('--args');<br />
AProcess.Parameters.Add('argument1');<br />
AProcess.Parameters.Add('argument2');<br />
</syntaxhighlight><br />
<br />
=== Run detached program ===<br />
<br />
Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:<br />
<br />
<syntaxhighlight><br />
var<br />
Process: TProcess;<br />
I: Integer;<br />
begin<br />
Process := TProcess.Create(nil);<br />
try<br />
Process.InheritHandles := False;<br />
Process.Options := [];<br />
Process.ShowWindow := swoShow;<br />
<br />
// Copy default environment variables including DISPLAY variable for GUI application to work<br />
for I := 1 to GetEnvironmentVariableCount do<br />
Process.Environment.Add(GetEnvironmentString(I));<br />
<br />
Process.Executable := '/usr/bin/gedit'; <br />
Process.Execute;<br />
finally<br />
Process.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== Example of "talking" with aspell process ===<br />
<br />
Inside [http://pasdoc.sourceforge.net/ pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.<br />
<br />
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.<br />
<br />
=== Replacing shell operators like "| < >" ===<br />
<br />
Sometimes you want to run a more complicated command that pipes its data to another command or to a file.<br />
Something like <br />
<syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight><br />
or<br />
<syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight><br />
<br />
Executing this with TProcess will not work. i.e:<br />
<br />
<syntaxhighlight>// this won't work<br />
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; <br />
Process.Execute;</syntaxhighlight><br />
<br />
==== Why using special operators to redirect output doesn't work ====<br />
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.<br />
It is possible to redirect output however just the way you wanted. See the [[Executing_External_Programs#How_to_redirect_output_with_TProcess |next section]].<br />
<br />
=== How to redirect output with TProcess ===<br />
<br />
You can redirect the output of a command to another command by using a TProcess instance for '''each''' command.<br />
<br />
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]<br />
<br />
Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.<br />
<br />
<syntaxhighlight>program Project1;<br />
<br />
uses<br />
Classes, sysutils, process;<br />
<br />
var<br />
FirstProcess,<br />
SecondProcess: TProcess;<br />
Buffer: array[0..127] of char;<br />
ReadCount: Integer;<br />
ReadSize: Integer;<br />
begin<br />
FirstProcess := TProcess.Create(nil);<br />
SecondProcess := TProcess.Create(nil);<br />
<br />
FirstProcess.Options := [poUsePipes];<br />
SecondProcess.Options := [poUsePipes,poStderrToOutPut];<br />
<br />
// FirstProcess.CommandLine := 'pwd'; -----> CommandLine is deprecated, USE:<br />
FirstProcess.Executable := 'pwd'; <br />
<br />
// SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -'; -----> CommandLine is deprecated, USE:<br />
SecondProcess.Executable := 'grep'; <br />
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); <br />
// this would be the same as "pwd | grep / -"<br />
<br />
FirstProcess.Execute;<br />
SecondProcess.Execute;<br />
<br />
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do<br />
begin<br />
if FirstProcess.Output.NumBytesAvailable > 0 then<br />
begin<br />
// make sure that we don't read more data than we have allocated<br />
// in the buffer<br />
ReadSize := FirstProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
// now read the output into the buffer<br />
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);<br />
// and write the buffer to the second process<br />
SecondProcess.Input.Write(Buffer[0], ReadCount);<br />
<br />
// if SecondProcess writes much data to it's Output then <br />
// we should read that data here to prevent a deadlock<br />
// see the previous example "Reading Large Output"<br />
end;<br />
end;<br />
// Close the input on the SecondProcess<br />
// so it finishes processing it's data<br />
SecondProcess.CloseInput;<br />
<br />
// and wait for it to complete<br />
// be carefull what command you run because it may not exit when<br />
// it's input is closed and the following line may loop forever<br />
while SecondProcess.Running do<br />
Sleep(1);<br />
// that's it! the rest of the program is just so the example<br />
// is a little 'useful'<br />
<br />
// we will reuse Buffer to output the SecondProcess's<br />
// output to *this* programs stdout<br />
WriteLn('Grep output Start:');<br />
ReadSize := SecondProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
if ReadSize > 0 then<br />
begin<br />
ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);<br />
WriteLn(Copy(Buffer,0, ReadCount));<br />
end<br />
else<br />
WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);<br />
WriteLn('Grep output Finish:');<br />
<br />
// free our process objects<br />
FirstProcess.Free;<br />
SecondProcess.Free;<br />
end.</syntaxhighlight><br />
<br />
That's it. Now you can redirect output from one program to another.<br />
<br />
==== Notes ====<br />
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:<br />
<syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight><br />
<br />
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.<br />
<br />
===Redirecting input and output and running under root===<br />
A common problem on Unixes (OSX) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ''ping'' command.<br />
<br />
If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). This sample runs <code>ls</code> on the <code>/root</code> directory, but can of course be adapted.<br />
<br />
A '''better way''' to do this is to use the policykit package, which should be available on all recent Linuxes. [http://lazarus.freepascal.org/index.php/topic,14479.0.html See the forum thread for details.]<br />
<br />
Large parts of this code are similar to the earlier example, but it also shows how to redirect stdout and stderr of the process being called separately to stdout and stderr of our own code.<br />
<br />
<syntaxhighlight><br />
program rootls;<br />
<br />
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,<br />
calling sudo on Linux/OSX, and supplying input on stdin}<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes,<br />
Math, {for min}<br />
Process;<br />
<br />
procedure RunsLsRoot;<br />
var<br />
Proc: TProcess;<br />
CharBuffer: array [0..511] of char;<br />
ReadCount: integer;<br />
ExitCode: integer;<br />
SudoPassword: string;<br />
begin<br />
WriteLn('Please enter the sudo password:');<br />
Readln(SudoPassword);<br />
ExitCode := -1; //Start out with failure, let's see later if it works<br />
Proc := TProcess.Create(nil); //Create a new process<br />
try<br />
Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr<br />
Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo<br />
// -S causes sudo to read the password from stdin.<br />
Proc.Execute; //start it. sudo will now probably ask for a password<br />
<br />
// write the password to stdin of the sudo program:<br />
SudoPassword := SudoPassword + LineEnding;<br />
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));<br />
SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof<br />
SudoPassword := ''; // and make the program a bit safer from snooping?!?<br />
<br />
// main loop to read output from stdout and stderr of sudo<br />
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or<br />
(Proc.Stderr.NumBytesAvailable > 0) do<br />
begin<br />
// read stdout and write to our stdout<br />
while Proc.Output.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Output.Read(CharBuffer, ReadCount);<br />
Write(StdOut, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
// read stderr and write to our stderr<br />
while Proc.Stderr.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Stderr.Read(CharBuffer, ReadCount);<br />
Write(StdErr, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
end;<br />
ExitCode := Proc.ExitStatus;<br />
finally<br />
Proc.Free;<br />
Halt(ExitCode);<br />
end;<br />
end;<br />
<br />
begin<br />
RunsLsRoot;<br />
end.<br />
</syntaxhighlight><br />
<br />
Other thoughts:<br />
It would no doubt be advisable to see if sudo actually prompts for a password. This can be checked consistently by setting the environment variable SUDO_PROMPT to something we watch for while reading the stdout of TProcess avoiding the problem of the prompt being different for different locales. Setting an environment variable causes the default values to be cleared(inherited from our process) so we have to copy the environment from our program if needed.<br />
<br />
=== Using fdisk with sudo on Linux ===<br />
The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.<br />
<br />
<syntaxhighlight><br />
program getpartitioninfo;<br />
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.<br />
Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}<br />
<br />
Uses<br />
Classes, SysUtils, FileUtil, Process;<br />
<br />
var<br />
hprocess: TProcess;<br />
sPass: String;<br />
OutputLines: TStringList;<br />
<br />
begin <br />
sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password<br />
OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure <br />
// OutputLines is freed... Same for hProcess.<br />
<br />
// The following example will open fdisk in the background and give us partition information<br />
// Since fdisk requires elevated priviledges we need to <br />
// pass our password as a parameter to sudo using the -S<br />
// option, so it will will wait till our program sends our password to the sudo application<br />
hProcess := TProcess.Create(nil);<br />
// On Linux/Unix/OSX, we need specify full path to our executable:<br />
hProcess.Executable := '/bin/sh';<br />
// Now we add all the parameters on the command line:<br />
hprocess.Parameters.Add('-c');<br />
// Here we pipe the password to the sudo command which then executes fdisk -l: <br />
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');<br />
// Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe<br />
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];<br />
// Now run:<br />
hProcess.Execute;<br />
<br />
// hProcess should have now run the external executable (because we use poWaitOnExit).<br />
// Now you can process the process output (standard output and standard error), eg:<br />
OutputLines.Add('stdout:');<br />
OutputLines.LoadFromStream(hprocess.Output);<br />
OutputLines.Add('stderr:');<br />
OutputLines.LoadFromStream(hProcess.Stderr);<br />
// Show output on screen:<br />
writeln(OutputLines.Text);<br />
<br />
// Clean up to avoid memory leaks:<br />
hProcess.Free;<br />
OutputLines.Free;<br />
<br />
//Below are some examples as you see we can pass illegal characters just as if done from terminal <br />
//Even though you have read elsewhere that you can not I assure with this method you can :)<br />
<br />
//hprocess.Parameters.Add('ping -c 1 www.google.com');<br />
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');<br />
<br />
//Using QuotedStr() is not a requirement though it makes for cleaner code;<br />
//you can use double quote and have the same effect.<br />
<br />
//hprocess.Parameters.Add('glxinfo | grep direct'); <br />
<br />
// This method can also be used for installing applications from your repository:<br />
<br />
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); <br />
<br />
end. <br />
</syntaxhighlight><br />
<br />
<br />
===Parameters which contain spaces (Replacing Shell Quotes)===<br />
<br />
In the Linux shell it is possible to write quoted arguments like this:<br />
<br />
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram<br />
<br />
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):<br />
#--batch<br />
#--eval-command=info symbol 0x0000DDDD<br />
#the full path to myprogram<br />
<br />
TProcess can also pass parameters containing spaces, but it uses a different quoting style. Instead of only quoting part of the parameter, quote all of it. Like this:<br />
<br />
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';<br />
<br />
And also remember to only pass full paths.<br />
<br />
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446<br />
<br />
==LCLIntf Alternatives==<br />
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.<br />
<br />
===Open document in default application===<br />
<br />
In some situations you need to open some document/file using default associated application rather than execute a particular program. <br />
This depends on running operating system. Lazarus provides a platform independent procedure '''OpenDocument''' which will handle it for you. Your application will continue running without waiting for the document process to close.<br />
<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenDocument('manual.pdf'); <br />
...<br />
</syntaxhighlight><br />
<br />
* [[opendocument|OpenDocument reference]]<br />
<br />
===Open web page in default web browser===<br />
<br />
Just pass the URL required, the leading http:// appears to be optional under certain circumstances. <br />
Also, passing a filename appears to give the same results as OpenDocument()<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenURL('www.lazarus.freepascal.org/');<br />
</syntaxhighlight><br />
<br />
See also:<br />
* [[openurl|OpenURL reference]]<br />
<br />
Or, you could use '''TProcess''' like this:<br />
<syntaxhighlight><br />
uses Process;<br />
<br />
procedure OpenWebPage(URL: string);<br />
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"<br />
var<br />
Browser, Params: string;<br />
begin<br />
FindDefaultBrowser(Browser, Params);<br />
with TProcess.Create(nil) do<br />
try<br />
Executable := Browser;<br />
Params:=Format(Params, [URL]);<br />
Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself<br />
Parameters.Add(Params);<br />
Options := [poNoConsole];<br />
Execute;<br />
finally<br />
Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]<br />
* [[opendocument]]<br />
* [[openurl]]<br />
* [[TProcessUTF8]]<br />
* [[TXMLPropStorage]]<br />
* [[Webbrowser]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Parallel programming]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Inter-process communication]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Executing_External_Programs&diff=94727Executing External Programs2015-08-11T12:30:35Z<p>Eny: Only use Executable; remove references to Commandline</p>
<hr />
<div>{{Executing External Programs}}<br />
<br />
== Overview : Comparison ==<br />
{| class="wikitable"<br />
!Method<br />
!Platforms<br />
!Single Line<br />
!Features<br />
|-<br />
|SysUtils.ExecuteProcess<br />
|Cross-Platform<br />
|Yes<br />
|Very limited, synchronous<br />
|-<br />
|(ShellApi) ShellExecute<br />
|MS Windows only<br />
|Yes<br />
|Many. Can start programs with elevation/admin permissions.<br />
|-<br />
|Unix fpsystem, fpexecve<br />
|Unix only<br />
|<br />
|<br />
|-<br />
|TProcess <br />
|Cross-Platform<br />
|No<br />
|Full<br />
|-<br />
|RunCommand(InDir)<br />
|Cross-Platform '''Requires FPC 2.6.2+'''<br />
|Yes<br />
|Covers common TProcess usage<br />
|-<br />
|(LCLIntf) OpenDocument<br />
|Cross-Platform<br />
|Yes<br />
|Only open document<br />
|}<br />
<br />
==(Process.)RunCommand==<br />
In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the [[Projects using Lazarus#fpcup|fpcup]] project.<br />
These functions are meant for basic and intermediate use and can capture output to a single string and fully support the ''large output'' case. <br />
<br />
A simple example is<br />
<syntaxhighlight><br />
uses Process;<br />
...<br />
var s : ansistring;<br />
...<br />
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then<br />
writeln(s); <br />
</syntaxhighlight> <br />
An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets p.CurrentDirectory):<br />
<syntaxhighlight><br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;<br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
</syntaxhighlight><br />
<br />
== SysUtils.ExecuteProcess ==<br />
(Cross-platform)<BR><br />
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :<br />
<br />
<syntaxhighlight>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight><br />
<br />
The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the next section about the prefered cross-platform '''TProcess''', or if you only wish to target Windows you may use '''ShellExecute'''.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess reference]<br />
<br />
== MS Windows : CreateProcess, ShellExecute and WinExec ==<br />
<br />
{{Note|While FPC/Lazarus has support for '''CreateProcess''', '''ShellExecute''' and/or '''WinExec''', this support is only in Win32/64. If your program is cross-platform, consider using '''RunCommand''' or '''TProcess'''.}}<br />
{{Note|WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.}}<br />
<br />
'''ShellExecute''' is a standard MS Windows function (ShellApi.h) with good [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx documentation on MSDN] (note their remarks about initialising COM if you find the function unreliable).<br />
<br />
<syntaxhighlight><br />
uses ..., ShellApi;<br />
<br />
// Simple one-liner (ignoring error returns) :<br />
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;<br />
<br />
// Execute a Batch File :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;<br />
<br />
// Open a command window in a given folder :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;<br />
<br />
// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;<br />
<br />
// or a useful procedure:<br />
procedure RunShellExecute(const prog,params:string);<br />
begin<br />
// ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' isn't always needed <br />
// path+prog, params, working folder,<br />
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...<br />
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success<br />
// return values 0..32 are errors<br />
end;<br />
</syntaxhighlight><br />
<br />
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.<br />
<br />
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.<br />
<br />
If in Delphi you used ShellExecute for '''documents''' like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).<br />
<br />
=== Using ShellExecuteEx for elevation/administrator permissions ===<br />
If you need to execute external program with administrator/elevated privileges, you can use the '''runas''' method with the alternative ShellExecuteEx function:<br />
<syntaxhighlight><br />
uses ShellApi, ...;<br />
<br />
function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;<br />
var<br />
sei: TShellExecuteInfoA;<br />
begin<br />
FillChar(sei, SizeOf(sei), 0);<br />
sei.cbSize := SizeOf(sei);<br />
sei.Wnd := Handle;<br />
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;<br />
sei.lpVerb := 'runas';<br />
sei.lpFile := PAnsiChar(Path);<br />
sei.lpParameters := PAnsiChar(Params);<br />
sei.nShow := SW_SHOWNORMAL;<br />
Result := ShellExecuteExA(@sei);<br />
end;<br />
<br />
procedure TFormMain.RunAddOrRemoveApplication;<br />
begin<br />
// Example that uses elevated rundll to open the Control Panel to Programs and features<br />
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');<br />
end;<br />
</syntaxhighlight><br />
<br />
== Unix fpsystem, fpexecve and shell ==<br />
<br />
These functions are platform dependent.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem reference]<br />
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve reference]<br />
* [http://www.freepascal.org/docs-html/rtl/unix/shell.html shell reference]<br />
<br />
Note that the 1.0.x '''Unix.Shell'' has been deprecated for a while, and is removed in trunk. Use fpsystem.<br />
<br />
== TProcess ==<br />
<br />
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:<br />
<br />
* Platform Independent<br />
* Capable of reading from stdout and writing to stdin.<br />
* Possible to wait for a command to finish or let it run while your program moves on.<br />
<br />
Important notes:<br />
* TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..<br />
* Presumably on Linux/Unix: you '''must''' specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.<br />
* On Windows, if the command is in the path, you don't need to specify the full path.<br />
* [[doc:fcl/process/tprocess.html|TProcess reference]]<br />
<br />
=== The Simplest Example ===<br />
<br />
A lot of typical cases have been prepared in the [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]] functions. Before you start copy and paste the examples below, check them out first.<br />
<br />
=== A Simple Example ===<br />
This example ('''that shouldn't be used in production, see Large Output or, better, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''') just shows you how to run an external program, nothing more:<br />
<syntaxhighlight><br />
// This is a demo program that shows<br />
// how to launch an external program.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This defines the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
var <br />
AProcess: TProcess;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler (i386 version that is)<br />
AProcess.Executable:= 'ppc386';<br />
// Pass -h together with ppc386 (so we're executing ppc386 -h):<br />
AProcess.Parameters.Add('-h');<br />
// Previous versions of FPC did not yet have .Executable and .Parameters and<br />
// used the .CommandLine property. While this is still available, it is deprecated<br />
// so it should not be used.<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. vvvvvvvvvvvvvv<br />
AProcess.Options := AProcess.Options + [poWaitOnExit];<br />
<br />
// Now let AProcess run the command in .Commandline:<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
That's it! You have just learned to run an external program from inside your own program.<br />
<br />
=== An improved example (but not correct yet)===<br />
That's nice, but how do I read the Output of a program that I have run?<br />
<br />
Well, let's expand our example a little and do just that:<br />
'''This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in [[#Reading large output]].'''<br />
<br />
<syntaxhighlight><br />
// This is a <br />
// FLAWED<br />
// demo program that shows<br />
// how to launch an external program<br />
// and read from its output.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This is defining the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
// Also now we are adding a TStringList to store the <br />
// data read from the programs output.<br />
var <br />
AProcess: TProcess;<br />
AStringList: TStringList;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Create the TStringList object.<br />
AStringList := TStringList.Create;<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler<br />
// AProcess.CommandLine := '/usr/bin/ppc386 -h'; -----> CommandLine is deprecated, <br />
<br />
// -----> CommandLine is deprecated, use:<br />
AProcess.Executable := '/usr/bin/ppc386'; <br />
AProcess.Parameters.Add('-h'); <br />
<br />
// Previous versions of FPC did not yet have .Executable and .Parameters and<br />
// used the .CommandLine property. While this is still available, it is deprecated<br />
// so it should not be used.<br />
<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. Also now we will tell it that<br />
// we want to read the output of the file.<br />
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];<br />
<br />
// Now that AProcess knows what the commandline is <br />
// we will run it.<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
<br />
// Now read the output of the program we just ran<br />
// into the TStringList.<br />
AStringList.LoadFromStream(AProcess.Output);<br />
<br />
// Save the output to a file.<br />
AStringList.SaveToFile('output.txt');<br />
<br />
// Now that the file is saved we can free the <br />
// TStringList and the TProcess.<br />
AStringList.Free;<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
<br />
=== Reading large output ===<br />
In the previous example we waited until the program exited. Then we read what the program has written to its output.<br />
<br />
Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from. <br />
<br />
But the calling program doesn't read from it until the called program has ended. A deadlock occurs.<br />
<br />
The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.<br />
<br />
If you want to read output from an external process, this is the code you should adapt for production use.<br />
<syntaxhighlight>program LargeOutputDemo;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes, SysUtils, Process; // Process is the unit that holds TProcess<br />
<br />
const<br />
BUF_SIZE = 2048; // Buffer size for reading the output in chunks<br />
<br />
var<br />
AProcess : TProcess;<br />
OutputStream : TStream;<br />
BytesRead : longint;<br />
Buffer : array[1..BUF_SIZE] of byte;<br />
<br />
begin<br />
// Set up the process; as an example a recursive directory search is used<br />
// because that will usually result in a lot of data.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// The commands for Windows and *nix are different hence the $IFDEFs<br />
{$IFDEF Windows}<br />
// In Windows the dir command cannot be used directly because it's a build-in<br />
// shell command. Therefore cmd.exe and the extra parameters are needed.<br />
AProcess.Executable := 'c:\windows\system32\cmd.exe';<br />
AProcess.Parameters.Add('/c');<br />
AProcess.Parameters.Add('dir /s c:\windows');<br />
{$ENDIF Windows}<br />
<br />
{$IFDEF Unix}<br />
AProcess.Executable := '/bin/ls';<br />
AProcess.Parameters.Add('--recursive');<br />
AProcess.Parameters.Add('--all');<br />
AProcess.Parameters.Add('-l');<br />
{$ENDIF Unix}<br />
<br />
// Process option poUsePipes has to be used so the output can be captured.<br />
// Process option poWaitOnExit can not be used because that would block<br />
// this program, preventing it from reading the output data of the process.<br />
AProcess.Options := [poUsePipes];<br />
<br />
// Start the process (run the dir/ls command)<br />
AProcess.Execute;<br />
<br />
// Create a stream object to store the generated output in. This could<br />
// also be a file stream to directly save the output to disk.<br />
OutputStream := TMemoryStream.Create;<br />
<br />
// All generated output from AProcess is read in a loop until no more data is available<br />
repeat<br />
// Get the new data from the process to a maximum of the buffer size that was allocated.<br />
// Note that all read(...) calls will block except for the last one, which returns 0 (zero).<br />
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);<br />
<br />
// Add the bytes that were read to the stream for later usage<br />
OutputStream.Write(Buffer, BytesRead)<br />
<br />
until BytesRead = 0; // Stop if no more data is available<br />
<br />
// The process has finished so it can be cleaned up<br />
AProcess.Free;<br />
<br />
// Now that all data has been read it can be used; for example to save it to a file on disk<br />
with TFileStream.Create('output.txt', fmCreate) do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
CopyFrom(OutputStream, OutputStream.Size);<br />
Free<br />
end;<br />
<br />
// Or the data can be shown on screen<br />
with TStringList.Create do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
LoadFromStream(OutputStream);<br />
writeln(Text);<br />
writeln('--- Number of lines = ', Count, '----');<br />
Free<br />
end;<br />
<br />
// Clean up<br />
OutputStream.Free;<br />
end.</syntaxhighlight><br />
Note that the above could also be accomplished by using RunCommand:<br />
<syntaxhighlight>var s: string;<br />
...<br />
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight><br />
<br />
=== Using input and output of a TProcess ===<br />
See processdemo example in the [https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].<br />
<br />
=== Hints on the use of TProcess ===<br />
When creating a cross-platform program, the OS-specific executable name can be set using directives "{$IFDEF}" and "{$ENDIF}".<br />
<br />
Example:<br />
<syntaxhighlight>{...}<br />
AProcess := TProcess.Create(nil)<br />
<br />
{$IFDEF WIN32}<br />
AProcess.Executable := 'calc.exe'; <br />
{$ENDIF}<br />
<br />
{$IFDEF LINUX}<br />
AProcess.Executable := 'kcalc'; <br />
{$ENDIF}<br />
<br />
AProcess.Execute;<br />
{...}</syntaxhighlight><br />
<br />
=== OS X show application bundle in foreground ===<br />
<br />
You can start an '''application bundle''' via TProcess by starting the executable within the bundle. For example:<br />
<syntaxhighlight><br />
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';<br />
</syntaxhighlight><br />
<br />
This will start the ''Calendar'', but the window will be behind the current application.<br />
To get the application in the foreground you can use the '''open''' utility with the '''-n''' parameter:<br />
<syntaxhighlight><br />
AProcess.Executable:='/usr/bin/open';<br />
AProcess.Parameters.Add('-n');<br />
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole<br />
AProcess.Parameters.Add('/Application/iCal.app');<br />
</syntaxhighlight><br />
<br />
If your application needs parameters, you can pass '''open''' the '''--args''' parameter, after which all parameters are passed to the application:<br />
<syntaxhighlight><br />
AProcess.Parameters.Add('--args');<br />
AProcess.Parameters.Add('argument1');<br />
AProcess.Parameters.Add('argument2');<br />
</syntaxhighlight><br />
<br />
=== Run detached program ===<br />
<br />
Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:<br />
<br />
<syntaxhighlight><br />
var<br />
Process: TProcess;<br />
I: Integer;<br />
begin<br />
Process := TProcess.Create(nil);<br />
try<br />
Process.InheritHandles := False;<br />
Process.Options := [];<br />
Process.ShowWindow := swoShow;<br />
<br />
// Copy default environment variables including DISPLAY variable for GUI application to work<br />
for I := 1 to GetEnvironmentVariableCount do<br />
Process.Environment.Add(GetEnvironmentString(I));<br />
<br />
Process.Executable := '/usr/bin/gedit'; <br />
Process.Execute;<br />
finally<br />
Process.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== Example of "talking" with aspell process ===<br />
<br />
Inside [http://pasdoc.sourceforge.net/ pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.<br />
<br />
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.<br />
<br />
=== Replacing shell operators like "| < >" ===<br />
<br />
Sometimes you want to run a more complicated command that pipes its data to another command or to a file.<br />
Something like <br />
<syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight><br />
or<br />
<syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight><br />
<br />
Executing this with TProcess will not work. i.e:<br />
<br />
<syntaxhighlight>// this won't work<br />
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; <br />
Process.Execute;</syntaxhighlight><br />
<br />
==== Why using special operators to redirect output doesn't work ====<br />
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.<br />
It is possible to redirect output however just the way you wanted. See the [[Executing_External_Programs#How_to_redirect_output_with_TProcess |next section]].<br />
<br />
=== How to redirect output with TProcess ===<br />
<br />
You can redirect the output of a command to another command by using a TProcess instance for '''each''' command.<br />
<br />
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]<br />
<br />
Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.<br />
<br />
<syntaxhighlight>program Project1;<br />
<br />
uses<br />
Classes, sysutils, process;<br />
<br />
var<br />
FirstProcess,<br />
SecondProcess: TProcess;<br />
Buffer: array[0..127] of char;<br />
ReadCount: Integer;<br />
ReadSize: Integer;<br />
begin<br />
FirstProcess := TProcess.Create(nil);<br />
SecondProcess := TProcess.Create(nil);<br />
<br />
FirstProcess.Options := [poUsePipes];<br />
SecondProcess.Options := [poUsePipes,poStderrToOutPut];<br />
<br />
// FirstProcess.CommandLine := 'pwd'; -----> CommandLine is deprecated, USE:<br />
FirstProcess.Executable := 'pwd'; <br />
<br />
// SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -'; -----> CommandLine is deprecated, USE:<br />
SecondProcess.Executable := 'grep'; <br />
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); <br />
// this would be the same as "pwd | grep / -"<br />
<br />
FirstProcess.Execute;<br />
SecondProcess.Execute;<br />
<br />
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do<br />
begin<br />
if FirstProcess.Output.NumBytesAvailable > 0 then<br />
begin<br />
// make sure that we don't read more data than we have allocated<br />
// in the buffer<br />
ReadSize := FirstProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
// now read the output into the buffer<br />
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);<br />
// and write the buffer to the second process<br />
SecondProcess.Input.Write(Buffer[0], ReadCount);<br />
<br />
// if SecondProcess writes much data to it's Output then <br />
// we should read that data here to prevent a deadlock<br />
// see the previous example "Reading Large Output"<br />
end;<br />
end;<br />
// Close the input on the SecondProcess<br />
// so it finishes processing it's data<br />
SecondProcess.CloseInput;<br />
<br />
// and wait for it to complete<br />
// be carefull what command you run because it may not exit when<br />
// it's input is closed and the following line may loop forever<br />
while SecondProcess.Running do<br />
Sleep(1);<br />
// that's it! the rest of the program is just so the example<br />
// is a little 'useful'<br />
<br />
// we will reuse Buffer to output the SecondProcess's<br />
// output to *this* programs stdout<br />
WriteLn('Grep output Start:');<br />
ReadSize := SecondProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
if ReadSize > 0 then<br />
begin<br />
ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);<br />
WriteLn(Copy(Buffer,0, ReadCount));<br />
end<br />
else<br />
WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);<br />
WriteLn('Grep output Finish:');<br />
<br />
// free our process objects<br />
FirstProcess.Free;<br />
SecondProcess.Free;<br />
end.</syntaxhighlight><br />
<br />
That's it. Now you can redirect output from one program to another.<br />
<br />
==== Notes ====<br />
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:<br />
<syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight><br />
<br />
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.<br />
<br />
===Redirecting input and output and running under root===<br />
A common problem on Unixes (OSX) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ''ping'' command.<br />
<br />
If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). This sample runs <code>ls</code> on the <code>/root</code> directory, but can of course be adapted.<br />
<br />
A '''better way''' to do this is to use the policykit package, which should be available on all recent Linuxes. [http://lazarus.freepascal.org/index.php/topic,14479.0.html See the forum thread for details.]<br />
<br />
Large parts of this code are similar to the earlier example, but it also shows how to redirect stdout and stderr of the process being called separately to stdout and stderr of our own code.<br />
<br />
<syntaxhighlight><br />
program rootls;<br />
<br />
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,<br />
calling sudo on Linux/OSX, and supplying input on stdin}<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes,<br />
Math, {for min}<br />
Process;<br />
<br />
procedure RunsLsRoot;<br />
var<br />
Proc: TProcess;<br />
CharBuffer: array [0..511] of char;<br />
ReadCount: integer;<br />
ExitCode: integer;<br />
SudoPassword: string;<br />
begin<br />
WriteLn('Please enter the sudo password:');<br />
Readln(SudoPassword);<br />
ExitCode := -1; //Start out with failure, let's see later if it works<br />
Proc := TProcess.Create(nil); //Create a new process<br />
try<br />
Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr<br />
Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo<br />
// -S causes sudo to read the password from stdin.<br />
Proc.Execute; //start it. sudo will now probably ask for a password<br />
<br />
// write the password to stdin of the sudo program:<br />
SudoPassword := SudoPassword + LineEnding;<br />
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));<br />
SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof<br />
SudoPassword := ''; // and make the program a bit safer from snooping?!?<br />
<br />
// main loop to read output from stdout and stderr of sudo<br />
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or<br />
(Proc.Stderr.NumBytesAvailable > 0) do<br />
begin<br />
// read stdout and write to our stdout<br />
while Proc.Output.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Output.Read(CharBuffer, ReadCount);<br />
Write(StdOut, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
// read stderr and write to our stderr<br />
while Proc.Stderr.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Stderr.Read(CharBuffer, ReadCount);<br />
Write(StdErr, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
end;<br />
ExitCode := Proc.ExitStatus;<br />
finally<br />
Proc.Free;<br />
Halt(ExitCode);<br />
end;<br />
end;<br />
<br />
begin<br />
RunsLsRoot;<br />
end.<br />
</syntaxhighlight><br />
<br />
Other thoughts:<br />
It would no doubt be advisable to see if sudo actually prompts for a password. This can be checked consistently by setting the environment variable SUDO_PROMPT to something we watch for while reading the stdout of TProcess avoiding the problem of the prompt being different for different locales. Setting an environment variable causes the default values to be cleared(inherited from our process) so we have to copy the environment from our program if needed.<br />
<br />
=== Using fdisk with sudo on Linux ===<br />
The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.<br />
<br />
<syntaxhighlight><br />
program getpartitioninfo;<br />
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.<br />
Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}<br />
<br />
Uses<br />
Classes, SysUtils, FileUtil, Process;<br />
<br />
var<br />
hprocess: TProcess;<br />
sPass: String;<br />
OutputLines: TStringList;<br />
<br />
begin <br />
sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password<br />
OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure <br />
// OutputLines is freed... Same for hProcess.<br />
<br />
// The following example will open fdisk in the background and give us partition information<br />
// Since fdisk requires elevated priviledges we need to <br />
// pass our password as a parameter to sudo using the -S<br />
// option, so it will will wait till our program sends our password to the sudo application<br />
hProcess := TProcess.Create(nil);<br />
// On Linux/Unix/OSX, we need specify full path to our executable:<br />
hProcess.Executable := '/bin/sh';<br />
// Now we add all the parameters on the command line:<br />
hprocess.Parameters.Add('-c');<br />
// Here we pipe the password to the sudo command which then executes fdisk -l: <br />
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');<br />
// Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe<br />
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];<br />
// Now run:<br />
hProcess.Execute;<br />
<br />
// hProcess should have now run the external executable (because we use poWaitOnExit).<br />
// Now you can process the process output (standard output and standard error), eg:<br />
OutputLines.Add('stdout:');<br />
OutputLines.LoadFromStream(hprocess.Output);<br />
OutputLines.Add('stderr:');<br />
OutputLines.LoadFromStream(hProcess.Stderr);<br />
// Show output on screen:<br />
writeln(OutputLines.Text);<br />
<br />
// Clean up to avoid memory leaks:<br />
hProcess.Free;<br />
OutputLines.Free;<br />
<br />
//Below are some examples as you see we can pass illegal characters just as if done from terminal <br />
//Even though you have read elsewhere that you can not I assure with this method you can :)<br />
<br />
//hprocess.Parameters.Add('ping -c 1 www.google.com');<br />
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');<br />
<br />
//Using QuotedStr() is not a requirement though it makes for cleaner code;<br />
//you can use double quote and have the same effect.<br />
<br />
//hprocess.Parameters.Add('glxinfo | grep direct'); <br />
<br />
// This method can also be used for installing applications from your repository:<br />
<br />
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); <br />
<br />
end. <br />
</syntaxhighlight><br />
<br />
<br />
===Parameters which contain spaces (Replacing Shell Quotes)===<br />
<br />
In the Linux shell it is possible to write quoted arguments like this:<br />
<br />
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram<br />
<br />
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):<br />
#--batch<br />
#--eval-command=info symbol 0x0000DDDD<br />
#the full path to myprogram<br />
<br />
TProcess can also pass parameters containing spaces, but it uses a different quoting style. Instead of only quoting part of the parameter, quote all of it. Like this:<br />
<br />
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';<br />
<br />
And also remember to only pass full paths.<br />
<br />
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446<br />
<br />
==LCLIntf Alternatives==<br />
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.<br />
<br />
===Open document in default application===<br />
<br />
In some situations you need to open some document/file using default associated application rather than execute a particular program. <br />
This depends on running operating system. Lazarus provides a platform independent procedure '''OpenDocument''' which will handle it for you. Your application will continue running without waiting for the document process to close.<br />
<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenDocument('manual.pdf'); <br />
...<br />
</syntaxhighlight><br />
<br />
* [[opendocument|OpenDocument reference]]<br />
<br />
===Open web page in default web browser===<br />
<br />
Just pass the URL required, the leading http:// appears to be optional under certain circumstances. <br />
Also, passing a filename appears to give the same results as OpenDocument()<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenURL('www.lazarus.freepascal.org/');<br />
</syntaxhighlight><br />
<br />
See also:<br />
* [[openurl|OpenURL reference]]<br />
<br />
Or, you could use '''TProcess''' like this:<br />
<syntaxhighlight><br />
uses Process;<br />
<br />
procedure OpenWebPage(URL: string);<br />
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"<br />
var<br />
Browser, Params: string;<br />
begin<br />
FindDefaultBrowser(Browser, Params);<br />
with TProcess.Create(nil) do<br />
try<br />
Executable := Browser;<br />
Params:=Format(Params, [URL]);<br />
Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself<br />
Parameters.Add(Params);<br />
Options := [poNoConsole];<br />
Execute;<br />
finally<br />
Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]<br />
* [[opendocument]]<br />
* [[openurl]]<br />
* [[TProcessUTF8]]<br />
* [[TXMLPropStorage]]<br />
* [[Webbrowser]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Parallel programming]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Inter-process communication]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Executing_External_Programs&diff=94726Executing External Programs2015-08-11T12:25:33Z<p>Eny: Add RunCommand(...) equivalent for completeness</p>
<hr />
<div>{{Executing External Programs}}<br />
<br />
== Overview : Comparison ==<br />
{| class="wikitable"<br />
!Method<br />
!Platforms<br />
!Single Line<br />
!Features<br />
|-<br />
|SysUtils.ExecuteProcess<br />
|Cross-Platform<br />
|Yes<br />
|Very limited, synchronous<br />
|-<br />
|(ShellApi) ShellExecute<br />
|MS Windows only<br />
|Yes<br />
|Many. Can start programs with elevation/admin permissions.<br />
|-<br />
|Unix fpsystem, fpexecve<br />
|Unix only<br />
|<br />
|<br />
|-<br />
|TProcess <br />
|Cross-Platform<br />
|No<br />
|Full<br />
|-<br />
|RunCommand(InDir)<br />
|Cross-Platform '''Requires FPC 2.6.2+'''<br />
|Yes<br />
|Covers common TProcess usage<br />
|-<br />
|(LCLIntf) OpenDocument<br />
|Cross-Platform<br />
|Yes<br />
|Only open document<br />
|}<br />
<br />
==(Process.)RunCommand==<br />
In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the [[Projects using Lazarus#fpcup|fpcup]] project.<br />
These functions are meant for basic and intermediate use and can capture output to a single string and fully support the ''large output'' case. <br />
<br />
A simple example is<br />
<syntaxhighlight><br />
uses Process;<br />
...<br />
var s : ansistring;<br />
...<br />
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then<br />
writeln(s); <br />
</syntaxhighlight> <br />
An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets p.CurrentDirectory):<br />
<syntaxhighlight><br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;<br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
</syntaxhighlight><br />
<br />
== SysUtils.ExecuteProcess ==<br />
(Cross-platform)<BR><br />
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :<br />
<br />
<syntaxhighlight>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight><br />
<br />
The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the next section about the prefered cross-platform '''TProcess''', or if you only wish to target Windows you may use '''ShellExecute'''.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess reference]<br />
<br />
== MS Windows : CreateProcess, ShellExecute and WinExec ==<br />
<br />
{{Note|While FPC/Lazarus has support for '''CreateProcess''', '''ShellExecute''' and/or '''WinExec''', this support is only in Win32/64. If your program is cross-platform, consider using '''RunCommand''' or '''TProcess'''.}}<br />
{{Note|WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.}}<br />
<br />
'''ShellExecute''' is a standard MS Windows function (ShellApi.h) with good [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx documentation on MSDN] (note their remarks about initialising COM if you find the function unreliable).<br />
<br />
<syntaxhighlight><br />
uses ..., ShellApi;<br />
<br />
// Simple one-liner (ignoring error returns) :<br />
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;<br />
<br />
// Execute a Batch File :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;<br />
<br />
// Open a command window in a given folder :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;<br />
<br />
// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;<br />
<br />
// or a useful procedure:<br />
procedure RunShellExecute(const prog,params:string);<br />
begin<br />
// ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' isn't always needed <br />
// path+prog, params, working folder,<br />
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...<br />
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success<br />
// return values 0..32 are errors<br />
end;<br />
</syntaxhighlight><br />
<br />
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.<br />
<br />
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.<br />
<br />
If in Delphi you used ShellExecute for '''documents''' like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).<br />
<br />
=== Using ShellExecuteEx for elevation/administrator permissions ===<br />
If you need to execute external program with administrator/elevated privileges, you can use the '''runas''' method with the alternative ShellExecuteEx function:<br />
<syntaxhighlight><br />
uses ShellApi, ...;<br />
<br />
function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;<br />
var<br />
sei: TShellExecuteInfoA;<br />
begin<br />
FillChar(sei, SizeOf(sei), 0);<br />
sei.cbSize := SizeOf(sei);<br />
sei.Wnd := Handle;<br />
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;<br />
sei.lpVerb := 'runas';<br />
sei.lpFile := PAnsiChar(Path);<br />
sei.lpParameters := PAnsiChar(Params);<br />
sei.nShow := SW_SHOWNORMAL;<br />
Result := ShellExecuteExA(@sei);<br />
end;<br />
<br />
procedure TFormMain.RunAddOrRemoveApplication;<br />
begin<br />
// Example that uses elevated rundll to open the Control Panel to Programs and features<br />
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');<br />
end;<br />
</syntaxhighlight><br />
<br />
== Unix fpsystem, fpexecve and shell ==<br />
<br />
These functions are platform dependent.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem reference]<br />
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve reference]<br />
* [http://www.freepascal.org/docs-html/rtl/unix/shell.html shell reference]<br />
<br />
Note that the 1.0.x '''Unix.Shell'' has been deprecated for a while, and is removed in trunk. Use fpsystem.<br />
<br />
== TProcess ==<br />
<br />
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:<br />
<br />
* Platform Independent<br />
* Capable of reading from stdout and writing to stdin.<br />
* Possible to wait for a command to finish or let it run while your program moves on.<br />
<br />
Important notes:<br />
* TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..<br />
* Presumably on Linux/Unix: you '''must''' specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.<br />
* On Windows, if the command is in the path, you don't need to specify the full path.<br />
* [[doc:fcl/process/tprocess.html|TProcess reference]]<br />
<br />
=== The Simplest Example ===<br />
<br />
A lot of typical cases have been prepared in the [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]] functions. Before you start copy and paste the examples below, check them out first.<br />
<br />
=== A Simple Example ===<br />
This example ('''that shouldn't be used in production, see Large Output or, better, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''') just shows you how to run an external program, nothing more:<br />
<syntaxhighlight><br />
// This is a demo program that shows<br />
// how to launch an external program.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This defines the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
var <br />
AProcess: TProcess;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler (i386 version that is)<br />
AProcess.Executable:= 'ppc386';<br />
// Pass -h together with ppc386 (so we're executing ppc386 -h):<br />
AProcess.Parameters.Add('-h');<br />
// Previous versions of FPC did not yet have .Executable and .Parameters and<br />
// used the .CommandLine property. While this is still available, it is deprecated<br />
// so it should not be used.<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. vvvvvvvvvvvvvv<br />
AProcess.Options := AProcess.Options + [poWaitOnExit];<br />
<br />
// Now let AProcess run the command in .Commandline:<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
That's it! You have just learned to run an external program from inside your own program.<br />
<br />
=== An improved example (but not correct yet)===<br />
That's nice, but how do I read the Output of a program that I have run?<br />
<br />
Well, let's expand our example a little and do just that:<br />
'''This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in [[#Reading large output]].'''<br />
<br />
<syntaxhighlight><br />
// This is a <br />
// FLAWED<br />
// demo program that shows<br />
// how to launch an external program<br />
// and read from its output.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This is defining the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
// Also now we are adding a TStringList to store the <br />
// data read from the programs output.<br />
var <br />
AProcess: TProcess;<br />
AStringList: TStringList;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Create the TStringList object.<br />
AStringList := TStringList.Create;<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler<br />
// AProcess.CommandLine := '/usr/bin/ppc386 -h'; -----> CommandLine is deprecated, <br />
<br />
// -----> CommandLine is deprecated, use:<br />
AProcess.Executable := '/usr/bin/ppc386'; <br />
AProcess.Parameters.Add('-h'); <br />
<br />
// Previous versions of FPC did not yet have .Executable and .Parameters and<br />
// used the .CommandLine property. While this is still available, it is deprecated<br />
// so it should not be used.<br />
<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. Also now we will tell it that<br />
// we want to read the output of the file.<br />
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];<br />
<br />
// Now that AProcess knows what the commandline is <br />
// we will run it.<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
<br />
// Now read the output of the program we just ran<br />
// into the TStringList.<br />
AStringList.LoadFromStream(AProcess.Output);<br />
<br />
// Save the output to a file.<br />
AStringList.SaveToFile('output.txt');<br />
<br />
// Now that the file is saved we can free the <br />
// TStringList and the TProcess.<br />
AStringList.Free;<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
<br />
=== Reading large output ===<br />
In the previous example we waited until the program exited. Then we read what the program has written to its output.<br />
<br />
Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from. <br />
<br />
But the calling program doesn't read from it until the called program has ended. A deadlock occurs.<br />
<br />
The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.<br />
<br />
If you want to read output from an external process, this is the code you should adapt for production use.<br />
<syntaxhighlight>program LargeOutputDemo;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes, SysUtils, Process; // Process is the unit that holds TProcess<br />
<br />
const<br />
BUF_SIZE = 2048; // Buffer size for reading the output in chunks<br />
<br />
var<br />
AProcess : TProcess;<br />
OutputStream : TStream;<br />
BytesRead : longint;<br />
Buffer : array[1..BUF_SIZE] of byte;<br />
<br />
begin<br />
// Set up the process; as an example a recursive directory search is used<br />
// because that will usually result in a lot of data.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// The commands for Windows and *nix are different hence the $IFDEFs<br />
{$IFDEF Windows}<br />
// In Windows the dir command cannot be used directly because it's a build-in<br />
// shell command. Therefore cmd.exe and the extra parameters are needed.<br />
AProcess.Executable := 'c:\windows\system32\cmd.exe';<br />
AProcess.Parameters.Add('/c');<br />
AProcess.Parameters.Add('dir /s c:\windows');<br />
{$ENDIF Windows}<br />
<br />
{$IFDEF Unix}<br />
AProcess.Executable := '/bin/ls';<br />
AProcess.Parameters.Add('--recursive');<br />
AProcess.Parameters.Add('--all');<br />
AProcess.Parameters.Add('-l');<br />
{$ENDIF Unix}<br />
<br />
// Process option poUsePipes has to be used so the output can be captured.<br />
// Process option poWaitOnExit can not be used because that would block<br />
// this program, preventing it from reading the output data of the process.<br />
AProcess.Options := [poUsePipes];<br />
<br />
// Start the process (run the dir/ls command)<br />
AProcess.Execute;<br />
<br />
// Create a stream object to store the generated output in. This could<br />
// also be a file stream to directly save the output to disk.<br />
OutputStream := TMemoryStream.Create;<br />
<br />
// All generated output from AProcess is read in a loop until no more data is available<br />
repeat<br />
// Get the new data from the process to a maximum of the buffer size that was allocated.<br />
// Note that all read(...) calls will block except for the last one, which returns 0 (zero).<br />
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);<br />
<br />
// Add the bytes that were read to the stream for later usage<br />
OutputStream.Write(Buffer, BytesRead)<br />
<br />
until BytesRead = 0; // Stop if no more data is available<br />
<br />
// The process has finished so it can be cleaned up<br />
AProcess.Free;<br />
<br />
// Now that all data has been read it can be used; for example to save it to a file on disk<br />
with TFileStream.Create('output.txt', fmCreate) do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
CopyFrom(OutputStream, OutputStream.Size);<br />
Free<br />
end;<br />
<br />
// Or the data can be shown on screen<br />
with TStringList.Create do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
LoadFromStream(OutputStream);<br />
writeln(Text);<br />
writeln('--- Number of lines = ', Count, '----');<br />
Free<br />
end;<br />
<br />
// Clean up<br />
OutputStream.Free;<br />
end.</syntaxhighlight><br />
Note that the above could also be accomplished by using RunCommand:<br />
<syntaxhighlight>var s: string;<br />
...<br />
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight><br />
<br />
=== Using input and output of a TProcess ===<br />
See processdemo example in the [https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].<br />
<br />
=== Hints on the use of TProcess ===<br />
If you are creating a cross-platform program, you can change commandline according to the OS, using directives "{$IFDEF}s" and "{$ENDIF}s".<br />
<br />
Example:<br />
<syntaxhighlight>{...}<br />
AProcess:=TProcess.Create(nil)<br />
{$IFDEF WIN32}<br />
// AProcess.CommandLine := 'calc.exe'; // -----> CommandLine is deprecated, USE:<br />
AProcess.Executable := 'calc.exe'; <br />
{$ENDIF}<br />
{$IFDEF LINUX}<br />
<br />
// AProcess.CommandLine := 'kcalc'; // -----> CommandLine is deprecated, USE:<br />
AProcess.Executable := 'kcalc'; <br />
{$ENDIF}<br />
AProcess.Execute; //in alternative, you can use AProcess.Active:=True<br />
{...}</syntaxhighlight><br />
<br />
=== OS X show application bundle in foreground ===<br />
<br />
You can start an '''application bundle''' via TProcess by starting the executable within the bundle. For example:<br />
<syntaxhighlight><br />
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';<br />
</syntaxhighlight><br />
<br />
This will start the ''Calendar'', but the window will be behind the current application.<br />
To get the application in the foreground you can use the '''open''' utility with the '''-n''' parameter:<br />
<syntaxhighlight><br />
AProcess.Executable:='/usr/bin/open';<br />
AProcess.Parameters.Add('-n');<br />
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole<br />
AProcess.Parameters.Add('/Application/iCal.app');<br />
</syntaxhighlight><br />
<br />
If your application needs parameters, you can pass '''open''' the '''--args''' parameter, after which all parameters are passed to the application:<br />
<syntaxhighlight><br />
AProcess.Parameters.Add('--args');<br />
AProcess.Parameters.Add('argument1');<br />
AProcess.Parameters.Add('argument2');<br />
</syntaxhighlight><br />
<br />
=== Run detached program ===<br />
<br />
Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:<br />
<br />
<syntaxhighlight><br />
var<br />
Process: TProcess;<br />
I: Integer;<br />
begin<br />
Process := TProcess.Create(nil);<br />
try<br />
Process.InheritHandles := False;<br />
Process.Options := [];<br />
Process.ShowWindow := swoShow;<br />
<br />
// Copy default environment variables including DISPLAY variable for GUI application to work<br />
for I := 1 to GetEnvironmentVariableCount do<br />
Process.Environment.Add(GetEnvironmentString(I));<br />
<br />
Process.Executable := '/usr/bin/gedit'; <br />
Process.Execute;<br />
finally<br />
Process.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== Example of "talking" with aspell process ===<br />
<br />
Inside [http://pasdoc.sourceforge.net/ pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.<br />
<br />
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.<br />
<br />
=== Replacing shell operators like "| < >" ===<br />
<br />
Sometimes you want to run a more complicated command that pipes its data to another command or to a file.<br />
Something like <br />
<syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight><br />
or<br />
<syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight><br />
<br />
Executing this with TProcess will not work. i.e:<br />
<br />
<syntaxhighlight>// this won't work<br />
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; <br />
Process.Execute;</syntaxhighlight><br />
<br />
==== Why using special operators to redirect output doesn't work ====<br />
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.<br />
It is possible to redirect output however just the way you wanted. See the [[Executing_External_Programs#How_to_redirect_output_with_TProcess |next section]].<br />
<br />
=== How to redirect output with TProcess ===<br />
<br />
You can redirect the output of a command to another command by using a TProcess instance for '''each''' command.<br />
<br />
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]<br />
<br />
Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.<br />
<br />
<syntaxhighlight>program Project1;<br />
<br />
uses<br />
Classes, sysutils, process;<br />
<br />
var<br />
FirstProcess,<br />
SecondProcess: TProcess;<br />
Buffer: array[0..127] of char;<br />
ReadCount: Integer;<br />
ReadSize: Integer;<br />
begin<br />
FirstProcess := TProcess.Create(nil);<br />
SecondProcess := TProcess.Create(nil);<br />
<br />
FirstProcess.Options := [poUsePipes];<br />
SecondProcess.Options := [poUsePipes,poStderrToOutPut];<br />
<br />
// FirstProcess.CommandLine := 'pwd'; -----> CommandLine is deprecated, USE:<br />
FirstProcess.Executable := 'pwd'; <br />
<br />
// SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -'; -----> CommandLine is deprecated, USE:<br />
SecondProcess.Executable := 'grep'; <br />
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); <br />
// this would be the same as "pwd | grep / -"<br />
<br />
FirstProcess.Execute;<br />
SecondProcess.Execute;<br />
<br />
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do<br />
begin<br />
if FirstProcess.Output.NumBytesAvailable > 0 then<br />
begin<br />
// make sure that we don't read more data than we have allocated<br />
// in the buffer<br />
ReadSize := FirstProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
// now read the output into the buffer<br />
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);<br />
// and write the buffer to the second process<br />
SecondProcess.Input.Write(Buffer[0], ReadCount);<br />
<br />
// if SecondProcess writes much data to it's Output then <br />
// we should read that data here to prevent a deadlock<br />
// see the previous example "Reading Large Output"<br />
end;<br />
end;<br />
// Close the input on the SecondProcess<br />
// so it finishes processing it's data<br />
SecondProcess.CloseInput;<br />
<br />
// and wait for it to complete<br />
// be carefull what command you run because it may not exit when<br />
// it's input is closed and the following line may loop forever<br />
while SecondProcess.Running do<br />
Sleep(1);<br />
// that's it! the rest of the program is just so the example<br />
// is a little 'useful'<br />
<br />
// we will reuse Buffer to output the SecondProcess's<br />
// output to *this* programs stdout<br />
WriteLn('Grep output Start:');<br />
ReadSize := SecondProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
if ReadSize > 0 then<br />
begin<br />
ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);<br />
WriteLn(Copy(Buffer,0, ReadCount));<br />
end<br />
else<br />
WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);<br />
WriteLn('Grep output Finish:');<br />
<br />
// free our process objects<br />
FirstProcess.Free;<br />
SecondProcess.Free;<br />
end.</syntaxhighlight><br />
<br />
That's it. Now you can redirect output from one program to another.<br />
<br />
==== Notes ====<br />
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:<br />
<syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight><br />
<br />
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.<br />
<br />
===Redirecting input and output and running under root===<br />
A common problem on Unixes (OSX) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ''ping'' command.<br />
<br />
If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). This sample runs <code>ls</code> on the <code>/root</code> directory, but can of course be adapted.<br />
<br />
A '''better way''' to do this is to use the policykit package, which should be available on all recent Linuxes. [http://lazarus.freepascal.org/index.php/topic,14479.0.html See the forum thread for details.]<br />
<br />
Large parts of this code are similar to the earlier example, but it also shows how to redirect stdout and stderr of the process being called separately to stdout and stderr of our own code.<br />
<br />
<syntaxhighlight><br />
program rootls;<br />
<br />
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,<br />
calling sudo on Linux/OSX, and supplying input on stdin}<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes,<br />
Math, {for min}<br />
Process;<br />
<br />
procedure RunsLsRoot;<br />
var<br />
Proc: TProcess;<br />
CharBuffer: array [0..511] of char;<br />
ReadCount: integer;<br />
ExitCode: integer;<br />
SudoPassword: string;<br />
begin<br />
WriteLn('Please enter the sudo password:');<br />
Readln(SudoPassword);<br />
ExitCode := -1; //Start out with failure, let's see later if it works<br />
Proc := TProcess.Create(nil); //Create a new process<br />
try<br />
Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr<br />
Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo<br />
// -S causes sudo to read the password from stdin.<br />
Proc.Execute; //start it. sudo will now probably ask for a password<br />
<br />
// write the password to stdin of the sudo program:<br />
SudoPassword := SudoPassword + LineEnding;<br />
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));<br />
SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof<br />
SudoPassword := ''; // and make the program a bit safer from snooping?!?<br />
<br />
// main loop to read output from stdout and stderr of sudo<br />
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or<br />
(Proc.Stderr.NumBytesAvailable > 0) do<br />
begin<br />
// read stdout and write to our stdout<br />
while Proc.Output.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Output.Read(CharBuffer, ReadCount);<br />
Write(StdOut, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
// read stderr and write to our stderr<br />
while Proc.Stderr.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Stderr.Read(CharBuffer, ReadCount);<br />
Write(StdErr, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
end;<br />
ExitCode := Proc.ExitStatus;<br />
finally<br />
Proc.Free;<br />
Halt(ExitCode);<br />
end;<br />
end;<br />
<br />
begin<br />
RunsLsRoot;<br />
end.<br />
</syntaxhighlight><br />
<br />
Other thoughts:<br />
It would no doubt be advisable to see if sudo actually prompts for a password. This can be checked consistently by setting the environment variable SUDO_PROMPT to something we watch for while reading the stdout of TProcess avoiding the problem of the prompt being different for different locales. Setting an environment variable causes the default values to be cleared(inherited from our process) so we have to copy the environment from our program if needed.<br />
<br />
=== Using fdisk with sudo on Linux ===<br />
The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.<br />
<br />
<syntaxhighlight><br />
program getpartitioninfo;<br />
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.<br />
Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}<br />
<br />
Uses<br />
Classes, SysUtils, FileUtil, Process;<br />
<br />
var<br />
hprocess: TProcess;<br />
sPass: String;<br />
OutputLines: TStringList;<br />
<br />
begin <br />
sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password<br />
OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure <br />
// OutputLines is freed... Same for hProcess.<br />
<br />
// The following example will open fdisk in the background and give us partition information<br />
// Since fdisk requires elevated priviledges we need to <br />
// pass our password as a parameter to sudo using the -S<br />
// option, so it will will wait till our program sends our password to the sudo application<br />
hProcess := TProcess.Create(nil);<br />
// On Linux/Unix/OSX, we need specify full path to our executable:<br />
hProcess.Executable := '/bin/sh';<br />
// Now we add all the parameters on the command line:<br />
hprocess.Parameters.Add('-c');<br />
// Here we pipe the password to the sudo command which then executes fdisk -l: <br />
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');<br />
// Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe<br />
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];<br />
// Now run:<br />
hProcess.Execute;<br />
<br />
// hProcess should have now run the external executable (because we use poWaitOnExit).<br />
// Now you can process the process output (standard output and standard error), eg:<br />
OutputLines.Add('stdout:');<br />
OutputLines.LoadFromStream(hprocess.Output);<br />
OutputLines.Add('stderr:');<br />
OutputLines.LoadFromStream(hProcess.Stderr);<br />
// Show output on screen:<br />
writeln(OutputLines.Text);<br />
<br />
// Clean up to avoid memory leaks:<br />
hProcess.Free;<br />
OutputLines.Free;<br />
<br />
//Below are some examples as you see we can pass illegal characters just as if done from terminal <br />
//Even though you have read elsewhere that you can not I assure with this method you can :)<br />
<br />
//hprocess.Parameters.Add('ping -c 1 www.google.com');<br />
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');<br />
<br />
//Using QuotedStr() is not a requirement though it makes for cleaner code;<br />
//you can use double quote and have the same effect.<br />
<br />
//hprocess.Parameters.Add('glxinfo | grep direct'); <br />
<br />
// This method can also be used for installing applications from your repository:<br />
<br />
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); <br />
<br />
end. <br />
</syntaxhighlight><br />
<br />
<br />
===Parameters which contain spaces (Replacing Shell Quotes)===<br />
<br />
In the Linux shell it is possible to write quoted arguments like this:<br />
<br />
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram<br />
<br />
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):<br />
#--batch<br />
#--eval-command=info symbol 0x0000DDDD<br />
#the full path to myprogram<br />
<br />
TProcess can also pass parameters containing spaces, but it uses a different quoting style. Instead of only quoting part of the parameter, quote all of it. Like this:<br />
<br />
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';<br />
<br />
And also remember to only pass full paths.<br />
<br />
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446<br />
<br />
==LCLIntf Alternatives==<br />
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.<br />
<br />
===Open document in default application===<br />
<br />
In some situations you need to open some document/file using default associated application rather than execute a particular program. <br />
This depends on running operating system. Lazarus provides a platform independent procedure '''OpenDocument''' which will handle it for you. Your application will continue running without waiting for the document process to close.<br />
<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenDocument('manual.pdf'); <br />
...<br />
</syntaxhighlight><br />
<br />
* [[opendocument|OpenDocument reference]]<br />
<br />
===Open web page in default web browser===<br />
<br />
Just pass the URL required, the leading http:// appears to be optional under certain circumstances. <br />
Also, passing a filename appears to give the same results as OpenDocument()<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenURL('www.lazarus.freepascal.org/');<br />
</syntaxhighlight><br />
<br />
See also:<br />
* [[openurl|OpenURL reference]]<br />
<br />
Or, you could use '''TProcess''' like this:<br />
<syntaxhighlight><br />
uses Process;<br />
<br />
procedure OpenWebPage(URL: string);<br />
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"<br />
var<br />
Browser, Params: string;<br />
begin<br />
FindDefaultBrowser(Browser, Params);<br />
with TProcess.Create(nil) do<br />
try<br />
Executable := Browser;<br />
Params:=Format(Params, [URL]);<br />
Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself<br />
Parameters.Add(Params);<br />
Options := [poNoConsole];<br />
Execute;<br />
finally<br />
Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]<br />
* [[opendocument]]<br />
* [[openurl]]<br />
* [[TProcessUTF8]]<br />
* [[TXMLPropStorage]]<br />
* [[Webbrowser]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Parallel programming]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Inter-process communication]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Executing_External_Programs&diff=94725Executing External Programs2015-08-11T12:20:09Z<p>Eny: Simplify the code and make it working; remove irrelevant/out-of-context comments</p>
<hr />
<div>{{Executing External Programs}}<br />
<br />
== Overview : Comparison ==<br />
{| class="wikitable"<br />
!Method<br />
!Platforms<br />
!Single Line<br />
!Features<br />
|-<br />
|SysUtils.ExecuteProcess<br />
|Cross-Platform<br />
|Yes<br />
|Very limited, synchronous<br />
|-<br />
|(ShellApi) ShellExecute<br />
|MS Windows only<br />
|Yes<br />
|Many. Can start programs with elevation/admin permissions.<br />
|-<br />
|Unix fpsystem, fpexecve<br />
|Unix only<br />
|<br />
|<br />
|-<br />
|TProcess <br />
|Cross-Platform<br />
|No<br />
|Full<br />
|-<br />
|RunCommand(InDir)<br />
|Cross-Platform '''Requires FPC 2.6.2+'''<br />
|Yes<br />
|Covers common TProcess usage<br />
|-<br />
|(LCLIntf) OpenDocument<br />
|Cross-Platform<br />
|Yes<br />
|Only open document<br />
|}<br />
<br />
==(Process.)RunCommand==<br />
In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the [[Projects using Lazarus#fpcup|fpcup]] project.<br />
These functions are meant for basic and intermediate use and can capture output to a single string and fully support the ''large output'' case. <br />
<br />
A simple example is<br />
<syntaxhighlight><br />
uses Process;<br />
...<br />
var s : ansistring;<br />
...<br />
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then<br />
writeln(s); <br />
</syntaxhighlight> <br />
An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets p.CurrentDirectory):<br />
<syntaxhighlight><br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;<br />
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;<br />
</syntaxhighlight><br />
<br />
== SysUtils.ExecuteProcess ==<br />
(Cross-platform)<BR><br />
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :<br />
<br />
<syntaxhighlight>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight><br />
<br />
The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the next section about the prefered cross-platform '''TProcess''', or if you only wish to target Windows you may use '''ShellExecute'''.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess reference]<br />
<br />
== MS Windows : CreateProcess, ShellExecute and WinExec ==<br />
<br />
{{Note|While FPC/Lazarus has support for '''CreateProcess''', '''ShellExecute''' and/or '''WinExec''', this support is only in Win32/64. If your program is cross-platform, consider using '''RunCommand''' or '''TProcess'''.}}<br />
{{Note|WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.}}<br />
<br />
'''ShellExecute''' is a standard MS Windows function (ShellApi.h) with good [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx documentation on MSDN] (note their remarks about initialising COM if you find the function unreliable).<br />
<br />
<syntaxhighlight><br />
uses ..., ShellApi;<br />
<br />
// Simple one-liner (ignoring error returns) :<br />
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;<br />
<br />
// Execute a Batch File :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;<br />
<br />
// Open a command window in a given folder :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;<br />
<br />
// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :<br />
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;<br />
<br />
// or a useful procedure:<br />
procedure RunShellExecute(const prog,params:string);<br />
begin<br />
// ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' isn't always needed <br />
// path+prog, params, working folder,<br />
// 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...<br />
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success<br />
// return values 0..32 are errors<br />
end;<br />
</syntaxhighlight><br />
<br />
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.<br />
<br />
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.<br />
<br />
If in Delphi you used ShellExecute for '''documents''' like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).<br />
<br />
=== Using ShellExecuteEx for elevation/administrator permissions ===<br />
If you need to execute external program with administrator/elevated privileges, you can use the '''runas''' method with the alternative ShellExecuteEx function:<br />
<syntaxhighlight><br />
uses ShellApi, ...;<br />
<br />
function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;<br />
var<br />
sei: TShellExecuteInfoA;<br />
begin<br />
FillChar(sei, SizeOf(sei), 0);<br />
sei.cbSize := SizeOf(sei);<br />
sei.Wnd := Handle;<br />
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;<br />
sei.lpVerb := 'runas';<br />
sei.lpFile := PAnsiChar(Path);<br />
sei.lpParameters := PAnsiChar(Params);<br />
sei.nShow := SW_SHOWNORMAL;<br />
Result := ShellExecuteExA(@sei);<br />
end;<br />
<br />
procedure TFormMain.RunAddOrRemoveApplication;<br />
begin<br />
// Example that uses elevated rundll to open the Control Panel to Programs and features<br />
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');<br />
end;<br />
</syntaxhighlight><br />
<br />
== Unix fpsystem, fpexecve and shell ==<br />
<br />
These functions are platform dependent.<br />
<br />
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem reference]<br />
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve reference]<br />
* [http://www.freepascal.org/docs-html/rtl/unix/shell.html shell reference]<br />
<br />
Note that the 1.0.x '''Unix.Shell'' has been deprecated for a while, and is removed in trunk. Use fpsystem.<br />
<br />
== TProcess ==<br />
<br />
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:<br />
<br />
* Platform Independent<br />
* Capable of reading from stdout and writing to stdin.<br />
* Possible to wait for a command to finish or let it run while your program moves on.<br />
<br />
Important notes:<br />
* TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..<br />
* Presumably on Linux/Unix: you '''must''' specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.<br />
* On Windows, if the command is in the path, you don't need to specify the full path.<br />
* [[doc:fcl/process/tprocess.html|TProcess reference]]<br />
<br />
=== The Simplest Example ===<br />
<br />
A lot of typical cases have been prepared in the [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]] functions. Before you start copy and paste the examples below, check them out first.<br />
<br />
=== A Simple Example ===<br />
This example ('''that shouldn't be used in production, see Large Output or, better, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''') just shows you how to run an external program, nothing more:<br />
<syntaxhighlight><br />
// This is a demo program that shows<br />
// how to launch an external program.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This defines the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
var <br />
AProcess: TProcess;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler (i386 version that is)<br />
AProcess.Executable:= 'ppc386';<br />
// Pass -h together with ppc386 (so we're executing ppc386 -h):<br />
AProcess.Parameters.Add('-h');<br />
// Previous versions of FPC did not yet have .Executable and .Parameters and<br />
// used the .CommandLine property. While this is still available, it is deprecated<br />
// so it should not be used.<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. vvvvvvvvvvvvvv<br />
AProcess.Options := AProcess.Options + [poWaitOnExit];<br />
<br />
// Now let AProcess run the command in .Commandline:<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
That's it! You have just learned to run an external program from inside your own program.<br />
<br />
=== An improved example (but not correct yet)===<br />
That's nice, but how do I read the Output of a program that I have run?<br />
<br />
Well, let's expand our example a little and do just that:<br />
'''This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in [[#Reading large output]].'''<br />
<br />
<syntaxhighlight><br />
// This is a <br />
// FLAWED<br />
// demo program that shows<br />
// how to launch an external program<br />
// and read from its output.<br />
program launchprogram;<br />
<br />
// Here we include files that have useful functions<br />
// and procedures we will need.<br />
uses <br />
Classes, SysUtils, Process;<br />
<br />
// This is defining the var "AProcess" as a variable <br />
// of the type "TProcess"<br />
// Also now we are adding a TStringList to store the <br />
// data read from the programs output.<br />
var <br />
AProcess: TProcess;<br />
AStringList: TStringList;<br />
<br />
// This is where our program starts to run<br />
begin<br />
// Now we will create the TProcess object, and<br />
// assign it to the var AProcess.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// Create the TStringList object.<br />
AStringList := TStringList.Create;<br />
<br />
// Tell the new AProcess what the command to execute is.<br />
// Let's use the Free Pascal compiler<br />
// AProcess.CommandLine := '/usr/bin/ppc386 -h'; -----> CommandLine is deprecated, <br />
<br />
// -----> CommandLine is deprecated, use:<br />
AProcess.Executable := '/usr/bin/ppc386'; <br />
AProcess.Parameters.Add('-h'); <br />
<br />
// Previous versions of FPC did not yet have .Executable and .Parameters and<br />
// used the .CommandLine property. While this is still available, it is deprecated<br />
// so it should not be used.<br />
<br />
<br />
// We will define an option for when the program<br />
// is run. This option will make sure that our program<br />
// does not continue until the program we will launch<br />
// has stopped running. Also now we will tell it that<br />
// we want to read the output of the file.<br />
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];<br />
<br />
// Now that AProcess knows what the commandline is <br />
// we will run it.<br />
AProcess.Execute;<br />
<br />
// This is not reached until ppc386 stops running.<br />
<br />
// Now read the output of the program we just ran<br />
// into the TStringList.<br />
AStringList.LoadFromStream(AProcess.Output);<br />
<br />
// Save the output to a file.<br />
AStringList.SaveToFile('output.txt');<br />
<br />
// Now that the file is saved we can free the <br />
// TStringList and the TProcess.<br />
AStringList.Free;<br />
AProcess.Free; <br />
end.</syntaxhighlight><br />
<br />
=== Reading large output ===<br />
In the previous example we waited until the program exited. Then we read what the program has written to its output.<br />
<br />
Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from. <br />
<br />
But the calling program doesn't read from it until the called program has ended. A deadlock occurs.<br />
<br />
The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.<br />
<br />
If you want to read output from an external process, this is the code you should adapt for production use.<br />
<syntaxhighlight>program LargeOutputDemo;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes, SysUtils, Process; // Process is the unit that holds TProcess<br />
<br />
const<br />
BUF_SIZE = 2048; // Buffer size for reading the output in chunks<br />
<br />
var<br />
AProcess : TProcess;<br />
OutputStream : TStream;<br />
BytesRead : longint;<br />
Buffer : array[1..BUF_SIZE] of byte;<br />
<br />
begin<br />
// Set up the process; as an example a recursive directory search is used<br />
// because that will usually result in a lot of data.<br />
AProcess := TProcess.Create(nil);<br />
<br />
// The commands for Windows and *nix are different hence the $IFDEFs<br />
{$IFDEF Windows}<br />
// In Windows the dir command cannot be used directly because it's a build-in<br />
// shell command. Therefore cmd.exe and the extra parameters are needed.<br />
AProcess.Executable := 'c:\windows\system32\cmd.exe';<br />
AProcess.Parameters.Add('/c');<br />
AProcess.Parameters.Add('dir /s c:\windows');<br />
{$ENDIF Windows}<br />
<br />
{$IFDEF Unix}<br />
AProcess.Executable := '/bin/ls';<br />
AProcess.Parameters.Add('--recursive');<br />
AProcess.Parameters.Add('--all');<br />
AProcess.Parameters.Add('-l');<br />
{$ENDIF Unix}<br />
<br />
// Process option poUsePipes has to be used so the output can be captured.<br />
// Process option poWaitOnExit can not be used because that would block<br />
// this program, preventing it from reading the output data of the process.<br />
AProcess.Options := [poUsePipes];<br />
<br />
// Start the process (run the dir/ls command)<br />
AProcess.Execute;<br />
<br />
// Create a stream object to store the generated output in. This could<br />
// also be a file stream to directly save the output to disk.<br />
OutputStream := TMemoryStream.Create;<br />
<br />
// All generated output from AProcess is read in a loop until no more data is available<br />
repeat<br />
// Get the new data from the process to a maximum of the buffer size that was allocated.<br />
// Note that all read(...) calls will block except for the last one, which returns 0 (zero).<br />
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);<br />
<br />
// Add the bytes that were read to the stream for later usage<br />
OutputStream.Write(Buffer, BytesRead)<br />
<br />
until BytesRead = 0; // Stop if no more data is available<br />
<br />
// The process has finished so it can be cleaned up<br />
AProcess.Free;<br />
<br />
// Now that all data has been read it can be used; for example to save it to a file on disk<br />
with TFileStream.Create('output.txt', fmCreate) do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
CopyFrom(OutputStream, OutputStream.Size);<br />
Free<br />
end;<br />
<br />
// Or the data can be shown on screen<br />
with TStringList.Create do<br />
begin<br />
OutputStream.Position := 0; // Required to make sure all data is copied from the start<br />
LoadFromStream(OutputStream);<br />
writeln(Text);<br />
writeln('--- Number of lines = ', Count, '----');<br />
Free<br />
end;<br />
<br />
// Clean up<br />
OutputStream.Free;<br />
end.</syntaxhighlight><br />
<br />
=== Using input and output of a TProcess ===<br />
See processdemo example in the [https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].<br />
<br />
=== Hints on the use of TProcess ===<br />
If you are creating a cross-platform program, you can change commandline according to the OS, using directives "{$IFDEF}s" and "{$ENDIF}s".<br />
<br />
Example:<br />
<syntaxhighlight>{...}<br />
AProcess:=TProcess.Create(nil)<br />
{$IFDEF WIN32}<br />
// AProcess.CommandLine := 'calc.exe'; // -----> CommandLine is deprecated, USE:<br />
AProcess.Executable := 'calc.exe'; <br />
{$ENDIF}<br />
{$IFDEF LINUX}<br />
<br />
// AProcess.CommandLine := 'kcalc'; // -----> CommandLine is deprecated, USE:<br />
AProcess.Executable := 'kcalc'; <br />
{$ENDIF}<br />
AProcess.Execute; //in alternative, you can use AProcess.Active:=True<br />
{...}</syntaxhighlight><br />
<br />
=== OS X show application bundle in foreground ===<br />
<br />
You can start an '''application bundle''' via TProcess by starting the executable within the bundle. For example:<br />
<syntaxhighlight><br />
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';<br />
</syntaxhighlight><br />
<br />
This will start the ''Calendar'', but the window will be behind the current application.<br />
To get the application in the foreground you can use the '''open''' utility with the '''-n''' parameter:<br />
<syntaxhighlight><br />
AProcess.Executable:='/usr/bin/open';<br />
AProcess.Parameters.Add('-n');<br />
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole<br />
AProcess.Parameters.Add('/Application/iCal.app');<br />
</syntaxhighlight><br />
<br />
If your application needs parameters, you can pass '''open''' the '''--args''' parameter, after which all parameters are passed to the application:<br />
<syntaxhighlight><br />
AProcess.Parameters.Add('--args');<br />
AProcess.Parameters.Add('argument1');<br />
AProcess.Parameters.Add('argument2');<br />
</syntaxhighlight><br />
<br />
=== Run detached program ===<br />
<br />
Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:<br />
<br />
<syntaxhighlight><br />
var<br />
Process: TProcess;<br />
I: Integer;<br />
begin<br />
Process := TProcess.Create(nil);<br />
try<br />
Process.InheritHandles := False;<br />
Process.Options := [];<br />
Process.ShowWindow := swoShow;<br />
<br />
// Copy default environment variables including DISPLAY variable for GUI application to work<br />
for I := 1 to GetEnvironmentVariableCount do<br />
Process.Environment.Add(GetEnvironmentString(I));<br />
<br />
Process.Executable := '/usr/bin/gedit'; <br />
Process.Execute;<br />
finally<br />
Process.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== Example of "talking" with aspell process ===<br />
<br />
Inside [http://pasdoc.sourceforge.net/ pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.<br />
<br />
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.<br />
<br />
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.<br />
<br />
=== Replacing shell operators like "| < >" ===<br />
<br />
Sometimes you want to run a more complicated command that pipes its data to another command or to a file.<br />
Something like <br />
<syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight><br />
or<br />
<syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight><br />
<br />
Executing this with TProcess will not work. i.e:<br />
<br />
<syntaxhighlight>// this won't work<br />
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; <br />
Process.Execute;</syntaxhighlight><br />
<br />
==== Why using special operators to redirect output doesn't work ====<br />
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.<br />
It is possible to redirect output however just the way you wanted. See the [[Executing_External_Programs#How_to_redirect_output_with_TProcess |next section]].<br />
<br />
=== How to redirect output with TProcess ===<br />
<br />
You can redirect the output of a command to another command by using a TProcess instance for '''each''' command.<br />
<br />
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]<br />
<br />
Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.<br />
<br />
<syntaxhighlight>program Project1;<br />
<br />
uses<br />
Classes, sysutils, process;<br />
<br />
var<br />
FirstProcess,<br />
SecondProcess: TProcess;<br />
Buffer: array[0..127] of char;<br />
ReadCount: Integer;<br />
ReadSize: Integer;<br />
begin<br />
FirstProcess := TProcess.Create(nil);<br />
SecondProcess := TProcess.Create(nil);<br />
<br />
FirstProcess.Options := [poUsePipes];<br />
SecondProcess.Options := [poUsePipes,poStderrToOutPut];<br />
<br />
// FirstProcess.CommandLine := 'pwd'; -----> CommandLine is deprecated, USE:<br />
FirstProcess.Executable := 'pwd'; <br />
<br />
// SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -'; -----> CommandLine is deprecated, USE:<br />
SecondProcess.Executable := 'grep'; <br />
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); <br />
// this would be the same as "pwd | grep / -"<br />
<br />
FirstProcess.Execute;<br />
SecondProcess.Execute;<br />
<br />
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do<br />
begin<br />
if FirstProcess.Output.NumBytesAvailable > 0 then<br />
begin<br />
// make sure that we don't read more data than we have allocated<br />
// in the buffer<br />
ReadSize := FirstProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
// now read the output into the buffer<br />
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);<br />
// and write the buffer to the second process<br />
SecondProcess.Input.Write(Buffer[0], ReadCount);<br />
<br />
// if SecondProcess writes much data to it's Output then <br />
// we should read that data here to prevent a deadlock<br />
// see the previous example "Reading Large Output"<br />
end;<br />
end;<br />
// Close the input on the SecondProcess<br />
// so it finishes processing it's data<br />
SecondProcess.CloseInput;<br />
<br />
// and wait for it to complete<br />
// be carefull what command you run because it may not exit when<br />
// it's input is closed and the following line may loop forever<br />
while SecondProcess.Running do<br />
Sleep(1);<br />
// that's it! the rest of the program is just so the example<br />
// is a little 'useful'<br />
<br />
// we will reuse Buffer to output the SecondProcess's<br />
// output to *this* programs stdout<br />
WriteLn('Grep output Start:');<br />
ReadSize := SecondProcess.Output.NumBytesAvailable;<br />
if ReadSize > SizeOf(Buffer) then<br />
ReadSize := SizeOf(Buffer);<br />
if ReadSize > 0 then<br />
begin<br />
ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);<br />
WriteLn(Copy(Buffer,0, ReadCount));<br />
end<br />
else<br />
WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);<br />
WriteLn('Grep output Finish:');<br />
<br />
// free our process objects<br />
FirstProcess.Free;<br />
SecondProcess.Free;<br />
end.</syntaxhighlight><br />
<br />
That's it. Now you can redirect output from one program to another.<br />
<br />
==== Notes ====<br />
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:<br />
<syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight><br />
<br />
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.<br />
<br />
===Redirecting input and output and running under root===<br />
A common problem on Unixes (OSX) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ''ping'' command.<br />
<br />
If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). This sample runs <code>ls</code> on the <code>/root</code> directory, but can of course be adapted.<br />
<br />
A '''better way''' to do this is to use the policykit package, which should be available on all recent Linuxes. [http://lazarus.freepascal.org/index.php/topic,14479.0.html See the forum thread for details.]<br />
<br />
Large parts of this code are similar to the earlier example, but it also shows how to redirect stdout and stderr of the process being called separately to stdout and stderr of our own code.<br />
<br />
<syntaxhighlight><br />
program rootls;<br />
<br />
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,<br />
calling sudo on Linux/OSX, and supplying input on stdin}<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
Classes,<br />
Math, {for min}<br />
Process;<br />
<br />
procedure RunsLsRoot;<br />
var<br />
Proc: TProcess;<br />
CharBuffer: array [0..511] of char;<br />
ReadCount: integer;<br />
ExitCode: integer;<br />
SudoPassword: string;<br />
begin<br />
WriteLn('Please enter the sudo password:');<br />
Readln(SudoPassword);<br />
ExitCode := -1; //Start out with failure, let's see later if it works<br />
Proc := TProcess.Create(nil); //Create a new process<br />
try<br />
Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr<br />
Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo<br />
// -S causes sudo to read the password from stdin.<br />
Proc.Execute; //start it. sudo will now probably ask for a password<br />
<br />
// write the password to stdin of the sudo program:<br />
SudoPassword := SudoPassword + LineEnding;<br />
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));<br />
SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof<br />
SudoPassword := ''; // and make the program a bit safer from snooping?!?<br />
<br />
// main loop to read output from stdout and stderr of sudo<br />
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or<br />
(Proc.Stderr.NumBytesAvailable > 0) do<br />
begin<br />
// read stdout and write to our stdout<br />
while Proc.Output.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Output.Read(CharBuffer, ReadCount);<br />
Write(StdOut, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
// read stderr and write to our stderr<br />
while Proc.Stderr.NumBytesAvailable > 0 do<br />
begin<br />
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more<br />
Proc.Stderr.Read(CharBuffer, ReadCount);<br />
Write(StdErr, Copy(CharBuffer, 0, ReadCount));<br />
end;<br />
end;<br />
ExitCode := Proc.ExitStatus;<br />
finally<br />
Proc.Free;<br />
Halt(ExitCode);<br />
end;<br />
end;<br />
<br />
begin<br />
RunsLsRoot;<br />
end.<br />
</syntaxhighlight><br />
<br />
Other thoughts:<br />
It would no doubt be advisable to see if sudo actually prompts for a password. This can be checked consistently by setting the environment variable SUDO_PROMPT to something we watch for while reading the stdout of TProcess avoiding the problem of the prompt being different for different locales. Setting an environment variable causes the default values to be cleared(inherited from our process) so we have to copy the environment from our program if needed.<br />
<br />
=== Using fdisk with sudo on Linux ===<br />
The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.<br />
<br />
<syntaxhighlight><br />
program getpartitioninfo;<br />
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.<br />
Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}<br />
<br />
Uses<br />
Classes, SysUtils, FileUtil, Process;<br />
<br />
var<br />
hprocess: TProcess;<br />
sPass: String;<br />
OutputLines: TStringList;<br />
<br />
begin <br />
sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password<br />
OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure <br />
// OutputLines is freed... Same for hProcess.<br />
<br />
// The following example will open fdisk in the background and give us partition information<br />
// Since fdisk requires elevated priviledges we need to <br />
// pass our password as a parameter to sudo using the -S<br />
// option, so it will will wait till our program sends our password to the sudo application<br />
hProcess := TProcess.Create(nil);<br />
// On Linux/Unix/OSX, we need specify full path to our executable:<br />
hProcess.Executable := '/bin/sh';<br />
// Now we add all the parameters on the command line:<br />
hprocess.Parameters.Add('-c');<br />
// Here we pipe the password to the sudo command which then executes fdisk -l: <br />
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');<br />
// Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe<br />
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];<br />
// Now run:<br />
hProcess.Execute;<br />
<br />
// hProcess should have now run the external executable (because we use poWaitOnExit).<br />
// Now you can process the process output (standard output and standard error), eg:<br />
OutputLines.Add('stdout:');<br />
OutputLines.LoadFromStream(hprocess.Output);<br />
OutputLines.Add('stderr:');<br />
OutputLines.LoadFromStream(hProcess.Stderr);<br />
// Show output on screen:<br />
writeln(OutputLines.Text);<br />
<br />
// Clean up to avoid memory leaks:<br />
hProcess.Free;<br />
OutputLines.Free;<br />
<br />
//Below are some examples as you see we can pass illegal characters just as if done from terminal <br />
//Even though you have read elsewhere that you can not I assure with this method you can :)<br />
<br />
//hprocess.Parameters.Add('ping -c 1 www.google.com');<br />
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');<br />
<br />
//Using QuotedStr() is not a requirement though it makes for cleaner code;<br />
//you can use double quote and have the same effect.<br />
<br />
//hprocess.Parameters.Add('glxinfo | grep direct'); <br />
<br />
// This method can also be used for installing applications from your repository:<br />
<br />
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); <br />
<br />
end. <br />
</syntaxhighlight><br />
<br />
<br />
===Parameters which contain spaces (Replacing Shell Quotes)===<br />
<br />
In the Linux shell it is possible to write quoted arguments like this:<br />
<br />
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram<br />
<br />
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):<br />
#--batch<br />
#--eval-command=info symbol 0x0000DDDD<br />
#the full path to myprogram<br />
<br />
TProcess can also pass parameters containing spaces, but it uses a different quoting style. Instead of only quoting part of the parameter, quote all of it. Like this:<br />
<br />
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';<br />
<br />
And also remember to only pass full paths.<br />
<br />
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446<br />
<br />
==LCLIntf Alternatives==<br />
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.<br />
<br />
===Open document in default application===<br />
<br />
In some situations you need to open some document/file using default associated application rather than execute a particular program. <br />
This depends on running operating system. Lazarus provides a platform independent procedure '''OpenDocument''' which will handle it for you. Your application will continue running without waiting for the document process to close.<br />
<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenDocument('manual.pdf'); <br />
...<br />
</syntaxhighlight><br />
<br />
* [[opendocument|OpenDocument reference]]<br />
<br />
===Open web page in default web browser===<br />
<br />
Just pass the URL required, the leading http:// appears to be optional under certain circumstances. <br />
Also, passing a filename appears to give the same results as OpenDocument()<br />
<syntaxhighlight><br />
uses LCLIntf;<br />
...<br />
OpenURL('www.lazarus.freepascal.org/');<br />
</syntaxhighlight><br />
<br />
See also:<br />
* [[openurl|OpenURL reference]]<br />
<br />
Or, you could use '''TProcess''' like this:<br />
<syntaxhighlight><br />
uses Process;<br />
<br />
procedure OpenWebPage(URL: string);<br />
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"<br />
var<br />
Browser, Params: string;<br />
begin<br />
FindDefaultBrowser(Browser, Params);<br />
with TProcess.Create(nil) do<br />
try<br />
Executable := Browser;<br />
Params:=Format(Params, [URL]);<br />
Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself<br />
Parameters.Add(Params);<br />
Options := [poNoConsole];<br />
Execute;<br />
finally<br />
Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]<br />
* [[opendocument]]<br />
* [[openurl]]<br />
* [[TProcessUTF8]]<br />
* [[TXMLPropStorage]]<br />
* [[Webbrowser]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Parallel programming]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Inter-process communication]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Talk:How_to_add_icons_my_package_components&diff=94220Talk:How to add icons my package components2015-07-25T15:08:13Z<p>Eny: </p>
<hr />
<div>This page is redundant.<br />
See: http://wiki.freepascal.org/Lazarus_Resources<br />
and: http://wiki.freepascal.org/package</div>Enyhttps://wiki.freepascal.org/index.php?title=Talk:How_to_add_icons_my_package_components&diff=94219Talk:How to add icons my package components2015-07-25T15:07:59Z<p>Eny: </p>
<hr />
<div>This page is redundant.<br />
See: http://wiki.freepascal.org/Lazarus_Resources<br />
And: http://wiki.freepascal.org/package</div>Enyhttps://wiki.freepascal.org/index.php?title=Talk:How_to_add_icons_my_package_components&diff=94218Talk:How to add icons my package components2015-07-25T15:07:15Z<p>Eny: Redundant page</p>
<hr />
<div>This page is redundant.<br />
See: http://wiki.freepascal.org/Lazarus_Resources</div>Enyhttps://wiki.freepascal.org/index.php?title=Talk:Small_Virtual_Machines&diff=93343Talk:Small Virtual Machines2015-07-18T22:24:24Z<p>Eny: Relevance for Lazarus?</p>
<hr />
<div>Why is this in the Lazarus WIki?<br />
There is no relation with Lazarus whatsoever.</div>Enyhttps://wiki.freepascal.org/index.php?title=Bit_manipulation&diff=93150Bit manipulation2015-07-17T08:26:42Z<p>Eny: Make the code work for values in the QWord range</p>
<hr />
<div>{{Bit manipulation}}<br />
<br><br><br />
<br />
==Masked operations==<br />
<br />
This is a basic low level approach to handle bit manipulation. Main advantage is that operations can be performed with groups of bits at once. But the user has to deal with all operations by himself. Another problem is max range of used values in function parameters. There must be separate implemented functions for each ordinal type for best performance (as opposed to C templates using preprocessor).<br />
<br />
<syntaxhighlight><br />
procedure ClearBit(var Value: QWord; Index: Byte);<br />
begin<br />
Value := Value and ((QWord(1) shl Index) xor High(QWord));<br />
end;<br />
<br />
procedure SetBit(var Value: QWord; Index: Byte);<br />
begin<br />
Value:= Value or (QWord(1) shl Index);<br />
end;<br />
<br />
procedure PutBit(var Value: QWord; Index: Byte; State: Boolean); <br />
begin<br />
Value := (Value and ((QWord(1) shl Index) xor High(QWord))) or (QWord(State) shl Index);<br />
end;<br />
<br />
function GetBit(Value: QWord; Index: Byte): Boolean;<br />
begin<br />
Result := ((Value shr Index) and 1) = 1;<br />
end;<br />
</syntaxhighlight><br />
<br />
==Bitpacked record==<br />
<br />
FPC has useful extension which allow not only byte packing but also bit packing of records. This allow not only to define bit structures using Boolean type or subrange type 0..1 but also n-state or n-bits fields in record, e.g. subrange type 0..3 for 2 bits. This conjunction with record case construction combined structure can be defined which allow to access memory as byte or as individual bits.<br />
<br />
<syntaxhighlight><br />
TByteBits = bitpacked record<br />
Bit0, Bit1, Bit2, Bit3, Bit4, Bit5, Bit6, Bit7: Boolean;<br />
end;<br />
<br />
TByteEx = packed record<br />
case Integer of<br />
0: (ByteAccess: Byte); <br />
1: (BitAccess: TByteBits);<br />
end;<br />
<br />
TSomeBitLevelStructure = bitpacked record<br />
OneBit: 0..1;<br />
TwoBits: 0..3;<br />
FourBits: 0..15;<br />
EightBits: 0..255<br />
end;<br />
</syntaxhighlight><br />
<br />
Bitpacking can be controlled using compiler directive [http://www.freepascal.org/docs-html/prog/progsu6.html $BITPACKING]<br />
<br />
==Set==<br />
<br />
Because sets are basically an array of all states which is of boolean type, then set can be also used as bit array. But it requires use of [http://www.freepascal.org/docs-html/prog/progsu59.html#x65-640001.1.59 $PACKSET] and [http://www.freepascal.org/docs-html/prog/progsu57.html#x63-620001.1.57 $PACKENUM] compiler directives to change set size. It has also some limitations on max size of set.<br />
<br />
<syntaxhighlight><br />
{$packset 1}<br />
{$packenum 1}<br />
<br />
type<br />
TByteBits = set of (Bit0, Bit1, Bit2, Bit3, Bit4, Bit5, Bit6, Bit7);<br />
</syntaxhighlight><br />
<br />
==TBits==<br />
<br />
This class is part of FPC RTL library contained in Classes unit and has a similar use as in Delphi. It provides only some basic methods for bit manipulation.<br />
<br />
<syntaxhighlight><br />
TBits = class(TObject)<br />
public<br />
constructor Create(TheSize : longint = 0); virtual;<br />
destructor Destroy; override;<br />
function GetFSize : longint;<br />
procedure SetOn(Bit : longint);<br />
procedure Clear(Bit : longint);<br />
procedure Clearall;<br />
procedure AndBits(BitSet : TBits);<br />
procedure OrBits(BitSet : TBits);<br />
procedure XorBits(BitSet : TBits);<br />
procedure NotBits(BitSet : TBits);<br />
function Get(Bit : longint) : boolean;<br />
procedure Grow(NBit : longint);<br />
function Equals(Obj : TObject): Boolean; override; overload;<br />
function Equals(BitSet : TBits) : Boolean; overload;<br />
procedure SetIndex(Index : longint);<br />
function FindFirstBit(State : boolean) : longint;<br />
function FindNextBit : longint;<br />
function FindPrevBit : longint;<br />
<br />
{ functions and properties to match TBits class }<br />
function OpenBit: longint;<br />
property Bits[Bit: longint]: Boolean read get write SetBit; default;<br />
property Size: longint read FBSize write setSize;<br />
end;<br />
</syntaxhighlight><br />
<br />
==Record property and value index==<br />
<br />
Another interesting implementation of bit aligned structure can be used with capabilities of advanced records (FPC 2.6.0+). For all properties you have to use setter and getter which can handle general bit manipulations and set index which is passed to these methods. For more information refer to [http://www.freepascal.org/docs-html/ref/refsu31.html Indexed properties]. Because index is only one, it has to be divided to two parameters to describe location and size on bit value. In case of the following example, the offset can be 0..255 and size of value 0..255 bits. Another problem is that you have to ensure that defined structure components will not overlay.<br />
<br />
<syntaxhighlight><br />
{$mode delphi}<br />
<br />
TSomeBitStructure = record<br />
private<br />
RawData: Word;<br />
function GetBits(const AIndex: Integer): Integer; inline;<br />
procedure SetBits(const AIndex: Integer; const AValue: Integer); inline;<br />
public<br />
// High byte of index offset, low byte of index is bit count<br />
property OneBit: Integer index $0001 read GetBits write SetBits;<br />
property TwoBits: Integer index $0102 read GetBits write SetBits;<br />
property FourBits: Integer index $0304 read GetBits write SetBits;<br />
property EightBits: Integer index $0708 read GetBits write SetBits;<br />
end;<br />
<br />
{$OPTIMIZATION ON}<br />
{$OVERFLOWCHECKS OFF}<br />
function TSomeBitStructure.GetBits(const AIndex: Integer): Integer;<br />
var<br />
Offset: Integer;<br />
BitCount: Integer;<br />
Mask: Integer;<br />
begin<br />
BitCount := AIndex and $FF;<br />
Offset := AIndex shr 8;<br />
Mask := ((1 shl BitCount) - 1);<br />
Result := (RawData shr Offset) and Mask;<br />
end;<br />
<br />
procedure TSomeBitStructure.SetBits(const AIndex: Integer; const AValue: Integer);<br />
var<br />
Offset: Integer;<br />
BitCount: Integer;<br />
Mask: Integer;<br />
begin<br />
BitCount := AIndex and $FF;<br />
Offset := AIndex shr 8;<br />
Mask := ((1 shl BitCount) - 1);<br />
Assert(aValue <= Mask);<br />
RawData := (RawData and (not (Mask shl Offset))) or (AValue shl Offset);<br />
end;<br />
</syntaxhighlight><br />
<br />
[[Category:Tutorials]]<br />
[[Category:FPC]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Lazarus_For_Delphi_Users&diff=88138Lazarus For Delphi Users2015-04-22T10:02:09Z<p>Eny: Fix syntax error</p>
<hr />
<div>{{Lazarus For Delphi Users}}<br />
<br />
This writeup is for people who are interested in Lazarus and already know Delphi. It describes the differences between the two.<br />
<br />
== Delphi -> Lazarus ==<br />
Lazarus is a Rapid Application Development (RAD) tool like Delphi. That means it comes with a visual component library and an Integrated Development Environment (IDE). The Lazarus component library (LCL) is very similar to Delphi's Visual Component Library (VCL). Most Lazarus units, classes and properties have the same name and functionality as their equivalents in Delphi. This makes porting Delphi applications to Lazarus relatively easy. Even though Lazarus is in many respects an open source Delphi clone, the compatibility is not 100%.<br />
<br />
=== The biggest differences between Lazarus and Delphi ===<br />
* Lazarus is completely open source<br />
* Lazarus is written in a platform-independent way<br />
* Lazarus uses the [[Free Pascal]] compiler (FPC)<br />
FPC runs on more than 15 platforms. But not all FPC packages and libraries have been ported to all 15 platforms, so Lazarus currently runs on:<br />
* Linux (i386, x86_64)<br />
* FreeBSD (i386)<br />
* Mac OS X (powerpc, i386)<br />
* Windows (i386, x86_64)<br />
<br />
''' Lazarus is not complete and nor, for that matter, is this text. We are always searching for new developers, packagers, porters, documentation writers...'''<br />
<br />
=== The first thing to do when converting a Delphi project to Lazarus ===<br />
Having opened Lazarus, you should go to to '''Tools''' and then '''Convert Delphi Project to Lazarus Project''' (Since Lazarus 1.2.6 the menu structure is '''Tools''' -> '''Delphi Conversion''' -> '''Convert Delphi Project to Lazarus Project''').<br />
This won't do everything for you, but nonetheless will take you a good deal of the way. Note that the Lazarus IDE's conversion tools are generally one-way conversions. If you need to retain Delphi compatibility (so you can compile your project with both Delphi and Lazarus) consider converting your files with the [http://wiki.lazarus.freepascal.org/XDev_Toolkit XDev Toolkit] instead.<br />
<br />
=== Unicode support ===<br />
<br />
Versions of Delphi up to Delphi 2007 didn't support Unicode, but used Windows ANSI encoding. Delphi supports Unicode by using UTF-16 encoded strings from the 2009 version.<br />
<br />
Lazarus on the other hand started its Unicode support earlier and uses UTF-8 encoded strings. For more information see [[LCL Unicode Support]].<br />
<br />
== Delphi IDE -> Lazarus IDE ==<br />
=== Projects ===<br />
The main file for a Delphi application is its .dpr file. A Delphi .dpr file is both the program main source, and also the file where the Delphi IDE stores information about relevant compiler switches and unit locations.<br><br />
A Lazarus application has a corresponding .lpr file, which is also the principal '''Pascal source''' file for the project. However, the main file of a Lazarus project is the .lpi file (Lazarus Project Information), which is created separately together with the .lpr file. All project-specific information (such as compiler switches used, paths to units used etc.) is stored in the .lpi file. So, the important file is the .lpi file. On most platforms, double-clicking on an .lpi file in the system's file explorer will start the Lazarus IDE with that project open.<br />
<br />
For example:<br />
<br />
Delphi stores the paths of the project's units in the .dpr file. For instance:<br />
unit1 in 'path/Unit1.pas';<br />
These 'in' paths are Delphi-specific and are not read by the Lazarus IDE. Don't use them. Instead, in the Project Options dialog ('''Project'''->'''Project Options...''') use the '''Paths''' page under '''Compiler Options''' to set unit paths where they differ from the project directory path. Note that most unit paths you will need are set automatically by the IDE when any package dependency is added. For instance, all standard Lazarus LCL projects have the LCL added as a dependency by default, and so any new Lazarus LCL project ('''Project'''->'''New Project'''->'''Application''') knows all paths to all LCL compiled units without further work on your part.<br />
<br />
Delphi stores compiler options in the .dpr file. For instance {$APPTYPE CONSOLE}. These are ignored by the Lazarus IDE. Don't use them. Use the Compiler Options page of the Project Options dialog instead.<br />
<br />
=== No "empty" IDE with Lazarus ===<br />
One important rule: There is ''always'' a project. <br />
The only way to "close" a project is to exit Lazarus, or open another project. This is because a Lazarus project is also a "session". So session information (e. g. the current editor settings) is also stored in the .lpi file and the Lazarus editor is restored to its last saved state when the project is later reopened.<br />
For example: You are debugging an application, and set a lot of breakpoints and bookmarks. You can save the project at any time, close Lazarus or open another project. When you reopen the project (even on another computer) all your breakpoints, bookmarks, open files, cursor positions, jump histories, etc. are restored.<br />
<br />
=== Source Editor ===<br />
Nearly all keys and short cuts can be defined in Tools -> Options -> Editor -> Key Mappings<br />
<br />
The Lazarus IDE has a lot of tools for sources. Many of them look and work very similar to Delphi. But there is one important difference: Lazarus does not use the compiler to get code information. It parses the sources directly. This has a lot of important advantages:<br />
<br />
The source editor works with "comments". For Delphi the comments in the source are just space between code. No code feature works there and when new code is auto inserted, your comments will travel. Under Lazarus you can do a find declaration even on code in comments. Although this is not completely reliable, it often works. And when new code is inserted, the IDE uses some heuristics to keep comment and code together. For example: It will not split the line "c: char; // comment".<br />
<br />
Delphi's "Code Completion" (Ctrl+Space) is called "Identifier Completion" under Lazarus. The Lazarus term "Code Completion" is a feature, combining "Automatic Class Completion" (same as under Delphi), "Local Variable Completion" and "Event Assignment Completion". All of them are invoked by Ctrl+Shift+C and the IDE determines by the cursor position, what is meant.<br />
<br />
==== Example for Local Variable Completion ====<br />
Assume you just created a new method and wrote the statement "i:=3;"<br />
<syntaxhighlight>procedure TForm1.DoSomething;<br />
begin<br />
i := 3;<br />
end;</syntaxhighlight><br />
<br />
Position the cursor over the identifier "i" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}} to get:<br />
<br />
<syntaxhighlight>procedure TForm1.DoSomething;<br />
var i: Integer;<br />
begin<br />
i := 3;<br />
end;</syntaxhighlight><br />
<br />
==== Example for Event Assignment Completion ====<br />
A nice feature of the object inspector is to auto create methods. You can get the source editor to create the events too.<br><br />
For example:<br />
<br />
<syntaxhighlight>Button1.OnClick:=</syntaxhighlight><br />
<br />
Position the cursor behind the assign operator ":=" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}}.<br />
<br />
==== Example for Procedure Call Completion ====<br />
Assume you just wrote the statement "DoSomething(Width);"<br />
<syntaxhighlight>procedure SomeProcedure;<br />
var<br />
Width: integer;<br />
begin<br />
Width:=3;<br />
DoSomething(Width);<br />
end;</syntaxhighlight><br />
<br />
Position the cursor over the identifier "DoSomething" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}} to get:<br />
<br />
<syntaxhighlight>procedure DoSomething(aWidth: LongInt);<br />
begin<br />
<br />
end;<br />
<br />
procedure SomeProcedure;<br />
var<br />
Width: integer;<br />
begin<br />
Width:=3;<br />
DoSomething(Width);<br />
end;</syntaxhighlight><br />
<br />
==== "Word Completion" Ctrl+W ====<br />
It works similar to the "Identifier Completion", but it does not work on pascal identifiers, but on all words. It lets you choose of all words in all open files beginning with the same letters.<br />
<br />
==== Supports Include files ====<br />
Delphi didn't support them, and so you probably haven't created many include files yet. But include files have a big advantage: They make it possible writing platform (in)dependent code without messing your code with IFDEFs.<br />
For example: Method jumping, Class Completion, find declaration, .. all work with include files.<br />
<br />
There are many options for the code features.<br />
<br />
=== Designer ===<br />
- Guidelines<br />
<br />
==== Object Inspector ====<br />
In the Delphi and Lazarus IDE's the Object Inspector is used to edit component properties and assign events etc. The following are a few minor differences to note in use :<br />
# Starting in Delphi 5 there is an Object Treeview which can be used to navigate and select objects according to hierarchy in addition to the traditional drop down list in the Object Inspector. In Lazarus this is part of the Object Inspector and is used in place of the default drop-down, you can select to use/not use it from the right click menu with "Show Component Tree"<br />
# In Delphi double clicking on a blank event will auto create one and open the Source Editor to that position, in Lazarus there is a button to the right of the selected drop-down which performs this action instead.<br />
# In Delphi you must manually delete the name of an event in the edit to remove the attatchement, in Lazarus you can drop down and select "(None)".<br />
# Similarly to Events, double clicking regular properties such as boolean will not change the value, you must select it from a drop down. And to open those with an assigned editor form, you must click the '...' button to the right of the edit/drop-down<br />
<br />
==== Packages ====<br />
Can Lazarus install and use Delphi Packages?<br />
:No, because they require Delphi compiler magic.<br />
Do we need ones specially made for lazarus?<br />
:Yes.<br />
:Create a new package, save it in the package source directory (normally same directory of the .dpk file), add the LCL as required package and finally add the .pas files. You can install it, or use it in your projects now. There are some [[packages|differences between Lazarus and Delphi packages]]<br />
<br />
== VCL -> LCL ==<br />
While the VCL and the LCL both serve much of the same purpose - of an Object Oriented Component Hierarchy especially geared toward rapid application development, they are not identical. For instance while the VCL provides many non-visual components, the LCL tries to only provide visual, while most non-visual components (such as db access) are provided by the FCL, included with [[Free Pascal]] .<br />
<br />
Additionally many controls may not exist in the LCL that are in the VCL, or vice versa, or even when controls do exist in both, they are not clones, and changes must be made in applications, components and controls if porting.<br />
<br />
The following is an attempt to provide fairly complete descriptions of major differences or incompatiblities between the two for the Delphi user. It covers differences primarily with the VCL of D4 especially, though at times D5, D6, or D7 as well; and with the current LCL, as is in CVS. As such it may not always be accurate to the version of Delphi you are used to, or completely match the current LCL you have. If you see inacuracies between the following and the LCL as in CVS, or your Delphi feel free to append and modify so as to keep this as comprehensive as possible for all people.<br />
<br />
=== TControl.Font/TControl.ParentFont ===<br />
In the VCL it is quite common and normal to use a specific font name and font properties such as bold and italics for controls, and expect this value to always be followed. Further is provided the TControl.ParentFont property which ensures that a control will always follow its parent's font. Again the implicit assumption being that these values will always be followed, even regardless of Windows Apearance Settings. <br />
<br />
This is not always true in the LCL, nor can it be. The LCL being cross-platform/cross-interface in nature prefers to take a balanced aproach, and instead will always try to use native Desktop/Toolkit Apearance or Theme settings on any widgets. For example if using a GTK interface, and the gtk theme supplies a specific font for buttons, then LCL buttons will always try to use this font. <br />
<br />
This means that most LCL controls do not have the same level of design control that is often expected in the VCL, rather only those custom controls which are Canvas drawn instead of interface allocated can consistantly be modified in this manner regardless of the Interface used.<br />
<br />
=== Control Dragging/Docking ===<br />
In the VCL most (Win)Controls implement methods and callback functions for handling dragging and docking of controls, eg. dragging a control from one panel, and docking it onto another panel at run time. <br />
<br />
This functionality is currently unimplemented/unfinished in the LCL, though it is currently in the initial stages of planning, and should eventually support some level of compatibility for this type of behavior, if not in the exact same manner. <br />
<br />
This currently means that no Control will inherit/use the following TControl functions, procedures, properties, or events -<br />
<syntaxhighlight>Protected<br />
function GetDockEdge(MousePos: TPoint): TAlign;<br />
function GetDragImages: TDragImageList;<br />
function GetFloating: Boolean;<br />
function GetFloatingDockSiteClass: TWinControlClass;<br />
procedure DoEndDrag(Target:TObject); X, Y: Integer);<br />
procedure DockTrackNoTarget(Source: TDragDockObject; X, Y: Integer);<br />
procedure DoEndDock(Target: TObject; X, Y: Integer);<br />
procedure DoDock(NewDockSite: TWinControl; var ARect: TRect);<br />
procedure DoStartDock(var DragObject: TDragObject);<br />
procedure DragCanceled;<br />
procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;<br />
var Accept: Boolean);<br />
procedure DoEndDrag(Target: TObject; X, Y: Integer);<br />
procedure DoStartDrag(var DragObject: TDragObject);<br />
procedure DrawDragDockImage(DragDockObject: TDragDockObject);<br />
procedure EraseDragDockImage(DragDockObject: TDragDockObject);<br />
procedure PositionDockRect(DragDockObject: TDragDockObject);<br />
procedure SetDragMode(Value: TDragMode);<br />
property DragKind: TDragKind;<br />
property DragCursor: TCursor;<br />
property DragMode: TDragMode;<br />
property OnDragDrop: TDragDropEvent;<br />
property OnDragOver: TDragOverEvent;<br />
property OnEndDock: TEndDragEvent;<br />
property OnEndDrag: TEndDragEvent;<br />
property OnStartDock: TStartDockEvent;<br />
property OnStartDrag: TStartDragEvent;<br />
public<br />
function Dragging: Boolean;<br />
function ManualDock(NewDockSite: TWinControl; DropControl: TControl;<br />
ControlSide: TAlign): Boolean;<br />
function ManualFloat(ScreenPos: TRect): Boolean;<br />
function ReplaceDockedControl(Control: TControl; NewDockSite: TWinControl;<br />
DropControl: TControl; ControlSide: TAlign): Boolean;<br />
procedure BeginDrag(Immediate: Boolean; Threshold: Integer);<br />
procedure Dock(NewDockSite: TWinControl; ARect: TRect);<br />
procedure DragDrop(Source: TObject; X, Y: Integer);<br />
procedure EndDrag(Drop: Boolean);<br />
property DockOrientation: TDockOrientation;<br />
property Floating: Boolean;<br />
property FloatingDockSiteClass: TWinControlClass;<br />
property HostDockSite: TWinControl;<br />
property LRDockWidth: Integer;<br />
property TBDockHeight: Integer;<br />
property UndockHeight: Integer;<br />
property UndockWidth: Integer;</syntaxhighlight><br />
<br />
that the following classes do not exist/are unusable -<br />
<br />
<syntaxhighlight>TDragImageList = class(TCustomImageList)<br />
TDockZone = class<br />
TDockTree = class(TInterfacedObject, IDockManager)<br />
TDragObject = class(TObject)<br />
TBaseDragControlObject = class(TDragObject)<br />
TDragControlObject = class(TBaseDragControlObject)<br />
TDragDockObject = class(TBaseDragControlObject) </syntaxhighlight><br />
<br />
and that the following functions are also unusable/incompatible -<br />
<br />
<syntaxhighlight>function FindDragTarget(const Pos: TPoint;<br />
AllowDisabled: Boolean) : TControl;<br />
procedure CancelDrag;<br />
function IsDragObject(sender: TObject): Boolean;</syntaxhighlight><br />
<br />
The start of docking manager is described here: [[Anchor Docking]]<br />
<br />
=== TEdit/TCustomEdit ===<br />
The Edit controls, while functioning essentialy the same in the LCL as the VCL, do have some issues to be aware of in converting -<br />
# Due to restrictions in the Interfaces, TEdit.PasswordChar does not work in all interfaces yet(though in time it may), instead TCustomEdit.EchoMode emPassword should be used in the event text needs to be hidden.<br />
# On Drag/Dock Events are not yet implemented. For more information please see earlier section on [[#Control Dragging/Docking | Control Dragging/Docking]].<br />
# Font Properties are usually ignored for interface consistancy, for detailed explanation as too why please see [[#TControl.Font/TControl.ParentFont | TControl.Font/TControl.ParentFont]]<br />
<br />
=== TDBImage ===<br />
Delphi and Lazarus both have a [[TDBImage]] control that shows images stored in a database field. In current stable versions (1.2.4), Lazarus stores information about the image type in the database field before the actual image data. See procedure ''TDBImage.UpdateData''<br />
This means that Delphi and older Lazarus implementations are not compatible. <br />
<br />
Current stable Lazarus (1.2.0+) has implemented changes that allow Delphi compatible behaviour. Please see [[Lazarus_1.2.0_release_notes#TDBImage]] for details on how to activate this.<br />
<br />
Current Lazarus trunk falls back to Delphi-compatible behaviour on reading Delphi-formatted database fields into TDBImage.<br />
<br />
=== (optional) TSplitter -> TPairSplitter ===<br />
'''Please Improve Me'''<br />
<br />
There is now a [[TSplitter]] control in the LCL, so no need to convert it.<br />
<br />
Nevertheless, if you want, here it is explained:<br />
<br />
The following is loosely based on questions by [[User:Vincent | Vincent Snijders]] on the mailing list, and responses by [http://lazarus-ccr.sourceforge.net/index.php?wiki=AndrewJohnson Andrew Johnson]:<br />
<br />
In the VCL, "Splitting" controls, that is a handle which can be dragged between two components to give one more or less space then the other, is accomplished by a TSplitter. This is often seen, for instance in the Delphi IDE between the docked Code Explorer and Source Viewer. <br />
<br />
The LCL provides its own Control called a TPairSplitter, which serves the same type of purpose, however it is not compatible, so "repairing" broken VCL code or Delphi DFM's will be necessary in the event of porting code, even though much is shared in common between the two.<br />
<br />
;So what exactly are the differences?<br />
<br />
Well the biggest differences are a VCL TSplitter has no children, instead it is placed between two controls aligned properly, and allows resizing between them at runtime, regardless its own size. It must have two controls aligned on each side to do anything. A simple example would be form with a Left Aligned Panel, a left aligned Splitter, and a second client aligned panel. On run time you could then realign the size given each panel by dragging on the handle provided by this Splitter control.<br />
<br />
On the LCL hand however, a TPairSplitter is a special kind of control, with two panels, and it can only be usefull if the controls to split are on these panels, but it will still perform a split between those panel whether or not anything is on them. So following the prior example, you would have a form with a TPairSplitter aligned client, and a panel aligned client on its left side, and a panel aligned client on its right side.<br />
<br />
The other important difference is that in the VCl, since the TSplitter is its own TControl, then the position is kept relative to the other controls on resize, so for instance a client panel will grow while the other panels will not, thus the split position is relative to the alignment of the split controls,<br />
<br />
In the LCL since the side panels are separate then the TPairSplitter has a Position property which is absolute relative to top or left. so on resize the actual position does not change according to contents, so a callback must be set to ensure the ratio is kept on resize if this is important.<br />
<br />
For example if the Right side of a vertical split needs to have alClient like behaviour, you need to add a form resize callback which does something like :<br />
PairSplitter.Position := PairSplitter.Width - PairSplitter.Position; <br />
<br />
;So how can I convert existing code using TSplitter to the TPairSplitter?<br />
<br />
If the splitter and controls are created within an actual function(like form oncreate), conversion shouldn't be too difficult, primarily reorganize the code to create the controls in order of new hierarchy and set the parents of the child controls to split to the left/top and right/bottom portions of the PairSplitter. An example of the changes being - <br />
<br />
{| class="code"<br />
|- <br />
| class="header" | '''VCL''' || class="header" | '''LCL'''<br />
|- class="code"<br />
| class="code" |<br />
<syntaxhighlight>var <br />
BottomPanel: TPanel;<br />
VerticalSplitter: TSplitter;<br />
LeftPanel: TPanel;<br />
HorizontalSplitter: TSplitter;<br />
MainPanel: TPanel;<br />
<br />
begin<br />
BottomPanel:= TPanel.Create(Self);<br />
with (BottomPanel) do<br />
begin<br />
Parent:= Self;<br />
Height:= 75;<br />
Align:= alBottom;<br />
end;<br />
<br />
VerticalSplitter:= TSplitter.Create(Self);<br />
with (VerticalSplitter) do<br />
begin<br />
Parent:= Self;<br />
Align:= alBottom;<br />
end;<br />
<br />
HorizontalSplitter:= TSplitter.Create(Self);<br />
with (HorizontalSplitter) do<br />
begin<br />
Parent:= Self;<br />
align:= alLeft;<br />
end;<br />
<br />
LeftPanel:= TPanel.Create(Self);<br />
with (LeftPanel) do<br />
begin<br />
Parent:= Self;<br />
Width:= 125;<br />
Align:= alLeft;<br />
end;<br />
<br />
MainPanel:= TPanel.Create(Self);<br />
with (MainPanel) do<br />
begin<br />
Parent:= Self;<br />
Align:= alClient;<br />
Caption:= 'Hello';<br />
end;<br />
end;</syntaxhighlight><br />
| class="code" | <br />
<syntaxhighlight>var<br />
BottomPanel: TPanel;<br />
VerticalSplitter: TPairSplitter;<br />
LeftPanel: TPanel;<br />
HorizontalSplitter: TPairSplitter;<br />
MainPanel: TPanel;<br />
<br />
begin<br />
VerticalSplitter:= TPairSplitter.Create(Self);<br />
with (VerticalSplitter) do<br />
begin<br />
Parent:= Self;<br />
Align:= alClient;<br />
Width:= Self.Width;<br />
Height:= Self.Height;<br />
SplitterType:= pstVertical;<br />
Position:= Height - 75;<br />
Sides[0].Width:= Width;<br />
Sides[0].Height:= Position;<br />
end;<br />
<br />
HorizontalSplitter:= TPairSplitter.Create(Self);<br />
with (HorizontalSplitter) do<br />
begin<br />
Parent:= VerticalSplitter.Sides[0];<br />
Width:= Self.Width;<br />
Height:= VerticalSplitter.Position;<br />
align:= alClient;<br />
SplitterType:= pstHorizontal;<br />
Position:= 125;<br />
end;<br />
<br />
LeftPanel:= TPanel.Create(Self);<br />
with (LeftPanel) do<br />
begin<br />
Parent:= HorizontalSplitter.Sides[0];<br />
Align:= alClient;<br />
end;<br />
<br />
MainPanel:= TPanel.Create(Self);<br />
with (MainPanel) do<br />
begin<br />
Parent:= HorizontalSplitter.Sides[1];<br />
Align:= alClient;<br />
Caption:= 'Hello';<br />
end;<br />
<br />
BottomPanel:= TPanel.Create(Self);<br />
with (BottomPanel) do<br />
begin<br />
Parent:= VerticalSplitter.Sides[1];<br />
Align:= alClient;<br />
end;<br />
end;</syntaxhighlight><br />
|}<br />
<br />
So as you can see, farely consistant with most control hierarchy. And if you are familiar with DFM's, the changes needed for DFM->LFM conversion should be farely obvious from the above, as they are the same sort of changes in Parent/Owner etc.<br />
<br />
So the above example would be something like -<br />
<br />
{| class="code"<br />
|- <br />
| class="header" | '''Delphi DFM''' <div style="font-weight: normal">'''(extraneous values removed)'''<br />
| class="header" | '''Lazarus LFM''' <div style="font-weight: normal">'''(most width, height, etc. removed)'''<br />
|- class="code"<br />
| class="code" |<br />
<syntaxhighlight>object VerticalSplitter: TSplitter<br />
Height = 3<br />
Cursor = crVSplit<br />
Align = alBottom<br />
end<br />
object HorizontalSplitter: TSplitter<br />
Width = 3<br />
Align = alLeft<br />
end<br />
object BottomPanel: TPanel<br />
Height = 75<br />
Align = alBottom<br />
end<br />
object LeftPanel: TPanel<br />
Width = 125<br />
Align = alLeft<br />
end<br />
object MainPanel: TPanel<br />
Align = alClient<br />
end</syntaxhighlight><br />
| class="code" |<br />
<syntaxhighlight>object VerticalSplitter: TPairSplitter<br />
Align = alClient<br />
SplitterType = pstVertical<br />
Position = 225<br />
Height = 300<br />
Width = 400<br />
object Pairsplitterside1: TPairSplitterIde<br />
object HorizontalSplitter: TPairSplitter<br />
Align = alClient<br />
Position = 125<br />
object Pairsplitterside3: TPairSplitterIde<br />
Width = 125<br />
object LeftPanel: TPanel<br />
Align = alClient<br />
Width = 125<br />
end<br />
end<br />
object Pairsplitterside4: TPairSplitterIde<br />
object MainPanel: TPanel<br />
Align = alClient<br />
end<br />
end<br />
end<br />
end<br />
object Pairsplitterside2: TPairSplitterIde<br />
object BottomPanel: TPanel<br />
Align = alClient<br />
Height = 75<br />
end<br />
end<br />
end</syntaxhighlight><br />
|}<br />
<br />
=== TCustomTreeView/TTreeView ===<br />
Both VCL and the LCL provide a TCustomTreeView/TTreeView component, used for tree structured lists of data with multiple nodes and advanced selection and Image lists, and while actual features are comparable, not all properties are entirely compatible. Primary differences are as follows -<br />
<br />
'''Incomplete list, also update to include TCustomTreeView Mark functions and protected methods '''<br />
<br />
# The LCL provides a TCustomTreeView.Options, a set of options which can be set on the control to change its behaviour and apearance. These options are :<br />
#* tvoAllowMultiselect - enables multi node select mode, equivalent to enabling TCustomTreeView.MultiSelect in the D6 VCL<br />
#* tvoAutoExpand - Auto Expand nodes, equivalent to enabling TCustomTreeView.AutoExpand<br />
#* tvoAutoInsertMark - Update the Drag preview on mouse move.<br />
#* tvoAutoItemHeight - Adjust the item heights automatically.<br />
#* tvoHideSelection - Do not mark the selected item.<br />
#* tvoHotTrack - use Hot Tracking, equivalent to enabling TCustomTreeview.HotTrack<br />
#* tvoKeepCollapsedNodes - When shrinking/folding nodes, keep the child nodes<br />
#* tvoReadOnly - make Treeview read only, equivalent to enabling TCustomTreeview.ReadOnly<br />
#* tvoRightClickSelect - allow using Mouse Right Clicks to select nodes, equivalent to enabling TCustomTreeView.RightClickSelect<br />
#* tvoRowSelect - allow selecting rows, equivalent to enabling TCustomTreeView.RowSelect<br />
#* tvoShowButtons - show buttons, equivalent to enabling TCustomTreeView.ShowButtons<br />
#* tvoShowLines - show node lines, equivalent to enabling TCustomTreeView.ShowLines<br />
#* tvoShowRoot - show root note, equivalent to enabling TCustomTreeView.ShowRoot<br />
#* tvoShowSeparators - show seperators<br />
#* tvoToolTips - show tooltips for individual nodes<br />
# The LCL provides additional properties:<br />
#* TCustomTreeView.OnSelectionChange event<br />
#* TCustomTreeView.DefaultItems, for the default number of Items<br />
#* TCustomTreeView.ExpandSignType to determine sign used on expandable/collapsible nodes<br />
# While most On Drag/Dock Events are available in the LCL they do not work. For more information please see earlier section on Control Dragging/Docking.<br />
<br />
=== Messages / Events ===<br />
<br />
The order and frequency of messages and events (OnShow, OnActivate, OnEnter, ...) differ from the VCL and depend on the [[Widgetset|widgetset]].<br />
The LCL provides a subset of WinAPI like messages to make porting of Delphi components easier, but almost all LCL messages work a little bit different than the VCL/WinAPI counterpart. The biggest part of Delphi code using WinAPI messages uses them, because the VCL lacks a feature or for speed reasons. Such things will seldom work the same under the LCL, so they must be checked manually. That's why LCL messages are called for example LM_SIZE instead of WM_SIZE (unit lmessages).<br />
<br />
'''Note on handling of custom messages!'''<br />
As of version 0.9.26 (December 2008), way of handling custom WinApi messages (e.g. WM_HOTKEY, WM_SYSCOMMAND) differs from the way of handling these messages in Delphi. At the moment you cannot handle them via '''message''' directive or via overriding of ''WndProc'' method of the form. The only way to handle them in the form is to hook Windows ''windowproc'' by yourself. Read more here: [[Win32/64_Interface#Processing_non-user_messages_in_your_window | Processing non-user messages in your window]]<br />
<br />
==See Also==<br />
<br />
* [[Code Conversion Guide| Code Conversion Guide (from Delphi & Kylix)]]<br />
* [[Compile With Delphi]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Delphi]]<br />
[[Category:Lazarus]]<br />
[[Category:Lazarus internals]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Lazarus_For_Delphi_Users&diff=88135Lazarus For Delphi Users2015-04-22T09:52:42Z<p>Eny: /* Delphi -> Lazarus */</p>
<hr />
<div>{{Lazarus For Delphi Users}}<br />
<br />
This writeup is for people who are interested in Lazarus and already know Delphi. It describes the differences between the two.<br />
<br />
== Delphi -> Lazarus ==<br />
Lazarus is a Rapid Application Development (RAD) tool like Delphi. That means it comes with a visual component library and an Integrated Development Environment (IDE). The Lazarus component library (LCL) is very similar to Delphi's Visual Component Library (VCL). Most Lazarus units, classes and properties have the same name and functionality as their equivalents in Delphi. This makes porting Delphi applications to Lazarus relatively easy. Even though Lazarus is in many respects an open source Delphi clone but the compatibility is not 100%.<br />
<br />
=== The biggest differences between Lazarus and Delphi ===<br />
* Lazarus is completely open source<br />
* Lazarus is written in a platform-independent way<br />
* Lazarus uses the [[Free Pascal]] compiler (FPC)<br />
FPC runs on more than 15 platforms. But not all FPC packages and libraries have been ported to all 15 platforms, so Lazarus currently runs on:<br />
* Linux (i386, x86_64)<br />
* FreeBSD (i386)<br />
* Mac OS X (powerpc, i386)<br />
* Windows (i386, x86_64)<br />
<br />
''' Lazarus is not complete and nor, for that matter, is this text. We are always searching for new developers, packagers, porters, documentation writers...'''<br />
<br />
=== The first thing to do when converting a Delphi project to Lazarus ===<br />
Having opened Lazarus, you should go to to '''Tools''' and then '''Convert Delphi Project to Lazarus Project''' (Since Lazarus 1.2.6 the menu structure is '''Tools''' -> '''Delphi Conversion''' -> '''Convert Delphi Project to Lazarus Project''').<br />
This won't do everything for you, but nonetheless will take you a good deal of the way. Note that the Lazarus IDE's conversion tools are generally one-way conversions. If you need to retain Delphi compatibility (so you can compile your project with both Delphi and Lazarus) consider converting your files with the [http://wiki.lazarus.freepascal.org/XDev_Toolkit XDev Toolkit] instead.<br />
<br />
=== Unicode support ===<br />
<br />
Versions of Delphi up to Delphi 2007 didn't support Unicode, but used Windows ANSI encoding. Delphi supports Unicode by using UTF-16 encoded strings from the 2009 version.<br />
<br />
Lazarus on the other hand started its Unicode support earlier and uses UTF-8 encoded strings. For more information see [[LCL Unicode Support]].<br />
<br />
== Delphi IDE -> Lazarus IDE ==<br />
=== Projects ===<br />
The main file for a Delphi application is its .dpr file. A Delphi .dpr file is both the program main source, and also the file where the Delphi IDE stores information about relevant compiler switches and unit locations.<br><br />
A Lazarus application has a corresponding .lpr file, which is also the principal '''Pascal source''' file for the project. However, the main file of a Lazarus project is the .lpi file (Lazarus Project Information), which is created separately together with the .lpr file. All project-specific information (such as compiler switches used, paths to units used etc.) is stored in the .lpi file. So, the important file is the .lpi file. On most platforms, double-clicking on an .lpi file in the system's file explorer will start the Lazarus IDE with that project open.<br />
<br />
For example:<br />
<br />
Delphi stores the paths of the project's units in the .dpr file. For instance:<br />
unit1 in 'path/Unit1.pas';<br />
These 'in' paths are Delphi-specific and are not read by the Lazarus IDE. Don't use them. Instead, in the Project Options dialog ('''Project'''->'''Project Options...''') use the '''Paths''' page under '''Compiler Options''' to set unit paths where they differ from the project directory path. Note that most unit paths you will need are set automatically by the IDE when any package dependency is added. For instance, all standard Lazarus LCL projects have the LCL added as a dependency by default, and so any new Lazarus LCL project ('''Project'''->'''New Project'''->'''Application''') knows all paths to all LCL compiled units without further work on your part.<br />
<br />
Delphi stores compiler options in the .dpr file. For instance {$APPTYPE CONSOLE}. These are ignored by the Lazarus IDE. Don't use them. Use the Compiler Options page of the Project Options dialog instead.<br />
<br />
=== No "empty" IDE with Lazarus ===<br />
One important rule: There is ''always'' a project. <br />
The only way to "close" a project is to exit Lazarus, or open another project. This is because a Lazarus project is also a "session". So session information (e. g. the current editor settings) is also stored in the .lpi file and the Lazarus editor is restored to its last saved state when the project is later reopened.<br />
For example: You are debugging an application, and set a lot of breakpoints and bookmarks. You can save the project at any time, close Lazarus or open another project. When you reopen the project (even on another computer) all your breakpoints, bookmarks, open files, cursor positions, jump histories, etc. are restored.<br />
<br />
=== Source Editor ===<br />
Nearly all keys and short cuts can be defined in Tools -> Options -> Editor -> Key Mappings<br />
<br />
The Lazarus IDE has a lot of tools for sources. Many of them look and work very similar to Delphi. But there is one important difference: Lazarus does not use the compiler to get code information. It parses the sources directly. This has a lot of important advantages:<br />
<br />
The source editor works with "comments". For Delphi the comments in the source are just space between code. No code feature works there and when new code is auto inserted, your comments will travel. Under Lazarus you can do a find declaration even on code in comments. Although this is not completely reliable, it often works. And when new code is inserted, the IDE uses some heuristics to keep comment and code together. For example: It will not split the line "c: char; // comment".<br />
<br />
Delphi's "Code Completion" (Ctrl+Space) is called "Identifier Completion" under Lazarus. The Lazarus term "Code Completion" is a feature, combining "Automatic Class Completion" (same as under Delphi), "Local Variable Completion" and "Event Assignment Completion". All of them are invoked by Ctrl+Shift+C and the IDE determines by the cursor position, what is meant.<br />
<br />
==== Example for Local Variable Completion ====<br />
Assume you just created a new method and wrote the statement "i:=3;"<br />
<syntaxhighlight>procedure TForm1.DoSomething;<br />
begin<br />
i := 3;<br />
end;</syntaxhighlight><br />
<br />
Position the cursor over the identifier "i" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}} to get:<br />
<br />
<syntaxhighlight>procedure TForm1.DoSomething;<br />
var i: Integer;<br />
begin<br />
i := 3;<br />
end;</syntaxhighlight><br />
<br />
==== Example for Event Assignment Completion ====<br />
A nice feature of the object inspector is to auto create methods. You can get the source editor to create the events too.<br><br />
For example:<br />
<br />
<syntaxhighlight>Button1.OnClick:=</syntaxhighlight><br />
<br />
Position the cursor behind the assign operator ":=" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}}.<br />
<br />
==== Example for Procedure Call Completion ====<br />
Assume you just wrote the statement "DoSomething(Width);"<br />
<syntaxhighlight>procedure SomeProcedure;<br />
var<br />
Width: integer;<br />
begin<br />
Width:=3;<br />
DoSomething(Width);<br />
end;</syntaxhighlight><br />
<br />
Position the cursor over the identifier "DoSomething" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}} to get:<br />
<br />
<syntaxhighlight>procedure DoSomething(aWidth: LongInt);<br />
begin<br />
<br />
end;<br />
<br />
procedure SomeProcedure;<br />
var<br />
Width: integer;<br />
begin<br />
Width:=3;<br />
DoSomething(Width);<br />
end;</syntaxhighlight><br />
<br />
==== "Word Completion" Ctrl+W ====<br />
It works similar to the "Identifier Completion", but it does not work on pascal identifiers, but on all words. It lets you choose of all words in all open files beginning with the same letters.<br />
<br />
==== Supports Include files ====<br />
Delphi didn't support them, and so you probably haven't created many include files yet. But include files have a big advantage: They make it possible writing platform (in)dependent code without messing your code with IFDEFs.<br />
For example: Method jumping, Class Completion, find declaration, .. all work with include files.<br />
<br />
There are many options for the code features.<br />
<br />
=== Designer ===<br />
- Guidelines<br />
<br />
==== Object Inspector ====<br />
In the Delphi and Lazarus IDE's the Object Inspector is used to edit component properties and assign events etc. The following are a few minor differences to note in use :<br />
# Starting in Delphi 5 there is an Object Treeview which can be used to navigate and select objects according to hierarchy in addition to the traditional drop down list in the Object Inspector. In Lazarus this is part of the Object Inspector and is used in place of the default drop-down, you can select to use/not use it from the right click menu with "Show Component Tree"<br />
# In Delphi double clicking on a blank event will auto create one and open the Source Editor to that position, in Lazarus there is a button to the right of the selected drop-down which performs this action instead.<br />
# In Delphi you must manually delete the name of an event in the edit to remove the attatchement, in Lazarus you can drop down and select "(None)".<br />
# Similarly to Events, double clicking regular properties such as boolean will not change the value, you must select it from a drop down. And to open those with an assigned editor form, you must click the '...' button to the right of the edit/drop-down<br />
<br />
==== Packages ====<br />
Can Lazarus install and use Delphi Packages?<br />
:No, because they require Delphi compiler magic.<br />
Do we need ones specially made for lazarus?<br />
:Yes.<br />
:Create a new package, save it in the package source directory (normally same directory of the .dpk file), add the LCL as required package and finally add the .pas files. You can install it, or use it in your projects now. There are some [[packages|differences between Lazarus and Delphi packages]]<br />
<br />
== VCL -> LCL ==<br />
While the VCL and the LCL both serve much of the same purpose - of an Object Oriented Component Hierarchy especially geared toward rapid application development, they are not identical. For instance while the VCL provides many non-visual components, the LCL tries to only provide visual, while most non-visual components (such as db access) are provided by the FCL, included with [[Free Pascal]] .<br />
<br />
Additionally many controls may not exist in the LCL that are in the VCL, or vice versa, or even when controls do exist in both, they are not clones, and changes must be made in applications, components and controls if porting.<br />
<br />
The following is an attempt to provide fairly complete descriptions of major differences or incompatiblities between the two for the Delphi user. It covers differences primarily with the VCL of D4 especially, though at times D5, D6, or D7 as well; and with the current LCL, as is in CVS. As such it may not always be accurate to the version of Delphi you are used to, or completely match the current LCL you have. If you see inacuracies between the following and the LCL as in CVS, or your Delphi feel free to append and modify so as to keep this as comprehensive as possible for all people.<br />
<br />
=== TControl.Font/TControl.ParentFont ===<br />
In the VCL it is quite common and normal to use a specific font name and font properties such as bold and italics for controls, and expect this value to always be followed. Further is provided the TControl.ParentFont property which ensures that a control will always follow its parent's font. Again the implicit assumption being that these values will always be followed, even regardless of Windows Apearance Settings. <br />
<br />
This is not always true in the LCL, nor can it be. The LCL being cross-platform/cross-interface in nature prefers to take a balanced aproach, and instead will always try to use native Desktop/Toolkit Apearance or Theme settings on any widgets. For example if using a GTK interface, and the gtk theme supplies a specific font for buttons, then LCL buttons will always try to use this font. <br />
<br />
This means that most LCL controls do not have the same level of design control that is often expected in the VCL, rather only those custom controls which are Canvas drawn instead of interface allocated can consistantly be modified in this manner regardless of the Interface used.<br />
<br />
=== Control Dragging/Docking ===<br />
In the VCL most (Win)Controls implement methods and callback functions for handling dragging and docking of controls, eg. dragging a control from one panel, and docking it onto another panel at run time. <br />
<br />
This functionality is currently unimplemented/unfinished in the LCL, though it is currently in the initial stages of planning, and should eventually support some level of compatibility for this type of behavior, if not in the exact same manner. <br />
<br />
This currently means that no Control will inherit/use the following TControl functions, procedures, properties, or events -<br />
<syntaxhighlight>Protected<br />
function GetDockEdge(MousePos: TPoint): TAlign;<br />
function GetDragImages: TDragImageList;<br />
function GetFloating: Boolean;<br />
function GetFloatingDockSiteClass: TWinControlClass;<br />
procedure DoEndDrag(Target:TObject); X, Y: Integer);<br />
procedure DockTrackNoTarget(Source: TDragDockObject; X, Y: Integer);<br />
procedure DoEndDock(Target: TObject; X, Y: Integer);<br />
procedure DoDock(NewDockSite: TWinControl; var ARect: TRect);<br />
procedure DoStartDock(var DragObject: TDragObject);<br />
procedure DragCanceled;<br />
procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;<br />
var Accept: Boolean);<br />
procedure DoEndDrag(Target: TObject; X, Y: Integer);<br />
procedure DoStartDrag(var DragObject: TDragObject);<br />
procedure DrawDragDockImage(DragDockObject: TDragDockObject);<br />
procedure EraseDragDockImage(DragDockObject: TDragDockObject);<br />
procedure PositionDockRect(DragDockObject: TDragDockObject);<br />
procedure SetDragMode(Value: TDragMode);<br />
property DragKind: TDragKind;<br />
property DragCursor: TCursor;<br />
property DragMode: TDragMode;<br />
property OnDragDrop: TDragDropEvent;<br />
property OnDragOver: TDragOverEvent;<br />
property OnEndDock: TEndDragEvent;<br />
property OnEndDrag: TEndDragEvent;<br />
property OnStartDock: TStartDockEvent;<br />
property OnStartDrag: TStartDragEvent;<br />
public<br />
function Dragging: Boolean;<br />
function ManualDock(NewDockSite: TWinControl; DropControl: TControl;<br />
ControlSide: TAlign): Boolean;<br />
function ManualFloat(ScreenPos: TRect): Boolean;<br />
function ReplaceDockedControl(Control: TControl; NewDockSite: TWinControl;<br />
DropControl: TControl; ControlSide: TAlign): Boolean;<br />
procedure BeginDrag(Immediate: Boolean; Threshold: Integer);<br />
procedure Dock(NewDockSite: TWinControl; ARect: TRect);<br />
procedure DragDrop(Source: TObject; X, Y: Integer);<br />
procedure EndDrag(Drop: Boolean);<br />
property DockOrientation: TDockOrientation;<br />
property Floating: Boolean;<br />
property FloatingDockSiteClass: TWinControlClass;<br />
property HostDockSite: TWinControl;<br />
property LRDockWidth: Integer;<br />
property TBDockHeight: Integer;<br />
property UndockHeight: Integer;<br />
property UndockWidth: Integer;</syntaxhighlight><br />
<br />
that the following classes do not exist/are unusable -<br />
<br />
<syntaxhighlight>TDragImageList = class(TCustomImageList)<br />
TDockZone = class<br />
TDockTree = class(TInterfacedObject, IDockManager)<br />
TDragObject = class(TObject)<br />
TBaseDragControlObject = class(TDragObject)<br />
TDragControlObject = class(TBaseDragControlObject)<br />
TDragDockObject = class(TBaseDragControlObject) </syntaxhighlight><br />
<br />
and that the following functions are also unusable/incompatible -<br />
<br />
<syntaxhighlight>function FindDragTarget(const Pos: TPoint;<br />
AllowDisabled: Boolean) : TControl;<br />
procedure CancelDrag;<br />
function IsDragObject(sender: TObject): Boolean;</syntaxhighlight><br />
<br />
The start of docking manager is described here: [[Anchor Docking]]<br />
<br />
=== TEdit/TCustomEdit ===<br />
The Edit controls, while functioning essentialy the same in the LCL as the VCL, do have some issues to be aware of in converting -<br />
# Due to restrictions in the Interfaces, TEdit.PasswordChar does not work in all interfaces yet(though in time it may), instead TCustomEdit.EchoMode emPassword should be used in the event text needs to be hidden.<br />
# On Drag/Dock Events are not yet implemented. For more information please see earlier section on [[#Control Dragging/Docking | Control Dragging/Docking]].<br />
# Font Properties are usually ignored for interface consistancy, for detailed explanation as too why please see [[#TControl.Font/TControl.ParentFont | TControl.Font/TControl.ParentFont]]<br />
<br />
=== TDBImage ===<br />
Delphi and Lazarus both have a [[TDBImage]] control that shows images stored in a database field. In current stable versions (1.2.4), Lazarus stores information about the image type in the database field before the actual image data. See procedure ''TDBImage.UpdateData''<br />
This means that Delphi and older Lazarus implementations are not compatible. <br />
<br />
Current stable Lazarus (1.2.0+) has implemented changes that allow Delphi compatible behaviour. Please see [[Lazarus_1.2.0_release_notes#TDBImage]] for details on how to activate this.<br />
<br />
Current Lazarus trunk falls back to Delphi-compatible behaviour on reading Delphi-formatted database fields into TDBImage.<br />
<br />
=== (optional) TSplitter -> TPairSplitter ===<br />
'''Please Improve Me'''<br />
<br />
There is now a [[TSplitter]] control in the LCL, so no need to convert it.<br />
<br />
Nevertheless, if you want, here it is explained:<br />
<br />
The following is loosely based on questions by [[User:Vincent | Vincent Snijders]] on the mailing list, and responses by [http://lazarus-ccr.sourceforge.net/index.php?wiki=AndrewJohnson Andrew Johnson]:<br />
<br />
In the VCL, "Splitting" controls, that is a handle which can be dragged between two components to give one more or less space then the other, is accomplished by a TSplitter. This is often seen, for instance in the Delphi IDE between the docked Code Explorer and Source Viewer. <br />
<br />
The LCL provides its own Control called a TPairSplitter, which serves the same type of purpose, however it is not compatible, so "repairing" broken VCL code or Delphi DFM's will be necessary in the event of porting code, even though much is shared in common between the two.<br />
<br />
;So what exactly are the differences?<br />
<br />
Well the biggest differences are a VCL TSplitter has no children, instead it is placed between two controls aligned properly, and allows resizing between them at runtime, regardless its own size. It must have two controls aligned on each side to do anything. A simple example would be form with a Left Aligned Panel, a left aligned Splitter, and a second client aligned panel. On run time you could then realign the size given each panel by dragging on the handle provided by this Splitter control.<br />
<br />
On the LCL hand however, a TPairSplitter is a special kind of control, with two panels, and it can only be usefull if the controls to split are on these panels, but it will still perform a split between those panel whether or not anything is on them. So following the prior example, you would have a form with a TPairSplitter aligned client, and a panel aligned client on its left side, and a panel aligned client on its right side.<br />
<br />
The other important difference is that in the VCl, since the TSplitter is its own TControl, then the position is kept relative to the other controls on resize, so for instance a client panel will grow while the other panels will not, thus the split position is relative to the alignment of the split controls,<br />
<br />
In the LCL since the side panels are separate then the TPairSplitter has a Position property which is absolute relative to top or left. so on resize the actual position does not change according to contents, so a callback must be set to ensure the ratio is kept on resize if this is important.<br />
<br />
For example if the Right side of a vertical split needs to have alClient like behaviour, you need to add a form resize callback which does something like :<br />
PairSplitter.Position := PairSplitter.Width - PairSplitter.Position; <br />
<br />
;So how can I convert existing code using TSplitter to the TPairSplitter?<br />
<br />
If the splitter and controls are created within an actual function(like form oncreate), conversion shouldn't be too difficult, primarily reorganize the code to create the controls in order of new hierarchy and set the parents of the child controls to split to the left/top and right/bottom portions of the PairSplitter. An example of the changes being - <br />
<br />
{| class="code"<br />
|- <br />
| class="header" | '''VCL''' || class="header" | '''LCL'''<br />
|- class="code"<br />
| class="code" |<br />
<syntaxhighlight>var <br />
BottomPanel: TPanel;<br />
VerticalSplitter: TSplitter;<br />
LeftPanel: TPanel;<br />
HorizontalSplitter: TSplitter;<br />
MainPanel: TPanel;<br />
<br />
begin<br />
BottomPanel:= TPanel.Create(Self);<br />
with (BottomPanel) do<br />
begin<br />
Parent:= Self;<br />
Height:= 75;<br />
Align:= alBottom;<br />
end;<br />
<br />
VerticalSplitter:= TSplitter.Create(Self);<br />
with (VerticalSplitter) do<br />
begin<br />
Parent:= Self;<br />
Align:= alBottom;<br />
end;<br />
<br />
HorizontalSplitter:= TSplitter.Create(Self);<br />
with (HorizontalSplitter) do<br />
begin<br />
Parent:= Self;<br />
align:= alLeft;<br />
end;<br />
<br />
LeftPanel:= TPanel.Create(Self);<br />
with (LeftPanel) do<br />
begin<br />
Parent:= Self;<br />
Width:= 125;<br />
Align:= alLeft;<br />
end;<br />
<br />
MainPanel:= TPanel.Create(Self);<br />
with (MainPanel) do<br />
begin<br />
Parent:= Self;<br />
Align:= alClient;<br />
Caption:= 'Hello';<br />
end;<br />
end;</syntaxhighlight><br />
| class="code" | <br />
<syntaxhighlight>var<br />
BottomPanel: TPanel;<br />
VerticalSplitter: TPairSplitter;<br />
LeftPanel: TPanel;<br />
HorizontalSplitter: TPairSplitter;<br />
MainPanel: TPanel;<br />
<br />
begin<br />
VerticalSplitter:= TPairSplitter.Create(Self);<br />
with (VerticalSplitter) do<br />
begin<br />
Parent:= Self;<br />
Align:= alClient;<br />
Width:= Self.Width;<br />
Height:= Self.Height;<br />
SplitterType:= pstVertical;<br />
Position:= Height - 75;<br />
Sides[0].Width:= Width;<br />
Sides[0].Height:= Position;<br />
end;<br />
<br />
HorizontalSplitter:= TPairSplitter.Create(Self);<br />
with (HorizontalSplitter) do<br />
begin<br />
Parent:= VerticalSplitter.Sides[0];<br />
Width:= Self.Width;<br />
Height:= VerticalSplitter.Position;<br />
align:= alClient;<br />
SplitterType:= pstHorizontal;<br />
Position:= 125;<br />
end;<br />
<br />
LeftPanel:= TPanel.Create(Self);<br />
with (LeftPanel) do<br />
begin<br />
Parent:= HorizontalSplitter.Sides[0];<br />
Align:= alClient;<br />
end;<br />
<br />
MainPanel:= TPanel.Create(Self);<br />
with (MainPanel) do<br />
begin<br />
Parent:= HorizontalSplitter.Sides[1];<br />
Align:= alClient;<br />
Caption:= 'Hello';<br />
end;<br />
<br />
BottomPanel:= TPanel.Create(Self);<br />
with (BottomPanel) do<br />
begin<br />
Parent:= VerticalSplitter.Sides[1];<br />
Align:= alClient;<br />
end;<br />
end;</syntaxhighlight><br />
|}<br />
<br />
So as you can see, farely consistant with most control hierarchy. And if you are familiar with DFM's, the changes needed for DFM->LFM conversion should be farely obvious from the above, as they are the same sort of changes in Parent/Owner etc.<br />
<br />
So the above example would be something like -<br />
<br />
{| class="code"<br />
|- <br />
| class="header" | '''Delphi DFM''' <div style="font-weight: normal">'''(extraneous values removed)'''<br />
| class="header" | '''Lazarus LFM''' <div style="font-weight: normal">'''(most width, height, etc. removed)'''<br />
|- class="code"<br />
| class="code" |<br />
<syntaxhighlight>object VerticalSplitter: TSplitter<br />
Height = 3<br />
Cursor = crVSplit<br />
Align = alBottom<br />
end<br />
object HorizontalSplitter: TSplitter<br />
Width = 3<br />
Align = alLeft<br />
end<br />
object BottomPanel: TPanel<br />
Height = 75<br />
Align = alBottom<br />
end<br />
object LeftPanel: TPanel<br />
Width = 125<br />
Align = alLeft<br />
end<br />
object MainPanel: TPanel<br />
Align = alClient<br />
end</syntaxhighlight><br />
| class="code" |<br />
<syntaxhighlight>object VerticalSplitter: TPairSplitter<br />
Align = alClient<br />
SplitterType = pstVertical<br />
Position = 225<br />
Height = 300<br />
Width = 400<br />
object Pairsplitterside1: TPairSplitterIde<br />
object HorizontalSplitter: TPairSplitter<br />
Align = alClient<br />
Position = 125<br />
object Pairsplitterside3: TPairSplitterIde<br />
Width = 125<br />
object LeftPanel: TPanel<br />
Align = alClient<br />
Width = 125<br />
end<br />
end<br />
object Pairsplitterside4: TPairSplitterIde<br />
object MainPanel: TPanel<br />
Align = alClient<br />
end<br />
end<br />
end<br />
end<br />
object Pairsplitterside2: TPairSplitterIde<br />
object BottomPanel: TPanel<br />
Align = alClient<br />
Height = 75<br />
end<br />
end<br />
end</syntaxhighlight><br />
|}<br />
<br />
=== TCustomTreeView/TTreeView ===<br />
Both VCL and the LCL provide a TCustomTreeView/TTreeView component, used for tree structured lists of data with multiple nodes and advanced selection and Image lists, and while actual features are comparable, not all properties are entirely compatible. Primary differences are as follows -<br />
<br />
'''Incomplete list, also update to include TCustomTreeView Mark functions and protected methods '''<br />
<br />
# The LCL provides a TCustomTreeView.Options, a set of options which can be set on the control to change its behaviour and apearance. These options are :<br />
#* tvoAllowMultiselect - enables multi node select mode, equivalent to enabling TCustomTreeView.MultiSelect in the D6 VCL<br />
#* tvoAutoExpand - Auto Expand nodes, equivalent to enabling TCustomTreeView.AutoExpand<br />
#* tvoAutoInsertMark - Update the Drag preview on mouse move.<br />
#* tvoAutoItemHeight - Adjust the item heights automatically.<br />
#* tvoHideSelection - Do not mark the selected item.<br />
#* tvoHotTrack - use Hot Tracking, equivalent to enabling TCustomTreeview.HotTrack<br />
#* tvoKeepCollapsedNodes - When shrinking/folding nodes, keep the child nodes<br />
#* tvoReadOnly - make Treeview read only, equivalent to enabling TCustomTreeview.ReadOnly<br />
#* tvoRightClickSelect - allow using Mouse Right Clicks to select nodes, equivalent to enabling TCustomTreeView.RightClickSelect<br />
#* tvoRowSelect - allow selecting rows, equivalent to enabling TCustomTreeView.RowSelect<br />
#* tvoShowButtons - show buttons, equivalent to enabling TCustomTreeView.ShowButtons<br />
#* tvoShowLines - show node lines, equivalent to enabling TCustomTreeView.ShowLines<br />
#* tvoShowRoot - show root note, equivalent to enabling TCustomTreeView.ShowRoot<br />
#* tvoShowSeparators - show seperators<br />
#* tvoToolTips - show tooltips for individual nodes<br />
# The LCL provides additional properties:<br />
#* TCustomTreeView.OnSelectionChange event<br />
#* TCustomTreeView.DefaultItems, for the default number of Items<br />
#* TCustomTreeView.ExpandSignType to determine sign used on expandable/collapsible nodes<br />
# While most On Drag/Dock Events are available in the LCL they do not work. For more information please see earlier section on Control Dragging/Docking.<br />
<br />
=== Messages / Events ===<br />
<br />
The order and frequency of messages and events (OnShow, OnActivate, OnEnter, ...) differ from the VCL and depend on the [[Widgetset|widgetset]].<br />
The LCL provides a subset of WinAPI like messages to make porting of Delphi components easier, but almost all LCL messages work a little bit different than the VCL/WinAPI counterpart. The biggest part of Delphi code using WinAPI messages uses them, because the VCL lacks a feature or for speed reasons. Such things will seldom work the same under the LCL, so they must be checked manually. That's why LCL messages are called for example LM_SIZE instead of WM_SIZE (unit lmessages).<br />
<br />
'''Note on handling of custom messages!'''<br />
As of version 0.9.26 (December 2008), way of handling custom WinApi messages (e.g. WM_HOTKEY, WM_SYSCOMMAND) differs from the way of handling these messages in Delphi. At the moment you cannot handle them via '''message''' directive or via overriding of ''WndProc'' method of the form. The only way to handle them in the form is to hook Windows ''windowproc'' by yourself. Read more here: [[Win32/64_Interface#Processing_non-user_messages_in_your_window | Processing non-user messages in your window]]<br />
<br />
==See Also==<br />
<br />
* [[Code Conversion Guide| Code Conversion Guide (from Delphi & Kylix)]]<br />
* [[Compile With Delphi]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Delphi]]<br />
[[Category:Lazarus]]<br />
[[Category:Lazarus internals]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Lazarus_For_Delphi_Users&diff=88134Lazarus For Delphi Users2015-04-22T09:51:39Z<p>Eny: Fix inconsistencies</p>
<hr />
<div>{{Lazarus For Delphi Users}}<br />
<br />
This writeup is for people who are interested in Lazarus and already know Delphi. It describes the differences between the two.<br />
<br />
== Delphi -> Lazarus ==<br />
Lazarus is a Rapid Application Development (RAD) tool like Delphi. That means it comes with a visual component library and an Integrated Development Environment (IDE). The Lazarus component library (LCL) is very similar to Delphi's Visual Component Library (VCL). Most Lazarus units, classes and properties have the same name and functionality as their equivalents in Delphi. This makes porting Delphi applications to Lazarus relatively easy. Even though Lazarus is in many respects an open source Delphi clone, the compatibility is not 100%.<br />
<br />
=== The biggest differences between Lazarus and Delphi ===<br />
* Lazarus is completely open source<br />
* Lazarus is written in a platform-independent way<br />
* Lazarus uses the [[Free Pascal]] compiler (FPC)<br />
FPC runs on more than 15 platforms. But not all FPC packages and libraries have been ported to all 15 platforms, so Lazarus currently runs on:<br />
* Linux (i386, x86_64)<br />
* FreeBSD (i386)<br />
* Mac OS X (powerpc, i386)<br />
* Windows (i386, x86_64)<br />
<br />
''' Lazarus is not complete and nor, for that matter, is this text. We are always searching for new developers, packagers, porters, documentation writers...'''<br />
<br />
=== The first thing to do when converting a Delphi project to Lazarus ===<br />
Having opened Lazarus, you should go to to '''Tools''' and then '''Convert Delphi Project to Lazarus Project''' (Since Lazarus 1.2.6 the menu structure is '''Tools''' -> '''Delphi Conversion''' -> '''Convert Delphi Project to Lazarus Project''').<br />
This won't do everything for you, but nonetheless will take you a good deal of the way. Note that the Lazarus IDE's conversion tools are generally one-way conversions. If you need to retain Delphi compatibility (so you can compile your project with both Delphi and Lazarus) consider converting your files with the [http://wiki.lazarus.freepascal.org/XDev_Toolkit XDev Toolkit] instead.<br />
<br />
=== Unicode support ===<br />
<br />
Versions of Delphi up to Delphi 2007 didn't support Unicode, but used Windows ANSI encoding. Delphi supports Unicode by using UTF-16 encoded strings from the 2009 version.<br />
<br />
Lazarus on the other hand started its Unicode support earlier and uses UTF-8 encoded strings. For more information see [[LCL Unicode Support]].<br />
<br />
== Delphi IDE -> Lazarus IDE ==<br />
=== Projects ===<br />
The main file for a Delphi application is its .dpr file. A Delphi .dpr file is both the program main source, and also the file where the Delphi IDE stores information about relevant compiler switches and unit locations.<br><br />
A Lazarus application has a corresponding .lpr file, which is also the principal '''Pascal source''' file for the project. However, the main file of a Lazarus project is the .lpi file (Lazarus Project Information), which is created separately together with the .lpr file. All project-specific information (such as compiler switches used, paths to units used etc.) is stored in the .lpi file. So, the important file is the .lpi file. On most platforms, double-clicking on an .lpi file in the system's file explorer will start the Lazarus IDE with that project open.<br />
<br />
For example:<br />
<br />
Delphi stores the paths of the project's units in the .dpr file. For instance:<br />
unit1 in 'path/Unit1.pas';<br />
These 'in' paths are Delphi-specific and are not read by the Lazarus IDE. Don't use them. Instead, in the Project Options dialog ('''Project'''->'''Project Options...''') use the '''Paths''' page under '''Compiler Options''' to set unit paths where they differ from the project directory path. Note that most unit paths you will need are set automatically by the IDE when any package dependency is added. For instance, all standard Lazarus LCL projects have the LCL added as a dependency by default, and so any new Lazarus LCL project ('''Project'''->'''New Project'''->'''Application''') knows all paths to all LCL compiled units without further work on your part.<br />
<br />
Delphi stores compiler options in the .dpr file. For instance {$APPTYPE CONSOLE}. These are ignored by the Lazarus IDE. Don't use them. Use the Compiler Options page of the Project Options dialog instead.<br />
<br />
=== No "empty" IDE with Lazarus ===<br />
One important rule: There is ''always'' a project. <br />
The only way to "close" a project is to exit Lazarus, or open another project. This is because a Lazarus project is also a "session". So session information (e. g. the current editor settings) is also stored in the .lpi file and the Lazarus editor is restored to its last saved state when the project is later reopened.<br />
For example: You are debugging an application, and set a lot of breakpoints and bookmarks. You can save the project at any time, close Lazarus or open another project. When you reopen the project (even on another computer) all your breakpoints, bookmarks, open files, cursor positions, jump histories, etc. are restored.<br />
<br />
=== Source Editor ===<br />
Nearly all keys and short cuts can be defined in Tools -> Options -> Editor -> Key Mappings<br />
<br />
The Lazarus IDE has a lot of tools for sources. Many of them look and work very similar to Delphi. But there is one important difference: Lazarus does not use the compiler to get code information. It parses the sources directly. This has a lot of important advantages:<br />
<br />
The source editor works with "comments". For Delphi the comments in the source are just space between code. No code feature works there and when new code is auto inserted, your comments will travel. Under Lazarus you can do a find declaration even on code in comments. Although this is not completely reliable, it often works. And when new code is inserted, the IDE uses some heuristics to keep comment and code together. For example: It will not split the line "c: char; // comment".<br />
<br />
Delphi's "Code Completion" (Ctrl+Space) is called "Identifier Completion" under Lazarus. The Lazarus term "Code Completion" is a feature, combining "Automatic Class Completion" (same as under Delphi), "Local Variable Completion" and "Event Assignment Completion". All of them are invoked by Ctrl+Shift+C and the IDE determines by the cursor position, what is meant.<br />
<br />
==== Example for Local Variable Completion ====<br />
Assume you just created a new method and wrote the statement "i:=3;"<br />
<syntaxhighlight>procedure TForm1.DoSomething;<br />
begin<br />
i := 3;<br />
end;</syntaxhighlight><br />
<br />
Position the cursor over the identifier "i" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}} to get:<br />
<br />
<syntaxhighlight>procedure TForm1.DoSomething;<br />
var i: Integer;<br />
begin<br />
i := 3;<br />
end;</syntaxhighlight><br />
<br />
==== Example for Event Assignment Completion ====<br />
A nice feature of the object inspector is to auto create methods. You can get the source editor to create the events too.<br><br />
For example:<br />
<br />
<syntaxhighlight>Button1.OnClick:=</syntaxhighlight><br />
<br />
Position the cursor behind the assign operator ":=" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}}.<br />
<br />
==== Example for Procedure Call Completion ====<br />
Assume you just wrote the statement "DoSomething(Width);"<br />
<syntaxhighlight>procedure SomeProcedure;<br />
var<br />
Width: integer;<br />
begin<br />
Width:=3;<br />
DoSomething(Width);<br />
end;</syntaxhighlight><br />
<br />
Position the cursor over the identifier "DoSomething" and press {{keypress|Ctrl}}+{{keypress|Shift}}+{{keypress|C}} to get:<br />
<br />
<syntaxhighlight>procedure DoSomething(aWidth: LongInt);<br />
begin<br />
<br />
end;<br />
<br />
procedure SomeProcedure;<br />
var<br />
Width: integer;<br />
begin<br />
Width:=3;<br />
DoSomething(Width);<br />
end;</syntaxhighlight><br />
<br />
==== "Word Completion" Ctrl+W ====<br />
It works similar to the "Identifier Completion", but it does not work on pascal identifiers, but on all words. It lets you choose of all words in all open files beginning with the same letters.<br />
<br />
==== Supports Include files ====<br />
Delphi didn't support them, and so you probably haven't created many include files yet. But include files have a big advantage: They make it possible writing platform (in)dependent code without messing your code with IFDEFs.<br />
For example: Method jumping, Class Completion, find declaration, .. all work with include files.<br />
<br />
There are many options for the code features.<br />
<br />
=== Designer ===<br />
- Guidelines<br />
<br />
==== Object Inspector ====<br />
In the Delphi and Lazarus IDE's the Object Inspector is used to edit component properties and assign events etc. The following are a few minor differences to note in use :<br />
# Starting in Delphi 5 there is an Object Treeview which can be used to navigate and select objects according to hierarchy in addition to the traditional drop down list in the Object Inspector. In Lazarus this is part of the Object Inspector and is used in place of the default drop-down, you can select to use/not use it from the right click menu with "Show Component Tree"<br />
# In Delphi double clicking on a blank event will auto create one and open the Source Editor to that position, in Lazarus there is a button to the right of the selected drop-down which performs this action instead.<br />
# In Delphi you must manually delete the name of an event in the edit to remove the attatchement, in Lazarus you can drop down and select "(None)".<br />
# Similarly to Events, double clicking regular properties such as boolean will not change the value, you must select it from a drop down. And to open those with an assigned editor form, you must click the '...' button to the right of the edit/drop-down<br />
<br />
==== Packages ====<br />
Can Lazarus install and use Delphi Packages?<br />
:No, because they require Delphi compiler magic.<br />
Do we need ones specially made for lazarus?<br />
:Yes.<br />
:Create a new package, save it in the package source directory (normally same directory of the .dpk file), add the LCL as required package and finally add the .pas files. You can install it, or use it in your projects now. There are some [[packages|differences between Lazarus and Delphi packages]]<br />
<br />
== VCL -> LCL ==<br />
While the VCL and the LCL both serve much of the same purpose - of an Object Oriented Component Hierarchy especially geared toward rapid application development, they are not identical. For instance while the VCL provides many non-visual components, the LCL tries to only provide visual, while most non-visual components (such as db access) are provided by the FCL, included with [[Free Pascal]] .<br />
<br />
Additionally many controls may not exist in the LCL that are in the VCL, or vice versa, or even when controls do exist in both, they are not clones, and changes must be made in applications, components and controls if porting.<br />
<br />
The following is an attempt to provide fairly complete descriptions of major differences or incompatiblities between the two for the Delphi user. It covers differences primarily with the VCL of D4 especially, though at times D5, D6, or D7 as well; and with the current LCL, as is in CVS. As such it may not always be accurate to the version of Delphi you are used to, or completely match the current LCL you have. If you see inacuracies between the following and the LCL as in CVS, or your Delphi feel free to append and modify so as to keep this as comprehensive as possible for all people.<br />
<br />
=== TControl.Font/TControl.ParentFont ===<br />
In the VCL it is quite common and normal to use a specific font name and font properties such as bold and italics for controls, and expect this value to always be followed. Further is provided the TControl.ParentFont property which ensures that a control will always follow its parent's font. Again the implicit assumption being that these values will always be followed, even regardless of Windows Apearance Settings. <br />
<br />
This is not always true in the LCL, nor can it be. The LCL being cross-platform/cross-interface in nature prefers to take a balanced aproach, and instead will always try to use native Desktop/Toolkit Apearance or Theme settings on any widgets. For example if using a GTK interface, and the gtk theme supplies a specific font for buttons, then LCL buttons will always try to use this font. <br />
<br />
This means that most LCL controls do not have the same level of design control that is often expected in the VCL, rather only those custom controls which are Canvas drawn instead of interface allocated can consistantly be modified in this manner regardless of the Interface used.<br />
<br />
=== Control Dragging/Docking ===<br />
In the VCL most (Win)Controls implement methods and callback functions for handling dragging and docking of controls, eg. dragging a control from one panel, and docking it onto another panel at run time. <br />
<br />
This functionality is currently unimplemented/unfinished in the LCL, though it is currently in the initial stages of planning, and should eventually support some level of compatibility for this type of behavior, if not in the exact same manner. <br />
<br />
This currently means that no Control will inherit/use the following TControl functions, procedures, properties, or events -<br />
<syntaxhighlight>Protected<br />
function GetDockEdge(MousePos: TPoint): TAlign;<br />
function GetDragImages: TDragImageList;<br />
function GetFloating: Boolean;<br />
function GetFloatingDockSiteClass: TWinControlClass;<br />
procedure DoEndDrag(Target:TObject); X, Y: Integer);<br />
procedure DockTrackNoTarget(Source: TDragDockObject; X, Y: Integer);<br />
procedure DoEndDock(Target: TObject; X, Y: Integer);<br />
procedure DoDock(NewDockSite: TWinControl; var ARect: TRect);<br />
procedure DoStartDock(var DragObject: TDragObject);<br />
procedure DragCanceled;<br />
procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;<br />
var Accept: Boolean);<br />
procedure DoEndDrag(Target: TObject; X, Y: Integer);<br />
procedure DoStartDrag(var DragObject: TDragObject);<br />
procedure DrawDragDockImage(DragDockObject: TDragDockObject);<br />
procedure EraseDragDockImage(DragDockObject: TDragDockObject);<br />
procedure PositionDockRect(DragDockObject: TDragDockObject);<br />
procedure SetDragMode(Value: TDragMode);<br />
property DragKind: TDragKind;<br />
property DragCursor: TCursor;<br />
property DragMode: TDragMode;<br />
property OnDragDrop: TDragDropEvent;<br />
property OnDragOver: TDragOverEvent;<br />
property OnEndDock: TEndDragEvent;<br />
property OnEndDrag: TEndDragEvent;<br />
property OnStartDock: TStartDockEvent;<br />
property OnStartDrag: TStartDragEvent;<br />
public<br />
function Dragging: Boolean;<br />
function ManualDock(NewDockSite: TWinControl; DropControl: TControl;<br />
ControlSide: TAlign): Boolean;<br />
function ManualFloat(ScreenPos: TRect): Boolean;<br />
function ReplaceDockedControl(Control: TControl; NewDockSite: TWinControl;<br />
DropControl: TControl; ControlSide: TAlign): Boolean;<br />
procedure BeginDrag(Immediate: Boolean; Threshold: Integer);<br />
procedure Dock(NewDockSite: TWinControl; ARect: TRect);<br />
procedure DragDrop(Source: TObject; X, Y: Integer);<br />
procedure EndDrag(Drop: Boolean);<br />
property DockOrientation: TDockOrientation;<br />
property Floating: Boolean;<br />
property FloatingDockSiteClass: TWinControlClass;<br />
property HostDockSite: TWinControl;<br />
property LRDockWidth: Integer;<br />
property TBDockHeight: Integer;<br />
property UndockHeight: Integer;<br />
property UndockWidth: Integer;</syntaxhighlight><br />
<br />
that the following classes do not exist/are unusable -<br />
<br />
<syntaxhighlight>TDragImageList = class(TCustomImageList)<br />
TDockZone = class<br />
TDockTree = class(TInterfacedObject, IDockManager)<br />
TDragObject = class(TObject)<br />
TBaseDragControlObject = class(TDragObject)<br />
TDragControlObject = class(TBaseDragControlObject)<br />
TDragDockObject = class(TBaseDragControlObject) </syntaxhighlight><br />
<br />
and that the following functions are also unusable/incompatible -<br />
<br />
<syntaxhighlight>function FindDragTarget(const Pos: TPoint;<br />
AllowDisabled: Boolean) : TControl;<br />
procedure CancelDrag;<br />
function IsDragObject(sender: TObject): Boolean;</syntaxhighlight><br />
<br />
The start of docking manager is described here: [[Anchor Docking]]<br />
<br />
=== TEdit/TCustomEdit ===<br />
The Edit controls, while functioning essentialy the same in the LCL as the VCL, do have some issues to be aware of in converting -<br />
# Due to restrictions in the Interfaces, TEdit.PasswordChar does not work in all interfaces yet(though in time it may), instead TCustomEdit.EchoMode emPassword should be used in the event text needs to be hidden.<br />
# On Drag/Dock Events are not yet implemented. For more information please see earlier section on [[#Control Dragging/Docking | Control Dragging/Docking]].<br />
# Font Properties are usually ignored for interface consistancy, for detailed explanation as too why please see [[#TControl.Font/TControl.ParentFont | TControl.Font/TControl.ParentFont]]<br />
<br />
=== TDBImage ===<br />
Delphi and Lazarus both have a [[TDBImage]] control that shows images stored in a database field. In current stable versions (1.2.4), Lazarus stores information about the image type in the database field before the actual image data. See procedure ''TDBImage.UpdateData''<br />
This means that Delphi and older Lazarus implementations are not compatible. <br />
<br />
Current stable Lazarus (1.2.0+) has implemented changes that allow Delphi compatible behaviour. Please see [[Lazarus_1.2.0_release_notes#TDBImage]] for details on how to activate this.<br />
<br />
Current Lazarus trunk falls back to Delphi-compatible behaviour on reading Delphi-formatted database fields into TDBImage.<br />
<br />
=== (optional) TSplitter -> TPairSplitter ===<br />
'''Please Improve Me'''<br />
<br />
There is now a [[TSplitter]] control in the LCL, so no need to convert it.<br />
<br />
Nevertheless, if you want, here it is explained:<br />
<br />
The following is loosely based on questions by [[User:Vincent | Vincent Snijders]] on the mailing list, and responses by [http://lazarus-ccr.sourceforge.net/index.php?wiki=AndrewJohnson Andrew Johnson]:<br />
<br />
In the VCL, "Splitting" controls, that is a handle which can be dragged between two components to give one more or less space then the other, is accomplished by a TSplitter. This is often seen, for instance in the Delphi IDE between the docked Code Explorer and Source Viewer. <br />
<br />
The LCL provides its own Control called a TPairSplitter, which serves the same type of purpose, however it is not compatible, so "repairing" broken VCL code or Delphi DFM's will be necessary in the event of porting code, even though much is shared in common between the two.<br />
<br />
;So what exactly are the differences?<br />
<br />
Well the biggest differences are a VCL TSplitter has no children, instead it is placed between two controls aligned properly, and allows resizing between them at runtime, regardless its own size. It must have two controls aligned on each side to do anything. A simple example would be form with a Left Aligned Panel, a left aligned Splitter, and a second client aligned panel. On run time you could then realign the size given each panel by dragging on the handle provided by this Splitter control.<br />
<br />
On the LCL hand however, a TPairSplitter is a special kind of control, with two panels, and it can only be usefull if the controls to split are on these panels, but it will still perform a split between those panel whether or not anything is on them. So following the prior example, you would have a form with a TPairSplitter aligned client, and a panel aligned client on its left side, and a panel aligned client on its right side.<br />
<br />
The other important difference is that in the VCl, since the TSplitter is its own TControl, then the position is kept relative to the other controls on resize, so for instance a client panel will grow while the other panels will not, thus the split position is relative to the alignment of the split controls,<br />
<br />
In the LCL since the side panels are separate then the TPairSplitter has a Position property which is absolute relative to top or left. so on resize the actual position does not change according to contents, so a callback must be set to ensure the ratio is kept on resize if this is important.<br />
<br />
For example if the Right side of a vertical split needs to have alClient like behaviour, you need to add a form resize callback which does something like :<br />
PairSplitter.Position := PairSplitter.Width - PairSplitter.Position; <br />
<br />
;So how can I convert existing code using TSplitter to the TPairSplitter?<br />
<br />
If the splitter and controls are created within an actual function(like form oncreate), conversion shouldn't be too difficult, primarily reorganize the code to create the controls in order of new hierarchy and set the parents of the child controls to split to the left/top and right/bottom portions of the PairSplitter. An example of the changes being - <br />
<br />
{| class="code"<br />
|- <br />
| class="header" | '''VCL''' || class="header" | '''LCL'''<br />
|- class="code"<br />
| class="code" |<br />
<syntaxhighlight>var <br />
BottomPanel: TPanel;<br />
VerticalSplitter: TSplitter;<br />
LeftPanel: TPanel;<br />
HorizontalSplitter: TSplitter;<br />
MainPanel: TPanel;<br />
<br />
begin<br />
BottomPanel:= TPanel.Create(Self);<br />
with (BottomPanel) do<br />
begin<br />
Parent:= Self;<br />
Height:= 75;<br />
Align:= alBottom;<br />
end;<br />
<br />
VerticalSplitter:= TSplitter.Create(Self);<br />
with (VerticalSplitter) do<br />
begin<br />
Parent:= Self;<br />
Align:= alBottom;<br />
end;<br />
<br />
HorizontalSplitter:= TSplitter.Create(Self);<br />
with (HorizontalSplitter) do<br />
begin<br />
Parent:= Self;<br />
align:= alLeft;<br />
end;<br />
<br />
LeftPanel:= TPanel.Create(Self);<br />
with (LeftPanel) do<br />
begin<br />
Parent:= Self;<br />
Width:= 125;<br />
Align:= alLeft;<br />
end;<br />
<br />
MainPanel:= TPanel.Create(Self);<br />
with (MainPanel) do<br />
begin<br />
Parent:= Self;<br />
Align:= alClient;<br />
Caption:= 'Hello';<br />
end;<br />
end;</syntaxhighlight><br />
| class="code" | <br />
<syntaxhighlight>var<br />
BottomPanel: TPanel;<br />
VerticalSplitter: TPairSplitter;<br />
LeftPanel: TPanel;<br />
HorizontalSplitter: TPairSplitter;<br />
MainPanel: TPanel;<br />
<br />
begin<br />
VerticalSplitter:= TPairSplitter.Create(Self);<br />
with (VerticalSplitter) do<br />
begin<br />
Parent:= Self;<br />
Align:= alClient;<br />
Width:= Self.Width;<br />
Height:= Self.Height;<br />
SplitterType:= pstVertical;<br />
Position:= Height - 75;<br />
Sides[0].Width:= Width;<br />
Sides[0].Height:= Position;<br />
end;<br />
<br />
HorizontalSplitter:= TPairSplitter.Create(Self);<br />
with (HorizontalSplitter) do<br />
begin<br />
Parent:= VerticalSplitter.Sides[0];<br />
Width:= Self.Width;<br />
Height:= VerticalSplitter.Position;<br />
align:= alClient;<br />
SplitterType:= pstHorizontal;<br />
Position:= 125;<br />
end;<br />
<br />
LeftPanel:= TPanel.Create(Self);<br />
with (LeftPanel) do<br />
begin<br />
Parent:= HorizontalSplitter.Sides[0];<br />
Align:= alClient;<br />
end;<br />
<br />
MainPanel:= TPanel.Create(Self);<br />
with (MainPanel) do<br />
begin<br />
Parent:= HorizontalSplitter.Sides[1];<br />
Align:= alClient;<br />
Caption:= 'Hello';<br />
end;<br />
<br />
BottomPanel:= TPanel.Create(Self);<br />
with (BottomPanel) do<br />
begin<br />
Parent:= VerticalSplitter.Sides[1];<br />
Align:= alClient;<br />
end;<br />
end;</syntaxhighlight><br />
|}<br />
<br />
So as you can see, farely consistant with most control hierarchy. And if you are familiar with DFM's, the changes needed for DFM->LFM conversion should be farely obvious from the above, as they are the same sort of changes in Parent/Owner etc.<br />
<br />
So the above example would be something like -<br />
<br />
{| class="code"<br />
|- <br />
| class="header" | '''Delphi DFM''' <div style="font-weight: normal">'''(extraneous values removed)'''<br />
| class="header" | '''Lazarus LFM''' <div style="font-weight: normal">'''(most width, height, etc. removed)'''<br />
|- class="code"<br />
| class="code" |<br />
<syntaxhighlight>object VerticalSplitter: TSplitter<br />
Height = 3<br />
Cursor = crVSplit<br />
Align = alBottom<br />
end<br />
object HorizontalSplitter: TSplitter<br />
Width = 3<br />
Align = alLeft<br />
end<br />
object BottomPanel: TPanel<br />
Height = 75<br />
Align = alBottom<br />
end<br />
object LeftPanel: TPanel<br />
Width = 125<br />
Align = alLeft<br />
end<br />
object MainPanel: TPanel<br />
Align = alClient<br />
end</syntaxhighlight><br />
| class="code" |<br />
<syntaxhighlight>object VerticalSplitter: TPairSplitter<br />
Align = alClient<br />
SplitterType = pstVertical<br />
Position = 225<br />
Height = 300<br />
Width = 400<br />
object Pairsplitterside1: TPairSplitterIde<br />
object HorizontalSplitter: TPairSplitter<br />
Align = alClient<br />
Position = 125<br />
object Pairsplitterside3: TPairSplitterIde<br />
Width = 125<br />
object LeftPanel: TPanel<br />
Align = alClient<br />
Width = 125<br />
end<br />
end<br />
object Pairsplitterside4: TPairSplitterIde<br />
object MainPanel: TPanel<br />
Align = alClient<br />
end<br />
end<br />
end<br />
end<br />
object Pairsplitterside2: TPairSplitterIde<br />
object BottomPanel: TPanel<br />
Align = alClient<br />
Height = 75<br />
end<br />
end<br />
end</syntaxhighlight><br />
|}<br />
<br />
=== TCustomTreeView/TTreeView ===<br />
Both VCL and the LCL provide a TCustomTreeView/TTreeView component, used for tree structured lists of data with multiple nodes and advanced selection and Image lists, and while actual features are comparable, not all properties are entirely compatible. Primary differences are as follows -<br />
<br />
'''Incomplete list, also update to include TCustomTreeView Mark functions and protected methods '''<br />
<br />
# The LCL provides a TCustomTreeView.Options, a set of options which can be set on the control to change its behaviour and apearance. These options are :<br />
#* tvoAllowMultiselect - enables multi node select mode, equivalent to enabling TCustomTreeView.MultiSelect in the D6 VCL<br />
#* tvoAutoExpand - Auto Expand nodes, equivalent to enabling TCustomTreeView.AutoExpand<br />
#* tvoAutoInsertMark - Update the Drag preview on mouse move.<br />
#* tvoAutoItemHeight - Adjust the item heights automatically.<br />
#* tvoHideSelection - Do not mark the selected item.<br />
#* tvoHotTrack - use Hot Tracking, equivalent to enabling TCustomTreeview.HotTrack<br />
#* tvoKeepCollapsedNodes - When shrinking/folding nodes, keep the child nodes<br />
#* tvoReadOnly - make Treeview read only, equivalent to enabling TCustomTreeview.ReadOnly<br />
#* tvoRightClickSelect - allow using Mouse Right Clicks to select nodes, equivalent to enabling TCustomTreeView.RightClickSelect<br />
#* tvoRowSelect - allow selecting rows, equivalent to enabling TCustomTreeView.RowSelect<br />
#* tvoShowButtons - show buttons, equivalent to enabling TCustomTreeView.ShowButtons<br />
#* tvoShowLines - show node lines, equivalent to enabling TCustomTreeView.ShowLines<br />
#* tvoShowRoot - show root note, equivalent to enabling TCustomTreeView.ShowRoot<br />
#* tvoShowSeparators - show seperators<br />
#* tvoToolTips - show tooltips for individual nodes<br />
# The LCL provides additional properties:<br />
#* TCustomTreeView.OnSelectionChange event<br />
#* TCustomTreeView.DefaultItems, for the default number of Items<br />
#* TCustomTreeView.ExpandSignType to determine sign used on expandable/collapsible nodes<br />
# While most On Drag/Dock Events are available in the LCL they do not work. For more information please see earlier section on Control Dragging/Docking.<br />
<br />
=== Messages / Events ===<br />
<br />
The order and frequency of messages and events (OnShow, OnActivate, OnEnter, ...) differ from the VCL and depend on the [[Widgetset|widgetset]].<br />
The LCL provides a subset of WinAPI like messages to make porting of Delphi components easier, but almost all LCL messages work a little bit different than the VCL/WinAPI counterpart. The biggest part of Delphi code using WinAPI messages uses them, because the VCL lacks a feature or for speed reasons. Such things will seldom work the same under the LCL, so they must be checked manually. That's why LCL messages are called for example LM_SIZE instead of WM_SIZE (unit lmessages).<br />
<br />
'''Note on handling of custom messages!'''<br />
As of version 0.9.26 (December 2008), way of handling custom WinApi messages (e.g. WM_HOTKEY, WM_SYSCOMMAND) differs from the way of handling these messages in Delphi. At the moment you cannot handle them via '''message''' directive or via overriding of ''WndProc'' method of the form. The only way to handle them in the form is to hook Windows ''windowproc'' by yourself. Read more here: [[Win32/64_Interface#Processing_non-user_messages_in_your_window | Processing non-user messages in your window]]<br />
<br />
==See Also==<br />
<br />
* [[Code Conversion Guide| Code Conversion Guide (from Delphi & Kylix)]]<br />
* [[Compile With Delphi]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Delphi]]<br />
[[Category:Lazarus]]<br />
[[Category:Lazarus internals]]</div>Enyhttps://wiki.freepascal.org/index.php?title=SqlDBHowto&diff=87719SqlDBHowto2015-04-02T11:49:43Z<p>Eny: Consistency in function naming</p>
<hr />
<div>{{SqlDBHowto}}<br />
<br />
== Introduction ==<br />
<br />
This page started as a translation of [[SqlDBHowto/nl]]. Initially, the Dutch text was leading, meanwhile, the two articles have been synchronized. <br />
<br />
This text is setup as a 'how-to'. I want to answer a number of questions one by one, and explain how you can use the various classes. All those questions are put one after the other and form a sort of tutorial. <br />
<br />
I will try to word it in such a way that the text can be used for Lazarus as well as FreePascal. However, the examples are for FreePascal (i.e. they are console applications.)<br />
<br />
== Where can I find official documentation? ==<br />
<br />
Please see the official documentation at [http://www.freepascal.org/docs-html/fcl/sqldb/index.html SQLDB documentation]. <br />
<br />
== How to connect to a database server? ==<br />
<br />
SqlDB doesn't connect to a database server directly but uses a client that corresponds to the used database server. SqlDB sends the commands to the client library; the client library connects to the database and and transfers the commands. This means that a client library must be installed on the computer to make a connection to a database. Under Windows a client is usually a .dll, under Linux an .so and under OS/X a .dylib.<br />
<br />
When the client library is installed properly you can connect to a database server using a TSQLConnection component. Various TSQLConnection components are available for different database servers (see [[SQLdb_Package]]):<br />
* Firebird/Interbase: TIBConnection (see [[firebird#Connection]])<br />
* MS SQL Server: TMSSQLConnection (available since FPC 2.6.1, see [[Lazarus_Database_Overview#Lazarus_and_MSSQL.2FSybase]])<br />
* MySQL v4.0: TMySQL40Connection (see [[mysql#SQLDB]])<br />
* MySQL v4.1: TMySQL41Connection (see [[mysql#SQLDB]])<br />
* MySQL v5.0: TMySQL50Connection (see [[mysql#SQLDB]])<br />
* MySQL v5.1: TMySQL51Connection (available since FPC version 2.5.1, see [[mysql#SQLDB]])<br />
* MySQL v5.5: TMySQL55Connection (available since Lazarus 1.0.8/FPC version 2.6.2, see [[mysql#SQLDB]])<br />
* MySQL v5.6: TMySQL56Connection (available in Lazarus 1.2.4/FPC version 2.6.4, see [[mysql#SQLDB]])<br />
* ODBC: TODBCConnection (see [[ODBCConn#TODBCConnection]])<br />
* Oracle: TOracleConnection (see [[oracle|Oracle]])<br />
* PostgreSQL: TPQConnection (see [[postgresql#SQLDB]])<br />
* Sqlite3: TSQLite3Connection (available since FPC version 2.2.2, see [[SQLite#Built-in_SQLDB]]) <br />
* Sybase ASE: TSybaseConnection (available since FPC 2.6.1, see [[Lazarus_Database_Overview#Lazarus_and_MSSQL.2FSybase]])<br />
<br />
Note for MySQL - There are many differences between the client versions to the extent that the clients and connections cannot be interchanged. If a MySQL client library version 4.1 is installed, you have to use a TMySQL41Connection. This is not related to the MySQL server; using the MySQL 4.1 client library you can probably connect to a MySQL 5.0 server (see MySQL documentation regarding what combinations are supported).<br />
<br />
Although details differ for the various databases, in general you need to set four properties to connect to a database server: <br />
* the server name or IP address<br />
* the name of the database<br />
* the username <br />
* the password<br />
When these properties are set, you can create a connection with the 'open' method. If the connection fails, a EDatabaseError exception is thrown. Use the property 'connected' to test if a connection has been made with the database server. Use the 'close' method to end the connection with the server.<br />
<br />
<syntaxhighlight><br />
Program ConnectDB;<br />
<br />
function CreateConnection: TIBConnection;<br />
begin<br />
result := TIBConnection.Create(nil);<br />
result.Hostname := 'localhost';<br />
result.DatabaseName := '/opt/firebird/examples/employee.fdb';<br />
result.UserName := 'sysdba';<br />
result.Password := 'masterkey';<br />
end;<br />
<br />
var <br />
AConnection : TIBConnection;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
AConnection.Open;<br />
if Aconnection.Connected then<br />
writeln('Successful connect!')<br />
else<br />
writeln('This is not possible, because if the connection failed, ' +<br />
'an exception should be raised, so this code would not ' +<br />
'be executed');<br />
AConnection.Close;<br />
AConnection.Free;<br />
end.<br />
</syntaxhighlight><br />
<br />
If an exception is thrown, read the error message carefully. It may be that the database server is not running, the user name or password are incorrect or the database name or IP address are typed incorrectly. If the error message states that the client library cannot be found, then check if the client is installed correctly. Often the error message states literally the name of the file looked for.<br />
<br />
== How to execute direct queries/make a table? ==<br />
<br />
SqlDB - the name says it all - only works with database server that make use of SQL. SQL stands for 'Structured Query Language' SQL is a language developed to allow working with relational databases. Virtually every database system has its own dialect, but a large number of SQL statements are the same for all database systems.<br />
<br />
In FPC, there is a difference between:<br />
* SQL statements that return information (a dataset). For this, you have to use the TSQLQuery component; see [[#How to read data from a table?]]. <br />
* statements that do not return information but do something else, e.g. update data. For this, you may also use the 'ExecuteDirect' method of a TSQLConnection. (You can also use this if you get a dataset back but are not interested in the results, e.g. in a selectable stored procedure).<br />
<br />
Most database system execute SQL statements within a transaction. If you want changes made within a transaction available in other transactions, or have those changes available even after closing the transaction(!), then you have to 'commit' the transaction. <br />
<br />
To support transactions Sqldb contains the TSQLTransaction component. A SQL statement that is executed by Sqldb must always be executed within a transaction, even if the database system does not support transactions. Also, there are database systems that do support transaction for which TSQLConnection does not (yet) support transaction. Even then, you must use the TSQLTransaction component.<br />
<br />
To use <tt>TSQLConnection.ExecuteDirect</tt> to execute a SQL statement you must specify which 'Transaction' must be used. In turn, to use TSQLTransaction you must specify which TSQLConnection component must be used.<br />
<br />
The following example creates a table 'TBLNAMES' with fields 'NAME' and 'ID' and inserts two records. The used SQL statements are not explained. For more information about the SQL statements, their use and syntax, please refer to the database system documentation. The procedure 'CreateConnection' is defined in the code example in [[#How to connect to a database server?]] above.<br />
<br />
<syntaxhighlight>program CreateTable;<br />
<br />
function CreateTransaction(pDB: TIBConnection): TSQLTransaction;<br />
begin<br />
result := TSQLTransaction.Create;<br />
result.Database := pDB;<br />
end;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
AConnection.Transaction := ATransaction;<br />
AConnection.Open;<br />
ATransaction.StartTransaction;<br />
AConnection.ExecuteDirect('create table TBLNAMES (ID integer, NAME varchar(40));'); <br />
<br />
// Some database-server types need a commit before you can use a newly created table. (Firebird)<br />
// With .Commit you also close the transaction<br />
ATransaction.Commit; <br />
<br />
ATransaction.StartTransaction;<br />
AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (1,'Name1');'); <br />
AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (2,'Name2');'); <br />
ATransaction.Commit; <br />
AConnection.Close;<br />
AConnection.Free;<br />
ATransaction.Free;<br />
end.</syntaxhighlight><br />
<br />
== How to read data from a table? ==<br />
<br />
Use the TSQLQuery component to read data from a table. A TSQLQuery component must be connected to a TSQLConnection component and a TSQLTransaction component to do its work. Setting the TSQLConnection and TSQLTransaction is discussed in [[#How to connect to a database server? ]] and [[#How to execute direct queries/make a table?]]. <br />
<br />
When the TSQLConnection, TSQLTransaction and TSQLQuery are connected, then TSQLQuery needs to be further configured to work. TSQLQuery has a 'SQL' property containing a TStrings object. The 'SQL' property contains a SQL statement that must be executed. If all data from a table <tt>tablename</tt> must be read, then set the 'SQL' property to:<br />
<syntaxhighlight lang="sql">'SELECT * FROM tablename;'</syntaxhighlight>.<br />
<br />
Use 'open' to read the table from the server and put the data in the TSQLQuery dataset. The data can be accessed through TSQLQuery until the query is closed using 'close'.<br />
<br />
TSQLQuery is a subclass of TDataset. TDataset has a 'Fields' collection that contains all columns of the table. The TDataset also keeps track of the current record. Use 'First', 'Next', 'Prior' and 'Last' to change the current record. 'Bof' returns 'True' if the first record is reached, and 'Eof' returns 'True' if the last record is reached. To read the value of a field in the current record, first find the right 'TField' object and then use 'AsString', 'AsInteger', etc.<br />
<br />
=== Example: reading data from a table ===<br />
Below is an example that displays all values of the table as it was made in [[#How to execute direct queries/make a table?]] above.<br />
<br />
<syntaxhighlight>Program ShowData;<br />
<br />
function CreateQuery(pConnection: TIBConnection; pTransaction: TSQLTransaction): TSQLQuery;<br />
begin<br />
result := TSQLQuery.Create;<br />
result.Database := pConnection;<br />
result.Transaction := pTransaction<br />
end;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
Query : TSQLQuery;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
Query := CreateQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'select * from tblNames';<br />
AConnection.Open;<br />
Query.Open;<br />
while not Query.Eof do<br />
begin<br />
Writeln('ID: ', Query.FieldByName('Name').AsInteger, 'Name: ' +<br />
Query.FieldByName('Name').AsString);<br />
Query.Next;<br />
end;<br />
Query.Close;<br />
AConnection.Close;<br />
Query.Free;<br />
ATransaction.Free;<br />
AConnection.Free;<br />
end.</syntaxhighlight><br />
<br />
(The code above of course is not quite finished, it misses 'try...finally' blocks. However, the above code intends to show the database code and thus the finishing touches are left out.)<br />
Please note that 'TSQLTransaction.StartTransaction' is not used. This is not necessary. When TSQLQuery is opened, the SQL statement is executed and if no transaction is available then a transaction is automatically started. The programmer does not need to start the transaction explicitly.<br />
The same applies for the connection maintained by TSQLConnection. The connection is opened as needed, the line 'Aconnection.Open' is not really required.<br />
If a TSQLTransaction is destroyed, an automatic 'rollback' will be executed. '''Possible changes to data contained in the transaction will be lost.''' <br />
<br />
=== Why does TSQLQuery.RecordCount always return 10? ===<br />
<br />
To count the records in a datase, use '.RecordCount'. However, notice that '.RecordCount' shows the number of records that is already loaded from the server. For performance reasons, SqlDB does not read all records when opening TSQLQuery by default, only the first 10. Only when the eleventh record is accessed will the next set of 10 records be loaded, etc. Using '.Last', all records will be loaded. <br />
<br />
When you want to know the real number of records on the server you can first call '.Last' and then call '.RecordCount'. <br />
<br />
An alternative is available. The number of records returned by the server is set by the '.PacketRecords' property. The default value is 10; if you make it -1 then all records will be loaded at once.<br />
<br />
In current stable FPC, '.RecordCount' does not take filters into account, i.e. it shows the unfiltered total.<br />
<br />
If you need the exact number of records, it often is a better idea to directly query the number of records in a query using another SQL query, but you would have to do that in the same transaction, as other transactions may have changed the number of records in the meanwhile.<br />
<br />
=== Lazarus ===<br />
<br />
Lazarus has various components to show data from a TDataset on a form. Instead of a While-loop and Writeln statements as used above, you can use the components to show the data in a table. Place the right TSQLConnection, TSQLTransaction and TSQLQuery components on a form, then connect them and set them properly. In addition you will need a TDatasource; set the 'TDatasource.Dataset' property to the TSQLQuery component you used. ('''Note''' do not set the 'TSQLQuery.Datasource' property to the TDatasource compnent you used. The 'TSQLQuery.Datasource' property is used only in master-detail tables - see [[MasterDetail]]) Subsequently you may put a TDBGrid onto the form and set the 'Datasource' property of the grid to the TDatasource component you added before.<br />
<br />
To see if it all works, set the 'Connected' property of the TSQLConnection to 'True' in the Lazarus IDE. The IDE will try to connect to the database server immediately. If this works you can set the 'TSQLQuery.Active' property to 'True'. If everything is right, you will see - within the IDE - all data from the table immediately on the screen.<br />
<br />
== How to change data in a table? ==<br />
<br />
To change the data in a record, the TDataset (from which TSQLQuery is derived) must be set to edit mode. To enter edit mode call the '.Edit', '.Insert' or '.Append' methods. Use the '.Edit' method to change the current record. Use '.Insert' to insert a new record before the current record. Use '.Append' to insert a new record at the end of the table. In edit mode you can change field values through the 'Fields' property. Use 'Post' to validate the new data, if the data is valid then the edit mode is left. If you move to another record - for example by using '.Next' - and the dataset is in edit mode, then first '.Post' is called. Use '.Cancel' to discard all changes you made since the last '.Post' call and leave the edit mode.<br />
<br />
<syntaxhighlight>Query.Edit;<br />
Query.FieldByName('NAME').AsString := 'Edited name';<br />
Query.Post;</syntaxhighlight><br />
<br />
The above is not the complete story yet. TSQLQuery is derived from TBufDataset which makes use of buffered updates. Buffered update means that after you called 'Post' the changes in the dataset are visible immediately, but they are not sent to the database server. What does happen is that the changes are maintained in a change log. When the '.ApplyUpdates' method is called, then all changes in the change log are sent to the database. Only then will database server know of the changes. The changes are sent to the server within a transaction of TSQLTransaction. Make sure to properly set the transaction before 'ApplyUpdates'. After applying the updates, a commit must be executed to save the changes on the database server.<br />
<br />
The below is an example of changing the data in a table, sending the changes to the server and comitting the transaction.<br />
<br />
<syntaxhighlight>Program EditData;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
Query : TSQLQuery;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
AConnection.Transaction := ATransaction;<br />
Query := CreateQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'select * from tblNames';<br />
Query.Open;<br />
Query.Edit;<br />
Query.FieldByName('NAME').AsString := 'Edited name';<br />
Query.Post;<br />
Query.UpdateMode := upWhereAll;<br />
Query.ApplyUpdates;<br />
ATransaction.Commit;<br />
Query.Free;<br />
ATransaction.Free;<br />
AConnection.Free;<br />
end.</syntaxhighlight><br />
<br />
For a discussion of 'UpdateMode' continue reading.<br />
<br />
== How does SqlDB send the changes to the database server? ==<br />
<br />
In the code example in [[#How to change data in a table?]], you will find the line<br />
<syntaxhighlight>Query.UpdateMode := upWhereAll;</syntaxhighlight><br />
without explanation of what it does. The best way to find out what that line does is to leave it out. If you leave out the statement and the followed this howto precisely, then you will receive the following error message:<br />
No update query specified and failed to generate one. (No fields for inclusion in where statement found)<br />
To understand what went wrong, you must understand how changes are sent to the database server. The only way to get data in a SQL server is by executing SQL queries. SQL has three types of queries for three different ways of manupulating a record. To create a new record, change or delete a record insert, update and delete statements are executed respectively. An update statement may be as follows:<br />
<syntaxhighlight lang="sql">update TBLNAMES set NAME='Edited name' where ID=1;</syntaxhighlight><br />
To send a change to the database server, Sqldb must assemble an update query. To assemble the query, three things are needed:<br />
; The name of the table : The table name is retrieved from parsing the select query, although this doesn't always work. <br />
; <tt>UPDATE</tt> or <tt>INSERT</tt> clause : These contain the fields that must be changed.<br />
; <tt>WHERE</tt> clause : This contains the fields that determine which records should be changed.<br />
<br />
Every field (each ''TField'' in ''Fields'') has a ProviderFlags property. Only fields with '''pfInUpdate''' in ''ProviderFlags'' will be used in the update or insert cluase of a query. By default all fields have ''pfInUpdate'' set in their ''ProviderFlags'' property.<br />
<br />
Which fields are used in the <tt>WHERE</tt> clause depends on the ''UpdateMode'' property of the query and the ''ProviderFlags'' property of the fields. Fields with ''pfInkey'' in their ''ProviderFlags'' are always used in the <tt>WHERE</tt> clause. A field will have the ''pfInKey'' flag set automatically if the field is part of the primary key of the table and 'TSQLQuery.UsePrimaryKeyAsKey' returns 'True'.<br />
<br />
The default value for ''UpdateMode'' of the query is ''upWhereKeyOnly''. In this update mode only fields with ''pfInkey'' in their ''ProviderFlags'' property are used in the <tt>WHERE</tt> clause. If none of the fields have their ''pfInKey'' flag set, then no fields are available for the <tt>WHERE</tt> clause and the error message from the beginning of this section will be returned. You can solve the issue by:<br />
* Adding a primary key to the table and set ''TSQLQuery.UsePrimaryKeyAsKey'' to 'True', or<br />
* Setting the ''pfInkey'' flag for one or more fields in code.<br />
<br />
The '''UpdateMode''' property knows two more possible values. 'upWhereAll' can be used to add all fields with the 'pfInWhere' flag set to the <tt>WHERE</tt> clause. By default all fields have this flag set. 'upWhereChanged' can be used to add only those fields that have the 'pfInWhere' flag set '''and''' that are changed in the current record.<br />
<br />
== How to execute a query using TSQLQuery? ==<br />
<br />
Next to statements that return a dataset (see [[#How to read data from a table?]]) SQL has statements that do not return data. For example <tt>INSERT</tt>, <tt>UPDATE</tt> and <tt>DELETE</tt> statements do not return data. These statements can be executed using ''[[#How to execute direct queries/make a table?|TSQLConnection.ExecuteDirect]]'', but TSQLQuery can also be used. If you do not expect return data use ''TSQLQuery.ExecSQL'' instead of ''TSQLQuery.Open''. As mentioned earlier, use ''TSQLQuery.Open'' to open the dataset returned by the SQL statement. <br />
<br />
The following procedure creates a table and inserts two records using TSQLQuery.<br />
<br />
<syntaxhighlight>procedure CreateTable;<br />
<br />
var <br />
Query : TSQLQuery;<br />
<br />
begin<br />
Query := CreateQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (1,''Name1'');';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (2,''Name2'');';<br />
Query.ExecSQL;<br />
<br />
Query.Close;<br />
Query.Free;<br />
end;</syntaxhighlight><br />
<br />
== How to use parameters in a query? ==<br />
<br />
In the code example of [[#How to execute a query using TSQLQuery?]] the same query is used twice, only the values to be inserted differ. A better way to do this is by using parameters in the query. <br />
<br />
The syntax of parameters in queries is different per database system, but the differences are handled by TSQLQuery. Replace the values in the query with a colon followed by the name of the parameter you want to use. For example:<br />
<syntaxhighlight>Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';</syntaxhighlight><br />
<br />
This query will create two parameters: 'ID' and 'NAME'.<br />
To determine the parameters, the query is parsed at the moment the text of ''TSQLQuery.SQL'' is assigned or changed. All existing parameters will be removed and the new parameters will be added to the 'TSQLQuery.Params' property. Assigning a value to a parameter is similar to assigning a value to a field in the dataset:<br />
<syntaxhighlight>Query.Params.ParamByName('Name').AsString := 'Name1'</syntaxhighlight>;<br />
<br />
You can't tell from the query what kind of data must be stored in the parameter. The data type of the parameter is determined at the moment a value is first assigned to the parameter. By assigning a value using '.AsString', the parameter is assigned the data type 'ftString'. You can determine the data type directly by setting the 'DataType' property. If an incorrect datatype is assigned to the parameter, then problems will occur during opening or executing the query.<br />
See [[Database field type]] for more information on data types.<br />
<br />
=== Select query ===<br />
An example of a select query with parameters would be to change something like this:<br />
<syntaxhighlight><br />
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = '''+Edit1.Text+''' ORDER BY NAME ';<br />
</syntaxhighlight><br />
to something like this:<br />
<syntaxhighlight><br />
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = :NAMEPARAM ORDER BY NAME ';<br />
Query.Params.ParamByName('NAMEPARAM').AsString := Edit1.Text;<br />
</syntaxhighlight><br />
<br />
=== Example ===<br />
The following example creates the same table as the previous example, but now parameters are used:<br />
<br />
<syntaxhighlight><br />
procedure CreateTableUsingParameters;<br />
<br />
var <br />
Query : TSQLQuery;<br />
<br />
begin<br />
Query := CreateQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';<br />
Query.Prepare;<br />
<br />
Query.Params.ParamByName('ID').AsInteger := 1;<br />
Query.Params.ParamByName('NAME').AsString := 'Name1';<br />
Query.ExecSQL;<br />
<br />
Query.Params.ParamByName('ID').AsInteger := 2;<br />
Query.Params.ParamByName('NAME').AsString := 'Name2';<br />
Query.ExecSQL;<br />
<br />
//Query.UnPrepare; // no need to call this; should be called by Query.Close<br />
Query.Close;<br />
Query.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
Notice that this example requires more code than the example without the parameters. Then what is the use of using parameters? <br />
<br />
Speed is one of the reasons. The example with parameters is faster, because the database server parses the query only once (in the .Prepare statement or at first run). <br />
<br />
Another reason to use prepared statements is prevention of [http://en.wikipedia.org/wiki/SQL_injection SQL-injection] (see also [[Secure programming]]. <br />
<br />
Finally, in some cases it just simplifies coding.<br />
<br />
== Troubleshooting: TSQLConnection logging ==<br />
You can let a TSQLConnection log what it is doing. This can be handy to see what your Lazarus program sends to the database exactly, to debug the database components themselves and perhaps to optimize your queries.<br />
NB: if you use prepared statements/parametrized queries (see section above), the parameters are often sent in binary by the TSQLConnection descendent (e.g. TIBConnection), so you can't just copy/paste the logged SQL into a database query tool.<br />
Regardless, connection logging can give a lot of insight in what your program is doing.<br />
<br />
Alternatives are: <br />
# you can use the debugger to step through the database code if you have built FPC (and Lazarus) with debugging enabled. <br />
# if you use ODBC drivers (at least on Windows) you could enable tracelog output in the ODBC control panel.<br />
# many databases allow you to monitor all statements sent to it from a certain IP address/connection.<br />
<br />
<br />
If you use TSQLConnection logging, two things are required:<br />
# indicate which event types your TSQLConnection should log<br />
# point TSQLConnection at a function that receives the events and processes them (logs them to file, prints them to screen, etc.).<br />
That function must be of type TDBLogNotifyEvent (see sqldb.pp), so it needs this signature:<br />
<syntaxhighlight><br />
TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String) of object;<br />
</syntaxhighlight><br />
<br />
=== FPC (or: the manual way) ===<br />
A code snippet can illustrate this:<br />
<syntaxhighlight><br />
uses<br />
...<br />
TSQLConnection, //or a child object like TIBConnection, TMSSQLConnection<br />
...<br />
var<br />
type <br />
TMyApplication = class(TCustomApplication); //this is our application that uses the connection<br />
...<br />
private<br />
// This example stores the logged events in this stringlist:<br />
FConnectionLog: TStringList;<br />
...<br />
protected<br />
// This procedure will receive the events that are logged by the connection:<br />
procedure GetLogEvent(Sender: TSQLConnection; EventType: TDBEventType; Const Msg : String);<br />
...<br />
procedure TMyApplication.GetLogEvent(Sender: TSQLConnection;<br />
EventType: TDBEventType; const Msg: String);<br />
// The procedure is called by TSQLConnection and saves the received log messages<br />
// in the FConnectionLog stringlist<br />
var<br />
Source: string;<br />
begin<br />
// Nicely right aligned...<br />
case EventType of<br />
detCustom: Source:='Custom: ';<br />
detPrepare: Source:='Prepare: ';<br />
detExecute: Source:='Execute: ';<br />
detFetch: Source:='Fetch: ';<br />
detCommit: Source:='Commit: ';<br />
detRollBack: Source:='Rollback:';<br />
else Source:='Unknown event. Please fix program code.';<br />
end;<br />
FConnectionLog.Add(Source + ' ' + Msg);<br />
end;<br />
<br />
...<br />
// We do need to tell our TSQLConnection what to log:<br />
FConnection.LogEvents:=LogAllEvents; //= [detCustom, detPrepare, detExecute, detFetch, detCommit, detRollBack]<br />
// ... and to which procedure the connection should send the events:<br />
FConnection.OnLog:=@Self.GetLogEvent;<br />
...<br />
// now we can use the connection and the FConnectionLog stringlist will fill with log messages.<br />
</syntaxhighlight><br />
<br />
You can also use TSQLConnection's GlobalDBLogHook instead to log everything from multiple connections.<br />
<br />
=== Lazarus (or: the quick way) ===<br />
Finally, the description above is the FPC way of doing things as indicated in the introduction; if using Lazarus, a quicker way is to assign an event handler to the TSQLConnection's OnLog event.<br />
<br />
[[Category:Databases]]<br />
<br />
== See also ==<br />
* [[Working With TSQLQuery]]<br />
<br />
[[Category:Databases]]</div>Enyhttps://wiki.freepascal.org/index.php?title=SqlDBHowto&diff=87718SqlDBHowto2015-04-02T11:47:40Z<p>Eny: spacing</p>
<hr />
<div>{{SqlDBHowto}}<br />
<br />
== Introduction ==<br />
<br />
This page started as a translation of [[SqlDBHowto/nl]]. Initially, the Dutch text was leading, meanwhile, the two articles have been synchronized. <br />
<br />
This text is setup as a 'how-to'. I want to answer a number of questions one by one, and explain how you can use the various classes. All those questions are put one after the other and form a sort of tutorial. <br />
<br />
I will try to word it in such a way that the text can be used for Lazarus as well as FreePascal. However, the examples are for FreePascal (i.e. they are console applications.)<br />
<br />
== Where can I find official documentation? ==<br />
<br />
Please see the official documentation at [http://www.freepascal.org/docs-html/fcl/sqldb/index.html SQLDB documentation]. <br />
<br />
== How to connect to a database server? ==<br />
<br />
SqlDB doesn't connect to a database server directly but uses a client that corresponds to the used database server. SqlDB sends the commands to the client library; the client library connects to the database and and transfers the commands. This means that a client library must be installed on the computer to make a connection to a database. Under Windows a client is usually a .dll, under Linux an .so and under OS/X a .dylib.<br />
<br />
When the client library is installed properly you can connect to a database server using a TSQLConnection component. Various TSQLConnection components are available for different database servers (see [[SQLdb_Package]]):<br />
* Firebird/Interbase: TIBConnection (see [[firebird#Connection]])<br />
* MS SQL Server: TMSSQLConnection (available since FPC 2.6.1, see [[Lazarus_Database_Overview#Lazarus_and_MSSQL.2FSybase]])<br />
* MySQL v4.0: TMySQL40Connection (see [[mysql#SQLDB]])<br />
* MySQL v4.1: TMySQL41Connection (see [[mysql#SQLDB]])<br />
* MySQL v5.0: TMySQL50Connection (see [[mysql#SQLDB]])<br />
* MySQL v5.1: TMySQL51Connection (available since FPC version 2.5.1, see [[mysql#SQLDB]])<br />
* MySQL v5.5: TMySQL55Connection (available since Lazarus 1.0.8/FPC version 2.6.2, see [[mysql#SQLDB]])<br />
* MySQL v5.6: TMySQL56Connection (available in Lazarus 1.2.4/FPC version 2.6.4, see [[mysql#SQLDB]])<br />
* ODBC: TODBCConnection (see [[ODBCConn#TODBCConnection]])<br />
* Oracle: TOracleConnection (see [[oracle|Oracle]])<br />
* PostgreSQL: TPQConnection (see [[postgresql#SQLDB]])<br />
* Sqlite3: TSQLite3Connection (available since FPC version 2.2.2, see [[SQLite#Built-in_SQLDB]]) <br />
* Sybase ASE: TSybaseConnection (available since FPC 2.6.1, see [[Lazarus_Database_Overview#Lazarus_and_MSSQL.2FSybase]])<br />
<br />
Note for MySQL - There are many differences between the client versions to the extent that the clients and connections cannot be interchanged. If a MySQL client library version 4.1 is installed, you have to use a TMySQL41Connection. This is not related to the MySQL server; using the MySQL 4.1 client library you can probably connect to a MySQL 5.0 server (see MySQL documentation regarding what combinations are supported).<br />
<br />
Although details differ for the various databases, in general you need to set four properties to connect to a database server: <br />
* the server name or IP address<br />
* the name of the database<br />
* the username <br />
* the password<br />
When these properties are set, you can create a connection with the 'open' method. If the connection fails, a EDatabaseError exception is thrown. Use the property 'connected' to test if a connection has been made with the database server. Use the 'close' method to end the connection with the server.<br />
<br />
<syntaxhighlight><br />
Program ConnectDB;<br />
<br />
function CreateConnection: TIBConnection;<br />
begin<br />
result := TIBConnection.Create(nil);<br />
result.Hostname := 'localhost';<br />
result.DatabaseName := '/opt/firebird/examples/employee.fdb';<br />
result.UserName := 'sysdba';<br />
result.Password := 'masterkey';<br />
end;<br />
<br />
var <br />
AConnection : TIBConnection;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
AConnection.Open;<br />
if Aconnection.Connected then<br />
writeln('Successful connect!')<br />
else<br />
writeln('This is not possible, because if the connection failed, ' +<br />
'an exception should be raised, so this code would not ' +<br />
'be executed');<br />
AConnection.Close;<br />
AConnection.Free;<br />
end.<br />
</syntaxhighlight><br />
<br />
If an exception is thrown, read the error message carefully. It may be that the database server is not running, the user name or password are incorrect or the database name or IP address are typed incorrectly. If the error message states that the client library cannot be found, then check if the client is installed correctly. Often the error message states literally the name of the file looked for.<br />
<br />
== How to execute direct queries/make a table? ==<br />
<br />
SqlDB - the name says it all - only works with database server that make use of SQL. SQL stands for 'Structured Query Language' SQL is a language developed to allow working with relational databases. Virtually every database system has its own dialect, but a large number of SQL statements are the same for all database systems.<br />
<br />
In FPC, there is a difference between:<br />
* SQL statements that return information (a dataset). For this, you have to use the TSQLQuery component; see [[#How to read data from a table?]]. <br />
* statements that do not return information but do something else, e.g. update data. For this, you may also use the 'ExecuteDirect' method of a TSQLConnection. (You can also use this if you get a dataset back but are not interested in the results, e.g. in a selectable stored procedure).<br />
<br />
Most database system execute SQL statements within a transaction. If you want changes made within a transaction available in other transactions, or have those changes available even after closing the transaction(!), then you have to 'commit' the transaction. <br />
<br />
To support transactions Sqldb contains the TSQLTransaction component. A SQL statement that is executed by Sqldb must always be executed within a transaction, even if the database system does not support transactions. Also, there are database systems that do support transaction for which TSQLConnection does not (yet) support transaction. Even then, you must use the TSQLTransaction component.<br />
<br />
To use <tt>TSQLConnection.ExecuteDirect</tt> to execute a SQL statement you must specify which 'Transaction' must be used. In turn, to use TSQLTransaction you must specify which TSQLConnection component must be used.<br />
<br />
The following example creates a table 'TBLNAMES' with fields 'NAME' and 'ID' and inserts two records. The used SQL statements are not explained. For more information about the SQL statements, their use and syntax, please refer to the database system documentation. The procedure 'CreateConnection' is defined in the code example in [[#How to connect to a database server?]] above.<br />
<br />
<syntaxhighlight>program CreateTable;<br />
<br />
function CreateTransaction(pDB: TIBConnection): TSQLTransaction;<br />
begin<br />
result := TSQLTransaction.Create;<br />
result.Database := pDB;<br />
end;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
AConnection.Transaction := ATransaction;<br />
AConnection.Open;<br />
ATransaction.StartTransaction;<br />
AConnection.ExecuteDirect('create table TBLNAMES (ID integer, NAME varchar(40));'); <br />
<br />
// Some database-server types need a commit before you can use a newly created table. (Firebird)<br />
// With .Commit you also close the transaction<br />
ATransaction.Commit; <br />
<br />
ATransaction.StartTransaction;<br />
AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (1,'Name1');'); <br />
AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (2,'Name2');'); <br />
ATransaction.Commit; <br />
AConnection.Close;<br />
AConnection.Free;<br />
ATransaction.Free;<br />
end.</syntaxhighlight><br />
<br />
== How to read data from a table? ==<br />
<br />
Use the TSQLQuery component to read data from a table. A TSQLQuery component must be connected to a TSQLConnection component and a TSQLTransaction component to do its work. Setting the TSQLConnection and TSQLTransaction is discussed in [[#How to connect to a database server? ]] and [[#How to execute direct queries/make a table?]]. <br />
<br />
When the TSQLConnection, TSQLTransaction and TSQLQuery are connected, then TSQLQuery needs to be further configured to work. TSQLQuery has a 'SQL' property containing a TStrings object. The 'SQL' property contains a SQL statement that must be executed. If all data from a table <tt>tablename</tt> must be read, then set the 'SQL' property to:<br />
<syntaxhighlight lang="sql">'SELECT * FROM tablename;'</syntaxhighlight>.<br />
<br />
Use 'open' to read the table from the server and put the data in the TSQLQuery dataset. The data can be accessed through TSQLQuery until the query is closed using 'close'.<br />
<br />
TSQLQuery is a subclass of TDataset. TDataset has a 'Fields' collection that contains all columns of the table. The TDataset also keeps track of the current record. Use 'First', 'Next', 'Prior' and 'Last' to change the current record. 'Bof' returns 'True' if the first record is reached, and 'Eof' returns 'True' if the last record is reached. To read the value of a field in the current record, first find the right 'TField' object and then use 'AsString', 'AsInteger', etc.<br />
<br />
=== Example: reading data from a table ===<br />
Below is an example that displays all values of the table as it was made in [[#How to execute direct queries/make a table?]] above.<br />
<br />
<syntaxhighlight>Program ShowData;<br />
<br />
function GetQuery(pConnection: TIBConnection; pTransaction: TSQLTransaction): TSQLQuery;<br />
begin<br />
result := TSQLQuery.Create;<br />
result.Database := pConnection;<br />
result.Transaction := pTransaction<br />
end;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
Query : TSQLQuery;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'select * from tblNames';<br />
AConnection.Open;<br />
Query.Open;<br />
while not Query.Eof do<br />
begin<br />
Writeln('ID: ', Query.FieldByName('Name').AsInteger, 'Name: ' +<br />
Query.FieldByName('Name').AsString);<br />
Query.Next;<br />
end;<br />
Query.Close;<br />
AConnection.Close;<br />
Query.Free;<br />
ATransaction.Free;<br />
AConnection.Free;<br />
end.</syntaxhighlight><br />
<br />
(The code above of course is not quite finished, it misses 'try...finally' blocks. However, the above code intends to show the database code and thus the finishing touches are left out.)<br />
Please note that 'TSQLTransaction.StartTransaction' is not used. This is not necessary. When TSQLQuery is opened, the SQL statement is executed and if no transaction is available then a transaction is automatically started. The programmer does not need to start the transaction explicitly.<br />
The same applies for the connection maintained by TSQLConnection. The connection is opened as needed, the line 'Aconnection.Open' is not really required.<br />
If a TSQLTransaction is destroyed, an automatic 'rollback' will be executed. '''Possible changes to data contained in the transaction will be lost.''' <br />
<br />
=== Why does TSQLQuery.RecordCount always return 10? ===<br />
<br />
To count the records in a datase, use '.RecordCount'. However, notice that '.RecordCount' shows the number of records that is already loaded from the server. For performance reasons, SqlDB does not read all records when opening TSQLQuery by default, only the first 10. Only when the eleventh record is accessed will the next set of 10 records be loaded, etc. Using '.Last', all records will be loaded. <br />
<br />
When you want to know the real number of records on the server you can first call '.Last' and then call '.RecordCount'. <br />
<br />
An alternative is available. The number of records returned by the server is set by the '.PacketRecords' property. The default value is 10; if you make it -1 then all records will be loaded at once.<br />
<br />
In current stable FPC, '.RecordCount' does not take filters into account, i.e. it shows the unfiltered total.<br />
<br />
If you need the exact number of records, it often is a better idea to directly query the number of records in a query using another SQL query, but you would have to do that in the same transaction, as other transactions may have changed the number of records in the meanwhile.<br />
<br />
=== Lazarus ===<br />
<br />
Lazarus has various components to show data from a TDataset on a form. Instead of a While-loop and Writeln statements as used above, you can use the components to show the data in a table. Place the right TSQLConnection, TSQLTransaction and TSQLQuery components on a form, then connect them and set them properly. In addition you will need a TDatasource; set the 'TDatasource.Dataset' property to the TSQLQuery component you used. ('''Note''' do not set the 'TSQLQuery.Datasource' property to the TDatasource compnent you used. The 'TSQLQuery.Datasource' property is used only in master-detail tables - see [[MasterDetail]]) Subsequently you may put a TDBGrid onto the form and set the 'Datasource' property of the grid to the TDatasource component you added before.<br />
<br />
To see if it all works, set the 'Connected' property of the TSQLConnection to 'True' in the Lazarus IDE. The IDE will try to connect to the database server immediately. If this works you can set the 'TSQLQuery.Active' property to 'True'. If everything is right, you will see - within the IDE - all data from the table immediately on the screen.<br />
<br />
== How to change data in a table? ==<br />
<br />
To change the data in a record, the TDataset (from which TSQLQuery is derived) must be set to edit mode. To enter edit mode call the '.Edit', '.Insert' or '.Append' methods. Use the '.Edit' method to change the current record. Use '.Insert' to insert a new record before the current record. Use '.Append' to insert a new record at the end of the table. In edit mode you can change field values through the 'Fields' property. Use 'Post' to validate the new data, if the data is valid then the edit mode is left. If you move to another record - for example by using '.Next' - and the dataset is in edit mode, then first '.Post' is called. Use '.Cancel' to discard all changes you made since the last '.Post' call and leave the edit mode.<br />
<br />
<syntaxhighlight>Query.Edit;<br />
Query.FieldByName('NAME').AsString := 'Edited name';<br />
Query.Post;</syntaxhighlight><br />
<br />
The above is not the complete story yet. TSQLQuery is derived from TBufDataset which makes use of buffered updates. Buffered update means that after you called 'Post' the changes in the dataset are visible immediately, but they are not sent to the database server. What does happen is that the changes are maintained in a change log. When the '.ApplyUpdates' method is called, then all changes in the change log are sent to the database. Only then will database server know of the changes. The changes are sent to the server within a transaction of TSQLTransaction. Make sure to properly set the transaction before 'ApplyUpdates'. After applying the updates, a commit must be executed to save the changes on the database server.<br />
<br />
The below is an example of changing the data in a table, sending the changes to the server and comitting the transaction.<br />
<br />
<syntaxhighlight>Program EditData;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
Query : TSQLQuery;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
AConnection.Transaction := ATransaction;<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'select * from tblNames';<br />
Query.Open;<br />
Query.Edit;<br />
Query.FieldByName('NAME').AsString := 'Edited name';<br />
Query.Post;<br />
Query.UpdateMode := upWhereAll;<br />
Query.ApplyUpdates;<br />
ATransaction.Commit;<br />
Query.Free;<br />
ATransaction.Free;<br />
AConnection.Free;<br />
end.</syntaxhighlight><br />
<br />
For a discussion of 'UpdateMode' continue reading.<br />
<br />
== How does SqlDB send the changes to the database server? ==<br />
<br />
In the code example in [[#How to change data in a table?]], you will find the line<br />
<syntaxhighlight>Query.UpdateMode := upWhereAll;</syntaxhighlight><br />
without explanation of what it does. The best way to find out what that line does is to leave it out. If you leave out the statement and the followed this howto precisely, then you will receive the following error message:<br />
No update query specified and failed to generate one. (No fields for inclusion in where statement found)<br />
To understand what went wrong, you must understand how changes are sent to the database server. The only way to get data in a SQL server is by executing SQL queries. SQL has three types of queries for three different ways of manupulating a record. To create a new record, change or delete a record insert, update and delete statements are executed respectively. An update statement may be as follows:<br />
<syntaxhighlight lang="sql">update TBLNAMES set NAME='Edited name' where ID=1;</syntaxhighlight><br />
To send a change to the database server, Sqldb must assemble an update query. To assemble the query, three things are needed:<br />
; The name of the table : The table name is retrieved from parsing the select query, although this doesn't always work. <br />
; <tt>UPDATE</tt> or <tt>INSERT</tt> clause : These contain the fields that must be changed.<br />
; <tt>WHERE</tt> clause : This contains the fields that determine which records should be changed.<br />
<br />
Every field (each ''TField'' in ''Fields'') has a ProviderFlags property. Only fields with '''pfInUpdate''' in ''ProviderFlags'' will be used in the update or insert cluase of a query. By default all fields have ''pfInUpdate'' set in their ''ProviderFlags'' property.<br />
<br />
Which fields are used in the <tt>WHERE</tt> clause depends on the ''UpdateMode'' property of the query and the ''ProviderFlags'' property of the fields. Fields with ''pfInkey'' in their ''ProviderFlags'' are always used in the <tt>WHERE</tt> clause. A field will have the ''pfInKey'' flag set automatically if the field is part of the primary key of the table and 'TSQLQuery.UsePrimaryKeyAsKey' returns 'True'.<br />
<br />
The default value for ''UpdateMode'' of the query is ''upWhereKeyOnly''. In this update mode only fields with ''pfInkey'' in their ''ProviderFlags'' property are used in the <tt>WHERE</tt> clause. If none of the fields have their ''pfInKey'' flag set, then no fields are available for the <tt>WHERE</tt> clause and the error message from the beginning of this section will be returned. You can solve the issue by:<br />
* Adding a primary key to the table and set ''TSQLQuery.UsePrimaryKeyAsKey'' to 'True', or<br />
* Setting the ''pfInkey'' flag for one or more fields in code.<br />
<br />
The '''UpdateMode''' property knows two more possible values. 'upWhereAll' can be used to add all fields with the 'pfInWhere' flag set to the <tt>WHERE</tt> clause. By default all fields have this flag set. 'upWhereChanged' can be used to add only those fields that have the 'pfInWhere' flag set '''and''' that are changed in the current record.<br />
<br />
== How to execute a query using TSQLQuery? ==<br />
<br />
Next to statements that return a dataset (see [[#How to read data from a table?]]) SQL has statements that do not return data. For example <tt>INSERT</tt>, <tt>UPDATE</tt> and <tt>DELETE</tt> statements do not return data. These statements can be executed using ''[[#How to execute direct queries/make a table?|TSQLConnection.ExecuteDirect]]'', but TSQLQuery can also be used. If you do not expect return data use ''TSQLQuery.ExecSQL'' instead of ''TSQLQuery.Open''. As mentioned earlier, use ''TSQLQuery.Open'' to open the dataset returned by the SQL statement. <br />
<br />
The following procedure creates a table and inserts two records using TSQLQuery.<br />
<br />
<syntaxhighlight>procedure CreateTable;<br />
<br />
var <br />
Query : TSQLQuery;<br />
<br />
begin<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (1,''Name1'');';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (2,''Name2'');';<br />
Query.ExecSQL;<br />
<br />
Query.Close;<br />
Query.Free;<br />
end;</syntaxhighlight><br />
<br />
== How to use parameters in a query? ==<br />
<br />
In the code example of [[#How to execute a query using TSQLQuery?]] the same query is used twice, only the values to be inserted differ. A better way to do this is by using parameters in the query. <br />
<br />
The syntax of parameters in queries is different per database system, but the differences are handled by TSQLQuery. Replace the values in the query with a colon followed by the name of the parameter you want to use. For example:<br />
<syntaxhighlight>Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';</syntaxhighlight><br />
<br />
This query will create two parameters: 'ID' and 'NAME'.<br />
To determine the parameters, the query is parsed at the moment the text of ''TSQLQuery.SQL'' is assigned or changed. All existing parameters will be removed and the new parameters will be added to the 'TSQLQuery.Params' property. Assigning a value to a parameter is similar to assigning a value to a field in the dataset:<br />
<syntaxhighlight>Query.Params.ParamByName('Name').AsString := 'Name1'</syntaxhighlight>;<br />
<br />
You can't tell from the query what kind of data must be stored in the parameter. The data type of the parameter is determined at the moment a value is first assigned to the parameter. By assigning a value using '.AsString', the parameter is assigned the data type 'ftString'. You can determine the data type directly by setting the 'DataType' property. If an incorrect datatype is assigned to the parameter, then problems will occur during opening or executing the query.<br />
See [[Database field type]] for more information on data types.<br />
<br />
=== Select query ===<br />
An example of a select query with parameters would be to change something like this:<br />
<syntaxhighlight><br />
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = '''+Edit1.Text+''' ORDER BY NAME ';<br />
</syntaxhighlight><br />
to something like this:<br />
<syntaxhighlight><br />
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = :NAMEPARAM ORDER BY NAME ';<br />
Query.Params.ParamByName('NAMEPARAM').AsString := Edit1.Text;<br />
</syntaxhighlight><br />
<br />
=== Example ===<br />
The following example creates the same table as the previous example, but now parameters are used:<br />
<br />
<syntaxhighlight><br />
procedure CreateTableUsingParameters;<br />
<br />
var <br />
Query : TSQLQuery;<br />
<br />
begin<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';<br />
Query.Prepare;<br />
<br />
Query.Params.ParamByName('ID').AsInteger := 1;<br />
Query.Params.ParamByName('NAME').AsString := 'Name1';<br />
Query.ExecSQL;<br />
<br />
Query.Params.ParamByName('ID').AsInteger := 2;<br />
Query.Params.ParamByName('NAME').AsString := 'Name2';<br />
Query.ExecSQL;<br />
<br />
//Query.UnPrepare; // no need to call this; should be called by Query.Close<br />
Query.Close;<br />
Query.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
Notice that this example requires more code than the example without the parameters. Then what is the use of using parameters? <br />
<br />
Speed is one of the reasons. The example with parameters is faster, because the database server parses the query only once (in the .Prepare statement or at first run). <br />
<br />
Another reason to use prepared statements is prevention of [http://en.wikipedia.org/wiki/SQL_injection SQL-injection] (see also [[Secure programming]]. <br />
<br />
Finally, in some cases it just simplifies coding.<br />
<br />
== Troubleshooting: TSQLConnection logging ==<br />
You can let a TSQLConnection log what it is doing. This can be handy to see what your Lazarus program sends to the database exactly, to debug the database components themselves and perhaps to optimize your queries.<br />
NB: if you use prepared statements/parametrized queries (see section above), the parameters are often sent in binary by the TSQLConnection descendent (e.g. TIBConnection), so you can't just copy/paste the logged SQL into a database query tool.<br />
Regardless, connection logging can give a lot of insight in what your program is doing.<br />
<br />
Alternatives are: <br />
# you can use the debugger to step through the database code if you have built FPC (and Lazarus) with debugging enabled. <br />
# if you use ODBC drivers (at least on Windows) you could enable tracelog output in the ODBC control panel.<br />
# many databases allow you to monitor all statements sent to it from a certain IP address/connection.<br />
<br />
<br />
If you use TSQLConnection logging, two things are required:<br />
# indicate which event types your TSQLConnection should log<br />
# point TSQLConnection at a function that receives the events and processes them (logs them to file, prints them to screen, etc.).<br />
That function must be of type TDBLogNotifyEvent (see sqldb.pp), so it needs this signature:<br />
<syntaxhighlight><br />
TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String) of object;<br />
</syntaxhighlight><br />
<br />
=== FPC (or: the manual way) ===<br />
A code snippet can illustrate this:<br />
<syntaxhighlight><br />
uses<br />
...<br />
TSQLConnection, //or a child object like TIBConnection, TMSSQLConnection<br />
...<br />
var<br />
type <br />
TMyApplication = class(TCustomApplication); //this is our application that uses the connection<br />
...<br />
private<br />
// This example stores the logged events in this stringlist:<br />
FConnectionLog: TStringList;<br />
...<br />
protected<br />
// This procedure will receive the events that are logged by the connection:<br />
procedure GetLogEvent(Sender: TSQLConnection; EventType: TDBEventType; Const Msg : String);<br />
...<br />
procedure TMyApplication.GetLogEvent(Sender: TSQLConnection;<br />
EventType: TDBEventType; const Msg: String);<br />
// The procedure is called by TSQLConnection and saves the received log messages<br />
// in the FConnectionLog stringlist<br />
var<br />
Source: string;<br />
begin<br />
// Nicely right aligned...<br />
case EventType of<br />
detCustom: Source:='Custom: ';<br />
detPrepare: Source:='Prepare: ';<br />
detExecute: Source:='Execute: ';<br />
detFetch: Source:='Fetch: ';<br />
detCommit: Source:='Commit: ';<br />
detRollBack: Source:='Rollback:';<br />
else Source:='Unknown event. Please fix program code.';<br />
end;<br />
FConnectionLog.Add(Source + ' ' + Msg);<br />
end;<br />
<br />
...<br />
// We do need to tell our TSQLConnection what to log:<br />
FConnection.LogEvents:=LogAllEvents; //= [detCustom, detPrepare, detExecute, detFetch, detCommit, detRollBack]<br />
// ... and to which procedure the connection should send the events:<br />
FConnection.OnLog:=@Self.GetLogEvent;<br />
...<br />
// now we can use the connection and the FConnectionLog stringlist will fill with log messages.<br />
</syntaxhighlight><br />
<br />
You can also use TSQLConnection's GlobalDBLogHook instead to log everything from multiple connections.<br />
<br />
=== Lazarus (or: the quick way) ===<br />
Finally, the description above is the FPC way of doing things as indicated in the introduction; if using Lazarus, a quicker way is to assign an event handler to the TSQLConnection's OnLog event.<br />
<br />
[[Category:Databases]]<br />
<br />
== See also ==<br />
* [[Working With TSQLQuery]]<br />
<br />
[[Category:Databases]]</div>Enyhttps://wiki.freepascal.org/index.php?title=SqlDBHowto&diff=87717SqlDBHowto2015-04-02T11:46:48Z<p>Eny: Fix global variable errors</p>
<hr />
<div>{{SqlDBHowto}}<br />
<br />
== Introduction ==<br />
<br />
This page started as a translation of [[SqlDBHowto/nl]]. Initially, the Dutch text was leading, meanwhile, the two articles have been synchronized. <br />
<br />
This text is setup as a 'how-to'. I want to answer a number of questions one by one, and explain how you can use the various classes. All those questions are put one after the other and form a sort of tutorial. <br />
<br />
I will try to word it in such a way that the text can be used for Lazarus as well as FreePascal. However, the examples are for FreePascal (i.e. they are console applications.)<br />
<br />
== Where can I find official documentation? ==<br />
<br />
Please see the official documentation at [http://www.freepascal.org/docs-html/fcl/sqldb/index.html SQLDB documentation]. <br />
<br />
== How to connect to a database server? ==<br />
<br />
SqlDB doesn't connect to a database server directly but uses a client that corresponds to the used database server. SqlDB sends the commands to the client library; the client library connects to the database and and transfers the commands. This means that a client library must be installed on the computer to make a connection to a database. Under Windows a client is usually a .dll, under Linux an .so and under OS/X a .dylib.<br />
<br />
When the client library is installed properly you can connect to a database server using a TSQLConnection component. Various TSQLConnection components are available for different database servers (see [[SQLdb_Package]]):<br />
* Firebird/Interbase: TIBConnection (see [[firebird#Connection]])<br />
* MS SQL Server: TMSSQLConnection (available since FPC 2.6.1, see [[Lazarus_Database_Overview#Lazarus_and_MSSQL.2FSybase]])<br />
* MySQL v4.0: TMySQL40Connection (see [[mysql#SQLDB]])<br />
* MySQL v4.1: TMySQL41Connection (see [[mysql#SQLDB]])<br />
* MySQL v5.0: TMySQL50Connection (see [[mysql#SQLDB]])<br />
* MySQL v5.1: TMySQL51Connection (available since FPC version 2.5.1, see [[mysql#SQLDB]])<br />
* MySQL v5.5: TMySQL55Connection (available since Lazarus 1.0.8/FPC version 2.6.2, see [[mysql#SQLDB]])<br />
* MySQL v5.6: TMySQL56Connection (available in Lazarus 1.2.4/FPC version 2.6.4, see [[mysql#SQLDB]])<br />
* ODBC: TODBCConnection (see [[ODBCConn#TODBCConnection]])<br />
* Oracle: TOracleConnection (see [[oracle|Oracle]])<br />
* PostgreSQL: TPQConnection (see [[postgresql#SQLDB]])<br />
* Sqlite3: TSQLite3Connection (available since FPC version 2.2.2, see [[SQLite#Built-in_SQLDB]]) <br />
* Sybase ASE: TSybaseConnection (available since FPC 2.6.1, see [[Lazarus_Database_Overview#Lazarus_and_MSSQL.2FSybase]])<br />
<br />
Note for MySQL - There are many differences between the client versions to the extent that the clients and connections cannot be interchanged. If a MySQL client library version 4.1 is installed, you have to use a TMySQL41Connection. This is not related to the MySQL server; using the MySQL 4.1 client library you can probably connect to a MySQL 5.0 server (see MySQL documentation regarding what combinations are supported).<br />
<br />
Although details differ for the various databases, in general you need to set four properties to connect to a database server: <br />
* the server name or IP address<br />
* the name of the database<br />
* the username <br />
* the password<br />
When these properties are set, you can create a connection with the 'open' method. If the connection fails, a EDatabaseError exception is thrown. Use the property 'connected' to test if a connection has been made with the database server. Use the 'close' method to end the connection with the server.<br />
<br />
<syntaxhighlight><br />
Program ConnectDB;<br />
<br />
<br />
function CreateConnection: TIBConnection;<br />
begin<br />
result := TIBConnection.Create(nil);<br />
result.Hostname := 'localhost';<br />
result.DatabaseName := '/opt/firebird/examples/employee.fdb';<br />
result.UserName := 'sysdba';<br />
result.Password := 'masterkey';<br />
end;<br />
<br />
var <br />
AConnection : TIBConnection;<br />
begin<br />
AConnection := CreateConnection;<br />
AConnection.Open;<br />
if Aconnection.Connected then<br />
writeln('Successful connect!')<br />
else<br />
writeln('This is not possible, because if the connection failed, ' +<br />
'an exception should be raised, so this code would not ' +<br />
'be executed');<br />
AConnection.Close;<br />
AConnection.Free;<br />
end.<br />
</syntaxhighlight><br />
<br />
If an exception is thrown, read the error message carefully. It may be that the database server is not running, the user name or password are incorrect or the database name or IP address are typed incorrectly. If the error message states that the client library cannot be found, then check if the client is installed correctly. Often the error message states literally the name of the file looked for.<br />
<br />
== How to execute direct queries/make a table? ==<br />
<br />
SqlDB - the name says it all - only works with database server that make use of SQL. SQL stands for 'Structured Query Language' SQL is a language developed to allow working with relational databases. Virtually every database system has its own dialect, but a large number of SQL statements are the same for all database systems.<br />
<br />
In FPC, there is a difference between:<br />
* SQL statements that return information (a dataset). For this, you have to use the TSQLQuery component; see [[#How to read data from a table?]]. <br />
* statements that do not return information but do something else, e.g. update data. For this, you may also use the 'ExecuteDirect' method of a TSQLConnection. (You can also use this if you get a dataset back but are not interested in the results, e.g. in a selectable stored procedure).<br />
<br />
Most database system execute SQL statements within a transaction. If you want changes made within a transaction available in other transactions, or have those changes available even after closing the transaction(!), then you have to 'commit' the transaction. <br />
<br />
To support transactions Sqldb contains the TSQLTransaction component. A SQL statement that is executed by Sqldb must always be executed within a transaction, even if the database system does not support transactions. Also, there are database systems that do support transaction for which TSQLConnection does not (yet) support transaction. Even then, you must use the TSQLTransaction component.<br />
<br />
To use <tt>TSQLConnection.ExecuteDirect</tt> to execute a SQL statement you must specify which 'Transaction' must be used. In turn, to use TSQLTransaction you must specify which TSQLConnection component must be used.<br />
<br />
The following example creates a table 'TBLNAMES' with fields 'NAME' and 'ID' and inserts two records. The used SQL statements are not explained. For more information about the SQL statements, their use and syntax, please refer to the database system documentation. The procedure 'CreateConnection' is defined in the code example in [[#How to connect to a database server?]] above.<br />
<br />
<syntaxhighlight>program CreateTable;<br />
<br />
function CreateTransaction(pDB: TIBConnection): TSQLTransaction;<br />
begin<br />
result := TSQLTransaction.Create;<br />
result.Database := pDB;<br />
end;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
AConnection.Transaction := ATransaction;<br />
AConnection.Open;<br />
ATransaction.StartTransaction;<br />
AConnection.ExecuteDirect('create table TBLNAMES (ID integer, NAME varchar(40));'); <br />
<br />
// Some database-server types need a commit before you can use a newly created table. (Firebird)<br />
// With .Commit you also close the transaction<br />
ATransaction.Commit; <br />
<br />
ATransaction.StartTransaction;<br />
AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (1,'Name1');'); <br />
AConnection.ExecuteDirect('insert into TBLNAMES (ID,NAME) values (2,'Name2');'); <br />
ATransaction.Commit; <br />
AConnection.Close;<br />
AConnection.Free;<br />
ATransaction.Free;<br />
end.</syntaxhighlight><br />
<br />
== How to read data from a table? ==<br />
<br />
Use the TSQLQuery component to read data from a table. A TSQLQuery component must be connected to a TSQLConnection component and a TSQLTransaction component to do its work. Setting the TSQLConnection and TSQLTransaction is discussed in [[#How to connect to a database server? ]] and [[#How to execute direct queries/make a table?]]. <br />
<br />
When the TSQLConnection, TSQLTransaction and TSQLQuery are connected, then TSQLQuery needs to be further configured to work. TSQLQuery has a 'SQL' property containing a TStrings object. The 'SQL' property contains a SQL statement that must be executed. If all data from a table <tt>tablename</tt> must be read, then set the 'SQL' property to:<br />
<syntaxhighlight lang="sql">'SELECT * FROM tablename;'</syntaxhighlight>.<br />
<br />
Use 'open' to read the table from the server and put the data in the TSQLQuery dataset. The data can be accessed through TSQLQuery until the query is closed using 'close'.<br />
<br />
TSQLQuery is a subclass of TDataset. TDataset has a 'Fields' collection that contains all columns of the table. The TDataset also keeps track of the current record. Use 'First', 'Next', 'Prior' and 'Last' to change the current record. 'Bof' returns 'True' if the first record is reached, and 'Eof' returns 'True' if the last record is reached. To read the value of a field in the current record, first find the right 'TField' object and then use 'AsString', 'AsInteger', etc.<br />
<br />
=== Example: reading data from a table ===<br />
Below is an example that displays all values of the table as it was made in [[#How to execute direct queries/make a table?]] above.<br />
<br />
<syntaxhighlight>Program ShowData;<br />
<br />
function GetQuery(pConnection: TIBConnection; pTransaction: TSQLTransaction): TSQLQuery;<br />
begin<br />
result := TSQLQuery.Create;<br />
result.Database := pConnection;<br />
result.Transaction := pTransaction<br />
end;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
Query : TSQLQuery;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'select * from tblNames';<br />
AConnection.Open;<br />
Query.Open;<br />
while not Query.Eof do<br />
begin<br />
Writeln('ID: ', Query.FieldByName('Name').AsInteger, 'Name: ' +<br />
Query.FieldByName('Name').AsString);<br />
Query.Next;<br />
end;<br />
Query.Close;<br />
AConnection.Close;<br />
Query.Free;<br />
ATransaction.Free;<br />
AConnection.Free;<br />
end.</syntaxhighlight><br />
<br />
(The code above of course is not quite finished, it misses 'try...finally' blocks. However, the above code intends to show the database code and thus the finishing touches are left out.)<br />
Please note that 'TSQLTransaction.StartTransaction' is not used. This is not necessary. When TSQLQuery is opened, the SQL statement is executed and if no transaction is available then a transaction is automatically started. The programmer does not need to start the transaction explicitly.<br />
The same applies for the connection maintained by TSQLConnection. The connection is opened as needed, the line 'Aconnection.Open' is not really required.<br />
If a TSQLTransaction is destroyed, an automatic 'rollback' will be executed. '''Possible changes to data contained in the transaction will be lost.''' <br />
<br />
=== Why does TSQLQuery.RecordCount always return 10? ===<br />
<br />
To count the records in a datase, use '.RecordCount'. However, notice that '.RecordCount' shows the number of records that is already loaded from the server. For performance reasons, SqlDB does not read all records when opening TSQLQuery by default, only the first 10. Only when the eleventh record is accessed will the next set of 10 records be loaded, etc. Using '.Last', all records will be loaded. <br />
<br />
When you want to know the real number of records on the server you can first call '.Last' and then call '.RecordCount'. <br />
<br />
An alternative is available. The number of records returned by the server is set by the '.PacketRecords' property. The default value is 10; if you make it -1 then all records will be loaded at once.<br />
<br />
In current stable FPC, '.RecordCount' does not take filters into account, i.e. it shows the unfiltered total.<br />
<br />
If you need the exact number of records, it often is a better idea to directly query the number of records in a query using another SQL query, but you would have to do that in the same transaction, as other transactions may have changed the number of records in the meanwhile.<br />
<br />
=== Lazarus ===<br />
<br />
Lazarus has various components to show data from a TDataset on a form. Instead of a While-loop and Writeln statements as used above, you can use the components to show the data in a table. Place the right TSQLConnection, TSQLTransaction and TSQLQuery components on a form, then connect them and set them properly. In addition you will need a TDatasource; set the 'TDatasource.Dataset' property to the TSQLQuery component you used. ('''Note''' do not set the 'TSQLQuery.Datasource' property to the TDatasource compnent you used. The 'TSQLQuery.Datasource' property is used only in master-detail tables - see [[MasterDetail]]) Subsequently you may put a TDBGrid onto the form and set the 'Datasource' property of the grid to the TDatasource component you added before.<br />
<br />
To see if it all works, set the 'Connected' property of the TSQLConnection to 'True' in the Lazarus IDE. The IDE will try to connect to the database server immediately. If this works you can set the 'TSQLQuery.Active' property to 'True'. If everything is right, you will see - within the IDE - all data from the table immediately on the screen.<br />
<br />
== How to change data in a table? ==<br />
<br />
To change the data in a record, the TDataset (from which TSQLQuery is derived) must be set to edit mode. To enter edit mode call the '.Edit', '.Insert' or '.Append' methods. Use the '.Edit' method to change the current record. Use '.Insert' to insert a new record before the current record. Use '.Append' to insert a new record at the end of the table. In edit mode you can change field values through the 'Fields' property. Use 'Post' to validate the new data, if the data is valid then the edit mode is left. If you move to another record - for example by using '.Next' - and the dataset is in edit mode, then first '.Post' is called. Use '.Cancel' to discard all changes you made since the last '.Post' call and leave the edit mode.<br />
<br />
<syntaxhighlight>Query.Edit;<br />
Query.FieldByName('NAME').AsString := 'Edited name';<br />
Query.Post;</syntaxhighlight><br />
<br />
The above is not the complete story yet. TSQLQuery is derived from TBufDataset which makes use of buffered updates. Buffered update means that after you called 'Post' the changes in the dataset are visible immediately, but they are not sent to the database server. What does happen is that the changes are maintained in a change log. When the '.ApplyUpdates' method is called, then all changes in the change log are sent to the database. Only then will database server know of the changes. The changes are sent to the server within a transaction of TSQLTransaction. Make sure to properly set the transaction before 'ApplyUpdates'. After applying the updates, a commit must be executed to save the changes on the database server.<br />
<br />
The below is an example of changing the data in a table, sending the changes to the server and comitting the transaction.<br />
<br />
<syntaxhighlight>Program EditData;<br />
<br />
var <br />
AConnection : TSQLConnection;<br />
ATransaction : TSQLTransaction;<br />
Query : TSQLQuery;<br />
<br />
begin<br />
AConnection := CreateConnection;<br />
ATransaction := CreateTransaction(AConnection);<br />
AConnection.Transaction := ATransaction;<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'select * from tblNames';<br />
Query.Open;<br />
Query.Edit;<br />
Query.FieldByName('NAME').AsString := 'Edited name';<br />
Query.Post;<br />
Query.UpdateMode := upWhereAll;<br />
Query.ApplyUpdates;<br />
ATransaction.Commit;<br />
Query.Free;<br />
ATransaction.Free;<br />
AConnection.Free;<br />
end.</syntaxhighlight><br />
<br />
For a discussion of 'UpdateMode' continue reading.<br />
<br />
== How does SqlDB send the changes to the database server? ==<br />
<br />
In the code example in [[#How to change data in a table?]], you will find the line<br />
<syntaxhighlight>Query.UpdateMode := upWhereAll;</syntaxhighlight><br />
without explanation of what it does. The best way to find out what that line does is to leave it out. If you leave out the statement and the followed this howto precisely, then you will receive the following error message:<br />
No update query specified and failed to generate one. (No fields for inclusion in where statement found)<br />
To understand what went wrong, you must understand how changes are sent to the database server. The only way to get data in a SQL server is by executing SQL queries. SQL has three types of queries for three different ways of manupulating a record. To create a new record, change or delete a record insert, update and delete statements are executed respectively. An update statement may be as follows:<br />
<syntaxhighlight lang="sql">update TBLNAMES set NAME='Edited name' where ID=1;</syntaxhighlight><br />
To send a change to the database server, Sqldb must assemble an update query. To assemble the query, three things are needed:<br />
; The name of the table : The table name is retrieved from parsing the select query, although this doesn't always work. <br />
; <tt>UPDATE</tt> or <tt>INSERT</tt> clause : These contain the fields that must be changed.<br />
; <tt>WHERE</tt> clause : This contains the fields that determine which records should be changed.<br />
<br />
Every field (each ''TField'' in ''Fields'') has a ProviderFlags property. Only fields with '''pfInUpdate''' in ''ProviderFlags'' will be used in the update or insert cluase of a query. By default all fields have ''pfInUpdate'' set in their ''ProviderFlags'' property.<br />
<br />
Which fields are used in the <tt>WHERE</tt> clause depends on the ''UpdateMode'' property of the query and the ''ProviderFlags'' property of the fields. Fields with ''pfInkey'' in their ''ProviderFlags'' are always used in the <tt>WHERE</tt> clause. A field will have the ''pfInKey'' flag set automatically if the field is part of the primary key of the table and 'TSQLQuery.UsePrimaryKeyAsKey' returns 'True'.<br />
<br />
The default value for ''UpdateMode'' of the query is ''upWhereKeyOnly''. In this update mode only fields with ''pfInkey'' in their ''ProviderFlags'' property are used in the <tt>WHERE</tt> clause. If none of the fields have their ''pfInKey'' flag set, then no fields are available for the <tt>WHERE</tt> clause and the error message from the beginning of this section will be returned. You can solve the issue by:<br />
* Adding a primary key to the table and set ''TSQLQuery.UsePrimaryKeyAsKey'' to 'True', or<br />
* Setting the ''pfInkey'' flag for one or more fields in code.<br />
<br />
The '''UpdateMode''' property knows two more possible values. 'upWhereAll' can be used to add all fields with the 'pfInWhere' flag set to the <tt>WHERE</tt> clause. By default all fields have this flag set. 'upWhereChanged' can be used to add only those fields that have the 'pfInWhere' flag set '''and''' that are changed in the current record.<br />
<br />
== How to execute a query using TSQLQuery? ==<br />
<br />
Next to statements that return a dataset (see [[#How to read data from a table?]]) SQL has statements that do not return data. For example <tt>INSERT</tt>, <tt>UPDATE</tt> and <tt>DELETE</tt> statements do not return data. These statements can be executed using ''[[#How to execute direct queries/make a table?|TSQLConnection.ExecuteDirect]]'', but TSQLQuery can also be used. If you do not expect return data use ''TSQLQuery.ExecSQL'' instead of ''TSQLQuery.Open''. As mentioned earlier, use ''TSQLQuery.Open'' to open the dataset returned by the SQL statement. <br />
<br />
The following procedure creates a table and inserts two records using TSQLQuery.<br />
<br />
<syntaxhighlight>procedure CreateTable;<br />
<br />
var <br />
Query : TSQLQuery;<br />
<br />
begin<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (1,''Name1'');';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (2,''Name2'');';<br />
Query.ExecSQL;<br />
<br />
Query.Close;<br />
Query.Free;<br />
end;</syntaxhighlight><br />
<br />
== How to use parameters in a query? ==<br />
<br />
In the code example of [[#How to execute a query using TSQLQuery?]] the same query is used twice, only the values to be inserted differ. A better way to do this is by using parameters in the query. <br />
<br />
The syntax of parameters in queries is different per database system, but the differences are handled by TSQLQuery. Replace the values in the query with a colon followed by the name of the parameter you want to use. For example:<br />
<syntaxhighlight>Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';</syntaxhighlight><br />
<br />
This query will create two parameters: 'ID' and 'NAME'.<br />
To determine the parameters, the query is parsed at the moment the text of ''TSQLQuery.SQL'' is assigned or changed. All existing parameters will be removed and the new parameters will be added to the 'TSQLQuery.Params' property. Assigning a value to a parameter is similar to assigning a value to a field in the dataset:<br />
<syntaxhighlight>Query.Params.ParamByName('Name').AsString := 'Name1'</syntaxhighlight>;<br />
<br />
You can't tell from the query what kind of data must be stored in the parameter. The data type of the parameter is determined at the moment a value is first assigned to the parameter. By assigning a value using '.AsString', the parameter is assigned the data type 'ftString'. You can determine the data type directly by setting the 'DataType' property. If an incorrect datatype is assigned to the parameter, then problems will occur during opening or executing the query.<br />
See [[Database field type]] for more information on data types.<br />
<br />
=== Select query ===<br />
An example of a select query with parameters would be to change something like this:<br />
<syntaxhighlight><br />
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = '''+Edit1.Text+''' ORDER BY NAME ';<br />
</syntaxhighlight><br />
to something like this:<br />
<syntaxhighlight><br />
Query.SQL.Text := 'select ID,NAME from TBLNAMES where NAME = :NAMEPARAM ORDER BY NAME ';<br />
Query.Params.ParamByName('NAMEPARAM').AsString := Edit1.Text;<br />
</syntaxhighlight><br />
<br />
=== Example ===<br />
The following example creates the same table as the previous example, but now parameters are used:<br />
<br />
<syntaxhighlight><br />
procedure CreateTableUsingParameters;<br />
<br />
var <br />
Query : TSQLQuery;<br />
<br />
begin<br />
Query := GetQuery(AConnection, ATransaction);<br />
Query.SQL.Text := 'create table TBLNAMES (ID integer, NAME varchar(40));';<br />
Query.ExecSQL;<br />
<br />
Query.SQL.Text := 'insert into TBLNAMES (ID,NAME) values (:ID,:NAME);';<br />
Query.Prepare;<br />
<br />
Query.Params.ParamByName('ID').AsInteger := 1;<br />
Query.Params.ParamByName('NAME').AsString := 'Name1';<br />
Query.ExecSQL;<br />
<br />
Query.Params.ParamByName('ID').AsInteger := 2;<br />
Query.Params.ParamByName('NAME').AsString := 'Name2';<br />
Query.ExecSQL;<br />
<br />
//Query.UnPrepare; // no need to call this; should be called by Query.Close<br />
Query.Close;<br />
Query.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
Notice that this example requires more code than the example without the parameters. Then what is the use of using parameters? <br />
<br />
Speed is one of the reasons. The example with parameters is faster, because the database server parses the query only once (in the .Prepare statement or at first run). <br />
<br />
Another reason to use prepared statements is prevention of [http://en.wikipedia.org/wiki/SQL_injection SQL-injection] (see also [[Secure programming]]. <br />
<br />
Finally, in some cases it just simplifies coding.<br />
<br />
== Troubleshooting: TSQLConnection logging ==<br />
You can let a TSQLConnection log what it is doing. This can be handy to see what your Lazarus program sends to the database exactly, to debug the database components themselves and perhaps to optimize your queries.<br />
NB: if you use prepared statements/parametrized queries (see section above), the parameters are often sent in binary by the TSQLConnection descendent (e.g. TIBConnection), so you can't just copy/paste the logged SQL into a database query tool.<br />
Regardless, connection logging can give a lot of insight in what your program is doing.<br />
<br />
Alternatives are: <br />
# you can use the debugger to step through the database code if you have built FPC (and Lazarus) with debugging enabled. <br />
# if you use ODBC drivers (at least on Windows) you could enable tracelog output in the ODBC control panel.<br />
# many databases allow you to monitor all statements sent to it from a certain IP address/connection.<br />
<br />
<br />
If you use TSQLConnection logging, two things are required:<br />
# indicate which event types your TSQLConnection should log<br />
# point TSQLConnection at a function that receives the events and processes them (logs them to file, prints them to screen, etc.).<br />
That function must be of type TDBLogNotifyEvent (see sqldb.pp), so it needs this signature:<br />
<syntaxhighlight><br />
TDBLogNotifyEvent = Procedure (Sender : TSQLConnection; EventType : TDBEventType; Const Msg : String) of object;<br />
</syntaxhighlight><br />
<br />
=== FPC (or: the manual way) ===<br />
A code snippet can illustrate this:<br />
<syntaxhighlight><br />
uses<br />
...<br />
TSQLConnection, //or a child object like TIBConnection, TMSSQLConnection<br />
...<br />
var<br />
type <br />
TMyApplication = class(TCustomApplication); //this is our application that uses the connection<br />
...<br />
private<br />
// This example stores the logged events in this stringlist:<br />
FConnectionLog: TStringList;<br />
...<br />
protected<br />
// This procedure will receive the events that are logged by the connection:<br />
procedure GetLogEvent(Sender: TSQLConnection; EventType: TDBEventType; Const Msg : String);<br />
...<br />
procedure TMyApplication.GetLogEvent(Sender: TSQLConnection;<br />
EventType: TDBEventType; const Msg: String);<br />
// The procedure is called by TSQLConnection and saves the received log messages<br />
// in the FConnectionLog stringlist<br />
var<br />
Source: string;<br />
begin<br />
// Nicely right aligned...<br />
case EventType of<br />
detCustom: Source:='Custom: ';<br />
detPrepare: Source:='Prepare: ';<br />
detExecute: Source:='Execute: ';<br />
detFetch: Source:='Fetch: ';<br />
detCommit: Source:='Commit: ';<br />
detRollBack: Source:='Rollback:';<br />
else Source:='Unknown event. Please fix program code.';<br />
end;<br />
FConnectionLog.Add(Source + ' ' + Msg);<br />
end;<br />
<br />
...<br />
// We do need to tell our TSQLConnection what to log:<br />
FConnection.LogEvents:=LogAllEvents; //= [detCustom, detPrepare, detExecute, detFetch, detCommit, detRollBack]<br />
// ... and to which procedure the connection should send the events:<br />
FConnection.OnLog:=@Self.GetLogEvent;<br />
...<br />
// now we can use the connection and the FConnectionLog stringlist will fill with log messages.<br />
</syntaxhighlight><br />
<br />
You can also use TSQLConnection's GlobalDBLogHook instead to log everything from multiple connections.<br />
<br />
=== Lazarus (or: the quick way) ===<br />
Finally, the description above is the FPC way of doing things as indicated in the introduction; if using Lazarus, a quicker way is to assign an event handler to the TSQLConnection's OnLog event.<br />
<br />
[[Category:Databases]]<br />
<br />
== See also ==<br />
* [[Working With TSQLQuery]]<br />
<br />
[[Category:Databases]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Greatest_common_divisor&diff=87182Greatest common divisor2015-03-10T12:42:23Z<p>Eny: Replace superfluous link with more detailed examples</p>
<hr />
<div>The greatest common divisor of two integers is the largest integer that divides them both.<br />
If numbers are 121 and 143 then greatest common divisor is 11.<br />
<br />
There are many methods to calculate this. For example, the division-based Euclidean algorithm version may be programmed <br />
<br />
== Function GreatestCommonDivisor ==<br />
<br />
<syntaxhighlight><br />
<br />
function GreatestCommonDivisor(a, b: Int64): Int64;<br />
var<br />
temp: Int64;<br />
begin<br />
while b <> 0 do<br />
begin<br />
temp := b;<br />
b := a mod b;<br />
a := temp<br />
end;<br />
result := a<br />
end; <br />
<br />
</syntaxhighlight><br />
<br />
== See also ==<br />
<br />
* [http://rosettacode.org/wiki/Greatest_common_divisor#Pascal_.2F_Delphi_.2F_Free_Pascal Recursive example]<br />
* [[Least common multiple]]<br />
* [[Mod]]<br />
<br />
[[Category:Mathematics]]<br />
[[Category:Code]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Greatest_common_divisor&diff=87181Greatest common divisor2015-03-10T12:36:32Z<p>Eny: </p>
<hr />
<div>The greatest common divisor of two integers is the largest integer that divides them both.<br />
If numbers are 121 and 143 then greatest common divisor is 11.<br />
<br />
There are many methods to calculate this. For example, the division-based Euclidean algorithm version may be programmed <br />
<br />
== Function GreatestCommonDivisor ==<br />
<br />
<syntaxhighlight><br />
<br />
function GreatestCommonDivisor(a, b: Int64): Int64;<br />
var<br />
temp: Int64;<br />
begin<br />
while b <> 0 do<br />
begin<br />
temp := b;<br />
b := a mod b;<br />
a := temp<br />
end;<br />
result := a<br />
end; <br />
<br />
</syntaxhighlight><br />
<br />
== See also ==<br />
<br />
* [https://www.youtube.com/watch?v=_-sAsp1WlhY| Pascal - Basic Maths 3 - Greatest Common Divisor (gcd) using Euclid's Algorithm ]<br />
* [[Least common multiple]]<br />
* [[Mod]]<br />
<br />
[[Category:Mathematics]]<br />
[[Category:Code]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Least_common_multiple&diff=87180Least common multiple2015-03-10T12:35:25Z<p>Eny: Clarify and simplify</p>
<hr />
<div><br />
The least common multiple of two integers a and b is the smallest positive integer that is divisible by both a and b.<br />
<br />
For example: for 12 and 9 then least common multiple is 36.<br />
<br />
==Function LeastCommonMultiple==<br />
<br />
<syntaxhighlight><br />
function LeastCommonMultiple(a, b: Int64): Int64;<br />
begin<br />
result := b * (a div GreatestCommonDivisor(a, b))<br />
end; <br />
</syntaxhighlight><br />
<br />
{{Note| Function [[Greatest common divisor#Function GreatestCommonDivisor |GreatestCommonDivisor ]] must be defined before this function}}<br />
<br />
<br />
== See also ==<br />
<br />
* [[Greatest common divisor]]<br />
<br />
<br />
[[Category:Mathematics]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Var&diff=87179Var2015-03-10T12:22:52Z<p>Eny: Fix errors and simplify (invalid doc references still need fixing)</p>
<hr />
<div>{{Var}}<br />
<br />
Var is a [[keyword]] used to mark the section where [[Variable|variables]] and their data [[Type|type]]s are declared. When used for a parameter of a [[Procedure|procedure]] or [[Function|function]] then '''var''' indicates the use of a [[Variable parameter|variable parameter]].<br />
<br />
Variables are typically declared at the beginning of a [[program]], [[procedure]], [[function]] or [[Unit|unit]].<br />
<br />
<syntaxhighlight>var<br />
age: integer;</syntaxhighlight> <br />
<br />
If you are planning to use various variables of the same type, they can be grouped so they share the same type definition. The variables must be separated by a [[comma]].<br />
<br />
<syntaxhighlight>var<br />
FirstName, LastName, address: string;</syntaxhighlight> <br />
<br />
== See also ==<br />
* [[Local variables]]<br />
* [[Global variables]]<br />
* [[doc:ref/refse19.html#x49-540004.2|Variable declaration]]<br />
* [[doc:ref/refse21.html#x51-560004.4|Thread Variables]]<br />
[[category:Pascal]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Local_variables&diff=87178Local variables2015-03-10T12:05:38Z<p>Eny: </p>
<hr />
<div>{{Local variables}}<br />
<br />
A local variable is defined inside a [[Procedure]], [[Function]], [[Method]] or the implementation section of a [[Unit]] and is only accessible from there. It is said to have local scope and cannot be accessed from outside (i.e. by another outside procedure, function or unit).<br />
procedure DoSomething; <br />
var x:type<br />
begin<br />
end<br />
<br />
[[Category:Pascal]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Global_variables&diff=87177Global variables2015-03-10T12:03:39Z<p>Eny: Fix errors</p>
<hr />
<div>A global [[Variable]] is a variabe that is declared in the main section of a program or the interface part of a unit. Globals declared in a program cannot be accessed from within a unit. Globals declared in a unit can be accessed from within a program or another unit.<br />
<br />
program GlobalVariables;<br />
var<br />
g: integer;<br />
begin<br />
end.<br />
<br />
'''g''' is a global variable.<br />
<br />
<br />
== External variable ==<br />
<br />
External variable is a global variable has been declared in [[Unit|unit]] [[Interface|interface]] section.<br />
<br />
== Read More ==<br />
* [[Var]]<br />
* [[Local_variables]]<br />
<br />
[[Category:Pascal]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Local_variables&diff=87176Local variables2015-03-10T11:57:57Z<p>Eny: </p>
<hr />
<div>{{Local variables}}<br />
<br />
A local variable is defined inside a [[Procedure]], [[Function]], [[Method]] or [[Unit]] and is only accessible from there. It is said to have local scope and cannot be accessed from outside (i.e. by another outside procedure or function).<br />
procedure DoSomething; <br />
var x:type<br />
begin<br />
end<br />
<br />
[[Category:Pascal]]</div>Enyhttps://wiki.freepascal.org/index.php?title=Local_variables&diff=87175Local variables2015-03-10T11:51:54Z<p>Eny: </p>
<hr />
<div>{{Local variables}}<br />
<br />
A local variable is defined inside a [[Procedure|procedure]], [[Function|function]] or [[Method|method]] and is only accessible from there.<br />
procedure DoSomething; <br />
var x:type<br />
begin<br />
end<br />
<br />
[[Category:Pascal]]</div>Eny