วันพุธที่ 27 มกราคม พ.ศ. 2559

[Tutorial] การเขียน Datagrid เบื้องต้น แบบ WPF/MVVM

หลังจากคราวก่อนที่สอนเขียน MVVM เบื้องต้นไปแล้ว
ในวันนี้จะมาทบทวนกันอีกครั้ง พร้อมกับการเขียน datagrid แบบ MVVM ด้วยภาษา C#
ซึ่งเราได้ค้นหาข้อมูลเพิ่มเติมในการเขียน และนำมาสรุปเพื่อให้ผู้อ่านทำความเข้าใจมากขึ้น

หลังจากการสร้างโปรเจก WPF Application แล้ว เราจะต้องสร้าง class เพิ่ม
ในตัวอย่างนี้ จะให้มีข้อมูล คือ ID และ ชื่อผู้ใช้ โดยเราสามารถแสดงข้อมูลทั้งหมดขึ้นมา เพิ่ม หรือลบข้อมูลได้ โดยเริ่มจากการทำความเข้าใจว่า ตัวอย่างนี้ ต้องการข้อมูลอะไรบ้าง ใช้ทำอะไร

มาเริ่มที่ส่วนแรกเลย คือ ส่วน view หน้าตาของโปรแกรม


อย่าลืมใส่ xmlns:local เท่ากับ namespace ของเราด้วยนะ
ภายใต้ Window.Resources เราจะใส่หน้าตาของ datagrid ของเรา ซึ่งเราเองก็ลอกมาอีกที ซึ่งเราจะใช้แค่ 2 ส่วนแรกเท่านั้น คือกำหนดพื้นหลัง และ style ให้มัน

จากนั้นเราสร้าง DockPanel ขึ้นมา เพื่อวางปุ่มที่เราต้องใช้ด้านบน โดยที่ปุ่มจะ binding ด้วย command ส่วน textbox จะ binding ด้วย text และวาง datagrid ของเราด้วย
ส่วน datagrid นั้น มีส่วนสำคัญๆ คือ
- DockPanel.Dock เราไว้ตรงไหน บนล่างซ้านขวา
- CanUserAddRows เราจะให้ผู้ใช้เพิ่มแถวไหม
- CanUserDeleteRows เราจะให้ผู้ใช้ลบแถวไหม
- AutoGenerateColumns ต้องการให้ generate column เพิ่มไหม ซึ่งถ้าไม่ใส่ หรือใส่เป็น True มันจะ generate เพิ่มมาอีก จากที่เรา binding ซึ่งมันผิด ต้องใส่เป็น False นะคะ
- ItemsSource ตัวข้อมูลทั้งหมด ซึ่งเราสร้าง tracedata ขึ้นมาตัวนึง เราจะต้อง binding ไปที่ tracedata
- SelectedItem ดูว่าเราเลือก row ไหน ซึ่งเอามาใช้ในการลบ เราจะต้อง binding ไปที่ selected item
จากนั้นใส่ column ให้มัน และเรา binding ค่า properties ที่เราสร้างขึ้นใน model และ view-model
ก่อนที่เราจะไปในส่วนอื่นต่อ อย่าลืมใส่ this.DataContext = new MainViewModel(); ที่ MainWindows.xaml.cs เพื่อจะได้เรียกตัว view-model ขึ้นมาทำงาน


ถัดมาคือส่วน model เราจะเขียน class UserDatabase เพื่อสร้างฐานข้อมูลขึ้นมาเบื้องต้นก่อน

ซึ่งมี ID และ name รวมทั้งความยาวของ string array ที่เราเก็บข้อมูลด้วย โดยความยาวเราจะใช้ความยาว array ทั้งหมด ลบด้วย 1 กันการวน loop แล้วเลขที่ array เกิน

และสร้าง class TraceData เพื่อสร้าง tracedata ที่เก็บข้อมูลทุก row ที่เราสร้างไว้
ส่วนนี้เราจะนำค่าทั้งหมดใน tracedata มาแสดงบน datagrid ของเรา โดยของเราจะเก็บค่า ID และชื่อไว้

ส่วนสุดท้าย คือ view-model เป็นการเขียนคำสั่งขึ้นมา เพื่อใช้งานในส่วน view
อย่าลืมใส่ class BaseViewModel กับ RelayCommand ก่อนนะ

