Arsip Tag: delphi

Sistem Informasi Persuratan (SISURAT) ver 2.0

Sistem Informasi Persuratan (SISURAT) ver 2.0
Sistem Informasi Persuratan (SISURAT) ver 2.0

Finally! Aplikasi yang sudah ratusan atau ribuan kali diunduh di internet secara gratis, kini berevolusi ke versi 2. Setelah peluncurannya lima tahun yang lalu, aplikasi ini sempat dikembangkan secara internal dalam beberapa versi, namun selalu dirombak karena masih belum puas dengan fiturnya. Tidak lama lagi aplikasi ini akan segera diluncurkan namun hanya akan keluar dalam bentuk versi trial version dan full version. Skema finalnya akan diberitahukan kemudian, trial versionnya akan membatasi apa saja, dan berapa harga full versionnya. Mengapa tidak freeware lagi seperti pendahulunya yang  versi 1? Karena untuk mengembangkan aplikasi ini saya terus memperpanjang lisensi Delphi dan membeli komponen hingga ribuan USD. Saya rasa cukup fair jika Anda menggunakan aplikasi ini untuk bisnis maka sedikit mengeluarkan biaya investasi untuk menunjang produktivitas di tempat kerja tidak menjadi masalah, bukan? Anda juga bisa membelikan komponen yang saya butuhkan kalau mau :) Komponen itu nanti bisa digunakan untuk membuat aplikasi gratis lainnya, sehingga insya Allah Anda juga ketularan pahalanya :) .

Komponen yang saya gunakan untuk mengembangkan aplikasi SISURAT memanfaatkan komponen dari sini. Sedangkan pacul yang saya gunakan, yaitu Code Gear Delphi bisa dilihat di sini.

Fitur aplikasi yang baru adalah:

1. Menggunakan database Firebird, bukan MS Access

2. Dapat mengekspor ke Excel untuk Daftar Surat Masuk, Surat Keluar, dan Log Pemakaian

3. Setiap surat masuk yang dibuat dapat didisposisikan ke lebih dari 1 orang dan dapat ditrack statusnya hingga selesai

4. Setiap surat masuk yang ditambahkan dapat diemail ke atasan untuk meminta disposisi, lalu atasan bisa membalas email tersebut menggunakan Blackberry atau Smart Mobile Phone lain di luar kantor untuk menjelaskan info disposisinya, dientri kembali ke aplikasi, dan isi disposisi bisa dikirim ke setiap orang dengan info disposisi yang berbeda-beda sesuai instruksi atasan. Dengan aplikasi ini, sekretaris bisa mengentri surat di kantor, namun untuk menunggu disposisi, tidak perlu menunggu atasan kembali ke kantor. Sekarang jamannya boss menggunakan smart phone, misal Blackberry. Sekretaris cukup menginput informasi surat masuk, scan dokumennya (atau ketik perihal suratnya), dan boss bisa melihat suratnya dari Blackberrynya.Reply disposisi, dan disposisi akan langsung dikirim ke anak buah masing-masing. Jadi boss bisa tahu, mana saja surat yang belum di follow up sampai tuntas. Biasanya kan boss uring-uringan karena dia belum tahu surat mana saja yang sudah selesai disposisinya. Sekretaris kelabakan mencari berkas hardcopy lalu sibuk mencari statusnya. Dengan aplikasi ini, semua bisa lebih terorganize, ke arah yang lebih baik tentunya.

5. Surat Masuk, Surat Keluar, dan Log Pemakaian dapat dikelompokkan per kolom secara mudah dengan komponen yang canggih dari Dev Express. Penyaringan dan sorting dapat dilakukan dengan sangat mudah.

Berikut adalah tampilan aplikasinya, sebagai teaser. Final trailer akan menyusul, dan launching produknya :)

This slideshow requires JavaScript.

Bagaimana pendapat Anda dengan fitur di versi terbaru ini? Ada masukan untuk fitur tambahan sebelum launching?

[Update tanggal 22 Mei 2012]

Ada fitur heboh statistik laporan surat masuk:

Statistik Laporan Surat Masuk

Laporan ini akan otomatis memberikan informasi:

