วันพุธที่ 11 มีนาคม พ.ศ. 2558

ทำแผ่นติดตั้งด้วย Setup Factory พร้อม MySQL Portable ง่ายๆ ครับ

สวัสดีครับ วันนี้ผมจะขอนำเสนอการติดตั้ง Database Server ของเจ้า MySQL Server กันครับ
แต่ที่ผ่านมา ผมพบกับความยุ่งยากเวลาเขียนโปรแกรมฐานข้อมูลติดต่อกับ MySQL เพราะเวลาลงโปรแกรมให้ลูกค้าเราต้องลงโปรแกรมเราเองก่อน เสร็จแล้วก็ต้องลง MySQL Server เอาไว้รัน Service ของเจ้า MySQL อีก แต่วันนี้ผมจะเปลี่ยนวิธีเดิมๆ มาเป็น MySQL Portable แทน ซึ่งมันง่ายต่อการติดตั้งของตัวลูกค้ามากๆครับ คือ เราทำแผ่นติดตั้งหรือตัว Installation ให้ลูกค้าแค่นั้นพอ เราไม่ต้องเสียเวลาไปเซ็ต โน่น นี่ นั่น ให้ลูกค้าเลยครับ ซึ่งการทำแผ่นติดตั้งในบทความนี้ผมใช้ Setup Factory 8.0 ครับ ซึ่งตอนนี้โปรแกรมเค้าออกเวอร์ชั่นใหม่กว่าแล้ว ก็ลองๆ ประยุกต์ดู Concept เดียวกันครับ

ก่อนอื่นก็ไปดาวน์โหลด โปรแกรม 2 ตัวนี้มาก่อนครับ

1. MySQL Portable : Download USBWebserver V8.6
2. Setup Factory : Setup Factory 9.5 Free Trial

ก่อนอื่นต้องขอเกริ่นครับว่า ผมจะเขียนโปรแกรมเก็บข้อมูลง่ายๆ (ตารางเดียวพอครับ) แต่จะเน้นการใช้งาน MySQL Portable กับ Setup Factory ว่ามีวิธีตั้งค่าอะไรยังงัยครับ ให้โปรแกรมเราติดตั้งได้ง่ายๆ แล้วลูกค้าก็จะแฮปปี้ครับ ^_^

1. Upzip ไฟล์ USBWebserver ออกมาก่อนครับ



2. ดับเบิลคลิกไฟล์ usbwebserver.exe จะแสดงหน้าต่างโปรแกรมตามรูปด้านล่าง จากนั้นไปที่แท็บ Setting ไปเปลี่ยน Port MySQL ไม่ให้ซ้ำกับ Port อื่น (ผมเอา 3350 ละกันครับ) เสร็จแล้วกดปุ่ม Save จากนั้นโปรแกรมจะให้เรา Restart โปรแกรม USBWebserver อีกทีครับ เราก็ทำตามนั้นเลยครับ พอเปิดโปรแกรมขึ้นมาอีกครั้ง Port MySQL ก็จะเปลี่ยน 3350 ตามที่เราเปลี่ยนครับ


3. ต่อมา เรามาลองสร้างตารางฐานข้อมูลกันดู ซึ่งตัวอย่างนี้จะติดต่อกับ MySQL Portable ด้วยโปรแกรม SQLyog ครับ (ใครถนัดตัวไหนก็แล้วแต่สะดวกครับ) เปิดโปรแกม SQLyog ขึ้นมาแล้วตั้งค่าตามภาพด้านล่างนี้เลยครับ เสร็จแล้วกดปุ่ม Connect 


4. คลิกที่ดาต้าเบส test แล้วสร้างตารางด้วยคำสั่ง SQL ในส่วน Query ครับ เสร็จแล้วกดปุ่ม F9