เราสร้างตัวแปร database ที่เราสร้างไว้ตอนแรก เพื่อดึงข้อมูลฐานข้อมูลที่เราสร้างไว้ ขึ้นมาแสดงในตอนแรก สร้างตัวแปร tracedata ขึ้นมา สร้างตัวแปรรับค่าจาก Textbox และสร้างตัวแปร selectitem ขึ้นมา

จากนั้นเรา set properties ขึ้นมา ทั้ง tracedata id name ที่เรารับค่ามา และ selecteditem

และเราตั้งค่าเบื้องต้นให้กับ tracedata ของเราซะ โดยเรานำค่าจาก database ของเรา มาใส่ใน tracedata
ซึ่งเราจะสร้าง function มากำหนดค่าเบื้องต้นของ tracedata

ส่วนคำสั่ง เรามีคำสั่งแสดงค่าทั้งหมดขึ้นมา เราเลยเคลียร์ทั้งหมดทิ้ง และสร้างใหม่ด้วย function เมื่อสักครู่นี้ ถ้า add ใหม่เลย มันจะสร้างซั้านะ


ส่วนเพิ่มข้อมูล เราจะตรวจสอบก่อนว่า ผู้ใช้ใส่ค่า ID และ name ครบแล้ว จากนั้นค่อยสร้าง row เพิ่มเพื่อใส่ข้อมูล โดยใช้คำสั่ง Add ถ้าใส่ไม่ครบมันจะมีหน้าต่างเตือนขึ้นมา

ส่วนลบข้อมูล หลักการทำงานคือ เราจะต้องรู้ก่อนว่า row ที่เราจะลบอยู่ตรงไหน โดยใช้ selecteditem เป็นตัวบอกว่าอยู่ตรงไหน จากนั้นค่อยสั่งลบที่เรา selecteditem มันก็จะลบออกให้เรา โดยใช้คำสัง Remove


หน้าตาโปรแกรมที่ได้ ID 001-010 เป็นส่วนที่เรากำหนดค่าไว้เบื้องต้น เรา add ID = 999 และ name = hhhh ส่วนที่เราเพิ่มจะอยู่ด้านล่าง ถ้าเราลบออกสัก 1 row กด delete ปุ๊ป แถวนั้นหายปั๊ป


สำหรับคนที่อ่านแล้วงงๆ หรืออยากเห็นตัวอย่างกับตา
มาดูที่นี่เลยจ้า >> https://github.com/mikkipastel/Datagrid

ป้ายกำกับ: ,

วันศุกร์ที่ 8 มกราคม พ.ศ. 2559

[Tutorial] การเขียนโปรแกรม Timer แบบ WPF/MVVM ฉบับมือใหม่เพิ่งหัดเขียน

ก่อนอื่นเรามาอธิบายความหมายมันก่อน ว่าคือสิ่งใด

WPF หรือ Windows Presentation Foundation เป็นแนวคิดการพัฒนาโปรแกรมที่ทำให้โปรแกรมของเรานั้น มีหน้าตา หรือ User Interface (UI) สวยงามขึ้น ใช้เทคโนโลยีของ .NET Framework โดยรองรับการเขียนโปรแกรมหลายแบบ เช่น MVVM

MVVM หรือ Model-View-View-model คือการแยกส่วนการของ function และ user interface ให้เป็นอิสระต่อกัน โดยใช้ data binding เป็นตัวเชื่อม มีส่วนประกอยคือ model, view และ view-model ซึ่งการเขียนแบบนี้จะเป็นการลดเวลาเขียนโปรแกรมภายในทีม ลดความยุ่งยากและซับซ้อนลง อีกทั้งแก้ไข้ได้ง่ายอีกด้วย

จุดต่างระหว่าง MVP และ MVVM
ปกติเราจะเขียนโปรแกรมแบบ MVP กันใช่เปล่า สมมุติเราเขียนโค้ดอันนี้ มีปุ่มสองอัน text สักอันนึงแสดงผล เวลาเราเขียนปุ่ม เราต้องบอกว่าปุ่มนี้ทำงานนี้นะ ถ้ามีอีกปุ่มทำงานเหมือนกันหล่ะ ก็ก็อปไปสิ ถ้าไม่ใช่เราก็ลบโค้ดทิ้ง หรือ comment out ถ้าหน้าตาเปลี่ยน บางทีโค้ดอาจจะต้องเปลี่ยนอีก เวลาแก้งานของคนในทีม จะเกิดความลำบาก เพราะต้องอ่านโค้ดให้เข้าใจก่อน แล้วค่อยมานั่งแก้ ใช้เวลามากกว่าจะเสร็จ ซึ่ง MVVM แยกการเขียน function กับ UI ออกจากกัน ดังนั้น developer กับ programmer อาจจะไม่ต้องมานั่งตบตีกัน แต่ละคนทำงานของตัวเองไป แล้วค่อยเอามารวมกันโดยการ binding อาจจะใช้เวลาในการ training แต่ไม่ต้องเสียเวลาแก้งานมากเกินไป