1. Apakah ada surat yang belum didisposisikan

2. Jika ada, akan ditampilkan semua surat tersebut

3. Dari surat yang telah didisposisikan, apakah ada yang belum difollow up?

4. Jika ada, semua penerima disposisi dan informasi suratnya akan ditampilkan

Laporan ini bisa dihasilkan secara ad hoc kapanpun diperlukan dengan range tanggal awal hingga akhir, maupun bisa dischedule otomatis, pada waktu tertentu akan mengirimkannya ke email yang telah ditunjuk untuk menerima laporan ini. Jadi aplikasi ini ditinggal ngopi, ngantuk, ngupil, atau nganu sekalipun akan tetap bisa mengirimkan laporan. Tujuannya adalah semua disposisi cepat difollow up hingga selesai.

Berikutnya mungkin akan diberikan fitur penomoran otomatis surat keluar per jenis surat. Jadi setiap jenis surat akan dapat diatur headernya masing-masing, misal SE/12/XXXX/IN dengan XXXX adalah counter dari 0001 hingga 9999. Semoga bisa diterapkan sebelum launching.

Bagaimana? Ada tambahan usulan?

Setting Master Detail dengan Dev Express Quantum Grid

Setting Master Detail dengan Dev Express Quantum Grid
Setting Master Detail dengan Dev Express Quantum Grid

Ternyata barang canggih konfigurasinya juga tidak trivial. Tulisan ini digunakan untuk siapa saja yang belum tahu atau lupa melakukan konfigurasi master detail dengan Quantum Grid dari Dev Express.

ExpressQuantumGrid is capable of displaying data from several datasets. You can present data either from independent datasets in one control or datasets that are linked by a master-detail relationship. One-to-many relationships or master-detail relationships can be represented in one of two ways. The first method is to use two grid controls. A detail grid control displays only the records corresponding to the current record in a master table. In the second method only one grid control is used to present master-detail relationships. This method is specific to the ExpressQuantumGrid and employs grid levels to create a hierarchical data structure.

You can see, for instance, the MasterDetailTableDemo for a complete example of implementing a master-detail relationship. This topic describes the basic principles involved in setting up master-detail relationships based on the database used in this demo.

Data in this example is loaded using the default loading mode (when Grid Mode is not applied). Grid mode is a special loading mode provided where there are a large number of records. The Grid Mode: Master-Detail section shows how to create a master-detail relationship between tables in grid mode.

EQGrid allows you to populate views with data from non data-aware sources using provider and unbound modes. Refer to the Provider Mode: Master-Detail and Unbound Mode: Master-Detail sections to see how to set up master-detail relationships in these modes.

Database structure
Let us consider two tables that are shipped with the EQGrid demos: FILMS and FILMSPERSONSSTAFF. The FILMS table contains information on the films in the catalog and contains fields such as CAPTION, YEAR, PHOTO, TAGLINE, ID and others. ID is the table key field name.

The FILMSPERSONSSTAFF table describes people involved in film production. It also has an ID field uniquely identifying records in the table. Other fields are PERSONID, PERSONLINEID, DESCRIPTION and FILMID. FILMID denotes a value of the ID field from the FILMS table. It associates each record in the FILMSPERSONSSTAFF table with a specific film from the FILMS table. Thus a master-detail relationship can be established between these tables in which FILMS is a master table and FILMSPERSONSSTAFF is a detail table.

Creating connections to tables
The Connecting to Data topic outlines the steps required to connect a grid control to a database. It involves:

creating TDataSet and TDataSource objects for all data tables in the project

linking TDataSet to a database table

linking TDataSource to the TDataSet

Other steps relate to adjusting the grid’s properties and will be discussed below.

We have created a table (dataset) and datasource objects for the FILMS table: tblFilms and dsFilms. The dataset and data source for the FILMSPERSONSSTAFF table are tblFilmsPersonsStaff and dsFilmsPersonsStaff, respectively.