คำสั่งสร้างตาราง tbl_class (เก็บรายชื่อห้องเรียนครับ เอาง่ายๆ เป็น Idea ครับ)
CREATE TABLE `tbl_class` (
  `class_id` char(10) NOT NULL,
  `class_name` varchar(70) DEFAULT NULL,
  PRIMARY KEY (`class_id`)
) ENGINE=InnoDB DEFAULT CHARSET=tis620




5. ที่นี้เรามาสร้างโปรเจคด้วย Delphi กันครับ ตามรูปด้านล่างเลยครับ เขียนง่ายๆ สไตล์ Delphi ครับ ลากแล้ววาง Code ไม่เขียน ซักตัวก็ทำงานได้ครับ 

ดาวน์โหลด Source code ของโปรเจค


6. ต่อไปเรามาสร้างแผ่น Install ของโปรแกรมเรากันครับ เริ่มต้นจากเปิดโปรแกรม Setup Factory ขึ้นมา แล้วทำตามขั้นตอนด้านล่างเลยครับ
















รูปด้านบนใส่ ตามนี้นะครับ
INIFile.SetValue(SessionVar.Expand("%AppFolder%\\MySQL\\my.ini"), "client", "port", "3350");
INIFile.SetValue(SessionVar.Expand("%AppFolder%\\MySQL\\my.ini"), "mysqld", "port", "3350");
INIFile.SetValue(SessionVar.Expand("%AppFolder%\\MySQL\\my.ini"), "mysqld", "innodb_data_home_dir", SessionVar.Expand("%AppFolder%\\MySQL\\").."data\\");
INIFile.SetValue(SessionVar.Expand("%AppFolder%\\MySQL\\my.ini"), "mysqld", "innodb_log_group_home_dir", SessionVar.Expand("%AppFolder%\\MySQL\\").."data\\");

result = Service.Query("MySQLPortableTest","");
if result == 0 then
File.Run(SessionVar.Expand("%AppFolder%\\MySQL\\bin\\mysqld_usbwv8"),"/install MySQLPortableTest","","",true);
end

File.Run("net","start MySQLPortableTest","","",true);



รูปด้านบนใส่ ตามนี้นะครับ
File.Run("net","stop MySQLPortableTest","","",true);
File.Run(SessionVar.Expand("%AppFolder%\\MySQL\\bin\\mysqld_usbwv8"),"/remove MySQLPortableTest","","",true);



รูปด้านบนใส่ ตามนี้นะครับ
Folder.DeleteTree(SessionVar.Expand("%AppFolder%\\MySQL\\"), nil);






7. ทีนี้เรามาลอง ติดตั้งโปรแกรมของเรากันดูครับ ว่าใช้ได้หรือเปล่า จากรูปด้านล่างจะเห็นว่าไฟล์ติดตั้งมีขนาดประมาณ 6 MB กว่าๆครับ











8. สุดท้ายเรามาลอง Run โปรแกรมกันดูครับ








การใช้งานทั้งหมดต้องการให้เห็นเพียง Concept การใช้งานเท่านั้น เราสามารถไปประยุกต์ใช้ต่อได้อีกมากมายครับ

สำหรับท่านใดที่ต้องการศึกษาการพัฒนาโปรแกรมด้วย Delphi อย่างรวดเร็ว ลัดสั้น ตรงจริง ผมขอแนะนำให้ศึกษาจากสื่อการสอนของบริษัท เดลส์เน็ท เอ็นเทอร์ไพรส์ จำกัด  (www.delsnet.com) ครับ ส่วนตัวผมก็เริ่มต้นจากตรงนั้น จนวันนี้เขียนโปรแกรมด้วย Delphi มาโดยตลอด จนเขียนโปรแกรมหารายได้เสริมตอนเรียนมหาลัยได้สบายๆ เลยครับ เพราะเข้าใจและทำตามเองได้ครับ










วันพุธที่ 4 มีนาคม พ.ศ. 2558

บันทึก/โหลด ค่าพร็อพเพอตี้ของคอนโทรลต่างๆ บนฟอร์ม

