หนึ่งในสิ่งที LEVEL51 ต้องการทำมาพักหนึ่งแล้ว ก็คือการ Rewrite ตัว SystemX ในส่วนที่เป็น UI ใหม่ โดยเราอยากให้มันเป็น App ลงมือถือ/Tablet ได้ด้วย และอยากให้มัน Share Code กันได้โดยไม่ต้องเขียนซ้ำหลายรอบ
สำหรับ ตัว SystemX นี้ เขียนด้วยเทคโนโลยี Windows Presentation Foundation (WPF) ซึ่งสำหรับผมแล้ว มันยังคงเป็น UI Framework ที่ดีที่สุด และใช้สร้าง Application ที่หน้าตาดูดีได้เร็วที่สุดตัวหนึ่ง ขอเอา Application ที่เราเคยเขียนด้วยเทคโนโลยีลูกหลานของ WPF คือ Silverlight และ Universal Windows App สมัยนู้นนนน มาให้ดูหน่อยบ้างแล้วกันนะ
ThaiPBS Client for Windows Phone
KFC App for Windows 8
NostraMap for Windows Phone
โปรแกรมที่หน้าตาดูอวกาศแบบนี้ เราทำได้ตั้งแต่เมื่อ 10 ปีที่แล้ว สงสัยเหมือนกันว่าทำไมปัจจุบันหน้าตาโปรแกรมมันยังไม่ไปต่อจากนี้กันซะที 😅
แต่ว่าจุดด้อยของ WPF คือ มันผูกติดอย่างเหนียวแน่นกับ Windows ตามชื่อ ทำให้การที่จะทำให้ App มัน Cross-Platform เรียกได้ว่าเป็นไปไม่ได้เลยในช่วงก่อนหน้านี้ นอกจากว่ามันจะ Closed Source แล้วก่อนหน้านี้ มันยังซับซ้อนมากอีกต่างหาก ตรงนี้ถึงขั้นที่ว่ามีคนเห็นโอกาสทางธุรกิจ ไปสร้างเป็น Framework/Tools ในการ Port Application ที่เขียนด้วย C#/WPF ไปเป็น Web Application ขึ้นมาเลยทีเดียว ชื่อว่า http://cshtml5.com/ ผมดูคร่าวๆ แล้วมันทำงานได้ดีเลยทีเดียว มันทำการแปลงโค๊ดหน้าจอที่เขียนด้วย XAML ให้เป็น HTML5 และแปลโค๊ด C# เป็น JavaScript ด้วย Bridge.net อีกที แน่นอนว่าเราก็สามารถเอาไฟล์ที่ Build ออกมาแล้ว ไปใส่ใน Cordova/Capacitor ของ Ionic แล้วทำให้มันกลายเป็น Mobile App ได้เลย
จาก: CSHTML5.Samples.Showcase พวกคุณเจ๋งมากอะ Render XAML เป็น HTML
และก็มีอีกกลุ่มที่พยายามทำอยู่เหมือนกันชื่อว่า uno platform https://platform.uno/ โดยกลุ่มนี้ ใช้การ Render UI เอง ที่เป็นความคิดแบบเดียวกับการทำงานของ WPF ที่ Render บน Windows ด้วย DirectX (WPF อายุ 14 ปีแล้ว~) แทนการพยายามใช้ Native Control ของแต่ละ Platform เหมือนกับ Flutter ที่ใช้ Skia ในการ Render UI ซึ่งในแง่ของ Branding แล้ว มันดีกว่าการทำ App ที่หน้าตาดูเหมือนชาวบ้านเขาไปหมดอยู่เหมือนกันนะ
พวกคุณก็เทพมาก UNO Platform
จริงๆ แล้วจะว่าไป สำหรับคนที่ใช้ C#/WPF มาก่อนแบบเรา Uno น่าจะเป็นตัวเลือกที่น่าสนใจมากถึงที่สุดแล้ว แต่เวลาเราจะเลือกใช้ Platform ตัวใดตัวหนึ่งก็ต้องคำนึงด้วยว่า มี Community แค่ไหน (จริงๆ ก็เยอะพอสมควรเลยละสำหรับ uno) และต้อง "ต่อสู้" ขนาดไหนในการใช้งานมัน หลังจากที่ทดลองเล่น Demo ของ Uno พักนึงแล้ว ก็เลยเริ่มพอจะเห็นว่า เราต้องพึ่งพาเขามากเลยทีเดียว เนื่องจากทีม Uno เป็นคนพัฒนาตัว Frontend ให้เราทั้งหมด อะไรที่ใช้งานไม่ได้ เราก็จะติดกับเขาไปจนกว่าจะแก้เสร็จ
ก็เลยต้องหากันต่อ...สุดท้ายวนกลับมาที่ Blazor อีกรอบ ก่อนจะงงว่า เฮ้ย แกเขียน App Windows ทำไมมายุ่งอะไรกับเทคโนโลยีเขียนเว็บ มาดู Blazor ไปพร้อมกันก่อน...
ทำความรู้จักกับ Blazor
Blazor เป็น Framework แบบ Open Source สำหรับการพัฒนา Single Page Application (SPA) โดยที่ไม่จำเป็นต้องเขียน JavaScript ที่ฝั่ง Client (ก็คือเขียนได้ถ้าว้อน) เลย ตัว Framework นี้ปัจจุบันถูกรวมเป็นส่วนหนึ่งของ ASP.NET Core เรียบร้อยแล้ว ซึ่งก็เป็นการพัฒนาของ Microsoft ในแนวทางใหม่ คือตัว ASP.NET Core ก็เป็นโปรเจคแบบ Open Source ด้วยเหมือนกัน ตัวโปรเจคได้รับการสนับสนุนโดย .NET Foundation ที่มีสมาชิกสนับสนุนคือ Microsoft, Amazon Web Service และ Telerik ซึ่งเป็นบริษัทลูกของบริษัท Progress เจ้าของระบบ ERP ชื่อดัง เป็นต้น
อยากทำบุญและมีชื่อในนี้ไหม? ทำบุญปีละ 1.5 ล้านบาท
ความแตกต่างระหว่าง Blazor กับ SPA Framework ตัวอื่นๆ ก็อยู่ที่ว่า เราไม่ต้องเสียเวลาเขียนโค๊ดใน JavaScript เพื่อที่จะทำให้ Server กับหน้าเว็บคุยกันได้ ก็ดูจาก Github ของระบบ NancyBlack (ระบบที่ใช้รันเว็บ LEVEL51) จะเห็นว่าแค่ JavaScript ก็ปาเข้าไป 40% ของโค๊ดทั้งหมดแล้ว เทียบสัดส่วนกับโค๊ด C# หลังบ้าน มากกว่ากันเกือบ 3 เท่า แต่จะว่าไปใน Repo มันมี Code ของ Angular/Jquery ที่มันปนอยู่ด้วยนะ ผลอาจจะไม่ค่อยตรงเท่าไหร่ แต่ถ้าเอาจากความรู้สึกก็อย่างน้อยเขียน JavaScript มากกว่า C# แน่นอน 🤣 นี่ยังไม่รวมโค๊ดของหน้าเว็บ LEVEL51 เลยนะ
โดยตอนที่ผมเลือกว่าจะเริ่มใช้ Blazor นั้นผมได้นั่งพิจารณาหลายตัวประกอบกันแล้ว เพราะว่ากำลังรู้สึกว่าควรจะรื้อระบบเว็บอายุ 10 ปีนี้ได้แล้ว มีเขียนอธิบายไว้ให้แล้วที่โพสนี้ตามลิงค์นี้นะ โดยโพสนั้นจะเป็นเรื่องการเอา Blazor ใช้ทำตัวระบบ CMS เป็นหลัก คือเน้นทำเว็บ ส่วนโพสนี้ จะเน้นเรื่องความ Interactive
นอกจากนี้ มีรุ่นน้องท่านนึงส่งมาให้ดู มี Framework อีก 2 ตัวที่พยายามจะทำคล้าย Blazor ก็คือลด JavaScript และให้ Server เป็นคน Render ตัวหน้าเว็บให้มากขึ้น สอง Framework ที่ว่าก็คือ
- Hotwrite - ให้เราทำการใส่ Component ชื่อ <turbo-frame> แล้วสิ่งที่อยู่ใน turbo-frame จะสามารถส่งเป็น Update มาจาก Server ได้ ซึ่งจะว่าไป ไอเดียนี้มันจะไปเหมือนกับ ASP.NET Ajax เมื่อ 13 ปีก่อนอยู่เหมือนกัน
Screencap จากวีดีโอบนหน้าเว็บ Hotwire
คล้ายกับ ASP.NET AJAX เมื่อปี 2007 เลยมั๊ย~ - Phoenix LiveView - ใช้ Websocket ในการดึง HTML ที่ Render จาก Server มาเหมือนกัน คล้ายกับ Hotwire โดยสามารถเขียนคำสั่งจับ Event จาก Client ได้ โดยใช้ Attribute phx-click โดยโค๊ดของ Component อยู่ประมาณนี้
ส่วนที่ Blazor "แจ่ม" กว่าก็คือว่า เราสามารถใส่โค๊ด Blazor ลงไปในหน้า HTML ได้เลย โดยที่ไม่ต้องเปลี่ยนแปลงส่วนต่างๆ ของหน้าจอให้มันแตกต่างออกไปจากเดิมมากนัก สำหรับท่านที่เคยใช้ AngularJS หรือ Vue มาก่อน ก็จะรู้สึกคุ้นตาทันที ก่อนจะเชื่อผม เราลองมาทำโปรแกรม Todo List แบบที่แชร์กันได้ข้ามเครื่องกัน พร้อมกันเลยดีกว่า
สำหรับโค๊ดตัวอย่างนี้ สามารถดูได้จาก Github นะ nantcom/coresharp-blazorinteractive1: Sample Code from CoreSharp Blog (github.com)
เริ่มสร้างโปรแกรม Todo List
สำหรับการสร้าง Project Blazor ใหม่ ศึกษาได้จาก Post ที่ผ่านมานะ ขอไม่เขียนซ้ำแล้ว ลองไปทำตามนั้นเลย
อย่างแรก เราก็จะต้องมี Class สำหรับเก็บข้อมูลก่อน เริ่มจากการสร้าง Class โดยการคลิกขวาใน Folder Data และเลือก Add / Class และตั้งชื่อว่า "Todo.cs"
และเราจะใส่โค๊ดไปตามนี้
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace CoreSharp.SampleBlazor.Data { public class Todo { public string Owner { get; set; } public bool IsDone { get; set; } public string Title { get; set; } public static ConcurrentBag<Todo> Items { get; } = new(); } }
นั่นก็คือใน Class ของเรา จะมี 3 Attribute คือ Owner, IsDone, Title โดยในภาษา C# การมี Get/Set ข้างหลังนี้คือ Getter/Setter และเราจะเรียกมันว่าเป็น Property แต่ว่าเราไม่ได้ทำอะไรพิเศษกับมัน (คือใส่แค่ Get/Set เฉยๆ) แบบนี้จะเรียก Auto Implemented Property มีค่าเท่ากับการสร้าง Field (ตัวแปรใน Class) แล้วใส่ Function Getter/Setter ที่อ่านและ Set ค่าตัวแปรตัวนั้นตรงๆ แบบนี้
private string _Owner; public string Owner { get { return _Owner; } set { _Owner = value; } }
ส่วน ConcurrentBag นั้นคือ List หรือ Array หรือ Vector แล้วแต่ว่ามาจากภาษาไหน แต่ว่าที่มันเรียกว่า ConcurrentBag คือมันสามารถรับการเพิ่มไอเทมเข้าไปได้จากหลาย Thread พร้อมกัน ส่วนที่มันประกาศแบบ Static ก็คือมันจะเป็น Property ที่มีแค่ 1 Instance ต่อ 1 Process ของโปรแกรมเท่านั้น มันจึงเป็น Singleton ไปด้วยเลยในตัว
และเราต้องการ Component ใหม่ สำหรับหน้าจอ todo เราจึงต้องสร้างมันขึ้นมา โดยใช้เมนู Add / Razor Component
และการที่จะบอกกับระบบของ Blazor ว่า Component นี้ สามารถเข้ามาได้ทาง /todo ก็เพียงแค่เพิ่ม Directive @page เข้าไป ตามนี้
@page "/todo"
ถ้าดูในไฟล์ จะเห็นว่ามีส่วนที่เขียนว่า @code จริงๆ แล้วเราสามารถแยกโค๊ด c# เป็นไฟล์ Code Behind ได้ด้วย แต่เพื่อความสะดวกในการทำความเข้าใจ เราจะรวมไว้ในนี้ก่อนแล้วกัน ทำการเพิ่มโค๊ดตามนี้ลงไป
public bool IsNameSet { get; set; } public string Name { get; set; } public void SetName() { if (string.IsNullOrEmpty(Name) == false) { this.IsNameSet = true; } }
โดยโค๊ดที่เราเพิ่มเข้าไป มี Property 2 ตัว และ Function สำหรับเซ็ตค่าของ Name โดยในฟังก์ชั่น จะเปลี่ยน Property IsNameSet เป็น True เมื่้อ Property Name ไม่ใช่ค่า Null หรือ ความว่างเปล่าเท่านั้น
จากนั้น เราก็เพิ่ม HTML ที่มี Directive ของ Razor เข้าไป ลักษณะคล้ายกับ Vue และ Angular ดังนี้
- ใน Attribute ของ Element เราสามารถใส่ Expression ลักษณะเดียวกับ AngularJS/Vue ได้ โดยใช้ @( … ) ครอบ Expression ไว้ ในตัวอย่างจะเป็นการซ่อน / แสดง Element โดยใช้คลาส d-none / d-block ของ Bootstrap
- ค่าของ input สามารถ Bind เข้ากับ Property ที่เราสร้างไว้ได้ ลักษณะเดียวกับ ng-model ของ AngularJS หรือ v-model ของ Vue
- ปุ่มธรรมดาของ HTML สามารถยิงเป็นคำสั่ง กลับมาเรียก Function ในฝั่ง Server ได้ โดยเพิ่ม @ ไว้หน้าชื่อ event เช่น @onclick="SetName" หมายถึง เรียกฟังก์ชั่น SetName ที่สร้างไว้เมื่อครู่นี้ได้
มี Tips เล็กๆ ว่า เราไม่งงแน่นอนระหว่าง Blazor กับ JS ถ้าหากเราพยายามเพิ่ม Tag Script ลงไป ตัว Compiler จะไม่ยอม และแจ้งเราให้ใช้วิธีอื่นที่ไม่ใช่การเพิ่ม Tag Script แทน (สามารถเรียก Script จาก c# ได้ด้วย)
พอลองกดรันดู ก็จะเห็นว่า เมื่อเราพิมพ์ชื่อลงไป แล้วกดปุ่ม ตัวหน้าจอ Form ก็จะถูก Hide (โดยใส่ CSS ว่า display none) และ alert ที่เขียนว่า Hello ตามด้วยชื่อที่พิมพ์ ก็แสดงขึ้นมา
ถ้าเราเช็ค Netowrk Activity จะเห็นว่า Blazor ทำการเชื่อมต่อกับ Server ผ่านทาง Websocket และมีการส่งค่าไปมาระหว่าง Server โดยใช้ Bandwidth เล็กมาก เพียง 350 Byte ต่อ 1 รอบการทำงาน นั่นก็คือ Blazor ไม่ได้ทำการส่ง HTML มาจาก Server แต่เท่าที่ทราบคือเป็นคำสั่งในการแก้ไขตัว DOM ให้เป็นไปตามที่ Server มองเห็น
นอกจากนี้ เรายังสามารถเขียน HTML ใน Razor ในแบบที่มองเห็นแล้วเข้าใจง่ายกว่า โดยการใช้ @if แบบนี้ ก็ได้เช่นกัน ลองตรวจสอบแล้วก็พบว่า ยังใช้ Bandwidth เท่าๆ วิธีการเดียวกับ Show/Hide ด้วย Class ที่เขียนเป็น Expression
ในจุดนี้ เราสามารถ Login หลอกๆ ได้แล้ว ก็มาถึงขั้นตอนในการทำให้หน้าจอ สามารถเพิ่ม Todo เข้าไปได้บ้าง เริ่มจาก เพิ่มโค๊ดเข้าไปอีกเล็กน้อยในส่วนของ Code ของหน้าจอ Todo.razor เพื่อรองรับการเพิ่ม Todo ใหม่เข้าไป
สาเหตุที่ต้องเขียนว่า Data.Todo เพราะว่า เราดันตั้งชื่อหน้านี้ว่า Todo.razor มันจะถือว่าหน้านี้ชื่อคลาสว่า Todo เหมือนกัน นอกจากนี้ คลาส Todo ที่เราสร้างนั้น อยู่คนละ Namespace กับเราในตอนนี้ เลยต้องใส่ Data เพื่อระบุให้ชัดเจน
public Data.Todo NewItem { get; set; } = new(); public void SendNewItem() { var item = this.NewItem; this.NewItem = new(); item.Owner = this.Name; Data.Todo.AddItem(item); }
จากนั้น ก็ทำการเพิ่มโค๊ด HTML เข้าไป โดยผูก Event และผูก Value เช่นเดียวกันกับครั้งก่อน
- ให้ Input ตัวนี้ ผูกกับ Property Title ของ Property NewItem ที่เป็นชนิด Todo (คลาส Todo ที่เราสร้างในขั้นแรก)
- สังเกตว่า เป็นการใช้คำสั่ง Foreach ในการ Iterate (Loop) เข้าไปใน ConcurrentBag ที่เราสร้างไว้ก่อนหน้านี้
และเราไม่ได้ Iterate เฉยๆ เราทำการเรียงข้อมูล โดยดูจาก Property Title ด้วย
สำหรับท่านที่เคยใช้ AngularJS / Vue มา คงจะคุ้นตามาก เพราะ ในข้อ 2) ก็คือแนวความคิดเดียวกันกับ ng-repeat และ v-for นั่นเอง แต่ว่า List ที่เราทำงานด้วย ไม่ใช่ List ในฝั่ง JavaScript แต่ว่าเป็น List ที่อยู่ในหน่วยความจำของ Server เลย
ถ้าเราลองรันดู ก็จะพบว่า ข้อมูลจาก Client 2 ที่ สามารถมองเห็นข้อมูลพร้อมกันได้ แต่อาจจะยังไม่ใช่อย่างที่เราต้องการสักเท่าไหร่
จุดสังเกต
- ถ้าเรา Unfocus ตัว Inputbox (ใน Gif คือ เมาส์คลิกตรงสีฟ้า) ข้อมูลจากอีกหน้าหนึ่ง จะแสดงขึ้นมาทันที นั่นก็คือ หลังจากเกิดการประมวลผลการ Binding ตัว Blazor จึงสามารถ Detect ได้ว่า ตัว List นั้นมีการเปลี่ยนแปลง
- ข้อมูลที่เราเพิ่มเข้าไป จะไม่แสดงในอีกหน้าจอในทันที จนกว่าจะเกิด Event Binding
สำหรับท่านที่เคยใช้ Framework อื่นๆ มา ก็จะเริ่มนึกถึงว่า เราจะต้องใช้พวก Reactive Framework เข้ามาช่วยจัดการแล้วสินะ (Observable, Subject, ReplaySubject, BehaviorSubject…) แต่สำหรับ C# นั้น มีระบบที่เรียกว่า Event อยู่แล้ว ซึ่งเราสามารถนำมาใช้ในกรณีนี้ได้
คราวนี้เรากลับไปที่ Todo.cs ใน Folder Data อีกครั้ง เพื่อเพิ่มโค๊ดเล็กน้อย ดังนี้
- สร้าง Event ใหม่ ชื่อว่า NewItemsAdded โดยชนิดของมันคือ Action - ซึ่งเป็น Function Pointer ที่ใน .NET เรียกว่า "Delegate" สำหรับ Function ที่ไม่รับค่าอะไร และไม่คืนค่าอะไร Function ที่สามารถถูกเก็บค่าโดย Action ได้ ก็อย่างเช่น public void DoSomething() { … } เป็นต้น
ส่วนการใส่ = delegate {} ไว้ข้างหลัง ก็คือเราจะใส่ Empty Function เอาไว้ก่อนเลย เพื่อที่ตอนเที่เราเรียกใช้ Delegate จะได้ไม่ต้องใส่ If เช็คว่า Event นี้ มีค่าเป็น Null หรือไม่ ทุกครั้ง - สร้าง Static Function ใหม่ เพื่อทำการ Add Item ใหม่ เข้าไปใน ConcurrentBag Items และทำการ Broadcast Event ที่ชื่อ NewItemsAdded ซึ่งการ Broadcast นั้น ก็เขียนเหมือนกับการเรียก Function ธรรมดา แต่ใส่เป็นชื่อ Event แทน
ส่วนในหน้า Razor ทำการแก้ไขโค๊ดอีกเล็กน้อย ดังนี้
- ทำการ Override Function OnInitialized() เพื่อใส่โค๊ดสำหรับทำงานหลังจากที่ Component นี้ เริ่มทำงาน โดยเพิ่มโค๊ดในการ Subscribe ไปยัง Event NewItemsAdded ที่เราสร้างขึ้น สังเกตที่ += คือการ Subscribe หรือ การเพิ่ม Delegate (Function Pointer) ของ Function เรา เข้าไปยัง Event NewItemsAdded
และตั้งแต่ () => … เป็นต้นไป คือการสร้าง Function ด้วย Lambda Expression (คล้ายกับใน JavaScript) ซึ่งในฟัง์ชั่นนี้ เราทำการเรียกใช้คำสั่ง InvokeAsync โดยคำสั่ง InvokeAsync รับ Parameter เป็น Delegate ของ Function ที่เราต้องการให้ทำงานใน UI Thread โดยเราส่ง Delegate ของฟังก์ชั่น StateHasChanged ของ Component ไป
โดยสรุปก็คือ เมื่อเกิด NewItemsAdded เราจะทำการแจ้งให้ Blazor ทราบว่า State ของ Component นี้ มีการเปลี่ยนแปลง ให้ทำการ Update หน้าจอตาม นั่นเอง - แทนที่เราจะเรียกใช้ Items.Add ตรงๆ เปลี่ยนไปเรียกใช้ Function ที่เราสร้างขึ้นแทน
พอลองรันโปรแกรม ก็จะได้ผลตามที่เราต้องการแล้ว คือ Add จากหน้าหนึ่ง ก็ไปแสดงที่อีกหน้าด้วย
Realtime ขึ้นไปอีก กับการ Sync การกด Done Task ด้วย!
การจะให้สถานะการกดนั้น Sync ข้ามหน้ากันด้วย ก็สามารถใช้ Event ทำได้อีกเหมือนกัน โดยคราวนี้ เราจะใช้การสร้าง Event เอาไว้ในหน้า Todo.razor โดยตรงเลย แบบนี้
private static event Action StateChanged = delegate { }; private void OnStateChanged() { Todo.StateChanged(); } protected override void OnInitialized() { Todo.StateChanged += () => { this.InvokeAsync(this.StateHasChanged); }; }
และตอนที่มีการคลิ๊กเลือก Done ก็สามารถใช้ Event Blur ช่วยในการ Trigger ให้มีการ Render HTML ใหม่ได้เช่นกัน
<input class="form-check-input" type="checkbox" @bind="item.IsDone" @onblur="OnStateChanged" >
แต่ว่าการ Sync จะเกิดขึ้น หลังจากที่ ไป Focus ที่ Control อื่นก่อนนะ สาเหตุที่เราไม่สามารถใช้ @onchange ได้ เพราะว่า @bind นั้น จะใช้ @onchange ไปแล้ว เนื่องจาก @bind จะถูกเปลี่ยนโดย Compiler ให้กลายเป็น @onchange ให้เรา (เป็น Syntactic Sugar) แต่นั้นก็แปลว่า จริงๆ เราสามารถเขียน Event Handler ที่ @onchange แบบ Inline ด้วยภาษา C# เองได้ด้วย!
แต่ว่าด้วยความ Complicate ของ Attribute checked ของ Tag input ซึ่งเราไม่สามารถใส่ว่า checked="@(item.IsDone)" ได้ เนื่องจากถ้ามี Attribute Changed มันจะถือว่ามัน "Checked" ทันที ไม่ว่าจะใส่ค่าว่าอะไรก็ตาม เราก็เลยต้องสร้าง Function สำหรับปล่อย Attribute checked ออกมา เมื่อค่าของ IsDone เป็น True เท่านั้น ดังนี้
private IEnumerable<KeyValuePair<string, object>> GetChecked(Data.Todo item) { if (item.IsDone) { yield return new KeyValuePair<string, object>("checked", "checked"); } yield break; }
โดยการเขียนแบบนี้เรียกว่า Iterator นั่นก็คือเราไม่ต้องสร้าง Array หรือ List ขึ้นมา เพื่อจะคืนค่าที่เป็น Series ออกไป สามารถใช้ yield return แบบนี้ได้เลย ลองศึกษาตัวอย่างต่อทางนี้
ส่วนโค๊ดตรง checkbox จะเปลี่ยนเป็นแบบนี้
<input class="form-check-input" type="checkbox" @onchange="(e)=> { item.IsDone = (bool)e.Value; this.OnStateChanged(); }" @attributes="GetChecked(item)">
จุดสังเกต
- ตรง @onchange เราสามารถเขียนเป็น Lambda ของ C# ได้เลย และสามารถอ้างอิงถึงตัวแปร item ซึ่งอยู่ใน Loop ได้
- @attributes นั้นไปเรียกฟังก์ชั่น GetChecked อีกที โดยส่งตัวแปร item เข้าไปให้ เพื่อให้มันปล่อย Attribute chcked ออกมา ต่อเมื่อค่าของ IsDone เป็นจริง เท่านั้น
เท่านี้ก็เสร็จ!
แล้วโปรแกรมที่ยุ่งกับการตั้งค่า Hardware มันจะรันด้วย Blazor ที่เป็นเว็บได้อย่างไร?
สำหรับ SystemX นี้เราออกแบบมาแต่ต้นแล้วว่า ตัวที่เป็นหน้าจอ กับตัวที่เป็นการตั้งค่า Hardware นั้น ทำงานแยกกัน คือหน้าจอที่เป็น UI นั้น ไม่มีโค๊ดโที่เกี่ยวข้องกับการตั้งค่า Hardware อยู่เลย นั่นก็คือโค๊ดที่ทำงานจริงๆ นั้น จะอยู่ในโปรแกรมที่เรียกว่า SystemX Agent ทั้งหมด และมันติดต่อส่งข้อมูลหากันด้วยระบบ SignalR อีกทีหนึ่้ง ดังนั้นเราจึงสามารถทำหน้า UI ใหม่ได้อย่างง่ายดาย เพราะว่า UI มันก็มีแต่ UI จริงๆ
จะเห็นว่านอกจากหน้าจอโปรแกรม Windows แล้ว เรายังมีหน้าจอที่เป็น Web ที่สามารถเรียกดูได้จากมือถือ คือ SystemXZ, SystemX Deck ที่ใช้สั่งงานเครื่องได้ และ SystemX Log Viewer ที่ก็เป็นหน้าเว็บอีกเช่นกัน
SystemZ |
SystemX Deck |
SystemX Log Viewer |
ดังนั้นทุกอย่างมีพร้อมอยู่แล้วที่จะเปลี่ยนหน้าจอ WPF ของ SystemX ให้เป็นเว็บไปด้วย เราแค่ยังไม่ได้ทำเท่านั้นเอง 😅
ส่วนว่า เราจะทำหน้าจอ SystemX เดิมด้วย HTML5 แล้วให้มันรันเป็นโปรแกรม Windows ได้อย่างไร มาติดตามกันต่อตอนหน้านะ สวัสดีครับ~
สำหรับโค๊ด ตามไปทางนี้ nantcom/coresharp-blazorinteractive1: Sample Code from CoreSharp Blog (github.com)