ในที่นี่ จะอธิบายการเขียนโปรแกรมจับเวลา โดยใช้หลักการของ MVVM
มี feature คือ กด start เพื่อเริ่มต้นจับเวลา pause เพื่อหยุดการจับเวลา restart ล้างค่าเดิมและเริ่มต้นจับเวลาใหม่ และ stop คือหยุดจับเวลาและล้างค่าเดิมทิ้ง

วิธีการสร้าง project 
เราใช้โปรแกรม Visual Studio 2013 ในการเขียนโปรแกรม
- ไปที่ menubar เลือก File -> New -> Project
- เลือก Templates -> Visual C# -> WPF Application
- ใส่ชื่อ project ที่ Name


ในโปรเจกจะมีแค่ไฟล์ view คือ MainWindows.xaml เท่านั้น เราจะต้อง add .cs เข้าไปเพิ่ม โดยคลิกขวาที่โปรเจก เลือก Add -> Class



เรามาอธิบายแต่ละส่วนของ MVVM ว่ามีอะไรบ้าง ทำงานอย่างไร

(1) model : เป็น class ที่กำหนด object ทั้งหมดที่ใช้ในโปรแกรมนี้ (.cs) มีทั้งหมด 2 ส่วน คือ


#region Members : ประกาศตัวแปรที่ใช้ในนี้

#region Properties : สร้าง class รับส่งค่า โดย get คือ คืนค่าออกไป และ set คือ กำหนดค่าของตัวแปรที่เราสร้าง

(2) view-model : เป็นตัวกลางระหว่างส่วน view และ model เราเขียนการทำงานของโปรแกรม และนำไป binding ใน view สามารถมีได้มากกว่า 1 ไฟล์ (.cs) สืบทอด class จาก INotifyPropertyChanged ทำให้ส่วนทีมีการ binding เปลี่ยนค่า มีทั้งหมด 4+2 ส่วน นั่นคือ 6 ส่วนนั่นเอง ทำไมถึงเป็น 4+2 ส่วนหล่ะ 

ครึ่งแรกเป็นส่วนที่เราสร้างขึ้นเพื่อใช้งานในโปรแกรมของเรา 


- #region Members : ประกาศ object class และตัวแปรที่ใช้

เราประกาศ class timer เพื่อสร้าง object Timer ที่เราสร้างจากในส่วน model และตัวแปร  time_cnt เพื่อนับรอบของ ticker และ aTimer เพื่อสร้าง Timer โดยให้รอบนึงนาน 500 ms หรือครึ่งวินาที

- #region Properties : มีสองส่วนคือ class รับส่งค่าของ object class และ class ที่รับส่งค่าของตัวแปรอื่น โดยจะอ้างอิงตัวแปรจาก model class ในส่วนของ set เราสามารถเขียนแบบไหนก็ได้ โดยทั่วไปจะเขียนแบบในรูปนี้ มีการใช้คำสั่ง RaisePropertyChanged เพื่อ set ค่าเบื้องต้นใหม่ ซึ่งเกี่ยวข้องกับส่วนครึ่งหลัง


- #region Constructors : กำหนดค่าเบื้องต้นให้กับที่เราประกาศ object โดยสร้าง function ชื่อเดียวกับ class หลัก

เรา set ค่าเบื้องต้นของ timer โดยให้ setTimer เท่ากับ "0" (เริ่มต้นที่ 0) และ aTimer เราสร้าง ElapsedEventHandler ขึ้นมา มีการทำงานคือ ถ้าครบ 1 รอบ ตามเวลาที่เราตั้งไว้ จะทำให้ทำคำสั่งนี้นะ ในที่นี้จะให้นับจำนวนรอบที่ทำงาน และเก็บค่านี้ที่ setTimer ส่วนนี้เราสามารถเขียน function แยกและมาเรียกใช้ใน OnTimedEvent ได้ แล้วแต่ถนัด