พอดีมีเพื่อนที่มหาลัยที่เขียน Delphi ด้วยกัน Line มาถามผมว่า "อยากให้ค่าต่างๆ ของคอนโทรลต่างๆบนฟอร์มไม่ถูกลบตอนปิดโปรแกรม พอเปิดโปรแกรมขึ้นมาอีกครั้งให้ค่าพวกนั้นมันอยู่เหมือนเดิมได้มั้ย" ผมก็รีบตอบเลยครับว่า "ได้" จริงมีวิธีอยู่หลายวิธีนะครับ แต่วันผมจะใช้วิธี เก็บบันทึกค่า Property ที่ต้องการของ Control ทั้งหมดที่อยู่บนฟอร์ม ไว้ที่ไฟล์ ini หรือ Initialize File  ซึ่งมันก็คือ Text File ธรรมดาๆนี่เองแหละครับ โดยไฟล์เหล่านี้จะมีนามสกุลเป็น INI ซึ่งเราจะบันทึกค่าล่าสุดไว้ก่อนปิดโปรแกรม และโหลดมันขึ้นมาตอนเปิดโปรแกรมง่ายๆ อย่างงี้เลยครับ จริงๆวิธีการนี้ก็มีคนเค้าใช้กันมานานแล้ว อาจจะเก่าไปแล้วด้วยซ้ำ เนื่องจากถูกเอกสาร XML (eXtensive Markup Language) เข้ามาแทนที่มันไปแล้วครับ แต่ว่าวิธีนี้ก็ยังใช้ได้ดีแล้วที่สำคัญคือมันเข้าใจได้อย่างง่ายดายเลยทีเดียว

INI File จะมีส่วนสำคัญ 3 ส่วน ดังนี้คือ

  1. Section ส่วนของชื่อที่ปิดด้วยเครื่องหมาย Bracket [] ... เพื่อตั้งชื่อเป็นกลุ่มข้อมูล
  2. Key เป็นชื่อเฉพาะที่มีค่าไม่ซ้ำกัน หรือ ก็คือตัวแปรดีๆนี่เองแหละครับ
  3. Value เป็นค่าที่ถูกกำหนดให้กับ Key เพื่อใช้ในการอ่าน และ เขียนเข้าไปใน INI File

เริ่มกันเลยครับ

1. สร้างโปรเจคขึ้นมาก่อนแล้ววาง Control ต่างตามรูปด้านล่างเลยครับ เอาเฉพาะ Control มาตรฐานนะครับ ซึ่งเราจะเก็บค่าสำคัญๆ ของ Control เหล่านั้นไว้

2. กด F12 เข้าไปในส่วน Code ของโปรแกรม ไปที่ส่วน Interface ของโปรแกรมให้ Uses ยูนิตไฟล์ IniFiles เข้าด้วยครับ


3. ประกาศ Array ไว้เก็บชื่อ Property ของ Control ทั้งหมดบนฟอร์มไว้ 
ใส่ชื่อ Property ที่สำคัญๆ ที่ต้องการเก็บตามรูปด้านล่างเลยครับ เช่นว่า ถ้าเป็น EditText ก็อาจจะเก็บ ค่า Text หรือ CheckBox อาจจะเก็บค่า Checked ไว้ ว่าถูกติ๊กอยู่หรือไม่ หรืออาจจะเก็บค่า Position หรือตำแหน่งของ Trackbar เอาไว้ 
ส่วนต่อมาให้ประกาศฟังก์ชั่น ขึ้นมา 4 ตัว ตามรูปเลยครับ เอาไว้ Load / Save ค่า Control ต่างๆ 



4. ในส่วน implementation เขียน Code ของฟังก์ชั่น SaveControls ตามนี้เลยครับ เอาไว้บันทึกค่า ของ Property ใน Control ลงไฟล์ ini

5. ตามด้วยเขียน Code ของฟังก์ชั่น LoadControls ตามนี้เลยครับ เอาไว้โหลดค่า ของ Property  Control จากไฟล์ ini มาสู่ Control บนฟอร์ม