Creating the data structure
To display a master-detail relationship, you need to have at least two grid levels with associated DB views (one for a master table and the other for its detail table). Views must be connected to data sources identifying master and detail tables respectively. Please refer to the Working With Levels topic to learn more about creating a data structure within a grid control. The following grid levels (TcxGridLevel) and views (TcxGridDBTableView) were created to represent the FILMS and FILMSPERSONSSTAFF tables:

The lvFilms level is linked to the tvFilms view connected to the dsFilms data source.

The lvFilmsPersonsStaff level is a child level of lvFilms. It is associated with the tvFilmsPersonsStaff view connected to the dsFilmsPersonsStaff data source.

The following image shows the Structure Navigator with the levels and views created:

Structure Navigator

Also, you have to link views to corresponding TDataSource objects and add columns within these views to display the data in the tables. See Connecting to Data and Create And Delete Columns for more details.

 

Setting up a master-detail relationship

To link the tables’ data, you need to set the DetailKeyFieldNames and MasterKeyFieldNames properties of the detail view’s data controller.

  • Set MasterKeyFieldNames of the detail view’s data controller to ID. It identifies the field from a master table (FILMS).
  • Set DetailKeyFieldNames to FILMID. FILMID specifies the field in the current detail table (FILMSPERSONSSTAFF). It corresponds to the field(s) set via MasterKeyFieldNames.
  • Set KeyFieldNames to ID. The KeyFieldNames property specifies one or more key field names to identify each dataset record uniquely. This is not required for the actual master-detail relationship, but it is necessary for some operations to work, such as editing detail view data, deleting records, etc.

At design time, select the tvFilmsPersonsStaff view so that the Object Inspector displays its properties and then expand the DataController property. The following image shows the Object Inspector with MasterKeyFieldNames, DetailKeyFieldNames and KeyFieldNames set to values as described above:

Setting for the detail

The following code performs the same operations:

//Delphi

tvFilmsPersonsStaff.DataController.KeyFieldNames := ‘ID’;

tvFilmsPersonsStaff.DataController.MasterKeyFieldNames := ‘ID’;

tvFilmsPersonsStaff.DataController.DetailKeyFieldNames := ‘FILMID’;

Sorting the detail dataset

The last step of setting up a master-detail relationship is to sort the detail dataset (for parameterized queries this is not necessary). Providing that the detail dataset is sorted, the data controller will correctly retrieve all the necessary records from it. The detail dataset must be sorted against the fields specified by the DetailKeyFieldNames property.

To enable sorting for query datasets, you should use the “ORDER BY” SQL command. For TTable and TCustomADODataSet components, the sorting of dataset records is implemented via the IndexFieldNames property. In this example, we need to set the IndexFieldNames property of the dsFilmsPersonsStaff dataset to FILMID (the same value as for the tvFilmsPersonsStaff.DataController.DetailKeyFieldNames property).

//Delphi

dsFilmsPersonsStaff.IndexFieldNames := ‘FILMID‘;

The grid below demonstrates two tables linked by the ID & FILMID fields:

Example final master detail view

Word Cruncher Helper for Draw Something Phenomenon

Word Cruncher Helper for Draw Something Phenomenon
Word Cruncher Helper for Draw Something Phenomenon

If you get stucked or start to yelling WTF of the meaning of your opponent’s drawing when playing the famous Social Drawing Application – Draw Something, this simple tool may help you to reduce the millions or probably trillions of the possibility answer for the question. Use this application wisely since it may spoil the fun of the game. Use it just when you want to keep the long streak that you have achieved with your opponent and you are in a deep stress unable guessing the question.

This tool was coded using Delphi and it uses dictionary found by googling from this site. Of course the result of this tool really depends with the entry of the dictionary. You may add your own words, celebrity names, famous phrases, etc by manually add your own entries in the dictionary.txt file which comes along with the application. Ensure the file is located in the same folder with the application. You may share the application freely or share the link among of your friends who play the game. You may download the application from this link.

Actually you may get help from the bomb feature to eliminate some unused letters when you get stucked. The problem is, you have a limited bomb to use. When you are running out of bombs, you may purchase it from the store, adding some money to Draw Something. This tool eliminate you from purchasing any bombs (1200 coins for 4.99 USD, 3000 coins for 9.99 USD, and 10000 coins for 24.99 USD). So this cool tool really save you money!!!