- #region Commands : ส่วนคำสั่งที่เราจะใช้ในโปรแกรมของเรา ซึ่งเอาชื่อของคำสั่งมา blinding กับส่วน view โดยมีหลักการเขียน คือ
1. เขียน function void <command_name>Execute() เพื่อใส่คำสั่งตามที่เราต้องการ
2. เขียน function bool Can<command_name>Execute() ใส่ข้างในว่า return true; เราไม่ต้องทำอะไรกับ function นี้ก็ได้
3. สร้าง command โดยใส่ดังนี้ public ICommand <command_name> { get { return new RelayCommand (<command_name>Execute, Can<command_name>Execute);} } 
ตัว RelayCommand เราสามารถ copy ไปใส่ได้เลย เป็นอีก class นึงในโปรเจก
อย่าลืมใส่ using System.Windows.Input; ด้วยนะ ไม่งั้นจะไม่รู้จัก ICommand ของเรา
4. เอา <command_name> ไปใส่ในส่วน view ใน tag ที่เราต้องการ Command = { Binding <command_name> }
การดึง Timer มาใช้งาน
aTimer.Start(); เริ่มต้นจับเวลา โดยจะ set enable เป็น true ให้เลย
aTimer.Stop(); หยุดจับเวลา โดยจะ set enable เป็น false ให้เลย แต่จะไม่ reset ค่า เมื่อกด start มันจะนับต่อ ถ้าจะ reset ใหม่ เราต้อง set ค่า time_cnt ให้เป็น 0 เพื่อเริ่มนับรอบใหม่
aTimer.Dispose(); เป็นการล้าง aTimer ของเราทิ้ง ซึ่งไม่เหมาะกับโปรแกรมนี้ เพราะถ้าเรากด start มันจะทำงานไม่ได้ เพราะ aTimer มันโดนลบทิ้งไปแล้ว

ครึ่งหลังเป็น INotifyPropertyChange เหมือนเป็นการดูว่ามีการเปลี่ยนแปลงใน component หรือไม่ ซึ่งเราสามารถ copy ไปใช้ในงานของเราได้เลย 


- #region INotifyPropertyChanged Members : ประกาศตัวแปร PropertyChangedEventHandler ใช้ใน function RaisePropertyChanged
- #region Methods : function RaisePropertyChanged ถ้า handler ไม่เท่ากับ null ให้สร้าง instance ใหม่

ในครึ่งหลังนี้ เราอาจจะเขียนเป็น class แยกออกมาก็ได้ แต่ตัว view-model หลักของเรา ต้องสืบทอดจาก class ใหม่ที่เราสร้าง แทน INotifyPropertyChanged เพราะในอันใหม่นี้ เราได้สืบทอด INotifyPropertyChanged ไปแล้ว ดังนี้


(3) view : เป็นส่วนของ user interface โดยจะทำการ blinding function และ variable ที่นี่ (.xaml)


ในขั้นแรก ใส่ xmlns:local="clr-namespace:<namespace>" ใน <Window>
ในที่นี้ namespace คือ Timer ดังนั้นใส่ xmlns:local="clr-namespace:Timer"

ต่อมา เพิ่มโค้ดส่วน Windows:DataContext ไว้ก่อน layout ซึ่ง local จะไปเรียกส่วน view-model ไม่งั้นส่วนที่เรา binding จะไม่ทำงาน


ในส่วนการ binding เราจะแบ่งเป็นสองแบบ โดยจะนำจาก view-model มาใส่

แบบแรก : ในส่วนการแสดงผล ขึ้นอยู่กับแต่ละตัว เช่น Label TextBox TextBlock ใส่ชื่อ class ที่เราสร้าง
Content/Text = { Binding <object> } 
ในที่นี้ เราจะแสดงจำนวนรอบที่จับเวลา จึงใส่ setTimer

แบบสอง : ในส่วนการทำงาน เช่น Button MenuItem เราใส่ชื่อคำสั่งที่ทำขึ้นมา
Command = { Binding <command_name> }

เว็บไชต์อ้างอิง ประกอบการเขียน
- การใช้ library Timer

ตัวอย่างโค้ดนี้มีให้ไปศึกษาการทำงานแบบ MVVM และ Timer
>> https://github.com/mikkipastel/Timer
การศึกษาให้เข้าใจ นอกจากจะอ่านแล้ว ต้องลงมือทำด้วยนะ :)

ป้ายกำกับ: ,