6. สุดท้าย เขียนอีก 2 ฟังก์ชั่นที่เหลือ ครับ แล้วพอเราจะใช้งานกันจริงๆ ก็ไปที่ Event OnClose กับ OnCreate ของ Form ครับ ใน OnClose เราจะบันทึกค่า Property ของ Control ก่อนปิดโปรแกรม ดังนั้นเราจึงเรียกใช้ฟังก์ชั่น SaveControlsToFile() และพอเวลาโปรแกรมจะเปิด เราก็ไปโหลดค่าต่างที่เก็บใน ini เข้าสู่ Control โดยใช้ฟังก์ชั่น LoadControlsFromFile() ซึ่งทั้ง 2 ฟังก์ชั่นจะรับพารามิเตอร์ 2 ตัวคือ 1) ชื่อไฟล์ ini และ 2) ตัว Form ที่ต้องการเก็บ Control ซึ่งคำว่า Self ก็คือตัวอ้างอิง Object ของ Class TForm1 ซึ่ง สืบทอดมาจาก Class TForm นั่นเอง หรือพูดให้เข้าใจง่ายๆก็คือ ตัวมันเองหรือฟอร์มๆนั้น นั่นแหละครับ



7. ลองทดสอบโปรแกรมดูครับ พอเรา Run โปรแกรมครั้งแรก ค่า Default ของ Control ต่างๆ บนฟอร์มคงยังเหมือนเดิมกับตอนออกแบบครับ แล้วลองเข้าไปดูที่ Folder เก็บ Exe ไฟล์ของโปรแกรมเราจะสังเกตว่ายังไม่การสร้างไฟล์ ini เลยครับ

8. ที่นี้เราลองเปลี่ยนแปลงค่าต่างๆ ของ Control แล้วปิดโปรแกรมครับ


10. ทีนี้เราลองมาดูข้อมูลในไฟล์ ini ครับ

11. ทีนี้ถ้าเราเปิดโปรแกรมครั้งต่อไป ค่าต่างๆที่บันทึกใน ini ก็จะถูกโหลดเข้าสู่ Control บนฟอร์มเองครับ