Here is the source code (you may enhance it or put some refactoring to increase the speed) or recoding it with your favorite programming language. Click this link to see the code.

Here is the sample of images that you may not find in the dictionary. You’re on your own.

The reason I made this tool was one night, I was really sleepy in my bedroom, and yet still playing this addictive game. I wrote a sentence to my opponent before drawing the question : This is the last drawing before go to bed. Good Night. And then I touched the erase icon to erase the sentence and drew the question. The next morning I grabbed my iPad, my opponent replied my sentence with Good Night, and she forgot to draw the question!!! There are thousands of possible words that I need to try. I have 36 turn in streak that both of us always correctly guess the answer. I don’t want to start from all over again, so I decided to code this tool. Hahaha.. another excuse to code for a programmer, huh?

The other reason was when my son  playing the same game in his iPod touch, his opponent drew numbers 1, 2, 3, and 4. And NO OTHER CLUE. We were thinking about words: COUNTING, NUMBERS, MATH, etc. When we gave up, the actual answer was PASSWORD. This is a kind of WTF drawing that impossible to guess, and with this tool you may avoid crunching all possibilities with just a reasonable number of guess.

Guessing PASSWORD

How to use the application:

  1. Download the zip file from the link above, and extract it to a folder
  2. There are 2 files, first is the application, WordCruncher.exe and dictionary.txt. Run the application by double clicking on the WordCruncher.exe
  3. Put all the letters available in Choice of letters (12 length)
  4. Choose the length of the answer
  5. Click Crunch button to get the possible result
  6. You may double click the word in the result box to launch the browser with the meaning of the word

Enjoy the game (you can get it from Apple AppStore or Google Play (Androit Market)) ,  and enjoy this tool but use it wisely!

TTreeView and TClientDataSet Sample

TTreeView and TClientDataSet Sample
TTreeView and TClientDataSet Sample

Mas Rahman, menanyakan tentang penggunaan TreeView dengan database. Aku sendiri menganjurkan TreeView dari DevExpress karena sangat mudah dan intuitif. Jedi VCL juga memiliki JDbTreeView tetapi penggunaannya sangat tidak intuitif dan manualnya sulit didapatkan. Searching di google, di milis Jedi saja ada komentar berikut:

I had successfully implemented DevExpress’s DBTreeview when I worked for
my last employer, so I thought I could apply what I had learned in my
home project using JvDBTreeview, but failed after many hours.
Apparently, they are similar, but not similar enough. I also searched
the help files and the internet extensively for any info about this
component and finally gave up looking and bought DevExpress’s DBTreeview
Suite. With the aid of their very well written help files and online
support files, I was able to create exactly what I wanted in about two
hours.

Warning: Woll2Woll’s very nice DB treeview is easier to implement than
DevExpress’s, but shields the programmer from learning how the treeview
really works and actually creates work for the programmer when they try
to override its default behavior.
I recommend DevExpress’s treeview over W2W’s. If yo

u buy it, I’ll help
you get it running.

Good luck.
Regards,
Sam Hunt

Jadi menurut saya kalau memang tidak beli TreeView dari DevExpress ya mau tidak mau menggunakan TTreeView bawaa Delphi. Nah butuh rekursif prosedur untuk bisa membaca seluruhnya.

Nah berikut ini adalah coding sample penggunaan untuk loading tree structure dari database. Intinya, setiap row butuh 1 parent ID dan ID dia sendiri, termasuk field untuk menampilkan data di treenya. Jadi minimal butuh 3 field. Parent ID, Child ID, dan Caption yang akan dimunculkan.

Untuk membuatnya, drop komponen berikut ke form:

  1. TTreeView
  2. Button dengan caption Refresh Tree
  3. TClientDataSet (atau gunakan komponen TTable atau TQuery atau turunan TDataSet)
  4. TDataSource
  5. TDBGrid
  6. TDBNavigator
 
unit unitMain;

interface

uses
  Windows, Messages, SysUtils, Variants,
  Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls, ExtCtrls,
  DBCtrls, Grids, DBGrids,  ComCtrls, DB,
  DBClient;