Source code ของโปรแกรมครับ เขียนด้วย XE7 นะครับ
 unit Unit1;  
 interface  
 uses  
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,  
  System.Classes, Vcl.Graphics,  
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,  
   Vcl.ComCtrls, cxTextEdit, cxCurrencyEdit, IniFiles, cxGraphics, cxControls,  
  cxLookAndFeels, cxLookAndFeelPainters, cxContainer, cxEdit;  
 type  
  TForm1 = class(TForm)  
   Edit1: TEdit;  
   CheckBox1: TCheckBox;  
   cxCurrencyEdit1: TcxCurrencyEdit;  
   TrackBar1: TTrackBar;  
   Memo1: TMemo;  
   Label1: TLabel;  
   Label2: TLabel;  
   Label3: TLabel;  
   Label4: TLabel;  
   procedure FormCreate(Sender: TObject);  
   procedure FormClose(Sender: TObject; var Action: TCloseAction);  
  private  
   { Private declarations }  
  public  
   { Public declarations }  
  end;  
 var  
  Form1: TForm1;  
 const CONTROL_PROPS: array[0..9] of string =  
    ( 'Left',  
     'Top',  
     'Width',  
     'Height',  
     'Visible',  
     'Caption',  
     'Text',  
     'Checked',  
     'Value',  
     'Position'  
    );  
  procedure SaveControls(toIniFile: TIniFile; fromForm: TForm);  
  procedure LoadControls(fromIniFIle: TIniFile; toForm: TForm);  
  procedure SaveControlsToFile(const FileName: string; fromForm: TForm);  
  procedure LoadControlsFromFile(const FileName: string; toForm: TForm);  
 implementation  
 {$R *.dfm}  
 uses TypInfo;  
 procedure SaveControls(toIniFile: TIniFile; fromForm: TForm);  
 var  
  i, j : integer;  
  obj : TComponent;  
  s, sec : string;  
 begin  
    // เก็บชื่อ Section  
    sec := fromForm.Name;  
    // วนลูปไปบนคอมโพเน้นท์ทุกตัวบนฟอร์ม  
    for i := 0 to fromForm.ComponentCount -1 do begin  
    // เก็บค่า Reference (ค่าอ้างอิงตัวคอมโพเน้นท์)  
      obj := fromForm.Components[i];  
    // วนลูปเข้าสู่ Property ที่ประกาศไว้บน Array  
      for j := Low(CONTROL_PROPS) to High(CONTROL_PROPS) do  
    // เช็คว่าคอมโพเน้ท์มีพร็อเพอตี้ที่ประกาศไว้หรือไม่  
        if IsPublishedProp(obj, CONTROL_PROPS[j]) then begin  
    // เก็บค่าในรูปแบบ "ชื่อคอมโพเน้นท์.พร็อพเพอตี้"  
         s := Format('%s.%s', [obj.Name, CONTROL_PROPS[j]]);  
    // เขียนข้อมูลลงไฟล์ ini  
         toIniFile.WriteString(sec, s, GetPropValue(obj, CONTROL_PROPS[j]));  
        end;  
    end;  
 end;  
 procedure LoadControls(fromIniFIle: TIniFile; toForm: TForm);  
 var i, j : integer;  
   obj : TComponent;  
   s, sec, value : string;  
 begin  
    // เก็บชื่อ Section  
    sec := toForm.Name;  
    // วนลูปไปบนคอมโพเน้นท์ทุกตัวบนฟอร์ม  
    for i := 0 to toForm.ComponentCount -1 do begin  
    // เก็บค่า Reference (ค่าอ้างอิงตัวคอมโพเน้นท์)  
      obj := toForm.Components[i];  
    // วนลูปเข้าสู่ Property ที่ประกาศไว้บน Array  
      for j := Low(CONTROL_PROPS) to High(CONTROL_PROPS) do  
    // เช็คว่าคอมโพเน้ท์มีพร็อเพอตี้ที่ประกาศไว้หรือไม  
        if IsPublishedProp(obj, CONTROL_PROPS[j]) then begin  
    // เก็บค่าในรูปแบบ "ชื่อคอมโพเน้นท์.พร็อพเพอตี้"  
         s := Format('%s.%s', [obj.Name, CONTROL_PROPS[j]]);  
    //อ่านข้อมูลจากไฟล์ ini ตาม Section และ Identity  
         value := fromIniFIle.ReadString(sec, s, EmptyStr);  
    // ตรวจสอบค่าที่อ่านว่ามีอยู่ใน ini หรือไม่  
         if value <> EmptyStr then  
    // กำหนดค่า ลง Property ของ Component นั้นๆ  
           SetPropValue(obj, CONTROL_PROPS[j], value);  
        end;  
    end;  
 end;  
 procedure SaveControlsToFile(const FileName: string; fromForm: TForm);  
 var ini : TIniFile;  
 begin  
    ini := TIniFile.Create(ExtractFilePath(Application.ExeName)+FileName);  
    SaveControls(ini, fromForm);  
    FreeAndNil(ini);  
 end;  
 procedure LoadControlsFromFile(const FileName: string; toForm: TForm);  
 var ini : TIniFile;  
 begin  
    ini := TIniFile.Create(ExtractFilePath(Application.ExeName)+FileName);  
    LoadControls(ini, toForm);  
    FreeAndNil(ini);  
 end;  
 procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);  
 begin  
  SaveControlsToFile('config.ini', Self);  
 end;  
 procedure TForm1.FormCreate(Sender: TObject);  
 begin  
  LoadControlsFromFile('config.ini', Self);  
 end;  
 end.