type
  TformMain = class(TForm)
    treeView: TTreeView;
    tbmSample: TClientDataSet;
    tbmSampleParentID: TStringField;
    tbmSampleChildID: TStringField;
    tbmSampleCaption: TStringField;
    DBGrid1: TDBGrid;
    DBNavigator1: TDBNavigator;
    dtsSample: TDataSource;
    btnRefresh: TButton;
    procedure btnRefreshClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    procedure CreateTree(parentID: String; ParentNode: TTreeNode);
    { Private declarations }
  public
    { Public declarations }
  end;

var
  formMain: TformMain;

implementation

{$R *.dfm}

procedure TformMain.CreateTree(parentID: String; ParentNode: TTreeNode);
var
  list: TStringList;
  i: integer;
  newNode: TTreeNode;
begin
  list := TStringList.Create;

  tbmSample.Filtered := False;
  tbmSample.Filter := 'ParentID = ' + QuotedStr(parentID);
  tbmSample.Filtered := True;

  while Not tbmSample.EOF do
  begin
    if tbmSample.FieldByName('ParentID').AsString = parentID then
      list.Add(tbmSample.FieldByName('ChildID').AsString + '=' +
        tbmSample.FieldByName('Caption').AsString);
    tbmSample.Next;
  end;

  i := 0;
  while i < list.Count do
  begin
    begin
      newNode := treeView.Items.AddChild(ParentNode,
        list.Values[list.Names[i]]);

      CreateTree(list.Names[i], newNode);

      Inc(i);

      if i Mod 10 = 0 then
        Application.ProcessMessages;
    end;
  end;

  list.Free;
end;

procedure TformMain.FormCreate(Sender: TObject);
begin
  tbmSample.Open;
  tbmSample.Insert;
  tbmSampleParentID.AsString := '0';
  tbmSampleChildID.AsString := '1';
  tbmSampleCaption.AsString := 'Wisnu';
  tbmSample.Post;

  tbmSample.Insert;
  tbmSampleParentID.AsString := '1';
  tbmSampleChildID.AsString := '2';
  tbmSampleCaption.AsString := 'Rayyan';
  tbmSample.Post;

  tbmSample.Insert;
  tbmSampleParentID.AsString := '1';
  tbmSampleChildID.AsString := '3';
  tbmSampleCaption.AsString := 'Aila';
  tbmSample.Post;

  tbmSample.Insert;
  tbmSampleParentID.AsString := '0';
  tbmSampleChildID.AsString := '4';
  tbmSampleCaption.AsString := 'Rachman';
  tbmSample.Post;

  tbmSample.Insert;
  tbmSampleParentID.AsString := '4';
  tbmSampleChildID.AsString := '5';
  tbmSampleCaption.AsString := 'Fina';
  tbmSample.Post;

  tbmSample.Insert;
  tbmSampleParentID.AsString := '4';
  tbmSampleChildID.AsString := '6';
  tbmSampleCaption.AsString := 'Fani';
  tbmSample.Post;
end;

procedure TformMain.btnRefreshClick(Sender: TObject);
var
  topNode: TTreeNode;
begin
  topNode := treeView.Items.Add(Nil, 'Root Node');
  CreateTree('0', topNode);
  tbmSample.Filtered := False;
end;

end.
 

Untuk project lengkapnya, download saja di sini.

Tampilan Aplikasi

Tampilan Aplikasi

Start and Stop Service using Delphi

Start and Stop Service using Delphi

Lagi bikin tools kecil untuk start dan stop Delphi, setelah googling nemu di sini. Tapi karena procedural, jadi gatel untuk mengubah ke class wised. Hal lain kalau mau menjalankan atau menghentikan service mesti menyebutkan WorkstationNamenya. Berarti kalau mau mendapatkan nama computer di mana program ini berjalan, mesti cari lagi. Hasil google mengarah ke sini. Ternyata kalau ada di domain, nama workstation saja masih tidak dikenali. Harus lengkap beserta domainnya. Lihat help di Delphi (F1), dapatlah GetComputerNameEx. Nah aku tambahkan juga method yang bisa mengenali workstation di mana programnya dijalankan. Aku sengaja tidak mengubah rutin yang di dapat dari Sadikhov forum di atas, hanya ditambahkan wrapper saja.

unit unitServiceControl;
interface
uses WinSvc, SysUtils, Windows;
{
  Main Routine for Services : TARZAN from http://www.sadikhov.com/forum/index.php?showtopic=9039
  Main Routine for ComputerName : Zarko Gajic from http://delphi.about.com/cs/adptips2002/a/bltip0102_3.htm
  OOP Wrapper and minor enhancement and variable type correction : Wisnu Widiarta (7 April 2011)
}
type
  TServiceControl = class
    public
      class function StartService(workstationName, serviceName: String) : boolean;
      class function StopService(workstationName, serviceName: String) : boolean;
      class function StartServiceInCurrentWorkstation(serviceName: String): Boolean;
      class function StopServiceInCurrentWorkstation(serviceName: String): Boolean;
      class function GetComputerNetName: String;
  end;
implementation
{ TServiceControl }
//
// start service
//
// return TRUE if successful
//
// sMachine:
//   machine name, ie: \SERVER
//   empty = local machine
//
// sService
//   service name, ie: Alerter
//

class function TServiceControl.StartService(workstationName,
  serviceName: string): boolean;
var
//
  // service control
  // manager handle
  schm,
  //
  // service handle
  schs   : SC_Handle;
  //
  // service status
  ss     : TServiceStatus;
  //
  // temp char pointer
  psTemp : PChar;
  //
  // check point
  dwChkP : Word;
begin
  ss.dwCurrentState := 0;
  // connect to the service
  // control manager
  schm := OpenSCManager(
    PChar(workstationName),
    Nil,
    SC_MANAGER_CONNECT);
  // if successful...
  if(schm > 0)then
  begin
    // open a handle to
    // the specified service
    schs := OpenService(
      schm,
      PChar(serviceName),
      // we want to
      // start the service and
      SERVICE_START or
      // query service status
      SERVICE_QUERY_STATUS);
    // if successful...
    if(schs > 0)then
    begin
      psTemp := Nil;
      if(WinSvc.StartService(
           schs,
           0,
           psTemp))then
      begin
        // check status
        if(QueryServiceStatus(
             schs,
             ss))then
        begin
          while(SERVICE_RUNNING
            <> ss.dwCurrentState)do
          begin
            //
            // dwCheckPoint contains a
            // value that the service
            // increments periodically
            // to report its progress
            // during a lengthy
            // operation.
            //
            // save current value
            //
            dwChkP := ss.dwCheckPoint;
            //
            // wait a bit before
            // checking status again
            //
            // dwWaitHint is the
            // estimated amount of time
            // the calling program
            // should wait before calling
            // QueryServiceStatus() again
            //
            // idle events should be
            // handled here...
            //
            Sleep(ss.dwWaitHint);
            if(not QueryServiceStatus(
                 schs,
                 ss))then
            begin
              // couldn't check status
              // break from the loop
              break;
            end;
            if(ss.dwCheckPoint <
              dwChkP)then
            begin
              // QueryServiceStatus
              // didn't increment
              // dwCheckPoint as it
              // should have.
              // avoid an infinite
              // loop by breaking
              break;
            end;
          end;
        end;
      end;
      // close service handle
      CloseServiceHandle(schs);
    end;
    // close service control
    // manager handle
    CloseServiceHandle(schm);
  end;
  // return TRUE if
  // the service status is running
  Result :=
    SERVICE_RUNNING =
      ss.dwCurrentState;
end;

//
// stop service
//
// return TRUE if successful
//
// sMachine:
//   machine name, ie: \SERVER
//   empty = local machine
//
// sService
//   service name, ie: Alerter
//
class function TServiceControl.StartServiceInCurrentWorkstation(
  serviceName: String): Boolean;
begin
  Result := StartService(GetComputerNetName, serviceName);
end;
class function TServiceControl.StopService(workstationName,
  serviceName: string): boolean;
var
  //
  // service control
  // manager handle
  schm,
  //
  // service handle
  schs   : SC_Handle;
  //
  // service status
  ss     : TServiceStatus;
  //
  // check point
  dwChkP : Word;
begin
  // connect to the service
  // control manager
  schm := OpenSCManager(
    PChar(workstationName),
    Nil,
    SC_MANAGER_CONNECT);
  // if successful...
  if(schm > 0)then
  begin
    // open a handle to
    // the specified service
    schs := OpenService(
      schm,
      PChar(serviceName),
      // we want to
      // stop the service and
      SERVICE_STOP or
      // query service status
      SERVICE_QUERY_STATUS);
    // if successful...
    if(schs > 0)then
    begin
      if(ControlService(
           schs,
           SERVICE_CONTROL_STOP,
           ss))then
      begin
        // check status
        if(QueryServiceStatus(
             schs,
             ss))then
        begin
          while(SERVICE_STOPPED
            <> ss.dwCurrentState)do
          begin
            //
            // dwCheckPoint contains a
            // value that the service
            // increments periodically
            // to report its progress
            // during a lengthy
            // operation.
            //
            // save current value
            //
            dwChkP := ss.dwCheckPoint;
            //
            // wait a bit before
            // checking status again
            //
            // dwWaitHint is the
            // estimated amount of time
            // the calling program
            // should wait before calling
            // QueryServiceStatus() again
            //
            // idle events should be
            // handled here...
            //
            Sleep(ss.dwWaitHint);
            if(not QueryServiceStatus(
                 schs,
                 ss))then
            begin
              // couldn't check status
              // break from the loop
              break;
            end;
            if(ss.dwCheckPoint <
              dwChkP)then
            begin
              // QueryServiceStatus
              // didn't increment
              // dwCheckPoint as it
              // should have.
              // avoid an infinite
              // loop by breaking
              break;
            end;
          end;
        end;
      end;
      // close service handle
      CloseServiceHandle(schs);
    end;
    // close service control
    // manager handle
    CloseServiceHandle(schm);
  end;
  // return TRUE if
  // the service status is stopped
  Result :=
    SERVICE_STOPPED =
      ss.dwCurrentState;
end;
class function TServiceControl.StopServiceInCurrentWorkstation(
  serviceName: String): Boolean;
begin
  Result := StopService(GetComputerNetName, serviceName);
end;
class function TServiceControl.GetComputerNetName: String;
var
  buffer: PWideChar;
  size: Cardinal;
begin
  size := 256;
  GetMem(buffer, size);
  if GetComputerNameEx(ComputerNameDnsFullyQualified, buffer, size) then
    Result := buffer
  else
    Result := '';
  FreeMem(buffer, size);
end;
end.

Penggunaannya simple saja:

const
  SERVICE_NAME = 'BlackfishSQL';
{$R *.dfm}
procedure TformMain.btnStartClick(Sender: TObject);
begin
  Log('Trying to stop SERVICE ' + SERVICE_NAME + ' in ' + TServiceControl.GetComputerNetName + '...');
  if TServiceControl.StopServiceInCurrentWorkstation(SERVICE_NAME) then
    Log('Service ' + SERVICE_NAME + ' has been stopped...')
  else
    Log('Service ' + SERVICE_NAME + ' cannot be stopped. Try to stopped it manually.');
  //Untuk dikomputer xxx
  if TServiceControl.StopService('xxx', SERVICE_NAME) then
    Log('Service ' + SERVICE_NAME + ' has been stopped...')
  else
    Log('Service ' + SERVICE_NAME + ' cannot be stopped. Try to stopped it manually.');
end;
procedure TformMain.Log(event: String);
begin
  mmLog.Lines.Add(FormatDateTime('DD/MM/YY hh:mm:ss', Now) + ' >> ' + event);
end;

PS: Kode ini kalau dijalankan di beberapa PC looping infinite. Kalau itu terjadi mungkin bisa mencoba cara lain dengan jalan memanggil aplikasi net.exe dari dos dengan perintah dari Delphi memanggil function ShellExecute yang bisa diperoleh dari unit ShellApi. Perintah untuk start adalah net.exe start <nama service>. Untuk menghentikan service, net.exe stop <nama service>.