<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://coredeveloper.net/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>meekob</title><link>http://coredeveloper.net/blogs/meekob/default.aspx</link><description /><dc:language>en</dc:language><generator>CommunityServer 2007.1 (Build: 20917.1142)</generator><item><title>พัฒนา Silverlight Project ด้วย MVVM</title><link>http://coredeveloper.net/blogs/meekob/archive/2009/04/19/silverlight-project-mvvm.aspx</link><pubDate>Sun, 19 Apr 2009 04:32:17 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:668</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=668</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2009/04/19/silverlight-project-mvvm.aspx#comments</comments><description>&lt;p&gt;ในการพัฒนา Rich Internet Application ด้วย Silverlight ถึงแม้ว่ากระบวนการทำงานจะถูกออกแบบมาเพื่อให้นักพัฒนา และนักออกแบบสามารถทำงานร่วมกันได้ โดยแบ่งการทำงานออกเป็นส่วนตามความถนัดของแต่ละฝ่าย แต่นักพัฒนาส่วนใหญ่ ยังคงทำการเขียนโค้ดลงในส่วน Code-Behind ของ UserControl เป็นหลัก เนื่องจากมีความสะดวก รวดเร็ว จึงเหมาะกับการพัฒนาโครงการขนาดเล็ก อย่างไรก็ดี การเขียนโค้ดแบบนี้มักจะขาดประสิทธิภาพในการพัฒนาโครงการขนาดใหญ่ ซึ่งมักจะมีนักพัฒนาหลายคนร่วมกันพัฒนา จะทำให้แบ่งส่วนการทำงานไม่ชัดเจน และการทดสอบโค้ดแต่ละส่วน (Unit-Testing) ก็ทำได้ยากด้วยเช่นกัน เพื่อให้การทำงานเป็นไปอย่างคล่องตัว และการพัฒนามีความยืดหยุ่น MVVM จึงเป็นวิธีการหนึ่งซึ่งนักพัฒนาในต่างประเทศนิยมใช้ในการแก้ปัญหา&lt;/p&gt;  &lt;p&gt;MVVM หรือ Model View ViewModel เป็น Design Pattern ชนิดหนึ่งซึ่งได้รับอิทธิพลจาก Model View Controller (MVC) Pattern ซึ่งจะแบ่งการเขียนโค้ดออกเป็น 3 ส่วนหลักๆ กล่าวคือ&lt;/p&gt;  &lt;p&gt;1. Model เป็นโค้ดส่วนที่แทนโครงสร้างของข้อมูลที่อยู่เบื้องหลังแอพพลิเคชั่น&lt;/p&gt;  &lt;p&gt;2. View เป็นส่วนที่เกี่ยวข้องกับส่วนติดต่อผู้ใช้ ซึ่งมักจะเป็นส่วนที่นักออกแบบต้องทำงานด้วยมากที่สุด จึงมักจะเป็นส่วนของ XAML ล้วนๆ และจะมีส่วนที่เป็นโค้ดน้อยที่สุด โดยมากมักจะเป็นโค้ดที่เกี่ยวกับการทำ Data-Binding เพื่อระบุตำแหน่งที่จะแสดงผลของข้อมูลเท่านั้น&lt;/p&gt;  &lt;p&gt;3. ViewModel เป็นส่วนที่ใช้เชื่อมระหว่าง Model และ View เป็นส่วนที่ดึงข้อมูลตาม Model จากแหล่งข้อมูลต่างๆ (เช่น เว็บเซอร์วิส หรือฐานข้อมูล) มาเตรียมเพื่อแสดงผลใน View&lt;/p&gt;  &lt;p&gt;ที่จริงแล้วยังไม่มีการกำหนดลำดับก่อนหลังให้กับการพัฒนาแบบนี้อย่างเป็นทางการ นักพัฒนาอาจจะเริ่มจากการพัฒนา Model ก่อน แล้วจึงพัฒนา ViewModel หรือ พัฒนาตาม View ที่นักออกแบบเตรียมไว้ให้ก็ได้ ในตัวอย่างที่จะยกต่อไปนี้ จะเริ่มจาก Model ก่อน แล้วจึงพัฒนา ViewModel และนำไปรวมกับ View เป็นลำดับสุดท้าย เพื่อให้เห็นลำดับชั้นการทำงานได้อย่างชัดเจนยิ่งขึ้น&lt;/p&gt;  &lt;h3&gt;มาดูโจทย์ของเรากันเลยดีกว่า&lt;/h3&gt;  &lt;p&gt;เพื่อให้ง่ายกับการทำความเข้าใจ เราจะทำแอพพลิเคชั่นง่ายๆ เช่น แบบทดสอบคณิตศาสตร์ขึ้นมา แบบทดสอบที่ว่า จะมีการแสดงโจทย์ เช่น 1+1=? และมีช่องคำตอบซึ่งเป็น TextBox เตรียมไว้ให้ เมื่อผู้ใช้กรอกคำตอบก็จะไปตรวจสอบว่าคำตอบถูกต้องหรือไม่ แล้วแสดงผลบอกผู้ใช้ทาง CheckBox ท้ายที่สุดผู้ใช้สามารถกดปุ่มเพื่อส่งผลไปยังเซอร์ฟเวอร์เพื่อบันทึกคำตอบได้ แอพพลิเคชั่นนี้จะเน้นที่การแบ่งส่วนการทำงานเป็นลำดับชั้น ดังนั้นจะไม่นำเสนอโค้ดส่วนที่ไม่เกี่ยวข้องต่างๆ เช่น การตรวจสอบรูปแบบของคำตอบ เป็นต้น เพื่อไม่ให้เกิดความสับสน ก่อนอื่น ให้เราเปิด Visual Studio ขึ้นมาเพื่อสร้าง Silverlight Project กันก่อน (ผู้เขียนไม่ขอกล่าวถึงขั้นตอนการติดตั้ง Silverlight Templates บน Visual Studio เนื่องจากเป็นเรื่องพื้นฐาน ขอให้ผู้อ่านดูขั้นตอนการติดตั้งได้จาก http://silverlight.net/GetStarted/)&lt;/p&gt;  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/p1_1ABA6B62.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="p1" border="0" alt="p1" src="http://coredeveloper.net/blogs/meekob/p1_thumb_560DD120.png" width="243" height="151" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;h3&gt;เริ่มจาก Model กันก่อน&lt;/h3&gt;  &lt;p&gt;ดังที่กล่าวไปแล้วข้างต้น Model เป็นส่วนที่ใช้แทนโครงสร้างข้อมูล ดังนั้นในที่นี้จึงเป็นคลาสที่มีคุณสมบัติ (Properties) ต่างๆของข้อมูลคำถามอยู่ Model จะอิมพลีเมนท์ INotifyPropertyChanged เพื่อให้คลาสที่นำออปเจ็คของมันไปใช้ทราบเมื่อเกิดการเปลี่ยนแปลงของข้อมูลภายใน&lt;/p&gt;  &lt;p&gt;เมื่อสร้าง Silverlight Project เรียบร้อยแล้ว ให้เราสร้างโฟลเดอร์ชื่อ Model ขึ้นมา จากนั้นเพิ่มคลาสใหม่ชื่อ Question เพื่อใช้บรรจุข้อมูลคำถาม คลาส Question นี้จะอิมพลีเมนท์อินเทอร์เฟส INotifyPropertyChanged ซึ่งจะบังคับให้คลาสจะต้องมีอีเวนท์ที่ชื่อ PropertyChanged เกิดขึ้น จากนั้นเราจะเพิ่มเมธอดชื่อ RaisePropertyChanged เข้าไป เพื่อใช้ช่วยในการสร้างอีเวนท์เมื่อเกิดการเปลี่ยนแปลงของข้อมูล&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 1&lt;b&gt; &lt;/b&gt;&lt;b&gt;Imports&lt;/b&gt;&lt;b&gt; System.ComponentModel&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 2&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 3&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Class&lt;/b&gt;&lt;b&gt; Question&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 4&lt;b&gt; &lt;/b&gt;&lt;b&gt;Implements&lt;/b&gt;&lt;b&gt; INotifyPropertyChanged&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 5&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 6&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Event&lt;/b&gt;&lt;b&gt; PropertyChanged(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; sender &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Object&lt;/b&gt;&lt;b&gt;, _&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 7&lt;b&gt; &lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; e &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; System.ComponentModel.PropertyChangedEventArgs) _&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 8&lt;b&gt; &lt;/b&gt;&lt;b&gt;Implements&lt;/b&gt;&lt;b&gt; System.ComponentModel.INotifyPropertyChanged.PropertyChanged&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 9&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 10&lt;b&gt; &lt;/b&gt;&lt;b&gt;Private&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt; RaisePropertyChanged(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; propertyName &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 11&lt;b&gt; &lt;/b&gt;&lt;b&gt;RaiseEvent&lt;/b&gt;&lt;b&gt; PropertyChanged(&lt;/b&gt;&lt;b&gt;Me&lt;/b&gt;&lt;b&gt;, &lt;/b&gt;&lt;b&gt;New&lt;/b&gt;&lt;b&gt; PropertyChangedEventArgs(propertyName))&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 12&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 13&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Class&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;จากนั้นเราจะเพิ่มคุณสมบัติต่างๆของคำถามเข้าไปในคลาส เช่น Text แทนตัวคำถาม, ProvidedAnswer แทนคำตอบที่ผู้ใช้จะป้อนเข้ามา, ActualAnswer แทนคำตอบที่ถูกต้อง และ IsCorrectAnswer แทนความถูกต้องของคำตอบ&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 3&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Class&lt;/b&gt;&lt;b&gt; Question&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 4&lt;b&gt; &lt;/b&gt;&lt;b&gt;Implements&lt;/b&gt;&lt;b&gt; INotifyPropertyChanged&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 5&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 6&lt;b&gt; &lt;/b&gt;&lt;b&gt;Private&lt;/b&gt;&lt;b&gt; _text &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 7&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt; Text() &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 8&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 9&lt;b&gt; &lt;/b&gt;&lt;b&gt;Return&lt;/b&gt;&lt;b&gt; _text&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 10&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 11&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; value &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 12&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; _text = value&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 13&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 14&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 15&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 16&lt;b&gt; &lt;/b&gt;&lt;b&gt;Private&lt;/b&gt;&lt;b&gt; _actualAnswer &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 17&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt; ActualAnswer() &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 18&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 19&lt;b&gt; &lt;/b&gt;&lt;b&gt;Return&lt;/b&gt;&lt;b&gt; _actualAnswer&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 20&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 21&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; value &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 22&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; _actualAnswer = value&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 23&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 24&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 25&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 26&lt;b&gt; &lt;/b&gt;&lt;b&gt;Private&lt;/b&gt;&lt;b&gt; _providedAnswer &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 27&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt; ProvidedAnswer() &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 28&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 29&lt;b&gt; &lt;/b&gt;&lt;b&gt;Return&lt;/b&gt;&lt;b&gt; _providedAnswer&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 30&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 31&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; value &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;String&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 32&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; _providedAnswer = value&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 33&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RaisePropertyChanged(&lt;/b&gt;&lt;b&gt;&amp;quot;ProvidedAnswer&amp;quot;&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 34&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RaisePropertyChanged(&lt;/b&gt;&lt;b&gt;&amp;quot;IsCorrectAnswer&amp;quot;&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 35&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 36&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 37&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 38&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt; IsCorrectAnswer() &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Boolean&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 39&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 40&lt;b&gt; &lt;/b&gt;&lt;b&gt;Return&lt;/b&gt;&lt;b&gt; (ActualAnswer = ProvidedAnswer)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 41&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 42&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; value &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Boolean&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 43&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RaisePropertyChanged(&lt;/b&gt;&lt;b&gt;&amp;quot;IsCorrectAnswer&amp;quot;&lt;/b&gt;&lt;b&gt;)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 44&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 45&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;จะสังเกตว่าข้อมูลที่จะมีการเปลี่ยนแปลงจากอินพุตของผู้ใช้ (หรือจากโค้ด) เช่น ProvidedAnswer และ IsCorrectAnswer เมื่อถูก Set จะเรียกไปยังเมธอด RaisePropertyChanged ด้วย เพื่อแจ้งให้สิ่งที่เรียกใช้ออปต์เจ็คตัวนี้ทราบเมื่อมีการเปลี่ยนแปลงของข้อมูลนั่นเอง เมื่อสร้างคลาสนี้เสร็จ เราก็จะไปเรียกใช้งานมันจากส่วน ViewModel กันต่อในลำดับถัดไป&lt;/p&gt;  &lt;h3&gt;หน้าที่ของ ViewModel&lt;/h3&gt;  &lt;p&gt;หน้าที่ของ ViewModel ก็คือกาวประสานระหว่างอินเทอร์เฟส (View) กับข้อมูล (Model) ดังนั้นมันจะทำหน้าที่ต่างๆ เช่น การเรียกข้อมูลจากแหล่งข้อมูลต่างๆมาเก็บไว้ใน Model เพื่อให้ตรงกับ View ที่ต้องการนำไปแสดงผล หรือจะบันทึกข้อมูลที่ได้จาก View กลับลงไปยังแหล่งข้อมูลที่กำหนดก็ได้เช่นกัน ให้เราสร้างโฟลเดอร์ชื่อ ViewModel ขึ้นมา จากนั้นเพิ่มคลาส QuestionViewModel เข้าไปภายใน เนื่องจากเราจะเรียกใช้ ObservableCollection ในคลาส จึงต้องอิมพอร์ต System.Collections.ObjectModel ไว้ที่ต้นคลาสด้วย จากนั้นเราจะเพิ่มคุณสมบัติ Questions ซึ่งเป็นลิสต์ของ ObservableCollection(Of Question) เข้าไปเพื่อเก็บรายการคำถาม ObservableCollection เป็นลิสต์ของออปต์เจ็คที่จะแจ้งเตือนทุกครั้งเมื่อมีการเพิ่ม, ลบรายการ หรือมีการรีเฟรชลิสต์ ดังนั้นเมื่อมีการเปลี่ยนแปลงค่าในคำถามแต่ละข้อ แอพพลิเคชั่นก็จะได้รับการอัพเดตโดยอัตโนมัติ&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 1&lt;b&gt; &lt;/b&gt;&lt;b&gt;Imports&lt;/b&gt;&lt;b&gt; System.Collections.ObjectModel&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 2&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 3&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Class&lt;/b&gt;&lt;b&gt; QuestionViewModel&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 4&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 5&lt;b&gt; &lt;/b&gt;&lt;b&gt;Private&lt;/b&gt;&lt;b&gt; _questions &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; ObservableCollection(&lt;/b&gt;&lt;b&gt;Of&lt;/b&gt;&lt;b&gt; Question)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 6&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt; Questions() &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; ObservableCollection(&lt;/b&gt;&lt;b&gt;Of&lt;/b&gt;&lt;b&gt; Question)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 7&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 8&lt;b&gt; &lt;/b&gt;&lt;b&gt;Return&lt;/b&gt;&lt;b&gt; _questions&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 9&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Get&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 10&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; value &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; ObservableCollection(&lt;/b&gt;&lt;b&gt;Of&lt;/b&gt;&lt;b&gt; Question))&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 11&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; _questions = value&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 12&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 13&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Property&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 14&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 15&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Class&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;จากนั้นจะเราจะเพิ่มเมธอด FetchQuestions() เพื่อเรียกข้อมูลมาแสดง ในตัวอย่างนี้เราจะสร้างข้อมูลตัวอย่างขึ้นมาเพื่อความสะดวก แต่ในความเป็นจริงเราอาจเรียกข้อมูลจากแหล่งข้อมูลอื่นมาแสดงได้ตามที่ต้องการ จากนั้นนำลิสต์ของข้อมูลที่ได้ไปเก็บไว้ในคุณสมบัติ Questions เพื่อรอให้ View นำไปใช้&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 15&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt; FetchQuestions()&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 16&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 17&lt;b&gt; &lt;/b&gt;&lt;b&gt;Dim&lt;/b&gt;&lt;b&gt; q &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;New&lt;/b&gt;&lt;b&gt; ObservableCollection(&lt;/b&gt;&lt;b&gt;Of&lt;/b&gt;&lt;b&gt; Question)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 18&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 19&lt;b&gt; &lt;/b&gt;&lt;b&gt;&amp;#39; You may get actual data from the database&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 20&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; q.Add(&lt;/b&gt;&lt;b&gt;New&lt;/b&gt;&lt;b&gt; Question &lt;/b&gt;&lt;b&gt;With&lt;/b&gt;&lt;b&gt; {.Text = &lt;/b&gt;&lt;b&gt;&amp;quot;1 + 1 = ?&amp;quot;&lt;/b&gt;&lt;b&gt;, .ActualAnswer = &lt;/b&gt;&lt;b&gt;&amp;quot;2&amp;quot;&lt;/b&gt;&lt;b&gt;})&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 21&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; q.Add(&lt;/b&gt;&lt;b&gt;New&lt;/b&gt;&lt;b&gt; Question &lt;/b&gt;&lt;b&gt;With&lt;/b&gt;&lt;b&gt; {.Text = &lt;/b&gt;&lt;b&gt;&amp;quot;1 + 2 = ?&amp;quot;&lt;/b&gt;&lt;b&gt;, .ActualAnswer = &lt;/b&gt;&lt;b&gt;&amp;quot;3&amp;quot;&lt;/b&gt;&lt;b&gt;})&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 22&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 23&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Questions = q&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 24&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 25&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;เมื่อเสร็จในส่วนนี้เราก็จะมี ViewModel พร้อมให้ใช้งานแล้ว จะสังเกตได้ว่า โค้ดทั้งสองส่วนนี้ จะถูกออกแบบให้สามารถทำ Unit-Testing ได้ โดยไม่ผูกพันกับอินเทอร์เฟส ทำให้การพัฒนามีความถูกต้องแม่นยำมากขึ้น ในส่วนต่อไปเราจะไปดูการทำงานของ View และการนำข้อมูลจาก ViewModel ไปใช้กัน&lt;/p&gt;  &lt;h3&gt;ส่วนติดต่อผู้ใช้ใน View&lt;/h3&gt;  &lt;p&gt;ส่วนของ View เป็นส่วนติดต่อผู้ใช้ที่โดยมากนักออกแบบจะเป็นผู้รับผิดชอบ หากถูกออกแบบมาดี ก็มักจะมีโค้ดน้อยและเน้นไปในเรื่องของดีไซน์ ซึ่งในที่นี้ก็คือการใช้ Declarative Markup เช่น XAML เสียเป็นส่วนใหญ่ จะมีโค้ดมาเกี่ยวข้องอยู่บ้างก็แต่ในส่วนของการทำ Data-Binding ของข้อมูลไปยังตำแหน่งต่างๆที่จะแสดงผล เราจะสร้างโฟลเดอร์ชื่อ View ขึ้นแล้วเพิ่ม Silverlight UserControl ชื่อ QuestionView.xaml เข้าไปในโฟลเดอร์นั้น เพื่อใช้แสดงรายการคำถาม&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 1&lt;b&gt; &amp;lt;UserControl x:&lt;/b&gt;&lt;b&gt;Class&lt;/b&gt;&lt;b&gt;=&lt;/b&gt;&lt;b&gt;&amp;quot;SLMVVM1.QuestionView&amp;quot;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 2&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; xmlns=&lt;/b&gt;&lt;b&gt;&amp;quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&amp;quot;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 3&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; xmlns:x=&lt;/b&gt;&lt;b&gt;&amp;quot;http://schemas.microsoft.com/winfx/2006/xaml&amp;quot;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 4&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Width=&lt;/b&gt;&lt;b&gt;&amp;quot;400&amp;quot;&lt;/b&gt;&lt;b&gt; Height=&lt;/b&gt;&lt;b&gt;&amp;quot;300&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 5&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;Grid x:Name=&lt;/b&gt;&lt;b&gt;&amp;quot;LayoutRoot&amp;quot;&lt;/b&gt;&lt;b&gt; Background=&lt;/b&gt;&lt;b&gt;&amp;quot;White&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 6&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;StackPanel Orientation=&lt;/b&gt;&lt;b&gt;&amp;quot;Vertical&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 7&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;ItemsControl ItemsSource=&lt;/b&gt;&lt;b&gt;&amp;quot;{Binding Path=Questions}&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 8&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;ItemsControl.ItemTemplate&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 9&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;DataTemplate&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 10&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;StackPanel Orientation=&lt;/b&gt;&lt;b&gt;&amp;quot;Horizontal&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 11&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;TextBlock x:Name=&lt;/b&gt;&lt;b&gt;&amp;quot;QuestionText&amp;quot;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 12&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Text=&lt;/b&gt;&lt;b&gt;&amp;quot;{Binding Path=Text}&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&amp;lt;/TextBlock&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 13&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;TextBox x:Name=&lt;/b&gt;&lt;b&gt;&amp;quot;QuestionAnswer&amp;quot;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 14&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Text=&lt;/b&gt;&lt;b&gt;&amp;quot;{Binding Path=ProvidedAnswer, Mode=TwoWay}&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 15&lt;b&gt; &lt;/b&gt;&amp;lt;/TextBox&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 16&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;CheckBox x:Name=&lt;/b&gt;&lt;b&gt;&amp;quot;GradeCheckBox&amp;quot;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 17&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; IsChecked=&lt;/b&gt;&lt;b&gt;&amp;quot;{Binding Path=IsCorrectAnswer}&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&amp;lt;/CheckBox&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 18&lt;b&gt; &lt;/b&gt;&amp;lt;/StackPanel&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 19&lt;b&gt; &lt;/b&gt;&amp;lt;/DataTemplate&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 20&lt;b&gt; &lt;/b&gt;&amp;lt;/ItemsControl.ItemTemplate&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 21&lt;b&gt; &lt;/b&gt;&amp;lt;/ItemsControl&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 22&lt;b&gt; &lt;/b&gt;&amp;lt;/StackPanel&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 23&lt;b&gt; &lt;/b&gt;&amp;lt;/Grid&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 24&lt;b&gt; &lt;/b&gt;&amp;lt;/UserControl&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;โดยปรกติ หากเราต้องการจะผูกข้อมูลเข้ากับส่วนติดต่อผู้ใช้ เราจะกำหนดแหล่งข้อมูลให้กับ DataContext ของ View ก่อน จากนั้นจึงจะกำหนดตำแหน่งของข้อมูลด้วยแท็ก “{Binding}” โดยกำหนด Path ให้ตรงกับข้อมูลที่จะแสดง ในที่นี้เรากำหนดรายการคำถามให้กับ StackPanel ผ่านทาง ItemSource ส่วนค่าของแต่ละฟิลด์ที่จะถูกนำไปผูกกับคอนโทรลต่างๆตามชื่อของข้อมูล ก็จะได้รายการคำถามมาแสดง ในส่วนของ DataContext นั้นเราจะไปกำหนดเมื่อเราเรียกใช้ UserControl นี้ในส่วนถัดไป อย่างไรก็ดีจะสังเกตได้ว่า ในส่วนของ View ที่เราสร้างขึ้นมาใหม่นี้ ยังไม่มีการเขียนโค้ดเลย และส่วนของ DataBinding ก็ทำไปเพื่อกำหนดตำแหน่งของข้อมูลที่จะนำมาแสดงเท่านั้น&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 1&lt;b&gt; &amp;lt;UserControl x:&lt;/b&gt;&lt;b&gt;Class&lt;/b&gt;&lt;b&gt;=&lt;/b&gt;&lt;b&gt;&amp;quot;SLMVVM1.Page&amp;quot;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 2&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; xmlns=&lt;/b&gt;&lt;b&gt;&amp;quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&amp;quot;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 3&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; xmlns:x=&lt;/b&gt;&lt;b&gt;&amp;quot;http://schemas.microsoft.com/winfx/2006/xaml&amp;quot;&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 4&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; xmlns:views=&lt;/b&gt;&lt;b&gt;&amp;quot;clr-namespace:SLMVVM1&amp;quot;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 5&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Width=&lt;/b&gt;&lt;b&gt;&amp;quot;400&amp;quot;&lt;/b&gt;&lt;b&gt; Height=&lt;/b&gt;&lt;b&gt;&amp;quot;300&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 6&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;Grid x:Name=&lt;/b&gt;&lt;b&gt;&amp;quot;LayoutRoot&amp;quot;&lt;/b&gt;&lt;b&gt; Background=&lt;/b&gt;&lt;b&gt;&amp;quot;White&amp;quot;&lt;/b&gt;&lt;b&gt;&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 7&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &amp;lt;views:QuestionView x:Name=&lt;/b&gt;&lt;b&gt;&amp;quot;QuestionDataView&amp;quot;&lt;/b&gt;&lt;b&gt; /&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 8&lt;b&gt; &lt;/b&gt;&amp;lt;/Grid&amp;gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 9&lt;b&gt; &lt;/b&gt;&amp;lt;/UserControl&amp;gt;&lt;/p&gt;  &lt;p&gt;&lt;b&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/p2_038EF0E4.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="p2" border="0" alt="p2" src="http://coredeveloper.net/blogs/meekob/p2_thumb_5A86E298.png" width="244" height="100" /&gt;&lt;/a&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;จากนั้นเราจะนำ View ที่สร้างไปใส่ไว้ใน UserControl ที่เป็นหน้าหลัก ซึ่งในที่นี้ก็คือ Page.xaml ซึ่งถูกสร้างมาเมื่อเราสร้าง Silverlight Project ใหม่ โดยกำหนด xmlns ชื่อ views ให้ จากนั้นเพิ่ม UserControl เข้าไปตามปรกติ (นักออกแบบอาจใช้ Microsoft Expression Blend 2 ในการเพิ่ม UserControl นี้เข้ามาก็ได้) จากนั้นเพิ่มโค้ดเพื่อโหลดออปต์เจ็คมาเก็บไว้ใน DataContext ให้กับ View ในส่วนฟังก์ชั่น Page_Loaded ใน Code-Behind&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 8&lt;b&gt; &lt;/b&gt;&lt;b&gt;Private&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt; Page_Loaded(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; sender &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Object&lt;/b&gt;&lt;b&gt;, _&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 9&lt;b&gt; &lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; e &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; System.Windows.RoutedEventArgs) _&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 10&lt;b&gt; &lt;/b&gt;&lt;b&gt;Handles&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Me&lt;/b&gt;&lt;b&gt;.Loaded&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 11&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 12&lt;b&gt; &lt;/b&gt;&lt;b&gt;Dim&lt;/b&gt;&lt;b&gt; qdata = &lt;/b&gt;&lt;b&gt;New&lt;/b&gt;&lt;b&gt; QuestionViewModel()&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 13&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; qdata.FetchQuestions()&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 14&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; QuestionDataView.DataContext = qdata&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 15&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 16&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;จากนั้นกด F5 เพื่อทำการ Build ทดสอบ Visual Studio จะเปิดเบราเซอร์ขึ้นมาและแสดงรายการคำถาม เมื่อเรากรอกตัวเลขคำตอบใน TextBox ที่เตรียมไว้ให้ ค่าของออปต์เจ็คคำถามซึ่งอยู่ในลิสต์ใน DataContext ก็จะเปลี่ยนแปลง ทำให้เกิดอีเวนท์ PropertyChanged ของคำถาม ขึ้น ObservableCollection ของคำถามที่อยู่ใน DataContext ก็จะแจ้งให้ View รับรู้และเปลี่ยนแปลงค่าของข้อมูลที่ผูกไว้อีกทอดหนึ่งโดยอัตโนมัติ เช่น หากเรากรอกคำตอบ 2 ให้กับคำถามข้อแรก ซึ่งเป็นคำตอบที่ถูกต้อง คุณสมบัติ ProvidedAnswer ของคำถามจะมีการเปลี่ยนแปลง และมีการเรียกเมธอด RaisePropertyChanged เพื่อสร้างอีเวนท์ไปแจ้งให้กับ ObsevableCollection ได้รู้ เมื่อ ObservableCollection รับรู้, DataContext ของ View ก็จะรู้การเปลี่ยนแปลงและทำการเรียกค่าใหม่มาแสดง ซึ่งเมื่อเรียกไปยังค่า IsCorrectAnswer ของคำถามก็จะพบค่าใหม่ จึงทำการปรับปรุงคุณสมบัติ IsChecked ใน CheckBox ซึ่งเราผูกข้อมูลไว้ โดยเปลี่ยนค่าจากเดิม False ไปเป็น True ทำให้ CheckBox แสดงเครื่องหมายถูกขึ้นมา เป็นต้น&lt;/p&gt;  &lt;h3&gt;การบันทึกข้อมูลที่เปลี่ยนแปลง&lt;/h3&gt;  &lt;p&gt;จากรายการคำถามหากเราต้องการบันทึกข้อมูลคำตอบของผู้ใช้ก็สามารถทำได้ผ่าน View และ ViewModel โดยง่าย โดยการเพิ่มเมธอดในการบันทึกเข้าไปใน ViewModel และเพิ่มเมธอดเพื่อสั่งให้บันทึกในส่วน Code-Behind ของ View ในที่นี้จะเราจะเพิ่มเมธอด SubmitChanges() เข้าไปใน ViewModel ดังนี้&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 27&lt;b&gt; &lt;/b&gt;&lt;b&gt;Public&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt; SubmitChanges()&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 28&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 29&lt;b&gt; &lt;/b&gt;&lt;b&gt;&amp;#39; Persist this ViewModel to the database here.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 30&lt;b&gt; &lt;/b&gt;&lt;b&gt;Dim&lt;/b&gt;&lt;b&gt; correctQuestions &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Integer&lt;/b&gt;&lt;b&gt; = &lt;/b&gt;&lt;b&gt;0&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 31&lt;b&gt; &lt;/b&gt;&lt;b&gt;For&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Each&lt;/b&gt;&lt;b&gt; q &lt;/b&gt;&lt;b&gt;In&lt;/b&gt;&lt;b&gt; Questions&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 32&lt;b&gt; &lt;/b&gt;&lt;b&gt;If&lt;/b&gt;&lt;b&gt; q.IsCorrectAnswer &lt;/b&gt;&lt;b&gt;Then&lt;/b&gt;&lt;b&gt; correctQuestions += &lt;/b&gt;&lt;b&gt;1&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 33&lt;b&gt; &lt;/b&gt;&lt;b&gt;Next&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 34&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MessageBox.Show(correctQuestions)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 35&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 36&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;โค้ดในฟังก์ชั่นจะแสดง MessageBox บอกจำนวนคำถามที่ผู้ใช้ตอบถูก โดยดึงข้อมูลจากลิสต์ Questions ในออปต์เจ็คเดียวกัน จากนั้นให้เพิ่มปุ่ม Submit เข้าไปใน QuestionView.xaml ดังนี้ &lt;/p&gt;  &lt;p&gt;&lt;b&gt;&amp;lt;&lt;/b&gt;&lt;b&gt;Button x&lt;/b&gt;&lt;b&gt;:&lt;/b&gt;&lt;b&gt;Name&lt;/b&gt;&lt;b&gt;=&amp;quot;SubmitButton&amp;quot;&lt;/b&gt;&lt;b&gt; Content&lt;/b&gt;&lt;b&gt;=&amp;quot;Submit&amp;quot;&lt;/b&gt;&lt;b&gt; Click&lt;/b&gt;&lt;b&gt;=&amp;quot;SubmitButton_Click&amp;quot;/&amp;gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;แล้วเพิ่มเมธอด SubmitButton_Click ในส่วน Code-Behind ของ QuestionView.xaml ดังนี้&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160; 9&lt;b&gt; &lt;/b&gt;&lt;b&gt;Private&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt; SubmitButton_Click(&lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; sender &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; System.Object, _&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 10&lt;b&gt; &lt;/b&gt;&lt;b&gt;ByVal&lt;/b&gt;&lt;b&gt; e &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; System.Windows.RoutedEventArgs)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 11&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 12&lt;b&gt; &lt;/b&gt;&lt;b&gt;Dim&lt;/b&gt;&lt;b&gt; data &lt;/b&gt;&lt;b&gt;As&lt;/b&gt;&lt;b&gt; QuestionViewModel = &lt;/b&gt;&lt;b&gt;CType&lt;/b&gt;&lt;b&gt;(&lt;/b&gt;&lt;b&gt;Me&lt;/b&gt;&lt;b&gt;.DataContext, QuestionViewModel)&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 13&lt;b&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; data.SubmitChanges()&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 14&lt;b&gt; &lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160; 15&lt;b&gt; &lt;/b&gt;&lt;b&gt;End&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Sub&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;ใน View จะปรากฎปุ่มเพื่อให้เราส่งข้อมูลขึ้นมา เมื่อผู้ใช้กดก็จะทำการดึงออปต์เจ็คที่อยู่ใน DataContext ออกมาแล้วเรียกเมธอด SubmitChanges() ในออปต์เจ็คนั้น เมื่อเมธอดนี้ถูกเรียกก็จะทำการแสดง MessageBox ตามที่เราต้องการออกมา นักพัฒนาอาจจะปรับปรุงโค้ดส่วนนี้ให้ทำการส่งข้อมูลกลับไปบันทึกยังฐานข้อมูลบนเซอร์ฟเวอร์ได้เช่นกัน&lt;/p&gt;  &lt;h3&gt;บทสรุป&lt;/h3&gt;  &lt;p&gt;จากตัวอย่างที่ผ่านข้างต้นคงพอจะอธิบายโครงสร้างง่ายๆของการพัฒนา Silverlight ด้วย Model View ViewModel Pattern ให้ผู้อ่านได้เข้าใจพอสมควรแล้ว ในการพัฒนางานจริง นักพัฒนาอาจเลือกวิธีการที่ซับซ้อนยิ่งขึ้น เช่น การเลือกใช้อินเทอร์เฟสช่วยในการกำหนดโครงสร้างในส่วน ViewModel เพื่อให้สามารถสับเปลี่ยนแหล่งที่มาของข้อมูลได้ง่าย หรือปรับปรุงการทำงานในส่วน ViewModel ให้มีความซับซ้อนยิ่งขึ้นตามความต้องการ อย่างไรก็ดี นักพัฒนาก็จะสามารถแยกส่วนการทำงานระหว่างอินเทอร์เฟสและโค้ดออกมาเพื่อพัฒนาและทำการทดสอบได้สะดวกยิ่งขึ้น รวมถึงสามารถจัดการแบ่งส่วนงานได้อย่างมีประสิทธิภาพยิ่งขึ้นอีกด้วย&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=668" width="1" height="1"&gt;</description></item><item><title>รู้จักกับ Silverlight 2.0</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/11/13/silverlight-2-0.aspx</link><pubDate>Thu, 13 Nov 2008 11:33:51 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:560</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>7</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=560</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/11/13/silverlight-2-0.aspx#comments</comments><description>&lt;p&gt;เมื่อไม่กี่ปีที่ผ่านมาเราได้รับการแนะนำให้รู้จักเทคโนโลยีใหม่ๆมากมาย ที่ช่วยให้การใช้งานแอพพลิเคชั่นผ่านเว็บเบราเซอร์เกิดความสะดวกยิ่งขึ้น มีการตอบสนองที่รวดเร็วและถี่กว่าเดิม รวมถึงเสนอทางเลือกใหม่ๆในการเข้าถึงข้อมูลที่หลายหลายยิ่งขึ้นให้กับเรา  &lt;p&gt;จากการนำเสนอเนื้อหาทางเดียวให้เราอ่าน ฟัง หรือดู เปลี่ยนไปเป็นการตอบสนองและนำเสนอข้อมูลตามความต้องการของผู้ใช้ เราสามารถเรียกดูแผนที่ ย่อหรือขยายไปยังพื้นที่เฉพาะที่ต้องการ ลากแล้วปล่อยเพื่อไปยังตำแหน่งต่างๆของแผนที่นั้นๆ รวมถึงค้นหาและแสดงผลข้อมูลที่เกี่ยวกับตำแหน่งเป้าหมาย และบริเวณใกล้เคียงได้  &lt;p&gt;ความหลากหลายในการใช้งานนี้มาพร้อมกับความสามารถของเบราเซอร์รุ่นใหม่ๆ ที่สนับสนุนเทคโนโลยีที่เราเรียกว่า Asynchronous JavaScript And Xml หรือ AJAX ที่นักพัฒนาในสมัยนี้คงคุ้นชื่อกันดีแล้ว เพราะเป็นองค์ประกอบสำคัญในการพัฒนาเว็บแอพพลิเคชั่นยุค 2.0 นี้เอง  &lt;p&gt;แต่การพัฒนาเว็บแอพพลิเคชั่นที่ตอบสนองต่อการใช้งานให้ดียิ่งขึ้นนั้น ไม่ได้หยุดยั้งแต่เพียงเท่านี้ ในอีกไม่ช้า เราคงจะได้เห็นแอพพลิเคชั่นที่มีความสามารถในการแสดงผลที่น่าตื่นตาตื่นใจยิ่งขึ้นไปกว่านี้อีก เนื่องจากผู้ผลิตซอฟต์แวร์พื้นฐานชั้นนำต่างให้ความสำคัญและลงทุนพัฒนาเครื่องไม้เครื่องมือที่จำเป็น รวมถึงการให้กำเนิดเฟรมเวิร์คใหม่ๆที่มีประสิทธิภาพและความสามารถมากขึ้น สามารถใช้งานได้ในหลายแพลตฟอร์ม และไม่ขึ้นอยู่กับเบราเซอร์ ซึ่งล้วนเป็นปัจจัยที่เอื้ออำนวยให้นักออกแบบ และนักพัฒนาสร้างสรรผลงานที่แตกต่างจากเดิมออกมาได้อย่างไม่หยุดยั้ง  &lt;p&gt;เราต้องการแอพพลิเคชั่นที่การทำงานที่สมบูรณ์แบบ ใกล้เคียงกับการทำงานบนเดสก์ทอป แต่สามารถรันอยู่ในเบราเซอร์ ไม่ต้องการการติดตั้ง รวมถึงมีการจัดสรรทรัพยาการระบบเป็นสัดส่วนแยกออกไปเป็นของตนเอง หรือที่เราเรียกมันว่า Rich Internet Application เนื่องจากเราต้องการความง่าย และบันเทิงในการใช้งาน แอพพลิเคชั่นที่ใช้งานง่าย ย่อมสร้างความนิยมและคุณค่าเหนือแอพพลิเคชั่นที่น่าเบื่อ ยุ่งยาก และสับสน  &lt;p&gt;ดังนั้นผู้ที่จะชนะในการแข่งขันในตลาดซอฟต์แวร์ยุคถัดไป ก็คือผู้ที่เข้าใจถึงการพัฒนา User Experience (UX) หรือ “ประสพการณ์การใช้งาน” ที่ดีให้กับผู้ใช้นั่นเอง &lt;br /&gt;&amp;nbsp; &lt;h3&gt;RIA คืออะไร?&lt;/h3&gt; &lt;p&gt;เพื่อสร้างประสพการณ์การใช้งานที่ดีดังกล่าว เราจึงต้องออกแบบแอพพลิเคชั่นที่รันผ่านเว็บเบราเซอร์ให้มีความใกล้เคียงกับการใช้งานกับสิ่งที่เราสามารถทำได้กับแอพพลิเคชั่นในเครื่องเดสก์ทอปให้มากที่สุด เช่น ให้มีความสามารถในการแสดงผลกราฟิกที่คมชัด, สามารถเล่นวิดีโอได้เรียบลื่น สามารถลากไอเท็มต่างๆไปมาเพื่อวางไว้บนตำแหน่งที่ต้องการได้ เป็นต้น ความสามารถเหล่านี้เราใช้งานมานานแล้วบนเดสก์ทอป แต่ด้วยข้อจำกัดด้านต่างๆทำให้พัฒนาได้ยากบนเว็บรุ่นแรกๆ แต่เมื่อกาลเวลาผ่านไป เทคโนโลยีก็ก้าวหน้าขึ้นเรื่อยๆ แบนด์วิธที่กว้างขึ้นทำให้เราสามารถเรียกชมวิดีโอที่คมชัดยิ่งขึ้นได้โดยไม่ติดขัด และสามารถพัฒนาโปรแกรมที่ติดต่อกับผู้ใช้ได้ “เฟรนด์ลี่” ยิ่งขึ้นไปทุกที  &lt;p&gt;เทคนิคที่ใช้ในการพัฒนา RIA ยุคเริ่มแรกก็คือ การเอา JavaScript มาจัดการกับ Document Object Model (DOM) ของเอกสาร เพื่อให้สามารถควบคุมการแสดงผลพื้นที่บางส่วนของเอกสารได้โดยไม่ต้องทำการรีเฟรชหน้าจอของเบราเซอร์ทั้งหมด ต่อมาเมื่อเบราเซอร์รุ่นใหม่ๆ เช่น ไฟร์ฟ๊อกซ์ นำเอาคอมโพเนนท์ที่มีชื่อว่า XmlHttpRequest หรือ XHR ซึ่งสมัยก่อนสนับสนุนแต่ในอินเทอร์เน็ตเอ็กซ์พลอเรอร์เข้ามาผนวก และกูเกิ้ลได้ทำให้การใช้งาน AJAX เป็นที่รู้จักจากการทำ TextBox แบบ Auto Complete ก็เลยทำให้เป็นจุดเริ่มต้นของการพัฒนาโปรแกรมแบบ RIA ยุคใหม่ที่สมบูรณ์ขึ้นมา  &lt;p&gt;ในอีกด้านหนึ่งมาโครมีเดียแฟลช หรือในชื่อใหม่ว่า อโดบีแฟลช ซึ่งเป็นผู้นำในการแสดงผลด้านอนิเมชั่นแบบเวคเตอร์บนอินเทอร์เน็ตรายใหญ่มานาน กลับไม่ได้มุ่งเน้นมากนักในด้านการพัฒนาแอพพลิเคชั่นในระยะแรก สังเกตได้จากการไม่ค่อยสนับสนุนการเขียนโปรแกรมเพื่อดึงข้อมูลในฝั่งเซอร์ฟเวอร์มาแสดง รวมถึงการไม่มีคอมโพเนนท์มาตรฐานต่างๆ เช่น DropDownList หรือ DataGrid ให้ใช้งาน เป็นต้น (ถึงแม้ว่าภายหลังจะให้ความสำคัญมากขึ้นโดยการเข็นเอา Flex ออกมาแล้วก็ตาม ก็ยังไม่ได้รับความนิยมจากนักพัฒนาเท่าที่ควร) ในขณะที่ไมโครซอฟท์ ซึ่งใช้เวลาในการพัฒนาเฟรมเวิร์คใหม่สำหรับการแสดงผลบนวินโดวส์ไปมากมาย จนได้ Windows Presentation Foundation หรือ WPF ซึ่งเป็นเฟรมเวิร์คที่สนับสนุนการแสดงผลในแบบ Declarative ก็ได้เล็งเห็นโอกาสที่จะผนวกความสามารถใหม่นี้เข้ากับการแสดงผลผ่านเว็บด้วย เช่นกัน ผลได้ก็คือเฟรมเวิร์คใหม่ภายใต้ชื่ออย่างเป็นทางการว่า Silverlight&lt;br /&gt;&amp;nbsp; &lt;h3&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="SilverlightLogo" align="right" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/SilverlightLogo_3.jpg" width="215" height="240" /&gt; &lt;/h3&gt; &lt;h3&gt;Silverlight 2 คืออะไร?&lt;/h3&gt; &lt;p&gt;สรุปโดยย่อ Silverlight ก็คือผลิตภัณฑ์ใหม่จากไมโครซอฟท์ ที่สนับสนุนความสามารถในการแสดงผลแบบมัลติมีเดียคุณภาพสูงผ่านเว็บและเครื่องมือสื่อสารแบบพกพา ผ่านปลั๊กอินสำหรับเบราเซอร์ที่ทรงพลังที่สุดบนเว็บในขณะนี้  &lt;p&gt;Silverlight นั้นเป็นปลั๊กอินที่สามารถติดตั้งได้อย่างรวดเร็วบนเบราเซอร์หลักมากกว่า 95% ของผู้ใช้อินเทอร์เน็ตทั้งหมด ซึ่งก็คือ อินเทอร์เน็ตเอ็กซ์พลอเรอร์, ไฟร์ฟ๊อกซ์ และซาฟารี เราสามารถใช้งานแอพพลิเคชั่นที่รันบน Silverlight ได้บนเบราเซอร์ดังกล่าวทั้งบนวินโดว์ส, แมคอินทอช และลินุกซ์ (โดยผ่านโปรเจ็คที่ชื่อว่า “Moonlight”)  &lt;p&gt;ในขณะที่เขียนบทความนี้ Silverlight เพิ่งจะเปิดตัว Silverlight 2.0 อย่างเป็นทางการไปได้เพียงแค่ไม่กี่วัน ถึงแม้ว่า Silverlight 1.0 จะเพิ่งเปิดตัวอย่างเป็นทางการไปได้เพียงแค่ 1 ปีก่อนหน้านี้ และถือว่าปลั๊กอินที่มีความสามารถสูง สำหรับการแสดงผลแบบมัลติมีเดีย แต่ก็ยังขาดความสามารถบางอย่าง และขาดคอมโพเนนท์ที่น่าสนใจหลายชิ้น ซึ่งก็ได้เพิ่มเติมเข้ามาใหม่ใน Silverlight 2.0 นี้เอง นอกจากนี้ยังมีการปรับการพัฒนาบางส่วนให้เข้ากับการพัฒนาแบบ WPF มากยิ่งขึ้น ดังนั้นสำหรับผู้ที่ไม่เคยพัฒนา RIA ด้วย Silverlight มาก่อน จึงเป็นการดีที่จะเริ่มเรียนรู้และพัฒนาด้วย Silverlight 2.0 ไปเลยโดยไม่จำเป็นต้องมีพื้นฐานจาก Silverlight 1.0 แต่อย่างใด  &lt;p&gt;ในขณะที่ Silverlight 1.0 เน้นหนักฟีเจอร์ไปทางการสนับสนุนการแสดงผลสื่อคุณภาพสูงต่างๆ เช่น วิดีโอ ผ่านเว็บ และสนับสนุนการพัฒนาส่วนการใช้งานต่างๆด้วย JavaScript, Silverlight 2.0 ก็ถูกต่อยอดให้เน้นหนักไปในการสนับสนุนการพัฒนาแอพพลิเคชั่นประเภท RIA โดยมีฟีเจอร์เด่นต่างๆดังต่อไปนี้  &lt;ul&gt; &lt;li&gt;มีการรวมเอา .NET Framework เวอร์ชั่นพิเศษซึ่งมีขนาดเล็กและสามารถรันได้บนทุกๆแพลตฟอร์มที่เบราเซอร์ติดตั้งอยู่เข้าไป ทำให้เราสามารถพัฒนา Silverlight Application ได้ด้วยภาษาต่างๆที่ .NET สนับสนุน เช่น VB, C#, JavaScript, IronPython และ IronRuby เป็นต้น นอกจากนี้ยังมีเครื่องไม้เครื่องมือที่ช่วยในการพัฒนาครบครัน ไม่ว่าจะเป็นการสนับสนุนการพัฒนาใน Visual Studio 2008 หรือสนับสนุนการออกแบบอินเทอร์เฟส, อนิเมชั่น และการจัดการวิดีโอด้วย Microsoft Expression Studio 2 เป็นต้น&lt;br /&gt; &lt;li&gt;เมื่อมี .NET Framework Runtime และเน้นเรื่องของการออกแบบที่สวยงาน ก็ย่อมมีการสนับสนุนการทำงานแบบเดียวกับ WPF เช่น Graphics แบบเวคเตอร์ และ Animation Engine รวมถึงคอนโทรลมาตรฐานต่างๆ, การจัดการเลย์เอาท์แบบต่างๆ, การผูกข้อมูล (Data-Binding), ธีม, สไตล์, เทมเพลต และการจัดการสกิน เป็นต้น ถึงแม้ว่า Runtime ตัวนี้จะไม่ใช่ตัวที่มีความสามารถเต็มที่เช่นเดียวกับ WPF (ขาดความสามารถบางอย่างเช่น 3D เป็นต้น) แต่ก็เป็น Runtime ที่มีความสามารถสูงในการพัฒนา RIA และช่วยให้นักพัฒนาไม่จำเป็นต้องเรียนรู้วิธีการพัฒนาใหม่ๆ สามารถนำความรู้เดิมที่เคยใช้กับการพัฒนา WPF มาใช้ได้ในทันที&lt;br /&gt; &lt;li&gt;Silverlight 2.0 จะมาพร้อมกับคอนโทรลมาตรฐานต่างๆเพื่อให้นักออกแบบและนักพัฒนาสามารถเลือกนำไปใช้งานได้ทันที เช่น TextBox, CheckBox, RadioButton, StackPanel, Grid, Slider, ScrollViewer, Calendar, DatePicker, DataGrid และ ListBox เป็นต้น คอนโทรลเหล่านี้จะอนุญาตให้นักออกแบบสามารถแก้ไขรูปร่างหน้าตาให้สวยงามตามที่ต้องการได้โดยที่ไม่กระทบกับส่วนการทำงานของแอพพลิเคชั่น ทำให้การทำงานร่วมกันระหว่างนักออกแบบและนักพัฒนาราบรื่นและรวดเร็วยิ่งขึ้น&lt;br /&gt; &lt;li&gt;สนับสนุนการเชื่อมต่อสื่อสารสมบูรณ์แบบ ไม่ว่าจะเป็นการเรียกดูข้อมูลจากเซอร์ฟเวอร์ผ่าน REST, WS*/SOAP, POX, RSS หรือ HTTP มาตรฐาน นอกจากนี้ยังอนุญาตให้เรียกใช้งานทรัพยากรต่างๆ เช่น วิดีโอ, ฟอนท์ ฯลฯ ผ่านเว็บจากฝั่งไคลเอนท์ได้โดยตรง มีการสนับสนุนการทำงานข้ามโดเมนด้วยการกำหนด Policy รวมถึงสนับสนุนการสื่อสารผ่าน Socket และการสื่อสารในแบบ Duplex อีกด้วย&lt;br /&gt; &lt;li&gt;Base Class Library (BCL) ของเฟรมเวิร์คสนับสนุนส่วนสำคัญต่างๆจากเฟรมเวิร์คใหญ่ เช่น Collection, IO, Generics, Threading, Globalization, XML, Isolated Storage เป็นต้น นอกจากนี้ยังสามารถเชื่อมต่อและเข้าถึง HTML DOM / JavaScript ด้วย .NET Code ในด้านการคิวรีข้อมูลก็มี LINQ และ LINQ To XML ที่ช่วยให้การจัดการกับข้อมูลสะดวกง่ายดายยิ่งขึ้น&lt;br /&gt; &lt;li&gt;ไม่จำเป็นต้องมี .NET Framework ติดตั้งอยู่บนเครื่องก็รันได้ ตัวปลั๊กอินมีขนาดราว 4.8 Mb ซึ่งจะใช้เวลาดาวน์โหลดเพียงราวๆ 10 กว่าวินาทีบนอินเทอร์เน็ตแบบบรอดแบนด์ และมีขั้นตอนการติดตั้งที่ไม่ยุ่งยากซับซ้อน นอกจากนี้เนื่องจากมีการประชาสัมพันธ์อย่างต่อเนื่องมาตลอด ทำให้มีผู้ใช้จำนวนมากมีปลั๊กอินที่พร้อมใช้งานทันที ติดตั้งอยู่แล้วภายในเครื่องที่ใช้งาน&lt;br /&gt; &lt;li&gt;แม้เครื่องไม้เครื่องมือที่ใช้ในการพัฒนา Silverlight Application จะอยู่บนวินโดวส์ แต่เมื่อพัฒนาเสร็จแล้วสามารถนำไปติดตั้งบนเว็บเซอร์ฟเวอร์ที่รันแพลตฟอร์มใดก็ได้ ไม่ว่าจะเป็น Internet Information Server บนวินโดว์ส หรือ Apache บนลินุกซ์ เป็นต้น&lt;br /&gt; &lt;li&gt;สนับสนุนเทคโนโลยี Deep Zoom ช่วยในการสำรวจและขยายภาพขนาดเล็กจิ๋วให้มีรายละเอียดชัดเจนยิ่งขึ้น&lt;br /&gt; &lt;li&gt;มีคอนโทรลเสริมต่างๆมากมาย จาก Silverlight Toolkit ซึ่งเปิดตัวในงาน PDC2008 โดยจะมีการสนับสนุนคอนโทรลเพิ่มเติมทั้งที่มีใน WPF และไม่มีเช่น AutoCompleteBox, Chart, WrapPanel, DockPanel, ImplecitStlyeManager, NumericUpDown, TreeView และ ViewBox เป็นต้น เนื่องจาก Silverlight Toolkit เป็นโปรเจ็คนอกเหนือจากวงจรการพัฒนาปรกติสำหรับ Silverlight ของไมโครซอฟท์ ที่มีการพัฒนาแบบโอเพนซอร์สจึงมีซอร์สโค้ดให้ดาวน์โหลดมาเพื่อศึกษาและต่อยอดเองได้ ตัวโครงการจะได้รับการพัฒนาอย่างต่อเนื่องจากนักพัฒนามืออาชีพของไมโครซอฟท์ ซึ่งจะทำให้มีชุดคอนโทรลใหม่ๆเพิ่มเติมอย่างต่อเนื่องอีกมากในอนาคต&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;ยังมีฟีเจอร์อื่นๆที่น่าสนใจที่มากับเฟรมเวิร์คนี้อีกมาก ซึ่งคงจะได้มีโอกาสกล่าวถึงโดยละเอียดในตอนต่อๆไป แต่ตอนนี้เรามาดูกันก่อนดีกว่าว่า ถ้าเราจะพัฒนา RIA ด้วย Silverlight 2 นั้นจะต้องใช้เครี่องไม้เครื่องมืออะไรบ้าง &lt;br /&gt;&amp;nbsp; &lt;h3&gt;เครื่องมือที่ใช้ในการพัฒนา&lt;/h3&gt; &lt;p&gt;แน่นอนว่าการพัฒนาแอพพลิเคชั่นด้วย Silverlight 2 ก็เหมือนกับการพัฒนาแอพพลิเคชั่นด้วย .NET Framework ทั่วไป กล่าวคือ นักพัฒนาสามารถเขียนโค้ดและคอมไพล์ได้ทันที จาก Complier ที่มาพร้อมกับ .NET Framework แต่วิธีการนี้จะไม่ค่อยมีประสิทธิภาพมากนัก เพราะต้องเขียนโค้ดมาก และทดสอบการทำงานของแอพพลิเคชั่นได้ยากกว่าการใช้เครื่องมือช่วยอำนวยความสะดวก  &lt;p&gt;ไมโครซอฟท์จึงได้ตระเตรียมเครื่องไม้เครื่องมือเพื่อช่วยอำนวยความสะดวกในการทำงานให้กับนักพัฒนาด้วยการออกเครื่องมือใหม่ และเพิ่มฟีเจอร์เพื่อสนับสนุนการพัฒนานี้ในเครื่องมือที่มีอยู่เดิม โดยมีการออกเครื่องมือชุด Expression Studio 2 เพิ่มขึ้นสำหรับนักออกแบบ และผนวก Silverlight Tools เข้ากับ Visual Studio 2008 เวอร์ชั่นต่างๆ เพื่อให้นักพัฒนาสามารถพัฒนา Silverlight Application ได้รวดเร็วและมีประสิทธิภาพมากขึ้น  &lt;p&gt;นักพัฒนาสามารถเริ่มต้นได้จากเครื่องมือที่แจกฟรี และรุ่นทดสอบ โดยถ้าหากนักพัฒนาต้องการศึกษาเรียนรู้การทำงานและสร้างแอพพลิเคชั่นที่ไม่ซับซ้อนมากนัก ก็สามารถดาวน์โหลดเครื่องมือเหล่านี้ มาทดลองใช้พัฒนาได้เลยทันทีโดยไม่มีค่าใช้จ่ายเพิ่มเติมใดๆ เครื่องมือที่นักพัฒนาต้องการนั้นมีดังต่อไปนี้  &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Visual Studio 2008&lt;/strong&gt; เป็นเครื่องมือหลักสำหรับนักพัฒนาที่ใช้ในการพัฒนาเว็บด้วย ASP.NET โดยสามารถเริ่มต้นได้จากรุ่นเล็กที่สุดคือ Visual Web Developer Express Edition ซึ่งสามารถดาวน์โหลดได้ฟรีจาก &lt;a href="http://www.microsoft.com/express/vwd/"&gt;http://www.microsoft.com/express/vwd/&lt;/a&gt;&amp;nbsp;&lt;br /&gt; &lt;li&gt;&lt;strong&gt;Silverlight Tool For Visual Studio 2008&lt;/strong&gt; เครื่องมือจำเป็นเพิ่มเติม ที่มาพร้อมกับ Visual Studio Updates, Silverlight Project Templates, Developer Runtime และ Silverlight SDK สำหรับ Visual Studio ช่วยให้นักพัฒนาสร้างโซลูชั่นและโปรเจ็คสำหรับ Silverlight ได้ง่ายขึ้น ดาวน์โหลดได้ฟรีจาก &lt;a href="http://go.microsoft.com/fwlink/?LinkId=129043"&gt;http://go.microsoft.com/fwlink/?LinkId=129043&lt;/a&gt; &lt;br /&gt; &lt;li&gt;&lt;strong&gt;Microsoft Expression Blend 2&lt;/strong&gt; และ Service Pack 1 เป็นเครื่องมือสำหรับนักออกแบบเพื่อใช้ในการออกแบบส่วนติดต่อกับผู้ใช้ (User Interface) ที่เป็นกราฟิกและอนิเมชั่น รวมถึงการสร้าง Resources เพื่อใช้ระหว่างแอพพลิเคชั่น สามารถดาวน์โหลดรุ่นทดลองสำหรับ Blend 2 ได้จาก &lt;a href="http://www.microsoft.com/downloads/details.aspx?familyid=5FF08106-B9F4-43CD-ABAD-4CC9D9C208D7&amp;amp;displaylang=en"&gt;http://www.microsoft.com/downloads/details.aspx?familyid=5FF08106-B9F4-43CD-ABAD-4CC9D9C208D7&amp;amp;displaylang=en&lt;/a&gt; และ SP1 ได้จาก &lt;a href="http://www.microsoft.com/downloads/details.aspx?familyid=EB9B5C48-BA2B-4C39-A1C3-135C60BBBE66&amp;amp;displaylang=en"&gt;http://www.microsoft.com/downloads/details.aspx?familyid=EB9B5C48-BA2B-4C39-A1C3-135C60BBBE66&amp;amp;displaylang=en&lt;/a&gt;&amp;nbsp; &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;แม้ว่าเครื่องมือดังที่กล่าวมาข้างต้นจะเพียงพอกับความต้องการขั้นต้นในการพัฒนา Silverlight Application แล้ว แต่ไมโครซอฟท์ก็ยังนำเสนอเครื่องมืออื่นๆ เพื่อช่วยให้การพัฒนาเฉพาะทางง่ายขึ้นอีก เช่น  &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Microsoft Expression Design 2&lt;/strong&gt; ซึ่งเป็นเครื่องมือช่วยในการออกแบบกราฟิกแบบเวคเตอร์เป็นชิ้นงาน เพื่อนำไปประกอบใน Expression Blend 2 อีกต่อหนึ่ง มีความสามารถในการสร้างและจัดการกราฟิกได้ดีกว่า Blend และออกแบบมาเพื่อทำกราฟิกทั้งสำหรับ WPF และ Silverlight เป็นหลัก&lt;br /&gt; &lt;li&gt;&lt;strong&gt;Microsoft Expression Encoder 2&lt;/strong&gt; เป็นเครื่องมือสำหรับแปลงไฟล์วิดีโอให้อยู่ในรูปแบบและขนาดที่เหมาะสมกับการเอาไปใช้ร่วมกับ WPF และ Silverlight สามารถย่อขยายขนาดของวิดีโอ สร้าง Media Player ด้วย Silverlight แบบสำเร็จรูปได้, สามารถดูคุณภาพของวิดีโอได้ในระหว่างที่ถูกเรนเดอร์ หรือนำเข้าวิดีโอจากแหล่งข้อมูลสด เช่น เว็บแคม หรือวีดีโอการ์ดได้ เป็นต้น &lt;br /&gt; &lt;li&gt;&lt;strong&gt;Deep Zoom Composer&lt;/strong&gt; เครื่องมือที่ช่วยในการเตรียมชุดภาพที่จะใช้กับเทคโนโลยี Deep Zoom ซึ่งเป็นฟีเจอร์เด่นฟีเจอร์หนึ่งใน Silverlight 2.0 ที่ทำให้ผู้ใช้สามารถดูภาพผ่านเว็บได้ในแบบที่ไม่เคยมีมาก่อน กล่าวคือผู้ใช้สามารถซูมเข้าออก เลื่อนภาพไปยังทิศทางต่างๆได้อย่างราบรื่น ผู้ใช้จะสามารถชมภาพที่มีความละเอียดสูงผ่านเว็บโดยไม่ต้องรอโหลดนานๆ และสามารถซูมเพื่อเลือกดูรายละเอียดของภาพ เช่น ลายมือบนโปสการ์ดได้ โดยไม่สูญเสียรายละเอียดของภาพไป เป็นต้น เราสามารถดาวน์โหลด Deep Zoom Composer ได้จาก &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=457B17B7-52BF-4BDA-87A3-FA8A4673F8BF&amp;amp;displaylang=en"&gt;http://www.microsoft.com/downloads/details.aspx?FamilyID=457B17B7-52BF-4BDA-87A3-FA8A4673F8BF&amp;amp;displaylang=en&lt;/a&gt;&amp;nbsp; &lt;br /&gt; &lt;li&gt;&lt;strong&gt;Silverlight Toolkit&lt;/strong&gt; เป็นชุดคอนโทรล, คอมโพเนนท์ และเครื่องมือที่ทางไมโครซอฟท์สร้างเสริมขึ้นมาเพื่อเพิ่มฟังก์ชั่นใหม่ๆให้กับ Silverlight โดยไม่ต้องรอเวลาครบรอบในการออกเวอร์ชั่นใหม่ ทั้งนี้เพื่อให้เกิดความรวดเร็ว คล่องตัว รวมถึงต้องการรับความคิดเห็นจากนักพัฒนาและนักออกแบบไปเพื่อใช้ในการพัฒนา Silverlight รุ่นต่อไปให้สมบูรณ์ยิ่งขึ้น Silverlight Toolkit มาพร้อมกับ Source Code, Unit Testing, ตัวอย่าง และเอกสารประกอบการใช้งาน ในเวอร์ชั่นแรกประกอบด้วยคอนโทรล 12 ชนิดที่ครอบคลุมไปถึง คอนโทรลแผนผังข้อมูล คอนโทรลที่ช่วยในการจัดการรูปร่างหน้าตาและตำแหน่ง รวมถึงคอนโทรลที่ใช้ในการรับข้อมูลจากผู้ใช้แบบต่างๆ เราสามารถดาวน์โหลด Silverlight Toolkit มาใช้งานได้จาก &lt;a href="http://www.codeplex.com/silverlight"&gt;http://www.codeplex.com/silverlight&lt;/a&gt;&amp;nbsp; &lt;/li&gt;&lt;/ul&gt; &lt;p&gt;เมื่อติดตั้งเครื่องมือที่ใช้ในการพัฒนา Silverlight ลงบนเครื่องครบแล้วก็ถึงเวลาที่เราจะได้ทดลองสร้างแอพพลิเคชั่นเล็กๆด้วยเครื่องมือเหล่านี้กันดู เพื่อจะศึกษาขั้นตอนการทำงานระหว่างนักออกแบบและนักพัฒนา รวมถึงทำความรู้จักกับเครื่องมือต่างๆ ที่ต้องใช้งาน&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="twitter_logo" align="right" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/twitter_logo_3.jpg" width="240" height="88" /&gt;  &lt;h3&gt;Hello Silverlight, Hi twitter&lt;/h3&gt; &lt;p&gt;หลังจากติดตั้ง Microsoft Visual Web Developer 2008 Express Edition (VWD), Silverlight Tools และ Microsoft Expression Blend 2 Trial Edition ลงบนเครื่องของเราเรียบร้อยแล้ว เราก็สามารถที่จะเริ่มต้นพัฒนา Silverlight Application ได้ในทันที สำหรับแอพพลิเคชั่นแรกที่เราจะลองทำก็คือ Twitter Reader ซึ่งจะเป็นแอพพลิเคชั่นง่ายๆ ที่จะไปดึงเอา Public Timeline ของ Twitter มาแสดง พร้อมทั้งมีปุ่ม Refresh เพื่อเรียกข้อมูลใหม่ล่าสุดมาแสดงผลเพิ่มเติม  &lt;p&gt;Twitter เป็นเว็บไซต์ที่ให้บริการสมาชิกที่ต้องการทราบว่าเพื่อนๆ หรือคนที่ตนเองสนใจกำลัง “ทำอะไรอยู่” โดยผู้ที่เป็นสมาชิก สามารถพิมพ์ข้อความสั้นๆ ผ่านเว็บหรือโทรศัพท์มือถือเพื่ออัพเดตสถานะของตนเอง ผู้ที่เลือกติดตามความเคลื่อนไหวของสมาชิก ก็จะได้รับข้อความนั้นด้วย เมื่อมีผู้ใช้ร่วมกันเป็นจำนวนมาก จึงเกิดเป็นเครือข่ายที่ผู้คนใช้แลกเปลี่ยนข่าวสารต่อกันในแวดวงเพื่อนได้โดยง่ายและประหยัด บางครั้งการที่เราติดตามผู้คนที่เราสนใจก็อาจทำให้เราได้รับข้อมูลใหม่ๆที่เป็นประโยชน์กับตนเองและแวดวงของเพื่อนของเราอีกด้วย  &lt;p&gt;จุดเด่นที่ทำให้บริการของ Twitter ได้รับความนิยมก็คือการเปิดให้เข้าถึง API ของบริการ API ย่อมาจาก Application Programming Interfaces หมายถึงบริการจากแอพพลิเคชั่นที่เปิดให้นักพัฒนาสามารถเรียกใช้งานได้ เช่น การเรียกดู Public Timeline ล่าสุด หรือการเรียกดูรายการข้อความใหม่ของสมาชิกคนใดคนหนึ่ง เป็นต้น โดยการทำงานภายในจะถูกกำหนดและทำโดย Twitter เอง โดยที่นักพัฒนาที่ต้องการใช้งาน เพียงแต่ทราบวิธีการใช้งานก็เพียงพอ ไม่จำเป็นที่จะต้องรู้วิธีการทำงานภายในแต่อย่างใด  &lt;p&gt;การเปิดให้ใช้บริการเหล่านี้ ทำให้มีนักพัฒนาจำนวนมาก พัฒนาโปรแกรมเพื่อเรียกใช้บริการในรูปแบบต่างๆมากมาย ไม่ว่าจะเป็น การนำไปผสมผสานกับบริการของตนเองในเว็บอื่น (Mashup) หรือการสร้าง Windows Client เพื่อให้การโพสต์ข้อความ ภาพ หรือวิดีโอลงบน Twitter สะดวกขึ้น ในเครื่องมือสื่อสารพกพาก็มีการพัฒนาบริการที่จะทำให้สามารถส่งภาพถ่ายเข้าสู่ระบบ และแสดงเป็นลิงค์ไปยังภาพบนข้อความของ Twitter ได้เป็นต้น  &lt;p&gt;สำหรับ Public Timeline ก็คือข้อความที่สมาชิกพิมพ์เข้าสู่ระบบในช่วงเวลาใดช่วงเวลาหนึ่ง โดยมีสถานะข้อความเป็นสาธารณะนั่นเอง เนื่องจากข้อความเหล่านี้มีจำนวนมหาศาล เราจึงจะดึงเอาแค่ส่วนเล็กๆไม่กี่ข้อความที่เพิ่มเข้ามาในระบบล่าสุด มาแสดงผลในแต่ละครั้งที่ทำการ Refresh ข้อมูล  &lt;p&gt;เมื่อทราบความต้องการของระบบแล้วเราลองมาเริ่มสร้างระบบนี้กันเลยดีกว่า  &lt;ol&gt; &lt;li&gt;เปิด VWD ขึ้นมา จากนั้นเลือกเมนู File &amp;gt; New Project… จากนั้นเลือก Visual Basic &amp;gt; Silverlight จาก Project Types และเลือก Silverlight Application จาก Templates แล้วตั้งชื่อ Solution ตามต้องการ (หากต้องการพัฒนาด้วย C# ให้เลือก Visual C# &amp;gt; Silverlight จาก Project Types แทน) จากนั้นกด OK เพื่อสร้าง Solution ใหม่ &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_185254_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-05_185254" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_185254_thumb.png" width="244" height="170" /&gt;&lt;/a&gt;&amp;nbsp;&lt;br /&gt; &lt;li&gt;เนื่องจาก Silverlight Application จะต้องรันบนเว็บเพจเท่านั้น VWD จะให้เราเลือก ว่าจะสร้าง Web Application / Web site Project ขึ้นมาใหม่ หรือแค่สร้างหน้าทดสอบ เพื่อที่จะเรียกใช้งาน Silverlight Application ที่เราจะสร้าง ในกรณีนี้ เราจะเลือกตัวเลือกพื้นฐาน คือให้สร้าง Web Application Project ขึ้นใหม่ ภายใน Solution เดียวกันนี้ โดยให้ชื่อเหมือนกับ Silverlight Application แต่ลงท้ายเพิ่มเติมด้วย .Web ดังนั้นให้กด OK เพื่อสร้าง Project ใหม่ &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_185822_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-05_185822" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_185822_thumb.png" width="244" height="220" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;VWD จะสร้างโปรเจ็คใหม่ให้เรา 2 โปรเจ็คภายใต้โซลูชั่นที่เราสร้างขึ้น โปรเจ็คแรกจะบรรจุไฟล์ เช่น Page.xaml, App.xaml เป็นต้น จะเป็นส่วนที่เราใช้พัฒนา Silverlight Application ส่วนอีกโปรเจ็คหนึ่งที่มีไฟล์ เช่น Default.aspx และ TestPage.aspx จะเป็นไฟล์ที่ใช้ติดตั้งและเรียกใช้งาน Silverlight Application ของเรา โดยปรกติ VWD จะเปิดหน้าต่างออกแบบของไฟล์ Page.xaml ไว้ให้แล้วเป็นแบบ Split View คือ เห็นทั้งหน้าตาของแอพพลิเคชั่น และโค้ด ให้เราเพิ่มโค้ดลงไปจากโค้ดเดิมที่ VWD เตรียมไว้ให้ดังรายการที่ 1&lt;br /&gt;&lt;br /&gt; &lt;table border="1" cellspacing="0" cellpadding="10" width="474"&gt;  &lt;tr&gt; &lt;td valign="top" width="472"&gt; &lt;p&gt;&lt;b&gt;&lt;u&gt;รายการที่ 1&lt;/u&gt;&lt;/b&gt; &lt;/p&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;lt;UserControl x:Class=&amp;quot;HelloTwitter.Page&amp;quot; &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; xmlns=&amp;quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&amp;quot; &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; xmlns:x=&amp;quot;http://schemas.microsoft.com/winfx/2006/xaml&amp;quot; &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Width=&amp;quot;400&amp;quot; Height=&amp;quot;300&amp;quot;&amp;gt; &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;Grid x:Name=&amp;quot;LayoutRoot&amp;quot; Background=&amp;quot;White&amp;quot;&amp;gt;&lt;/font&gt;  &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;TextBlock&amp;gt;Hello, World&amp;lt;/TextBlock&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font size="2"&gt;&amp;lt;/Grid&amp;gt; &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;lt;/UserControl&amp;gt;&lt;/font&gt; &lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_190219_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-05_190219" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_190219_thumb.png" width="244" height="175" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt;จากนั้นกด F5 เพื่อรัน เนื่องจากเป็นการรันครั้งแรก VWD จะถามเราว่าต้องการแก้ไข Web.config ให้มีการ Debug เกิดขึ้นหรือไม่ ให้เราตอบ OK เพื่อตกลง แอพพลิเคชั่นที่มีการ Debug จะตรวจหาข้อผิดพลาดได้ง่าย แต่จะทำงานช้าลง อย่างไรก็ดี Debug จะถูกปิดการทำงานโดยอัตโนมัติเมื่อเรา Publish เว็บไซต์จึงไม่ส่งผลต่อการทำงานของเว็บไซต์จริงแต่อย่างใด ในขั้นตอนนี้เราจะเห็นว่า VWD เรียก Internet Exploror (IE) ขึ้นมาพร้อมกับแสดงคำว่า Hello, World บนหน้าเว็บเพจ ถ้าหากคลิ๊กขวาที่เว็บเพจจะเกิด Context Menu ที่มีคำว่า Silverlight Configuration เกิดขึ้น หากเลือกตัวเลือกนี้ IE จะแสดงหน้าต่าง Configuration ของ Silverlight Plug-in ขึ้นมา ในหน้าต่างนี้เราสามารถดูข้อมูลเกี่ยวกับปลั๊กอินต่างๆ เช่น เลขเวอร์ชั่น ได้ เป็นต้น หากทำสำเร็จในขั้นตอนนี้ แสดงว่า Silverlight Application ของเราสามารถรันได้สมบูรณ์ จากนี้เราจะเพิ่มฟีเจอร์ของ Twitter ที่เราต้องการเข้าไป &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_192552_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-05_192552" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-05_192552_thumb.png" width="244" height="115" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt;เนื่องจากเราจะทำการออกแบบหน้าจอ Twitter Reader ของเราใหม่ด้วย Microsoft Expression Blend 2 เราจึงต้องเอาโค้ดที่เราใช้ทดสอบการทำงานก่อนหน้านี้ออกก่อน โดยให้เราทำการลบโค้ด &amp;lt;TextBlock&amp;gt;Hello, World&amp;lt;/TextBlock&amp;gt; ที่เพิ่มเติมไปในขั้นตอนก่อนหน้านี้ออกไป จากนั้น Save โปรเจ็ค แล้วจึงคลิ๊กขวาบนไฟล์ Page.xaml ใน Solution Explorer แล้วเลือก Open in Expression Blend…&lt;br /&gt; &lt;li&gt;Blend จะแสดงหน้าต่าง Security Warning เพื่อให้เราแสดงความมั่นใจว่าเราต้องการจะเปิดโซลูชั่นนี้ด้วย Blend เพื่อแก้ไข ให้เราตอบ Yes เพื่อไปยังหน้าจอหลักของ Blend &lt;br /&gt; &lt;li&gt;เมื่อ Microsoft Expression Blend 2 เปิดไฟล์ Xaml ของเราขึ้นมาแสดง เราจะเห็นพื้นที่ว่างเปล่าซึ่งเกิดจากการสร้างขึ้นด้วย VWD อยู่ในส่วนกลางของหน้าจอที่เราเรียกกันว่า Art Board ซึ่งจะเป็นพื้นที่แสดงผลหน้าจอแบบเดียวกับหน้าต่าง Preview ใน VWD จะต่างกันก็แต่เพียงว่า เราสามารถออกแบบคอนโทรลที่ต้องการได้โดยตรงบน Art Board นี้เลย ไม่ใช่แค่ดูการแสดงผลได้อย่างเดียวเช่นในหน้าต่าง Preview ถ้ามองไปรอบๆเราจะเห็นชุดเครื่องมือ Tool Box อยู่ด้านซ้ายสุดของหน้าจอ ถัดมาจะเป็นหน้าต่างย่อย Interaction และ Object and Timeline ถ้ามองไปทางด้านขวาจะเห็นหน้าต่างย่อย Project, Properties, Resources และ Data หน้าต่างเหล่านี้จะแสดงเครื่องมือและวัตถุที่จะใช้เพื่อทำการออกแบบหน้าจอของเรา ในตอนนี้ให้เราพุ่งความสนใจไปเฉพาะเครื่องมือที่เราต้องใช้งานไปก่อน ในที่นี้เราจะลองปรับขนาดของ UserControl เพื่อปรับพื้นที่การแสดงผลหลักให้เป็นขนาดกว้าง 250 Pixel และสูง 400 Pixel เราสามารถปรับขนาดได้โดยการกดเลือกที่ UserControl ในหน้าต่าง Object and Timeline ก่อน เมื่อเลือกแล้วแถบ UserControl จะกลายเป็นสีขาว จากนั้นให้เราเลือกแท็บ Properties ตรงมุมบนขวาของหน้าจอ เพื่อแสดงคุณสมบัติต่างๆของ UserControl ที่เราเลือก ในหน้าต่างย่อยนี้เราจะเห็นกลุ่มของคุณสมบัติที่ชื่อ Layout ซึ่งมีความกว้างและความสูงของ UserControl อยู่ (ปรกติคือกว้าง 400 และสูง 300) ให้เราเปลี่ยนความกว้างให้เป็น 250 และความสูงให้เป็น 400 ตามลำดับ&lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-07_143501_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-07_143501" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-07_143501_thumb.png" width="244" height="175" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt;เมื่อเราสร้างโปรเจ็คใหม่ VWD จะสร้าง Grid คอนโทรลที่ชื่อ LayoutRoot ให้เราโดยอัตโนมัติ เพื่อให้เราสามารถวางคอนโทรลอื่นๆลงใน Grid ได้อีกที คอนโทรลใน Silverlight มีสองประเภท คือพวกที่สามารถมีคอนโทรลลูกได้หลายๆคอนโทรล เช่น Grid, StackPanel และคอนโทรลประเภทที่มีคอนโทรลลูกได้คอนโทรลเดียว เช่น UserControl เป็นต้น ในที่นี้เราจะเปลี่ยน Grid คอนโทรลให้เป็น StackPanel คอนโทรล ซึ่งเป็นคอนโทรลที่จะเรียงคอนโทรลลูกภายในให้เป็นชั้นๆแทน เพื่อความสะดวกในการแสดงผล ให้เราคลิ๊กขวาที่ LayoutRoot จากนั้นเลือก Change Layout Type &amp;gt; StackPanel จาก Context Menu Blend จะเปลี่ยน Grid ให้เป็น StackPanel โดยอัตโนมัติ ในขั้นตอนต่อไปเราจะเพิ่มคอนโทรลลูกอีก 3 ชนิดเข้าไปใน StackPanel นี้ นั่นคือ TextBlock, ListBox และ Button ตามลำดับ &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-07_165607_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-07_165607" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-07_165607_thumb.png" width="244" height="165" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt;เริ่มจาก TextBlock กันก่อน ตรวจดูให้แน่ใจว่า LayoutRoot ยังถูกเลือกอยู่ จากนั้นไปที่ Tool Box แล้วดับเบิ้ลคลิ๊กที่ปุ่ม TextBlock เพื่อเพิ่มคอนโทรลเข้าไปใน StackPanel จะสังเกตในหน้าต่าง Objects and Timeline ได้ว่ามีคอนโทรล [TextBlock] เพิ่มเข้าไปใต้ LayoutRoot แล้วจึงกดเพื่อเลือก TextBlock ที่สร้างขึ้นใหม่ จากนั้นไปที่แท็บ Properties อีกครั้ง แล้วเปลี่ยนคุณสมบัติ Text ให้เป็น “Twitter Public Timeline” จากนั้นในชุดคุณสมบัติ Text ให้กดที่แท็บ Paragraph จากนั้นเปลี่ยน Text Alignment ให้เป็น Center ด้วยการเลือกจากดรอปดาวน์ลิสต์ เพื่อจัดให้ตัวอักษรอยู่กึ่งกลาง &lt;br /&gt; &lt;li&gt;จากนั้นเลือก LayoutRoot ในหน้าต่างย่อย Objects and Timeline อีกครั้ง แล้วไปที่ Tool Box จากนั้นกดปุ่ม Button ค้างไว้ ตัวเลือกใน Tool Box จะขยายออกมาให้เห็นเพิ่มเติม ให้ดับเบิ้ลคลิ๊กที่ ListBox เพื่อเพิ่มคอนโทรลเข้าไปใน StackPanel จากนั้นเลือก [ListBox] ที่เพิ่มขึ้นมาใหม่จากหน้าต่างย่อย Objects and Timeline แล้วเลือกแท็บ Properites แก้ไข Name ให้เป็น TweetList และ Layout &amp;gt; Height ให้เป็น 300 &lt;br /&gt; &lt;li&gt;ตามปรกติตัวเลือกของดรอปดาวน์ลิสต์แต่ละรายการจะแสดงผลจากข้อมูลที่ผูกอยู่รายการละฟิลด์ แต่ในที่นี้เราต้องการให้แต่ละรายการแสดงทั้งเวลาที่ถูกโพสต์และข้อความ จึงต้องทำการแก้ไขการแสดงผลของตัวเลือกแต่ละรายการให้แสดงผลข้อมูลจากฟิลด์ที่เราต้องการเพิ่มเติมได้ โดยเริ่มจากตรวจดูให้แน่ใจว่า TweetList ในหน้าต่างย่อย Objects and Timeline ยังถูกเลือกอยู่ จากนั้นสังเกตที่ด้านบนของ Art Board จะเห็นดรอปดาวน์ลิสต์ชื่อ TweetList เช่นกัน ให้กดที่ดรอปดาวน์ลิสต์ จากนั้นเลือก Edit Other Templates &amp;gt; Edit Generated Items (ItemTemplate) &amp;gt; Create Empty… เพื่อเข้าสู่หน้าจอสำหรับออกแบบการแสดงผลของแต่ละตัวเลือกในดรอปดาวน์ลิสต์ &lt;br /&gt; &lt;li&gt;สังเกตในหน้าต่างย่อย Objects and Timeline จะเห็นว่าค่าของคอนโทรลต่างๆเปลี่ยนไป เช่น จาก UserControl กลายเป็น DataTemplate เป็นต้น นั่นเป็นเพราะเรากำลังทำการออกแบบการแสดงผลเฉพาะจุด ซึ่งในที่นี้ก็คือการออกแบบให้กับตัวเลือกแต่ละรายการของดรอปดาวน์ลิสต์ซึ่งเป็นส่วนที่ย่อยลงไปจากโปรแกรมหลักนั่นเอง แรกเริ่มเราจะเห็น Grid คอนโทรลวางอยู่โดยอัตโนมัติ ให้เราคลิ๊กขวาบน Grid แล้วเปลี่ยนชนิดของมันให้เป็น StackPanel แล้วเพิ่ม TextBlock คอนโทรลเข้าไปภายใน StackPanel นี้ 2 คอนโทรล &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_032809_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-10_032809" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_032809_thumb.png" width="240" height="168" /&gt;&lt;/a&gt;&amp;nbsp;&lt;br /&gt; &lt;li&gt;เพื่อผูกข้อมูลเข้ากับรายการ เราต้องกำหนดฟิลด์ที่จะแสดงผลสำหรับ TextBlock แต่ละคอนโทรล เริ่มจากเลือกที่ TextBlock คอนโทรลบนก่อน จากนั้นไปที่แท็บ Properties แล้วกดที่จุดเล็กๆทางด้านขวาของคุณสมบัติ Text แล้วเลือก Data Binding… จากตัวเลือกที่ขยายออกมา &lt;br /&gt; &lt;li&gt;เมื่อเข้าสู่หน้าต่างย่อย Create Data Binding ให้เลือกแท็บ Explicit Data Context จากนั้นเลือกที่เช็คบ๊อกซ์ Use a custom path expression แล้วเติมคำว่า DatePosted ลงไป เราจะใช้ฟิลด์นี้ในการแสดงเวลาที่โพสต์ข้อความ จากนั้นกด Finish เพื่อกลับสู่หน้าต่างหลัก &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_033727_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-10_033727" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_033727_thumb.png" width="244" height="229" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt;ทำเช่นเดียวกันกับ TextBlock คอนโทรลที่อยู่ด้านล่าง โดยกำหนดให้ชื่อฟิลด์เป็น Text แทน DatePosted เราจะใช้ TextBlock นี้แสดงผลข้อความที่ถูกโพสต์ จากนั้นกดปุ่ม Finish เพื่อกลับสู่หน้าต่างหลัก &lt;br /&gt; &lt;li&gt;กลับสู่หน้าต่างออกแบบอินเทอร์เฟสหลักโดยการกดที่ดรอปดาวน์ลิสต์ TweetList ด้านบนของ Art Board &lt;br /&gt; &lt;li&gt;เมื่อกลับสู่หน้าต่างออกแบบหลักแล้ว ให้เพิ่มปุ่มสำหรับการอัพเดตข้อความโดยการเลือกที่ LayoutRoot ก่อน จากนั้นดับเบิ้ลคลิ๊กเพื่อเพิ่มปุ่มจาก Tool Box แล้วเปลี่ยนคุณสมบัติ Name ให้เป็น RefreshButton และ Content ให้เป็น “Refresh” เราจะใช้ปุ่มนี้ในการอัพเดตข้อมูลใหม่จาก Public Timeline ของ Twitter ในเวลาที่เราต้องการ &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-08_182013_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-08_182013" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-08_182013_thumb.png" width="181" height="244" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt;เซฟงานทั้งหมดใน Blend จากนั้นกลับไปทำงานด้วย VWD เมื่อเราเรียก VWD ซึ่งเปิดพักไว้กลับมาใช้งาน VWD จะเตือนเราว่ามีการแก้ไขไฟล์เอกสารนอกระบบของ VWD และถามเราว่าจะ Reload เอกสารหรือไม่ ให้เราตอบ Yes เพื่ออัพเดตการแก้ไขเอกสารใน VWD &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-07_174716_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-07_174716" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-07_174716_thumb.png" width="244" height="88" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt;ขั้นตอนต่อไปจะเป็นการเขียนโค้ดเพื่อรองรับการทำงานของ Twitter Reader ที่เราออกแบบไว้ เราจะเริ่มต้นโดยการสร้างคลาสที่ชื่อ Tweet ขึ้นมาก่อน เพื่อใช้เป็นคลาสที่จะรองรับข้อมูลสำหรับการทำ Data Binding ให้กับดรอปดาวน์ลิสต์ที่เราออกแบบไว้ก่อนหน้านี้ โค้ดตัวอย่างคลาส Tweet นี้สามารถดูได้จากรายการที่ 2 โดยเราจะออกแบบให้คลาสมีคุณสมบัติเฉพาะเท่าที่เราจะใช้งานเท่านั้น เพื่อประหยัดเวลาในการทดสอบ&lt;br /&gt;&lt;br /&gt; &lt;table border="1" cellspacing="0" cellpadding="10" width="400"&gt;  &lt;tr&gt; &lt;td valign="top" width="400"&gt;&lt;b&gt;&lt;u&gt;รายการที่ 2&lt;br /&gt;&lt;br /&gt;&lt;/u&gt;&lt;/b&gt;&lt;font size="2"&gt;Public Class Tweet&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;Private _text As String&lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;Public Property Text() As String&lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;Get&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font size="2"&gt;Return _text &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;End Get &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;Set(ByVal value As String) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font size="2"&gt;_text = value &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;End Set &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;End Property &lt;br /&gt;&lt;br /&gt;&lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;Private _datePosted As String &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;Public Property DatePosted() As String &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;Get &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font size="2"&gt;Return _datePosted &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;End Get &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;Set(ByVal value As String) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font size="2"&gt;_datePosted = value &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;End Set &lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;End Property &lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font size="2"&gt;&lt;br /&gt;&lt;/font&gt;&lt;font size="2"&gt;End Class&lt;/font&gt; &lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt; &lt;li&gt;เมื่อได้คลาส Tweet แล้ว ขั้นตอนต่อไปเราจะทำการดึงข้อมูล Public Timeline จาก Twitter มาใช้งานกัน โดยปรกติ Twitter จะเปิดให้เราเรียกข้อมูล Public Timeline นี้ได้ผ่าน RSS Feed ที่ URL ดังต่อไปนี้&lt;br /&gt;&lt;br /&gt;&lt;a href="http://twitter.com/statuses/public_timeline.rss"&gt;http://twitter.com/statuses/public_timeline.rss&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;แต่ในกรณีนี้เราจะไม่สามารถจะเรียกไปยัง URL นี้โดยตรงจาก Silverlight Application ของเราได้ เนื่องจากเมื่อเรา Build โปรเจ็คแล้ว VWD จะทำการคอมไพล์ Silverlight Application ของเราให้กลายเป็นไฟล์ .xap เพื่อติดตั้งไว้บนเว็บเซอร์ฟเวอร์ เมื่อถูกเรียกใช้งานไฟล์ดังกล่าวนี้จะถูกดาวน์โหลดไปเพื่อทำงานในฝั่งไคลเอนท์ จึงทำให้การเรียกไปยัง URL เป้าหมายโดยตรงนั้นเกิด Security Error ขึ้น เพราะเป็นการเรียกไปยัง URL ที่อยู่ต่างโดเมนเนมกันหรือ Cross-domain นั่นเอง&lt;br /&gt;&lt;br /&gt;อย่างไรก็ดี จะมีข้อยกเว้นสำหรับเว็บไซต์ที่มีการกำหนดสิทธิ์ไว้ให้ Silverlight Application สามารถเข้าถึงบริการได้เช่นกัน โดยเว็บนั้นจะต้องระบุสิทธิ์ในการเข้าถึงลงไปในไฟล์ clientaccesspolicy.xml หรือ crossdomain.xml ซึ่งวางไว้ที่ Root ของเว็บไซต์นั้นในรูปแบบที่ Silverlight กำหนด ในกรณีของ Twitter ถึงแม้ว่าจะมีไฟล์ &lt;a href="http://www.twitter.com/crossdomain.xml" target="_blank"&gt;crossdomain.xml&lt;/a&gt; วางอยู่ แต่เนื้อหาภายในไม่อยู่ในรูปแบบที่เข้ากันได้กับ Silverlight ดังนั้น จึงทำให้ไม่สามารถเข้าถึงข้อมูลได้ ดังนั้นเราจึงต้องใช้วิธีทางอ้อมในการดึงข้อมูลมาแสดง ซึ่งมีหลายวิธี เช่น การเขียนโมดูล Web Proxy ไว้บนเซอร์ฟเวอร์ของเราเองแล้วให้ Silverlight Application ของเราไปเรียกใช้ หรือในกรณีนี้ เราจะไปใช้บริการของ Yahoo! Pipes (&lt;a href="http://pipes.yahoo.com/pipes/"&gt;http://pipes.yahoo.com/pipes/&lt;/a&gt;) แทน&lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_035625_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-10_035625" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_035625_thumb.png" width="244" height="175" /&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;Yahoo! Pipes เป็นเครื่องมือบนเว็บที่ใช้ในการรวบรวม, ปรับแต่ง และผสมผสานเนื้อหาที่ได้จากเว็บต่างๆเข้าด้วยกัน ทั้งนี้เพื่อให้เกิดรูปแบบข้อมูลใหม่สำหรับผู้สร้างเอง สำหรับวิธีการสร้าง Pipes นั้นทำได้ง่าย และสามารถดูตัวอย่างได้จากเว็บ Yahoo! Pipes เอง จึงจะไม่ขอกล่าวถึง อย่างไรก็ดี เราจะต้องสร้าง Pipes ของ Twitter Public Timeline ขึ้นมาใหม่จาก URL ของ RSS Feed ข้างต้นสำหรับใช้งานในแอพพลิเคชั่นของเรา เมื่อเราสร้างและ Publish Pipes แล้ว เราจะได้ URL ของ Pipes ที่เราสามารถเข้าถึงได้จากเว็บมา เช่น&lt;br /&gt;&lt;br /&gt;http://pipes.yahoo.com/pipes/pipe.info?_id=...&lt;br /&gt;&lt;br /&gt;หากลองเรียก URL ขึ้นมาได้สำเร็จ จะเห็นตัวเลือก More Options ซึ่งจะมีตัวเลือก Get as RSS ไว้ให้ ให้เราบันทึก URL ของ RSS Feed นี้มา&lt;br /&gt;&lt;br /&gt;http://pipes.&lt;b&gt;yahoo&lt;/b&gt;.com/pipes/pipe.run?_id=...&amp;amp;_render=rss&lt;br /&gt;&lt;br /&gt;จากนั้นแก้ไขโดเมนเนมให้เป็นดังนี้&lt;br /&gt;&lt;br /&gt;http://pipes.&lt;b&gt;yahooapis&lt;/b&gt;.com/pipes/pipe.run?_id=...&amp;amp;_render=rss&lt;br /&gt;&lt;br /&gt;สาเหตุที่ต้องมีการแก้ไขโดเมนก็เพราะว่า โดเมนเนม yahoo.com จะกำหนดสิทธิ์ไว้เช่นเดียวกันกับ twitter.com คือไม่อนุญาตให้แอพพลิเคชั่นในฝั่งไคลเอนท์ไปเรียกใช้บริการโดยตรงได้ ต่างกับโดเมนเนม yahooapis.com ซึ่งเปิดให้บริการไว้แล้วนั่นเอง&lt;br /&gt;&lt;br /&gt;เมื่อได้ URL ของ RSS Feed ที่ต้องการแล้ว เราก็จะมาทำการเรียกข้อมูล จากนั้นจึงนำไปผูกกับ ListBox ที่สร้างไว้ก่อนหน้านี้เพื่อแสดงผล โดยเริ่มจากการเปิดไฟล์ Code-Behind ของ Page.xaml ขึ้นมาก่อน ไฟล์นี้จะมีชื่อว่า Page.xaml.vb หรือ Page.xaml.cs ขึ้นอยู่กับภาษาที่เลือกใช้ตอนสร้างโปรเจ็ค การทำงานของ Code-Behind กับ Xaml ก็จะมีความคล้ายคลึงกับไฟล์ .aspx ใน ASP.NET ทั่วไปนั่นเอง โดยมีโค้ดตัวอย่างตามรายการที่ 3&lt;br /&gt;&lt;br /&gt; &lt;table border="1" cellspacing="0" cellpadding="10" width="400"&gt;  &lt;tr&gt; &lt;td valign="top" width="400"&gt;&lt;b&gt;&lt;u&gt;รายการที่&lt;/u&gt;&lt;/b&gt;&lt;b&gt;&lt;u&gt; 3&lt;br /&gt;&lt;br /&gt;&lt;/u&gt;&lt;/b&gt;&lt;font size="2"&gt;Private Sub Page_Loaded(ByVal sender As Object, _&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ByVal e As System.Windows.RoutedEventArgs) _&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Handles Me.Loaded &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; BindTweets() &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;End Sub &lt;/font&gt; &lt;p&gt;&amp;nbsp; &lt;p&gt;&lt;font size="2"&gt;Private Sub BindTweets() &lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim twitterUrl As String = _&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;quot;http://pipes.yahooapis.com/pipes/pipe.run?&amp;quot; +&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;quot;_id=&lt;strong&gt;&lt;em&gt;&amp;lt;your pipe id&amp;gt;&lt;/em&gt;&lt;/strong&gt;&amp;amp;_render=rss&amp;quot; &lt;/font&gt;&lt;/p&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim twitterService As New WebClient() &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; AddHandler twitterService.DownloadStringCompleted, _&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AddressOf TweetReq_Callback &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; twitterService.DownloadStringAsync(New Uri(twitterUrl), _&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; UriKind.Absolute) &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;End Sub&lt;/font&gt;  &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&lt;/font&gt;  &lt;p&gt;&lt;font size="2"&gt;Private Sub TweetReq_Callback(ByVal sender As Object, _&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ByVal e As DownloadStringCompletedEventArgs) &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; If e.Error Is Nothing Then &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font size="2"&gt;DisplayTweets(e.Result) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/font&gt;&lt;font size="2"&gt;End If &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;End Sub&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;โดยแอพพลิเคชั่นจะทำการเรียกฟังก์ชั่น BindTweets() ใช้งานเมื่อถูก Load ฟังก์ชั่นนี้จะทำการสร้างออปต์เจ็ค WebClient ขึ้น เพื่อดาวน์โหลด RSS Feed ของเราจากเว็บเป้าหมาย ซึ่งในที่นี้ก็คือ Pipes ของ Twitter Public Timeline ที่เราสร้างขึ้น และเมื่อดาวน์โหลดเสร็จ ก็จะส่งข้อมูลที่ได้จากการดาวน์โหลดมายังฟังก์ชั่น Callback ที่เรากำหนดไว้ หลังจากตรวจสอบว่าไม่เกิด Error ระหว่างที่ดาวน์โหลดแล้ว ก็จะส่งข้อมูลที่ได้ไปยังฟังก์ชั่นที่ใช้แสดงผลข้อมูลอีกทอดหนึ่ง&lt;br /&gt; &lt;li&gt;เมื่อได้รับผลจากฟังก์ชั่น Callback เรียบร้อยแล้ว เราก็จะนำผลที่ได้ ซึ่งอยู่ในรูปของ RSS Feed หรือ Xml ชนิดหนึ่งมาประมวลผลแล้วผูกข้อมูลเข้ากับดรอปดาวน์ลิสต์ ตามโค้ดตัวอย่างในรายการที่ 4&lt;br /&gt;&lt;br /&gt; &lt;table border="1" cellspacing="0" cellpadding="10" width="400"&gt;  &lt;tr&gt; &lt;td valign="top" width="400"&gt;&lt;b&gt;&lt;u&gt;รายการที่ 4&lt;br /&gt;&lt;br /&gt;&lt;/u&gt;&lt;/b&gt;&lt;font size="2"&gt;Private Sub DisplayTweets(ByVal xmlResponse As String) &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim xTweets As XDocument _&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = XDocument.Parse(xmlResponse) &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim tweets = From t In xTweets.Descendants(&amp;quot;item&amp;quot;) _ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Select New Tweet With { _ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .Text = CType(t.Element(&amp;quot;description&amp;quot;), String), _ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .DatePosted = CType(t.Element(&amp;quot;pubDate&amp;quot;), String) _ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; TweetList.ItemsSource = tweets &lt;/font&gt; &lt;p&gt;&lt;font size="2"&gt;End Sub&lt;/font&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;เราจะนำข้อมูลที่ได้รับกลับมาในรูปแบบ Xml (String) มาแปลงให้เป็นออปต์เจ็ค XDocument เสียก่อน เพื่อให้เราสามารถใช้ LINQ to XML ในการคิวรีข้อมูลได้ โดยใช้ Object Initializer ในการสร้างและบรรจุข้อมูลที่คิวรีได้ไปยังออปเจ็คต์ Tweet ใหม่ สุดท้ายเราก็จะได้ลิสต์ของออปต์เจ็ค Tweet เพื่อเอาไปผูกกับ ItemSource ของดรอปดาวน์ลิสต์ TweetList เพื่อแสดงข้อมูล หากเราไม่สามารถหาคลาสต่างๆที่เกี่ยวกับ LINQ to XML เจอ ให้ Imports System.Xml.Linq เข้ามาที่ต้นเอกสารก่อน ก็จะสามารถใช้งานได้ตามปรกติ &lt;br /&gt; &lt;li&gt;จากนั้นกด F5 เพื่อทดสอบการทำงาน หากแอพพลิเคชั่นของเราทำงานได้ถูกต้อง เราจะพบว่าเมื่อเริ่มใช้งาน จะมีข้อความล่าสุดจาก Twitter Public Timeline มาแสดงในดรอปดาวน์ลิสต์ของเรา เราสามารถทำให้ปุ่ม Refresh อัพเดตข้อมูลได้โดยการเรียกใช้งานฟังก์ชั่น BindTweets() เพิ่มเติมในอีเวนท์ Click ได้เช่นกัน หากการแสดงผลเรียบร้อยดี ก็เป็นอันว่าเสร็จสิ้นขั้นตอนในการทดสอบกระบวนการสร้าง Silverlight Application ด้วยเครื่องมือต่างๆตามที่ได้ตั้งใจเอาไว้แต่แรกเสียที &lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_050923_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="2008-11-10_050923" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/Silverlight2.0_F885/2008-11-10_050923_thumb.png" width="191" height="244" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;li&gt; &lt;p&gt;จากขั้นตอนทั้งหมดที่ผ่านมา นักออกแบบและนักพัฒนาอาจจะเห็นว่ามันดูซับซ้อนและใช้เวลานาน แต่ความจริงแล้ว เครื่องมือแต่ละชิ้นถูกออกแบบมาสำหรับการทำงานแบบแยกส่วน นักออกแบบสามารถใช้ Microsoft Expression Blend 2 ในการออกแบบรูปร่างหน้าตาของอินเทอร์เฟสไปได้พร้อมๆกับนักพัฒนา ซึ่งจะใช้ VWD ในการทำงานเป็นหลัก เนื่องจากเครื่องมือทั้งสองถูกออกแบบมาให้ใช้งานร่วมกันได้เป็นอย่างดี จึงทำให้การทำงานระหว่างนักออกแบบและนักพัฒนามีความสะดวก ราบรื่น ประหยัดเวลา และมีประสิทธิภาพเพิ่มขึ้น &lt;br /&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;บทสรุป&lt;/h3&gt; &lt;p&gt;ในบทนี้เราได้เรียนรู้ประวัติความเป็นมาของนวัตกรรมใหม่สำหรับ RIA จากไมโครซอฟท์นั่นก็คือ Silverlight รวมถึงได้สำรวจฟีเจอร์ใหม่ๆที่เพิ่มเติมเข้ามาใน Silverlight 2.0 นอกจากนี้ก็ยังได้ทำความรู้จักกับเหล่าเครื่องมือชนิดต่างๆที่ใช้ในการพัฒนาแอพพลิเคชั่น รวมถึงได้ทดลองพัฒนาแอพพลิเคชั่นง่ายๆด้วยเครื่องมือดังกล่าว เพื่อทำความเข้าใจกระบวนการพัฒนาแอพพลิเคชั่นร่วมกันอย่างมีประสิทธิภาพระหว่างนักออกแบบและนักพัฒนา ซึ่งเป็นพื้นฐานทั่วไปที่ควรรู้ในการพัฒนา Silverlight Application หวังว่าผู้อ่านจะเข้าใจภาพรวมในการพัฒนาและสามารถนำไปใช้เป็นข้อมูลเพื่อเริ่มต้นในการพัฒนาแอพพลิเคชั่นของตนเองได้ต่อไป&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=560" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/Silverlight/default.aspx">Silverlight</category><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/Expression+Blend/default.aspx">Expression Blend</category></item><item><title>Visual Studio 2008 SP1 และ Silverlight Tools Beta 2 สำหรับ Visual Studio 2008 ออกแล้ว</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/08/13/visual-studio-2008-sp1-silverlight-tools-beta-2-visual-studio-2008.aspx</link><pubDate>Tue, 12 Aug 2008 18:55:14 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:532</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=532</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/08/13/visual-studio-2008-sp1-silverlight-tools-beta-2-visual-studio-2008.aspx#comments</comments><description>&lt;p&gt;เมื่อวันจันทร์ที่ 11 สิงหาคมที่ผ่านมา ไมโครซอฟท์ได้ปล่อยอัพเดตสำคัญสำหรับ Visual Studio 2008 ออกมานั่นคือ Service Pack 1 หรือ SP1 นั่นเอง ซึ่งเป็นการรวมเอาอัพเดตสำคัญๆหลายๆตัวที่ปล่อยออกมาในช่วงที่ผ่านมาไว้ด้วยกัน รวมถึงฟีเจอร์ใหม่ๆของ WPF เช่น Client Deployment Pack สำหรับ .Net Framework ซึ่งจะทำให้ขนาดไฟล์ที่ใช้ในตอนติดตั้งโปรแกรมที่เราเขียนลดลงมากกว่า 80% (เฉพาะส่วนของ .Net Framework) ครับ&lt;/p&gt; &lt;p&gt;ส่วนทางทีม Silverlight ก็ไม่ยอมน้อยหน้า ออก Tools สำหรับ SP1 ตามมาในวันเดียวกัน ในการติดตั้งต้องทำตามลำดับโดยการไปดาวน์โหลดไฟล์ตามนี้มาก่อน&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=A494B0E0-EB07-4FF1-A21C-A4663E456D9D&amp;amp;displaylang=en" target="_blank"&gt;Visual Studio 2008 Service Pack Preparation Tool&lt;/a&gt; (14.5 Mb)&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=27673c47-b3b5-4c67-bd99-84e525b5ce61&amp;amp;DisplayLang=en" target="_blank"&gt;Microsoft Visual Studio 2008 Service Pack 1&lt;/a&gt; (831.5 Mb)&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=50A9EC01-267B-4521-B7D7-C0DBA8866434&amp;amp;displaylang=en" target="_blank"&gt;Microsoft Silverlight Tools Beta 2 for Visual Studio 2008&lt;/a&gt; (84.4 Mb)&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;จากนั้นก็รันไฟล์&lt;strong&gt;&lt;u&gt;ตามลำดับ&lt;/u&gt;&lt;/strong&gt;ครับ การอัพเดตทั้งหมดน่าจะใช้เวลาไม่เกิน 5 ชม. ขึ้นอยู่กับความซับซ้อนของโปรแกรมที่ลงไว้ในเครื่องของเรา (รวมถึง Beta อื่นๆทั้งหลาย) ก่อนจะลงแต่ละไฟล์ควรอ่านคำแนะนำในส่วน Overview ของหน้า Download ให้ละเอียดก่อนครับ โดยเฉพาะส่วนที่เกี่ยวกับการ Remove Windows Update Patch เก่าๆออก ขั้นตอนโดยละเอียดดูเพิ่มเติมได้ที่ &lt;a href="http://silverlighthack.com/post/2008/08/11/Silverlight-2-Tools-SQL-Server-2008-and-Visual-Studio-2008-SP1-Install-Together.aspx" target="_blank"&gt;Silverlight Hack&lt;/a&gt; ครับ&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=532" width="1" height="1"&gt;</description></item><item><title>MVP Hall of Fame Announced!</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/07/04/mvp-hall-of-fame-announced.aspx</link><pubDate>Fri, 04 Jul 2008 06:57:14 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:514</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=514</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/07/04/mvp-hall-of-fame-announced.aspx#comments</comments><description>&lt;p&gt;MVP Hall of Fame เกิดขึ้นครั้งแรกในปี 2006 เพื่อเป็นการประกาศเกียรติคุณกับ MVP ที่ได้รับรางวัลต่อเนื่องยาวนานมากกว่า 3 ปี จากในปีแรกที่มีผู้ได้รับรางวัล (เฉพาะ South East Asia) เพียง 12 ท่าน ในปีนี้มีถึง 59 ท่าน โดยเป็นชาวไทยเรา 7 ท่านด้วยกัน (ประเทศที่มีรายชื่อมากที่สุดคือ Singapore โดยมีผู้ได้รับการประกาศชื่อถึง 21 ท่าน) สำหรับรายชื่อของประเทศไทยมีดังต่อไปนี้ครับ&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;table cellspacing="0" cellpadding="5" width="600" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="193"&gt;MVP&lt;/td&gt; &lt;td valign="top" width="117"&gt;Expertise&lt;/td&gt; &lt;td valign="top" width="143"&gt;Award Since&lt;/td&gt; &lt;td valign="top" width="144"&gt;Award Year&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;&lt;a href="https://mvp.support.microsoft.com/profile=CB01D0EA-D981-4239-BFFF-4925BB034472"&gt;Henry Habermacher&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="117"&gt;Access&lt;/td&gt; &lt;td valign="top" width="142"&gt;Oct 02&lt;/td&gt; &lt;td valign="top" width="143"&gt;6&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;&lt;a href="https://mvp.support.microsoft.com/profile=67F24588-0A13-48BA-82A0-2665016BBE4A"&gt;Suwitcha Chandhorn&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="117"&gt;Client App Dev&lt;/td&gt; &lt;td valign="top" width="141"&gt;Oct 03&lt;/td&gt; &lt;td valign="top" width="142"&gt;5&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;&lt;a href="https://mvp.support.microsoft.com/profile=E7DEDC2C-34EF-49B7-B3E0-6F0BFB491049"&gt;Suthep Sangvirotjanaphat&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="117"&gt;Client App Dev&lt;/td&gt; &lt;td valign="top" width="141"&gt;Oct 04&lt;/td&gt; &lt;td valign="top" width="141"&gt;4&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;&lt;a href="https://mvp.support.microsoft.com/profile=898632C6-CDBC-4F87-B768-D4A0D98321A6"&gt;Rames Gantanant&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="117"&gt;ASP/ASP.NET&lt;/td&gt; &lt;td valign="top" width="140"&gt;Jan 05&lt;/td&gt; &lt;td valign="top" width="141"&gt;4&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;&lt;a href="https://mvp.support.microsoft.com/profile=CBB11571-6B4E-4C1C-A13B-E07F0D179D1A"&gt;Smith Suksmith&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="117"&gt;Team System&lt;/td&gt; &lt;td valign="top" width="140"&gt;Jan 05&lt;/td&gt; &lt;td valign="top" width="141"&gt;4&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;Songsak Channarukul&lt;/td&gt; &lt;td valign="top" width="117"&gt;Visual C#&lt;/td&gt; &lt;td valign="top" width="140"&gt;Jul 05&lt;/td&gt; &lt;td valign="top" width="141"&gt;4&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;&lt;a href="https://mvp.support.microsoft.com/profile=7B504FF5-5A40-4EFB-9BD4-A4BC6DC74485"&gt;Chalermpon Areepong&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="117"&gt;Client App Dev&lt;/td&gt; &lt;td valign="top" width="140"&gt;Jul 06&lt;/td&gt; &lt;td valign="top" width="141"&gt;3&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;&lt;a href="https://mvp.support.microsoft.com/profile=A76FB78A-EBA8-4F9C-BE5F-A6D5A49BDF09"&gt;Wiennat Mongkulmann&lt;/a&gt;&lt;/td&gt; &lt;td valign="top" width="117"&gt;ASP/ASP.NET&lt;/td&gt; &lt;td valign="top" width="141"&gt;Jul 06&lt;/td&gt; &lt;td valign="top" width="143"&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;&amp;nbsp; &lt;p&gt;ดีใจที่ได้รับเกียรตินี้อีกครั้ง และอยากเห็น MVP หน้าใหม่ๆเกิดขึ้นในประเทศไทยอีกหลายๆคน :-)&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=514" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/MVP/default.aspx">MVP</category></item><item><title>Minimize to Tray ด้วย VB.Net</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/05/05/minimize-to-tray-vb-net.aspx</link><pubDate>Mon, 05 May 2008 05:29:22 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:466</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>5</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=466</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/05/05/minimize-to-tray-vb-net.aspx#comments</comments><description>&lt;p&gt;ถึงแม้ปรกติเราจะสามารถใช้ NotifyIcon Control ในการสร้าง Icon ใน Tray ของ Windows ได้โดยไม่จำเป็นต้องเขียนโค้ดเพิ่มเติมแต่อย่างใด แต่จะทำอย่างไรล่ะ ถึงจะสามารถซ่อนหน้าต่างหลักเมื่อเรากดปุ่ม Minimize ของฟอร์มนั้น มันทำอย่างนี้ครับ&lt;/p&gt; &lt;ol&gt; &lt;li&gt;เมื่อกดปุ่ม Minimize จะเกิดอีเวนท์ Resize ขึ้น ให้เราดักดูว่า WindowState เป็น Minimized หรือไม่ ถ้าใช่ก็ให้ซ่อนฟอร์มซะ แล้วแสดง NotifyIcon ใน Tray เช่น&lt;br /&gt;&lt;br /&gt;&amp;#39; เพิ่ม Private Property ในฟอร์มเพื่อให้จำ WindowState ล่าสุดไว้&lt;br /&gt;Private currentWindowState As FormWindowState&lt;br /&gt;&lt;br /&gt;จากนั้นใน Form.Resize ก็ใช้โค้ดตามนี้&lt;br /&gt;&lt;br /&gt;If Me.WindowState = FormWindowState.Minimized Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.Hide()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; MainNotifyIcon.Visible = True&lt;br /&gt;Else&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; จำค่าปัจจุบันก่อนจะ Minimize ไว้&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; currentWindowState = Me.WindowState&lt;br /&gt;End &lt;br /&gt;&lt;/li&gt; &lt;li&gt;เมื่อจะ Restore Window สู่ขนาดเดิม ก็ให้ไปดักที่อีเวนท์ MouseDoubleClick ของ NotifyIcon แล้วดูว่าฟอร์มเรา Minimized ไว้หรือไม่ ถ้าใช่ก็ให้ Show และคืนค่า WindowState เดิมให้มันซะ ดังนี้&lt;br /&gt;&lt;br /&gt;If Me.WindowState = FormWindowState.Minimized Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; MainNotifyIcon.Visible = False&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.Show()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.WindowState = Me.currentWindowState&lt;br /&gt;End If&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;แค่นี้เองครับ&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=466" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/VB/default.aspx">VB</category></item><item><title>การรักษาความปลอดภัยใน ASP.Net 2.0 (1)</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/04/16/asp-net-2-0-1.aspx</link><pubDate>Wed, 16 Apr 2008 07:59:35 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:451</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=451</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/04/16/asp-net-2-0-1.aspx#comments</comments><description>&lt;p&gt;ASP.Net 2.0 มาพร้อมกับระบบรักษาความปลอดภัยที่ใช้งานได้สะดวกยิ่งขึ้นกว่าเดิม จากที่เราเคยต้องลงมือเขียนโค้ดเองในงานที่ต้องทำเป็นประจำหลายๆส่วน อาทิ การทำเว็บเพจสำหรับ Login หรือ สร้างบัญชีผู้ใช้ใหม่ ใน .Net Framework 2.0 ได้จัดเตรียมชุดคอนโทรลสำหรับรักษาความปลอดภัยนี้มาให้อย่างครบครัน นอกจากนี้ชุดเครื่องมือที่ชื่อ Login นี้ยังช่วยเพิ่มประสิทธิภาพในการทำงานบางอย่างที่ควรทำเพื่อให้ข้อมูลปลอดภัยและถูกจัดเก็บแบบมีระบบมากขึ้น ซึ่งช่วยได้มากโดยเฉพาะสำหรับนักพัฒนาที่ยังขาดประสพการณ์ หรือมีเวลาจำกัดในการทำงาน เช่น การเข้ารหัสพาสเวิร์ด เป็นต้น  &lt;p&gt;เครื่องมือชุด Login ในทูลบ๊อกซ์ของ Visual Studio นั้นประกอบด้วยคอนโทรลต่างๆ เช่น Login, LoginView, PasswordRecovery, LoginStatus, LoginName, CreateUserWizard และ ChangePassword คอนโทรล ซึ่งทำหน้าที่ช่วยในการรักษาความปลอดภัยของระบบ ดังที่ระบุไว้ในตารางที่ 1  &lt;p&gt;&amp;nbsp; &lt;p&gt;ตารางที่ 1: หน้าที่ต่างๆของชุด Login คอนโทรล&lt;/p&gt; &lt;table cellspacing="0" cellpadding="2" width="633" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="185"&gt;คอนโทรล&lt;/td&gt; &lt;td valign="top" width="446"&gt;หน้าที่&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="188"&gt;Login&lt;/td&gt; &lt;td valign="top" width="446"&gt;ใช้เพื่อให้ผู้ใช้ล๊อคอินเข้าใช้งานระบบ&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="191"&gt;LoginView&lt;/td&gt; &lt;td valign="top" width="446"&gt;ใช้เลือกแสดงผลระหว่างผู้ใช้ที่ล๊อคอินแล้วและยังไม่ได้ล๊อคอิน&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="193"&gt;PasswordRecovery&lt;/td&gt; &lt;td valign="top" width="446"&gt;ใช้กู้คืนพาสเวิร์ดในกรณีที่ผู้ใช้ลืม&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="195"&gt;LoginStatus&lt;/td&gt; &lt;td valign="top" width="446"&gt;ใช้แสดงสถานะการใช้งานของผู้ใช้ปัจจุบัน โดยแบ่งเป็นปุ่ม Login ซึ่งเชื่อมต่อไปยังหน้าล๊อคอิน และปุ่ม Logout เพื่อออกจากระบบรักษาความปลอดภัย&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="196"&gt;LoginName&lt;/td&gt; &lt;td valign="top" width="446"&gt;ใช้แสดงชื่อผู้ใช้ปัจจุบัน&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="197"&gt;CreateUserWizard&lt;/td&gt; &lt;td valign="top" width="446"&gt;ใช้เพื่อสร้างบัญชีผู้ใช้ใหม่&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="198"&gt;ChangePassword&lt;/td&gt; &lt;td valign="top" width="446"&gt;ใช้เพื่อเปลี่ยนพาสเวิร์ด&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;&amp;nbsp; &lt;p&gt;เพื่อให้เราได้เห็นภาพการใช้งานจริง เราจะลองทำระบบล๊อคอินอย่างง่ายๆขึ้นใช้งานกัน โดยปรกติขั้นตอนในการสร้างระบบรักษาความปลอดภัยให้แก่เว็บไซต์นั้น มักจะประกอบด้วยการสร้างเว็บเพจที่ใช้สำหรับล๊อคอิน, การตั้งค่าสำหรับ Form Authentication, การตั้งค่า Membership Provider และทดสอบการใช้งาน นอกจากนี้เรายังจะทดสอบการใช้งานคอนโทรลอื่นๆ เช่น CreateUserWizard คอนโทรล, LoginView คอนโทรล ฯลฯ เป็นต้น อีกด้วย เนื่องจากเราจะเก็บข้อมูลของบัญชีผู้ใช้ไว้ใน Microsoft SQL Server จึงต้องติดตั้งเตรียมไว้ในเครื่องที่จะใช้พัฒนาก่อนจะทดลองทำตามขั้นตอนต่อไป &lt;ol&gt; &lt;li&gt;ให้เราเปิด Visual Studio 2005 แล้วสร้าง ASP.Net Website ใหม่ ชื่อ ASPNetSecureSample ขึ้นมา&lt;br /&gt; &lt;li&gt;เพิ่มเว็บฟอร์มใหม่เข้ามาในโปรเจ็ค ตั้งชื่อว่า Login.aspx จากนั้นลาก Login คอนโทรลจากทูลบ๊อกซ์ เข้ามาวางในเว็บฟอร์ม&lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/ASP.Net2.01_CBFC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201_2.png"&gt;&lt;img height="240" alt="รูปที่ 1" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/ASP.Net2.01_CBFC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201_thumb.png" width="162" /&gt;&lt;/a&gt; &lt;/li&gt; &lt;p&gt;รูปที่ 1: คอนโทรลต่างๆในชุด Login จาก Toolbox ของ Visual Studio&lt;br /&gt; &lt;li&gt;จากนั้นเปิดหน้าต่าง Visual Studio 2005 Command Prompt จากเมนู Visual Studio Tools แล้วพิมพ์ aspnet_regsql เพื่อเปิดวิซาร์ดสำหรับสร้างฐานข้อมูลระบบรักษาความปลอดภัย โดยเราจะสร้างฐานข้อมูลนี้ไว้ใน Microsoft SQL Server (ผู้เขียนใช้เวอร์ชั่น 2005 แต่เราอาจใช้เวอร์ชั่น 2000 ทดแทนได้เช่นกัน)&lt;br /&gt;&lt;/li&gt; &lt;li&gt;ทำตามขั้นตอนการสร้างฐานข้อมูลไปเรื่อยๆจนจบ โดยเลือกเซอร์ฟเวอร์ที่ต้องการใช้ แล้วตั้งชื่อฐานข้อมูลว่า ASPNetAuthSample เมื่อเสร็จสิ้นขั้นตอนแล้วให้กด Finish เพื่อปิดหน้าต่างวิซาร์ด พิมพ์ Exit ที่ Command Prompt เพื่อปิดหน้าต่าง&lt;br /&gt; &lt;li&gt;จากนั้นเราจะต้องตั้งค่าเพื่อกำหนดให้ระบบรักษาความปลอดภัยของเราใช้ฐานข้อมูลที่เพิ่งสร้างขึ้นเป็นที่เก็บข้อมูลเสียก่อน จึงจะสามารถใช้งานได้ ซึ่งสามารถทำได้โดยการกำหนดค่า Connection String และ Membership ในไฟล์ web.config ดังรายการที่ 1 และรายการที่ 3 ทั้งนี้ค่าของ connectionString อาจแตกต่างไปบ้างตามแต่เครื่องที่ใช้พัฒนา เราสามารถหาดูค่านี้อย่างง่ายๆได้ โดยการติดต่อไปยังฐานข้อมูลที่ต้องการผ่านหน้าต่าง Server Explorer แล้วจึงดูค่านี้ในหน้าต่าง Properties ของการเชื่อมต่อดังกล่าวนั่นเอง&lt;br /&gt;&lt;/li&gt; &lt;table cellspacing="0" cellpadding="10" width="600" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="598"&gt; &lt;p&gt;รายการที่ 1: การกำหนด Connection String ใน web.config &lt;p&gt;&amp;lt;connectionStrings&amp;gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;clear/&amp;gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;add name=&amp;quot;LocalSQLServer&amp;quot; connectionString=&amp;quot;Data Source=.;  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Initial Catalog=ASPNetAuthSample; Integrated Security=True&amp;quot; /&amp;gt; &lt;p&gt;&amp;lt;/connectionStrings&amp;gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt; &lt;li&gt;แล้วจึงกำหนดค่า Authentication และ Authorization ใน web.config เพื่อบังคับให้ผู้ใช้ต้อง Login ทุกครั้งเมื่อใช้งานเว็บไซต์&lt;br /&gt;&lt;/li&gt; &lt;table cellspacing="0" cellpadding="10" width="600" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="600"&gt; &lt;p&gt;รายการที่ 2: การตั้งค่า Authentication และ Authorization ใน web.config &lt;p&gt;&amp;lt;authentication mode=&amp;quot;Forms&amp;quot;&amp;gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;forms name=&amp;quot;.AuthCookie&amp;quot; timeout=&amp;quot;10&amp;quot;/&amp;gt; &lt;p&gt;&amp;lt;/authentication&amp;gt; &lt;p&gt;&amp;lt;authorization&amp;gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;deny users=&amp;quot;?&amp;quot;/&amp;gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;allow users=&amp;quot;*&amp;quot;/&amp;gt; &lt;p&gt;&amp;lt;/authorization&amp;gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt; &lt;li&gt;ลาก Label คอนโทรลมาวางใน Default.aspx กำหนดค่า Text ให้เป็น Hello, World! และค่าขนาดฟ้อนท์ให้เป็น x-large เพื่อแสดงผลคำนี้หลังจากผู้ใช้ล๊อคอิน&lt;br /&gt;&lt;/li&gt; &lt;li&gt;กด F5 เพื่อทดสอบ Visual Studio จะเปิด Internet Explorer ขึ้นมา และระบบจะ Redirect เราไปยังหน้าล๊อคอินเสมอตามที่ตั้งค่า Authorization ไว้ใน web.config แต่เนื่องจากเรายังไม่มีบัญชีผู้ใช้ใดๆ จึงต้องสร้างขึ้นมาใหม่เสียก่อนจึงจะสามารถล๊อคอินเข้าใช้งานระบบได้&lt;br /&gt; &lt;li&gt;ให้ปิด Internet Explorer แล้วเปิดไฟล์ Login.aspx ขึ้นมา จากนั้นลาก CreateUserWizard คอนโทรลจากทูลบ๊อกซ์เข้ามาวางในฟอร์ม ตรงด้านล่างของ Login คอนโทรล&lt;br /&gt; &lt;li&gt;กด F5 เพื่อทดสอบระบบอีกครั้ง คราวนี้ให้ลองสร้างบัญชีผู้ใช้ใหม่โดยใช้ชื่อว่า “user1” และพาสเวิร์ด “qwerty!!!” โดยทั่วไป CreateUserWizard คอนโทรลจะกำหนดค่ากฏพื้นฐานของพาสเวิร์ดไว้ให้ไม่ต่ำกว่าหกตัวอักษรและมีเครื่องหมายใดเครื่องหมายหนึ่งอย่างน้อยหนึ่งเครื่องหมาย ทั้งนี้ก็เพื่อความปลอดภัย แต่เราสามารถปรับแต่งส่วนนี้ได้ภายหลัง&lt;br /&gt; &lt;li&gt;เมื่อสร้างบัญชีผู้ใช้สำเร็จให้ลองทดสอบ Login ดู คราวนี้เมื่อใส่ข้อมูลผู้ใช้ที่ถูกต้อง ระบบก็จะนำเราไปยังหน้า Default.aspx โดยอัตโนมัติ&lt;br /&gt;&lt;br /&gt;เมื่อถึงขั้นตอนนี้จะเห็นว่าเราสามารถล๊อคอินเข้าใช้งานระบบได้อย่างสมบูรณ์พอสมควรแล้ว แต่ยังมีรายละเอียดปลีกย่อยอื่นที่จำเป็นต้องกล่าวถึงอีกพอสมควร เพื่อให้สอดคล้องกับการใช้งานจริง เช่น ระบบควรจะแสดงชื่อผู้ใช้งานและปุ่มล๊อคเอาท์เมื่อผู้ใช้ล๊อคอินเข้ามาใช้งานระบบ หรือระบบควรอนุญาตให้ผู้ใช้ที่ยังไม่ได้ล๊อคอินสามารถใช้งานระบบบางส่วนได้ เป็นต้น เราจึงจะทดลองใช้งานคอนโทรลอื่นๆที่มีในชุดเครื่องมือ Login นี้กันต่อ&lt;br /&gt;&lt;/li&gt; &lt;li&gt;ปิด Internet Explorer แล้วเปิด Default.aspx ขึ้นมา ลาก LoginView คอนโทรลเข้าไป กดปุ่ม Tasks ตรงมุมบนขวาของคอนโทรลดูให้แน่ใจว่า Views: ของคอนโทรลเป็น AnonymousTemplate เพื่อให้ส่วนการแสดงผลที่จะกำหนดต่อไปนี้เป็นส่วนที่ผู้ใช้ที่ยังไม่ได้ทำการล๊อคอินเห็น&lt;br /&gt;&lt;/li&gt; &lt;li&gt;ลาก Label1 คอนโทรลที่เราทำไว้เดิมไปปล่อยบน LoginView คอนโทรลที่เราเพิ่งสร้างขึ้น เปลี่ยนค่า Text เดิมให้เป็นคำว่า “Hello Guest!” จากนั้นลาก LoginStatus คอนโทรลเข้ามาวางไว้ต่อจาก Label1 คอนโทรลนี้จะแสดงปุ่มเพื่อเชื่อมต่อผู้ใช้ไปยังหน้าสำหรับล๊อคอินในกรณีที่ยังไม่ได้ล๊อคอิน และแสดงค่าเป็นล๊อคเอาท์เพื่อให้ผู้ใช้กดเมื่อต้องการออกจากระบบรักษาความปลอดภัย&lt;br /&gt; &lt;li&gt;เลือก Label1 คอนโทรลแล้วกด Ctrl+C เพื่อ Copy หลังจากนั้นกดปุ่ม Tasks ของ LoginView คอนโทรลแล้วเปลี่ยน Views: ไปยัง LoggedInTemplate ซึ่งจะเป็นส่วนที่ผู้ใช้เห็นหลังจากที่ได้ล๊อคอินแล้ว&lt;br /&gt; &lt;li&gt;เมื่อคอนโทรลแสดง Template ว่างๆ ให้กดด้านในแล้วกด Ctrl+V เพื่อวาง Label คอนโทรลที่ Copy มาลงไป แล้วลบค่า Text ของคอนโทรลออกให้เหลือเพียงคำว่า “Hello” &lt;br /&gt; &lt;li&gt;จากนั้นลาก LoginName คอนโทรลและ LoginStatus คอนโทรลไปวางต่อจาก Label คราวนี้ใน Tasks ของ LoginStatus ให้เลือก Views: เป็น LoggedIn ลิงค์จะแสดงค่าคำว่า “Logout” ขึ้นมาแทนค่าเดิม&lt;br /&gt; &lt;li&gt;เปิดไฟล์ web.config ขึ้นมาแล้ว ลบส่วนที่เราตั้งค่าของ Authorization ไว้ก่อนหน้านี้ออกไป เพื่อกำหนดสิทธิ์ให้เว็บไซต์สามารถเข้าถึงได้จากผู้ใช้ทั่วไป&lt;br /&gt; &lt;li&gt;หลังจากนั้นกด F5 เพื่อทดสอบ ในหน้า Default.aspx เราจะเห็นคำว่า “Hello, Guest!” แสดงพร้อมกับลิงค์เพื่อไปยังหน้า Login เมื่อเราลอง Login ดูด้วยบัญชีผู้ใช้ “user1” ที่เพิ่งสร้างขึ้นจะพบว่าระบบนำเรากลับมายังหน้า Default.aspx พร้อมกับแสดงค่า “Hello, user1” แทน นอกจากนี้เรายังสามารถกด Logout เพื่อออกจากระบบได้อีกด้วย&lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/ASP.Net2.01_CBFC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%202_2.png"&gt;&lt;img height="156" alt="รูปที่ 2" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/ASP.Net2.01_CBFC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%202_thumb.png" width="240" /&gt;&lt;/a&gt;&amp;nbsp;&lt;br /&gt;รูปที่ 2: การแสดงผลเมื่อผู้ใช้ล๊อคอิน&lt;br /&gt;&lt;/li&gt; &lt;li&gt;ปิด Internet Explorer เปิดหน้า Login.aspx แล้วลบ CreateUserWizard คอนโทรลออก เราจะสร้างหน้าเว็บเพจใหม่สำหรับการสร้างบัญชีผู้ใช้ใหม่โดยเฉพาะในขั้นตอนต่อไป&lt;br /&gt; &lt;li&gt;สร้างไฟล์ Register.aspx ขึ้นใหม่ในโปรเจ็ค จากนั้นลาก CreateUserWizard คอนโทรลมาวาง เลือกกำหนดค่า ContinueDestinationPageUrl ให้เป็นไฟล์ Default.aspx เพื่อกำหนดให้เมื่อสร้างบัญชีใหม่เสร็จ ให้ระบบนำเราไปยังหน้าหลัก&lt;br /&gt; &lt;li&gt;กลับไปที่ไฟล์ Login.aspx ให้คลิ๊กที่ Login คอนโทรล แล้วตั้งค่า CreateUserText เป็น “Register” และเลือกไฟล์ Register.aspx สำหรับค่า CreateUserUrl เมื่อสังเกตที่คอนโทรลจะเห็นว่ามีลิงค์ไปยังหน้าลงทะเบียนเพิ่มขึ้นมา กด F5 เพื่อทดลองสร้าง account ผ่านหน้าที่สร้างขึ้นมาใหม่ เช่น user2 เป็นต้น&lt;br /&gt;&lt;/li&gt; &lt;table cellspacing="0" cellpadding="10" width="600" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="598"&gt; &lt;p&gt;รายการที่ 3: การกำหนดค่า MembershipProvider ให้ระบบรักษาความปลอดภัย &lt;p&gt;&amp;lt;membership defaultProvider=&amp;quot;LocalMembershipProvider&amp;quot;&amp;gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;providers&amp;gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;add name=&amp;quot;LocalMembershipProvider&amp;quot;  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; connectionStringName=&amp;quot;LocalSQLServer&amp;quot;  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; minRequiredPasswordLength=&amp;quot;5&amp;quot; &lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; minRequiredNonalphanumericCharacters=&amp;quot;0&amp;quot; &lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; passwordStrengthRegularExpression=&amp;quot;^[0-9a-zA-Z&amp;#39;.\s]{5,40}$&amp;quot; &lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; type=&amp;quot;System.Web.Security.SqlMembershipProvider&amp;quot;/&amp;gt;&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/providers&amp;gt; &lt;p&gt;&amp;lt;/membership&amp;gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt; &lt;li&gt;เนื่องจาก CreateUserWizard คอนโทรลถูกตั้งค่าไว้ให้รับพาสเวิร์ดไม่ต่ำกว่าหกตัวอักษร และต้องมีเครื่องหมายอย่างน้อยหนึ่งตัวอักษรดังที่กล่าวไปแล้วข้างต้น อาจทำให้ไม่สะดวกกับผู้ใช้หรือไม่เข้ากับกฏที่เว็บไซต์เคยใช้อยู่เดิม เราสามารถปรับแต่งข้อบังคับนี้ได้ ด้วยการกำหนดค่า PasswordRegularExpression ในคอนโทรล และค่า passwordStrengthRegularExpression ของ LocalMembershipProvider ในไฟล์ web.config ให้ลองกำหนดค่าทั้งสองเป็น ^[0-9a-zA-Z&amp;#39;.\s]{5,40}$ ซึ่งเป็น Regular Expression ที่แทนการรับค่าตัวอักษร เช่น 0-9, a-z, A-Z, ช่องว่างและเครื่องหมาย . หรือ ‘ เป็นต้น โดยมีความยาวระหว่าง 5 ถึง 40 ตัวอักษร เพื่อกำหนดให้ขอบเขตของกฏกว้างขึ้น&lt;br /&gt;&lt;/li&gt; &lt;li&gt;กฏที่เราตั้งบังคับความยาวของพาสเวิร์ดให้เริ่มที่ 5 ตัวอักษรและจะไม่กำหนดว่าต้องมีเครื่องหมายพิเศษ เราจึงต้องกำหนดค่า minRequiredPasswordLength และ minRequiredNonalphanumericCharacters ของ LocalMembershipProvider ให้เท่ากับ 5 และ 0 ตามลำดับ&lt;br /&gt;&lt;br /&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/ASP.Net2.01_CBFC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%203_2.png"&gt;&lt;img height="240" alt="รูปที่ 3" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/ASP.Net2.01_CBFC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%203_thumb.png" width="228" /&gt;&lt;/a&gt; &lt;br /&gt;รูปที่ 3: CreateUserWizard คอนโทรลขณะใช้งาน&lt;br /&gt;&lt;/li&gt; &lt;li&gt;เมื่อลองทดสอบสร้างบัญชีของผู้ใช้ใหม่เช่น user3 จะพบว่าเราสามารถตั้งพาสเวิร์ดได้อย่างอิสระมากขึ้น ทั้งนี้เราสามารถปรับแต่งกฏของเราได้ตามต้องการ โดยสามารถหาอ่านเรื่องเกี่ยวกับการใช้ Regular Expression เพิ่มเติมได้จากบทความเรื่อง How To: Use Regular Expressions to Constrain Input in ASP.NET ในเว็บไซต์ของไมโครซอฟท์ที่ http://msdn2.microsoft.com/en-us/library/ms998267.aspx &lt;/li&gt; &lt;p&gt;หากเราต้องการให้ผู้ใช้สามารถเปลี่ยนพาสเวิร์ดของตนเองได้ก็สามารถทำได้ง่ายๆ โดยใช้ ChangePassword คอนโทรล ดังตัวอย่าง&lt;br /&gt; &lt;li&gt;สร้างไฟล์ใหม่โดยตั้งชื่อว่า ChangePassword.aspx จากนั้นลาก ChangePassword คอนโทรลลงมาวาง กำหนดค่า ContinueDestinationPageUrl ให้เป็นไฟล์ Default.aspx &lt;br /&gt; &lt;li&gt;ต่อมาให้เราเปิดไปที่ไฟล์ Default.aspx เลือก LoggedInTemplate จาก Tasks ของ LoginView จากนั้นลาก HyperLink คอนโทรลมาวางระหว่าง LoginName คอนโทรลและ LoginStatus คอนโทรล กำหนดค่า Text เป็น “Change Password” และค่า NavigateUrl ให้เป็น “~/ChangePassword.aspx” &lt;br /&gt; &lt;li&gt;กด F5 เพื่อทดสอบ โดยการล๊อคอินด้วยบัญชีที่เราสร้างขึ้นในขั้นตอนก่อนๆ จากนั้น Browse ไปยังหน้า ChangePassword.aspx เพื่อทดลองเปลี่ยนพาสเวิร์ด&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;นอกจากนี้เรายังสามารถอนุญาตให้ผู้ใช้รีเซตพาสเวิร์ดได้ในกรณีที่ลืม โดยใช้ PasswordRecovery คอนโทรลซึ่งก็มีวิธีการใช้งานที่คล้ายคลึงกันกับ ChangePassword คอนโทรลมาก จะเห็นได้ว่าการใช้งานชุดเครื่องมือ Login เพื่อสร้างระบบรักษาความปลอดภัยพื้นฐานนั้นสะดวกและมีประสิทธิภาพมาก นอกจากนี้เรายังสามารถควบคุมคอนโทรลเหล่านี้ผ่านโค้ด และนำไปปรับใช้ได้ในหลายๆสถานการณ์ สามารถตรวจสอบสถานะการล๊อคอินผ่าน User.Identity.IsAuthenticated ก่อนจะแสดงผล และสามารถปรับแต่งการแสดงผลของคอนโทรลด้วย Auto Format… ใน Tasks ของแต่ละคอนโทรลได้ เป็นต้น นับว่าเป็นชุดเครื่องมือที่ช่วยทุ่นแรงนักพัฒนาไปได้อย่างยิ่งยวดจริงๆ ใช่ไหมครับ&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=451" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/ASP.Net/default.aspx">ASP.Net</category><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/Security/default.aspx">Security</category></item><item><title>ป้องกันการโพสต์ข้อความไม่พึงประสงค์ด้วย CAPTCHA ตอนที่ 2</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/03/31/captcha-2.aspx</link><pubDate>Mon, 31 Mar 2008 16:21:18 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:437</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=437</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/03/31/captcha-2.aspx#comments</comments><description>&lt;p&gt;ความเดิมจากตอนที่แล้ว เราเริ่มต้นด้วยการสร้างคลาสสำหรับสร้างรูปภาพ CAPTCHA ขึ้นมาคลาสหนึ่งให้ชื่อว่า CaptchaImage และได้อธิบายวิธีโครงสร้างของมันไปพอสมควร คราวนี้เราจะมาดูกันถึงวิธีการเรียกใช้งานคลาสดังกล่าวนี้จากหน้าเว็บเพจใน ASP.Net กันดูบ้างดีกว่า &lt;p&gt;ดังที่เคยกล่าวไว้ในฉบับที่แล้ว การสร้าง Captcha แบบง่ายๆขึ้นใช้งานนั้น ทำได้โดยการสร้างคลาสสำหรับใช้ Generate Captcha ที่ต้องการขึ้นมาก่อน เมื่อต้องการใช้ก็เรียกใช้งานคลาสนั้น โดยผ่านข้อความที่สุ่มขึ้นมาเข้าไปเพื่อให้ได้รูปภาพกลับคืนมาแสดงผล นอกจากนี้เราต้องเก็บข้อความที่สร้างขึ้นไว้ใน Session เพื่อนำมาเทียบกับสิ่งที่ผู้ใช้ป้อนในภายหลัง ถ้าหากป้อนข้อความกลับมาได้ถูกต้อง จึงอนุญาตให้ผู้ใช้ทำงานต่อไปได้ ดังนั้นขั้นตอนถัดไปเราจึงต้องสร้างเว็บเพจที่จะเรียกใช้ CaptchaImage ขึ้นมา &lt;h2&gt;เรียก CAPTCHA มาแสดงผล&lt;/h2&gt; &lt;p&gt;ก่อนอื่นเราต้องแสดงผลรูปภาพที่ได้จากคลาสโดยการเรียกผ่านเว็บเพจเสียก่อน เพื่อจะทำให้สามารถเรียกใช้งานผ่านอินเทอร์เน็ตได้ ซึ่งทำได้ง่ายๆโดยการเพิ่ม WebForm ชื่อ GetCaptchaImage เข้ามาในโปรเจ็ค และเพิ่มโค้ดดังรายการที่ 1 เข้าไปในอีเวนท์ Page_Load โค้ดจะทำการสร้างรูปภาพขึ้นจากข้อความที่เราเก็บไว้ในตัวแปร Session ตามขนาดและตัวอักษรที่เรากำหนด และส่งผลกลับออกไปยังเว็บเพจที่เรียกมาเป็นไฟล์ชนิด Jpeg ซึ่งเป็นรูปแบบการแสดงผลรูปภาพมาตรฐานหนึ่งในอินเทอร์เน็ตโดยการกำหนดค่า ContentType ของ Request ที่เข้ามา ทำให้เราไม่จำเป็นจะต้องแก้ไขโค้ดใดๆในส่วนของไฟล์ .aspx เพิ่มเติม เนื่องจากค่าที่ได้จะถูกคืนไปเป็นรูปภาพเมื่อเว็บเพจถูกเรียกใช้แทน&lt;/p&gt; &lt;table cellspacing="0" cellpadding="10" width="700" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="698"&gt; &lt;p&gt;&lt;em&gt;รายการที่ 1: การเรียกใช้คลาส CaptchaImage และส่งคืนรูปภาพชนิด Jpeg&lt;/em&gt;  &lt;p&gt;Partial Class GetCaptchaImage  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Inherits System.Web.UI.Page  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim ci As New CaptchaImage(Session(&amp;quot;captchaCode&amp;quot;).ToString(), 240, 60, &amp;quot;Tahoma&amp;quot;)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.Response.Clear()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.Request.ContentType = &amp;quot;image/jpeg&amp;quot;  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ci.Image.Save(Me.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ci.Dispose()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Sub  &lt;p&gt;End Class&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;สร้างฟอร์มสำหรับแสดงและเทียบข้อความ&lt;/h2&gt; &lt;p&gt;จากนั้นเราต้องสร้างฟอร์มขึ้นมาสำหรับแสดงและรับค่าเพื่อเทียบหาความถูกต้องเสียก่อน โดยการเพิ่มฟอร์มใหม่เข้ามาในโปรเจ็ค และเพิ่มคอนโทรลต่างๆ ดังตารางที่ 1 เราแสดงผลข้อความที่จะสุ่มขึ้นโดยใช้ Image คอนโทรลโดยกำหนด ImageUrl ไปยังไฟล์ GetCaptchaImage.aspx ที่เราเพิ่งสร้างขึ้น เมื่อโหลดคอนโทรลจะไปเรียกใช้ไฟล์ดังกล่าวและได้ค่าคืนมาเป็นรูปภาพอีกต่อหนึ่ง ทำให้เราไม่จำเป็นต้องสร้างรูปภาพจริงๆเตรียมไว้ในระบบ &lt;p&gt;ตารางที่ 1: คอนโทรลต่างๆที่ใช้ในเว็บเพจ&lt;/p&gt; &lt;table cellspacing="0" cellpadding="10" width="700" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="140"&gt;&lt;strong&gt;Control&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top" width="216"&gt;&lt;strong&gt;Property&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top" width="342"&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="140"&gt;Image&lt;/td&gt; &lt;td valign="top" width="216"&gt;ID&lt;/td&gt; &lt;td valign="top" width="341"&gt;imgCaptcha&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="140"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="216"&gt;ImageUrl&lt;/td&gt; &lt;td valign="top" width="340"&gt;~/GetCaptchaImage.aspx&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="140"&gt;TextBox&lt;/td&gt; &lt;td valign="top" width="216"&gt;ID&lt;/td&gt; &lt;td valign="top" width="339"&gt;txtMessage&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="140"&gt;Button&lt;/td&gt; &lt;td valign="top" width="216"&gt;ID&lt;/td&gt; &lt;td valign="top" width="339"&gt;btnCheck&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="140"&gt;&amp;nbsp;&lt;/td&gt; &lt;td valign="top" width="216"&gt;Text&lt;/td&gt; &lt;td valign="top" width="339"&gt;Check&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top" width="140"&gt;Label&lt;/td&gt; &lt;td valign="top" width="216"&gt;ID&lt;/td&gt; &lt;td valign="top" width="339"&gt;lblIsMatched&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;นอกจากนี้เรายังต้องมีกล่องข้อความ, ปุ่ม และแถบข้อความเพื่อใช้แสดงผลการเปรียบเทียบ เมื่อใส่คอนโทรลต่างๆเข้ามาแล้วควรจัดให้เป็นระเบียบสวยงาม เพื่อให้ง่ายต่อการใช้งาน &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA2_1464A/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201_2.png"&gt;&lt;img style="border-right:0px;border-top:0px;border-left:0px;border-bottom:0px;" height="179" alt="รูปที่ 1" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA2_1464A/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201_thumb.png" width="240" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 1: การจัดวางคอนโทรลต่างๆใน IDE&lt;/em&gt; &lt;h2&gt;สุ่มข้อความใน Code Behind&lt;/h2&gt; &lt;p&gt;เมื่อจัดแต่งหน้าตาเสร็จ เราต้องมีการสุ่มข้อความหรือในที่นี้คือชุดตัวเลขขึ้นมา โดยทำการเขียนโค้ดไว้ในอีเวนท์ Page_Load ในส่วน Code Behind ของเว็บเพจที่จะใช้ โดยเลือกสุ่มค่าเพื่อมาเก็บไว้ในตัวแปร Session แล้วจึงใช้ค่านั้นสอบเทียบกันเมื่อเกิดการ PostBack ในเว็บเพจ ในโค้ดตัวอย่าง เราจะทำการสุ่มค่าตัวเลขหกหลักขึ้นมาหนึ่งชุดเมื่อเว็บเพจนั้นถูกเรียกใช้ในครั้งแรก และเก็บค่าไว้ในตัวแปร Session เมื่อ Image คอนโทรลถูกโหลดมันจะวิ่งไปเรียกไฟล์ GetCaptchaImage.aspx ซึ่งจะคืนค่ารูปตามตัวแปรที่เก็บไว้กลับมาเพื่อแสดงผล หากเรากรอกข้อมูลแล้วกดปุ่ม Check โปรแกรมก็จะทำการตรวจสอบค่าที่เราป้อนเข้ามากับตัวแปรที่เก็บไว้ใน Session ก่อนจะแสดงผลการเปรียบเทียบออกมาทางหน้าจอ &lt;/p&gt; &lt;table cellspacing="0" cellpadding="10" width="700" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="700"&gt; &lt;p&gt;&lt;em&gt;รายการที่ 2: การสุ่มค่าข้อความเพื่อใช้ตรวจสอบ&lt;/em&gt;  &lt;p&gt;Partial Class _Default  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Inherits System.Web.UI.Page  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private Function GenerateRandomCode() As String  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim s As String = String.Empty  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim r As New Random  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; For i As Integer = 0 To 6  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; s = String.Concat(s, r.Next(10).ToString())  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Next  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Return s  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Function  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; If Not Page.IsPostBack Then  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Session(&amp;quot;captchaCode&amp;quot;) = GenerateRandomCode()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Else  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; If Not String.IsNullOrEmpty(Session(&amp;quot;captchaCode&amp;quot;).ToString()) AndAlso txtMessage.Text = _  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Session(&amp;quot;captchaCode&amp;quot;).ToString() Then  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lblIsMatched.Text = &amp;quot;Matched!&amp;quot;  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Else  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lblIsMatched.Text = &amp;quot;Not match, try again!&amp;quot;  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; txtMessage.Text = String.Empty  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Session(&amp;quot;captchaCode&amp;quot;) = GenerateRandomCode()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; End If  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; End If  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Sub  &lt;p&gt;End Class&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;ปรับแต่งรูปแบบการแสดงผล&lt;/h2&gt; &lt;p&gt;หากเราทดสอบการใช้งานได้ผลสำเร็จเป็นที่น่าพอใจแล้ว เราก็สามารถที่จะปรับแต่งรูปแบบการแสดงผลให้เป็นไปตามรูปแบบที่เราต้องการได้ หากต้องการเปลี่ยนข้อความที่สุ่มขึ้นก็สามารถทำได้เลยโดยการแก้ไขอัลกอริทึมของฟังก์ชั่น GenerateRandomCode() ในอีเวนท์ Page_Load ของเว็บเพจที่เรียกใช้ GetCaptchaImage.aspx แต่ถ้าหากต้องการปรับวิธีการแสดงผลของภาพ ก็ต้องย้อนกลับไปดูโค้ดในคลาส CaptchaImage กันครับ &lt;p&gt;ในการสร้างและปรับแต่งรูปภาพแบบ 2D หรือ 2 dimension (สองมิติ) เนมสเปซที่เกี่ยวข้องจะอยู่ในชุดของ System.Drawing ซึ่งมีเนมสเปซย่อยๆที่จำเป็นต้องเรียกใช้งานอีกหลายเนมสเปซ เช่น System.Drawing.Drawing2D, System.Drawing.Graphics และ System.Drawing.Imaging เป็นต้น เราต้องทำการอิมพอร์ตเข้ามาก่อนจึงจะสามารถเรียกใช้งานคลาสและฟังก์ชั่นต่างๆที่เกี่ยวกับกราฟิกใน .Net Framework 2.0 หรือ GDI+ ได้ &lt;p&gt;เราเริ่มสร้าง Captcha ของเราโดยการสร้างออปเจ็คต์ Bitmap ขึ้นมาก่อน จากนั้น Reference ไปยังคลาส Graphics เพื่อกำหนดให้เป็นพื้นที่ที่ใช้ในการเขียนกราฟิกต่างๆลงไปได้ เมื่อ New Bitmap เรากำหนดให้มีความกว้าง ความสูง และรูปแบบการแสดงผลผ่าน PixelFormat.Format32bppPArgb ซึ่งหมายถึง เรากำหนดรูปแบบของกราฟิกในรูปให้มีขนาด 32 บิตต่อจุด (pixel) และกำหนดค่าสีแบบ Premultiplied Alpha  &lt;p&gt;หลายคนอาจจะสงสัยว่า Premultiplied Alpha คืออะไร ขออธิบายโดยย่อว่า ในการแสดงค่าสีบนจอ เรามีแม่สีทั้งหมดสามสี นั่นคือ แดง เขียว น้ำเงิน สีอื่นๆจะเกิดจากการผสมระหว่างแม่สีสามสีนี้ แต่ในกรณีที่เราต้องการควบคุมความเข้มหรือจางของสี เราต้องกำหนดไว้ที่ค่า Alpha นั่นเอง ยกตัวอย่างเช่น จุดจุดหนึ่งเก็บค่าสีเรียงตามลำดับดังนี้ (r, g, b, a) หากเราต้องการแสดงสีขาว ก็ต้องเก็บค่าเป็น (1,1,1,1) หากต้องการให้โปร่งใสก็ต้องเก็บค่าเป็น (0,0,0,0) เป็นต้น ถ้าเป็นในกรณีที่เราเก็บค่าสีแบบ Premultiplied Alpha ค่าสีจะถูกเก็บแบบ (ar, ag, ab, a) ดังนั้นในกรณีที่เราต้องการแสดงผลเป็นสีขาวโปร่ง (50% White) การเก็บค่าสีแบบ Alpha จะให้ผลเป็นสีเทาเพราะเก็บค่า Alpha ไว้ที่เดียว ในขณะที่การเก็บแบบ Premultiplied Alpha จะให้ค่าสีขาว 50% ได้เพราะเก็บในลักษณะแยกช่องกัน (0.5,0.5,0.5,0.5) จึงสามารถแสดงผลได้ถูกต้องกว่านั่นเอง &lt;p&gt;จากนั้นเราก็จะทำการวาดพื้นที่สี่เหลี่ยมตามความกว้าง ความสูงที่เรากำหนดลงไปแล้วระบายด้วย HatchBruch หรือลวดลายที่กำหนด ซึ่งในตัวอย่างนี้ก็คือ HatchBrush.SmallGrid หรือรูปสี่เหลี่ยมเล็กนั่นเอง หากเราเปลี่ยนคุณสมบัตินี้ก็จะทำให้เราได้ลวดลายของพื้นหลัง CAPTCHA ที่แตกต่างออกไป ตัวอย่างลวดลายที่น่าสนใจก็มี เช่น DottedGrid, SmallConfetti เป็นต้น เราสามารถดูตัวอย่างของ HatchStyle แบบต่างๆได้ที่ &lt;a href="http://www.drewnoakes.com/snippets/GdiColorChart/"&gt;http://www.drewnoakes.com/snippets/GdiColorChart/&lt;/a&gt; &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA2_1464A/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%202_2.png"&gt;&lt;img style="border-right:0px;border-top:0px;border-left:0px;border-bottom:0px;" height="467" alt="รูปที่ 2" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA2_1464A/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%202_thumb.png" width="705" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 2: ตัวอย่างแบบต่างๆของ HatchStyle&lt;/em&gt; &lt;p&gt;ขั้นต่อไปเราจะหาขนาดที่เหมาะสมของแบบตัวอักษรที่เราจะใช้ โดยการกำหนดให้ขนาดเริ่มแรกเท่ากับความสูงของสี่เหลี่ยมที่เราสร้างขึ้นเสียก่อน จากนั้นจึงวนลูปเพื่อเปรียบเทียบความกว้างของคำกับความกว้างของสี่เหลี่ยมด้วยฟังก์ชั่น MeasureString() ถ้ายังกว้างกว่าก็จะลดขนาดของตัวอักษรลงมาเรื่อยๆ เมื่อได้ขนาดที่เหมาะสมแล้วจึงจัดวางให้อยู่กลางภาพโดยกำหนดให้ StringFormat.Alignment และ StringFormat.LineAlignment เท่ากับ StringAlignment.Center &lt;p&gt;ต่อมาสร้าง Path ของฟ้อนท์ขึ้นตามข้อความที่ได้ จากนั้นบิดด้วยเมธอด Warp เพื่อให้ชุดตัวอักษรบิดไปจากแนวระนาบ จากนั้นจึงวาดตัวอักษรลงไปบนสี่เหลี่ยมที่เราสร้างขึ้นในตอนแรก ระบายลวดลายลงบนตัวอักษรเพื่อทำให้โปรแกรมสแปมเมอร์อ่านได้ยากขึ้น &lt;p&gt;อันที่จริงเมื่อถึงขั้นตอนนี้กราฟิกที่เราสร้างขึ้นก็เรียกว่าใช้งานได้แล้ว แต่หากเราจะเพิ่มลูกเล่นเข้าไปเพื่อให้อ่านยากขึ้นอีกก็ทำได้ เช่น การวาดจุดแบบสุ่มลงไปตามตำแหน่งต่างๆ เป็นต้น &lt;p&gt;ขั้นตอนสุดท้ายคือการ Dispose ออปเจ็คต์ที่ไม่ใช้แล้วเพื่อคืน Resource กลับสู่ระบบ และนำ Bitmap ที่ได้ไปเก็บไว้ใน Image Property ของคลาสเป็นอันจบกระบวนการ &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA2_1464A/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%203_2.png"&gt;&lt;img style="border-right:0px;border-top:0px;border-left:0px;border-bottom:0px;" height="67" alt="รูปที่ 3" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA2_1464A/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%203_thumb.png" width="244" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 3: ผลสำเร็จของรูปจากคลาส CaptchaImage &lt;/em&gt;&lt;/p&gt; &lt;h2&gt;บทสรุป&lt;/h2&gt; &lt;p&gt;CAPTCHA เป็นวิธีการง่ายๆที่จะใช้ป้องกันการสแปมข้อความที่ไม่พึงประสงค์ และยังได้รับการใช้งานอย่างเป็นสากล วิธีการง่ายๆที่ใช้ในการสร้าง CAPTCHA ด้วย ASP.Net คือการสร้างและเรียกใช้ไฟล์ .aspx ที่คืนค่ากลับมาเป็นรูปภาพ แล้วนำข้อความที่ได้จากรูปภาพนั้นมาเปรียบเทียบกับสิ่งที่ผู้ใช้ป้อนเข้ามา แล้วจึงอนุญาตให้ผู้ใช้ทำงานในขั้นตอนอื่นต่อไป อย่างไรก็ดียังมีวิธีการอื่นๆอีกมากที่ใช้ป้องกันการโพสต์ข้อความขยะเหล่านี้ เช่น การกรองคำ (Filtering) หรือ ReverseDOS ซึ่งเป็นการดักจับพฤติกรรมการโพสต์ข้อความที่ผิดปรกติเป็นต้น แม้จะมีระบบการป้องกันที่ดีสักเพียงใด สิ่งที่นักพัฒนาและเจ้าของเว็บไซต์ปรารถนาเป็นอย่างยิ่งนั่นก็คือ การไม่พบข้อความขยะในระบบเลย ถึงแม้จะไม่มีการป้องกันใดๆนั่นเอง&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=437" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/GDI_2B00_/default.aspx">GDI+</category><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/ASP.Net/default.aspx">ASP.Net</category></item><item><title>ป้องกันการโพสต์ข้อความไม่พึงประสงค์ด้วย CAPTCHA ตอนที่ 1</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/03/19/captcha-1.aspx</link><pubDate>Tue, 18 Mar 2008 17:08:04 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:349</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>10</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=349</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/03/19/captcha-1.aspx#comments</comments><description>&lt;p&gt;นักพัฒนาท่านใดที่เคยพัฒนาโปรแกรมประเภทฟอรัม คงไม่พ้นที่จะเคยปวดหัวเมื่อมีผู้ไม่ประสงค์ดีมาโพสต์ข้อความขายของ, Work at Home หรือข้อความไม่พึงประสงค์อื่นๆ ลำพังถ้าเป็นคนมาโพสต์เอง ก็คงจะพอตามลบไหว แต่บางครั้งการโพสต์เกิดจากสคริปต์ที่ผู้ไม่ประสงค์ดีสร้างขึ้น (หรือได้มาใช้งาน) ทำให้เกิดข้อความกระจายตามกระทู้เต็มไปหมด อย่ากระนั้นเลย เรามาป้องกันไม่ให้สคริปต์เหล่านั้นทำได้สมบูรณ์กันดีกว่า&lt;/p&gt;&lt;!--more--&gt; &lt;p&gt; &lt;p&gt;วิธีป้องกันการโพสต์ด้วยสคริปต์นั้นมีด้วยกันหลายวิธี แต่วิธีที่จะกล่าวถึงต่อไปนี้ เราเรียกกันว่า “Captcha” หรือ “แคปชา” ซึ่งย่อมาจาก “Completely Automated Public Turing test to tells Computers and Humans Apart” ซึ่งมหาวิทยาคาเนกี เมลลอน (Carnegie Mellon University) เป็นผู้ตั้งขึ้น เป็นวิธีการทดสอบในการแยกคนออกจากคอมพิวเตอร์ด้วยวิธีการที่คนเราจะเข้าใจได้ง่ายๆ โดยทั่วไปก็คือการสร้างรูปของชุดตัวอักษรหรือตัวเลขขึ้นมาสักรูปหนึ่ง เพื่อให้คนสามารถป้อนข้อมูลจากรูปที่เห็นกลับเข้ามาในระบบได้ ในขณะที่คอมพิวเตอร์จะไม่สามารถตีความหมายออก &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA1_150DC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201.png"&gt;&lt;img height="202" alt="รูปที่ 1" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/CAPTCHA1_150DC/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201_thumb.png" width="240" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 1: ตัวอย่างการใช้งาน Captcha&lt;/em&gt; &lt;p&gt;ในการสร้าง Captcha แบบง่ายๆขึ้นใช้งานนั้น เราอาจทำได้โดยการสร้างคลาสสำหรับใช้ Generate Captcha ที่ต้องการขึ้นมาใช้งาน เมื่อต้องการใช้ก็เรียกใช้งานโดยผ่านข้อความที่สุ่มขึ้นมาเข้าไปเพื่อให้ได้รูปภาพกลับคืนมาแสดง นอกจากนี้เราต้องเก็บข้อความที่สร้างขึ้นไว้ใน Session เพื่อนำมาเทียบกับสิ่งที่ผู้ใช้ป้อนในภายหลัง ถ้าหากป้อนข้อความกลับมาได้ถูกต้อง จึงอนุญาตให้ทำงานในลำดับถัดไป  &lt;h3&gt;&lt;b&gt;สร้าง &lt;/b&gt;&lt;b&gt;CaptchaImage คลาสกันก่อน&lt;/b&gt;&lt;/h3&gt; &lt;p&gt;หัวใจของการสร้าง Captcha ก็คือคลาส CaptchaImage ซึ่งจะทำการ Generate ภาพให้เราตามข้อความหรือชุดตัวเลขที่เราส่งเข้าไป คลาสนี้มีเมธอดที่ซับซ้อนเพียงเมธอดเดียวเท่านั้นคือ GenerateGraphic() ซึ่งทำหน้าที่ในการสร้างและปรับแต่งเอฟเฟคต่างๆให้ภาพข้อความ &lt;p&gt;&lt;em&gt;รายการที่ 1: โค้ดของ CaptchaImage คลาส&lt;/em&gt;&lt;/p&gt; &lt;table cellspacing="0" cellpadding="10" width="600" border="1"&gt;  &lt;tr&gt; &lt;td valign="top" width="598"&gt; &lt;p&gt;&amp;#39; อย่าลืม Imports เนมสเปซต่างๆที่จะใช้งาน  &lt;p&gt;Imports System.Drawing  &lt;p&gt;Imports System.Drawing.Drawing2D  &lt;p&gt;Imports System.Drawing.Graphics  &lt;p&gt;Imports System.Drawing.Imaging  &lt;p&gt;Imports System.Random &lt;p&gt;&amp;nbsp; &lt;p&gt;Public Class CaptchaImage  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private _width As Integer  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private _height As Integer  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private _familyName As FontFamily  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private _text As String  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; เนื่องจากต้องคืนค่าเป็นรูปภาพจึงต้องกำหนดให้มี Property Image  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; เพื่อให้สามารถนำไปเรียกใช้งานได้  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private _image As Bitmap  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Public ReadOnly Property Image() As Bitmap  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Get  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Return _image  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Get  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Property  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private Sub GenerateGraphic()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; สร้างภาพแบบ Bitmap ขึ้นมาใหม่เพื่อใช้เป็นภาพพื้นฐานของ Captcha ของเรา  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim bitmap As New Bitmap(_width, _height, PixelFormat.Format32bppPArgb)  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; สร้าง Object กราฟิกขึ้นมาเพื่อใช้วาดสิ่งต่างๆ เช่น ชุดอักษรหรือเอฟเฟคลงบนภาพ  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim g As Graphics = Graphics.FromImage(bitmap)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim rect As New Rectangle(0, 0, _width, _height)  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; เริ่มจากการลงสีพื้นหลัง เราใช้ HatchBrush ในการกำหนด Pattern ในการวาด เช่น &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; ในตัวอย่างคือ การวาด Pattern เป็นตารางโดยใช้สีเทาอ่อนบนพื้นสีขาว  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim hatchBrush As New HatchBrush(HatchStyle.SmallGrid, _ &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Color.LightGray, Color.White)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; g.FillRectangle(hatchBrush, rect)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; ต่อมาคือการสร้างฟอนท์และปรับขนาดของฟอนท์  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim size As SizeF  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim fontSize As Single = rect.Height + 1  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim font As Font  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Do  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; fontSize = fontSize - 1  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; font = New Font(_familyName, fontSize, FontStyle.Bold)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; size = g.MeasureString(_text, font)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Loop While (size.Width &amp;gt; rect.Width)  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; ปรับรูปแบบของการจัดวางชุดอักษร  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim format As New StringFormat()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; format.Alignment = StringAlignment.Center  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; format.LineAlignment = StringAlignment.Center  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; สร้างเส้นพาร์ทจากชุดอักษร และบิดเพื่อให้ชุดอักษรอ่านได้ยากขึ้น  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim path As New GraphicsPath()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; path.AddString(_text, font.FontFamily, CInt(font.Style), font.Size, rect, format)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim v As Single = 4.0F  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim r As New Random  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim points As PointF() = { _  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; New PointF(r.Next(rect.Width) / v, r.Next(rect.Height) / v), _  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; New PointF(rect.Width - r.Next(rect.Width) / v, r.Next(rect.Height) / v), _  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; New PointF(r.Next(rect.Width) / v, rect.Height - r.Next(rect.Height) / v), _  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; New PointF(rect.Width - r.Next(rect.Width) / v, rect.Height - r.Next _ &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (rect.Height) / v) _  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim matrix As New Matrix()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; matrix.Translate(0.0F, 0.0F)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; path.Warp(points, rect, matrix, WarpMode.Perspective, 0.0F)  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; จากนั้นจึงวาดชุดอักษรลงบนภาพตามพาร์ทที่ได้ โดยใช้ Pattern  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; ช่วยให้ชุดอักษรมี Texture ที่ทำให้อ่านยากขึ้น  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; hatchBrush = New HatchBrush(HatchStyle.LargeConfetti, Color.Gray, Color.DarkGray)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; g.FillPath(hatchBrush, path)  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; เติมจุดบนภาพแบบสุ่มโดยทั่ว เพื่อเพิ่มความยากในการอ่าน  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; อาจเปลี่ยนเป็นแบบอื่นหรือไม่ใช้ได้ตามต้องการ  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim m As Integer = Math.Max(rect.Width, rect.Height)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; For i As Integer = 0 To CInt(rect.Width * rect.Height / 30.0F)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim x As Integer = r.Next(rect.Width)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim y As Integer = r.Next(rect.Height)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim w As Integer = r.Next(m / 50)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim h As Integer = r.Next(m / 50)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; g.FillEllipse(hatchBrush, x, y, w, h)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Next  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; เมื่อทุกอย่างสำเร็จเรียบร้อย ให้ล้าง Object ที่ไม่ใช้ออกจากหน่วยความจำ  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; font.Dispose()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; hatchBrush.Dispose()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; g.Dispose()  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; หลังจากนั้นจึงนำค่ารูปภาพที่ได้ไปใส่ไว้ใน Property เพื่อรอการใช้งานต่อไป  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _image = bitmap  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Sub  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; กำหนดรูปแบบของฟอนท์ที่จะใช้งาน ถ้าไม่มีฟอนท์แบบนั้นในเครื่อง  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; ก็ให้ใช้ฟอนท์ปรกติของเครื่องแทน  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Private Sub SetFamilyName(ByVal familyName As String)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Try  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim font As New Font(familyName, 12.0F)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _familyName = font.FontFamily  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; font.Dispose()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Catch ex As Exception  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _familyName = FontFamily.GenericSansSerif  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Try  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Sub  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; Constructor ของคลาสทำหน้าที่กำหนดค่าต่างๆที่จำเป็น &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39; และสร้างกราฟิกไว้ใน Property Image เพื่อให้โปรแกรมอื่นสามารถเรียกใช้งานได้  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Sub New(ByVal text As String, ByVal width As Integer, _ &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ByVal height As Integer, ByVal familyName As String)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _text = text  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _width = width  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _height = height  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.SetFamilyName(familyName)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.GenerateGraphic()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Sub  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Public Sub Dispose()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; GC.SuppressFinalize(Me)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Me.Dispose(True)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Sub  &lt;p&gt;&amp;nbsp; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Protected Sub Dispose(ByVal disposing As Boolean)  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; If disposing Then  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _image.Dispose()  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; End If  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End Sub  &lt;p&gt;End Class&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;ในการทำงานของคลาสโดยสรุป เราจะเริ่มจากการส่งค่าตัวแปรที่จำเป็นเช่น ข้อความที่ต้องการจะให้ Generate, ความกว้าง, ความสูงของภาพ และชื่อของรูปแบบฟอนท์ที่ต้องการใช้ เข้าไปในคลาสเสียก่อน ต่อจากนั้นจึงทำการเรียกเมธอด GenerateGraphic() เพื่อสร้างภาพขึ้นมาก่อนจะนำภาพไปเก็บไว้ใน Property Image เพื่อให้โปรแกรมอื่นๆสามารถเรียกใช้งานได้ภายหลัง &lt;h3&gt;ในตอนต่อไป...&lt;/h3&gt; &lt;p&gt;เนื่องจากโค้ดที่ใช้ในการสร้าง CaptchaImage คลาสค่อนข้างจะยาว ดังนั้นจึงจะขออธิบายแต่เฉพาะในจุดสำคัญๆเช่นการเรียกใช้เมธอดต่างๆของ System.Drawing เพื่อปรับแต่งเอฟเฟค และแสดงวิธีการเรียกใช้งาน CaptchaImage คลาสใน ASP.Net ต่อในตอนหน้า อย่างไรก็ดีท่านผู้อ่านสามารถศึกษาวิธีการทำงานของคลาสอย่างคร่าวๆได้ก่อนจากหมายเหตุที่เขียนไว้เหนือโค้ดแต่ละส่วนครับ &lt;p&gt;&amp;nbsp; &lt;h4&gt;&lt;b&gt;แหล่งอ้างอิง&lt;/b&gt;&lt;b&gt;:&lt;/b&gt;&lt;/h4&gt; &lt;p&gt;&lt;a href="http://www.codeproject.com/aspnet/CaptchaImage.asp" target="_blank"&gt;Code Project - CAPTCHA Image by Brian Jar&lt;/a&gt; &lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Captcha" target="_blank"&gt;แนะนำ Captcha ใน Wikipedia&lt;/a&gt; &lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Turing_test" target="_blank"&gt;แนะนำ Turing Test&lt;/a&gt;&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=349" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/GDI_2B00_/default.aspx">GDI+</category><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/VB/default.aspx">VB</category><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/ASP.Net/default.aspx">ASP.Net</category></item><item><title>ทำความรู้จัก Microsoft Expression Design</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/03/12/microsoft-expression-design.aspx</link><pubDate>Wed, 12 Mar 2008 06:30:59 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:278</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>3</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=278</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/03/12/microsoft-expression-design.aspx#comments</comments><description>&lt;p&gt;หนึ่งในแอพพลิเคชั่นที่มาพร้อมกับ Microsoft Expression Studio ก็คือแอพพลิเคชั่นที่มีชื่อว่า Microsoft Expression Design ซึ่งทำหน้าที่ในการออกแบบและตกแต่งกราฟิกที่จำเป็นต้องใช้ในการสร้างแอพพลิเคชั่นด้วย XAML ต่อไป ไม่ว่าจะเป็นแอพพลิเคชั่นบนเดสก์ท๊อปเช่น Windows Presentation Foundation (WPF) หรือประเภท Rich Internet Application (RIA) เช่น Silverlight เป็นต้น&lt;/p&gt;&lt;!--more--&gt; &lt;h2&gt;ระบบการแสดงผลที่ใช้ในปัจจุบัน และ XAML&lt;/h2&gt; &lt;p&gt;เราแบ่งระบบการแสดงผลที่ใช้ในคอมพิวเตอร์ทุกวันนี้เป็นสองแบบ นั่นคือแบบ Raster Graphics และแบบ Vector Graphics กราฟิกแบบ Raster หรือที่รู้จักกันในอีกชื่อที่คุ้นเคยกว่าว่าบิตแมพ ซึ่งเป็นการสร้างภาพจากการนำจุดแต่ละจุดหรือที่เรียกว่าพิกเซลมาเรียงต่อกันในแนวตั้งและแนวนอนบนหน้าจอ พิกเซลแต่ละพิกเซลจะมีค่าสีและความสว่างที่ต่างกันออกไป เมื่อเรียงต่อกันก็จะกลายเป็นภาพที่มีความเหมือนจริงทั้งมิติและรูปทรง อย่างไรก็ดีหากเราบันทึกภาพหรือกราฟิกของเราไว้เป็นแบบบิตแมพ แล้วนำมาย่อหรือขยายภายหลัง จะทำให้เราสูญเสียความละเอียดของภาพไปไม่มากก็น้อย  &lt;p&gt;ทางเลือกของการบันทึกภาพในคอมพิวเตอร์อีกแบบหรือที่เรียกว่าแบบเวกเตอร์ จึงเข้ามาเป็นทางออกของนักออกแบบกราฟิกที่ต้องการเก็บภาพที่สร้างขึ้นในคอมพิวเตอร์ด้วยความละเอียดสูง และสามารถย่อหรือขยายขนาดได้โดยไม่สูญเสียรายละเอียดของภาพ ภาพแบบเวกเตอร์จะถูกเก็บไว้รูปของผลการคำนวณทางคณิตศาสตร์แบบต่างๆ จึงทำให้รายละเอียดของภาพแปรผันไปตามตัวแปรในสูตรคำนวณ ข้อเสียของการเก็บภาพแบบนี้ก็คือ หากภาพมีความละเอียดสูงมาก จะทำให้การประมวลผลช้าลง และรายละเอียดของภาพชนิดภาพถ่ายมักจะต่ำกว่าการเก็บภาพแบบบิตแมพ  &lt;p&gt;XAML มักเก็บภาพในลักษณะสองมิติ (2D) ไว้ในกราฟิกแบบเวกเตอร์ ดังนั้นอินเทอร์เฟสที่สร้างด้วย XAML จึงเป็นอินเทอร์เฟสแบบ Scalable กล่าวคือสามารถย่อขยายขนาดได้ตามขนาดของวินโดว์สหรือคอนเทนเนอร์ที่ห่อหุ้มมันอยู่โดยไม่สูญเสียความละเอียดไป จึงเหมาะกับการสร้างแอพพลิเคชั่นสมัยใหม่ที่ต้องแสดงผลในขนาดหน้าจอของอุปกรณ์ที่มีขนาดต่างๆกันไปนั่นเอง  &lt;h2&gt;Expression Design กับการพัฒนาแอพพลิเคชั่นยุคใหม่&lt;/h2&gt; &lt;p&gt;ไมโครซอฟท์พัฒนา Expression Design ขึ้นเพื่อให้เป็นเครื่องมือ ที่ใช้ในการออกแบบกราฟิกและภาพประกอบ ที่เหมาะกับอินเทอร์เฟสของแอพพลิเคชั่นทั้งในแบบสำหรับเว็บและเดสก์ท๊อป มันจะมีความสามารถในการสร้างกราฟิกชนิดเวกเตอร์ในระดับที่ละเอียดซับซ้อนได้อย่างรวดเร็ว นอกจากนี้ยังสามารถ Export ไฟล์ชิ้นงานไปยังแอพพลิเคชั่นในกลุ่มเดียวกันอื่นๆ เช่น Expression Blend เพื่อทำงานต่อได้ทั้งในรูปแบบของ XAML และไฟล์กราฟิกแบบบิตแมพ เราสามารถสร้างสรรค์กราฟิกให้สวยงามได้ด้วยเครื่องมือชนิดต่างๆที่เตรียมไว้ให้อย่างเพียบพร้อมสำหรับนักออกแบบ ไม่ว่าจะเป็น Pen Tools, Shape Tools หรือเครื่องมือในการจัดการอื่นตามมาตรฐาน เรายังสามารถเพิ่มสีสันของงานได้ด้วยการเพิ่ม Effect ให้กับส่วนต่างๆของภาพ การปรับแต่ง Workspace ก็ทำได้ง่าย ทำให้นักออกแบบที่คุ้นเคยกับการใช้แอพพลิเคชั่นสำหรับการออกแบบอื่น สามารถเรียนรู้การใช้งานได้โดยใช้เวลาไม่นาน  &lt;h2&gt;ส่วนประกอบต่างๆในพื้นที่ทำงาน&lt;/h2&gt; &lt;p&gt;พื้นที่ทำงานของนักออกแบบใน Expression Design ประกอบด้วย 5 ส่วนหลักๆ คือ Menu, Toolbox, Art Board, Property Inspector และ Action Bar คำสั่งต่างๆจะถูกเรียงไว้อย่างเป็นระเบียบในส่วนของ Menu เหมือนกับวินโดว์สทั่วไป เพื่อให้เรียนรู้ได้ง่าย เครื่องมือที่ใช้ในการวาดและปรับรูปร่างหน้าตาของวัตถุต่างๆจะอยู่ในส่วนของ Toolbox ส่วนรูปวัตถุที่เรากำลังทำงานจะอยู่ในพื้นที่ตรงกลางในส่วนที่เรียกว่า Art Board เมื่อเรากดเลือกเครื่องมือชิ้นใดชิ้นหนึ่ง คุณสมบัติของเครื่องมือที่เราเลือกจะแสดงขึ้นมาที่ส่วนของ Property Inspector คุณสมบัติเหล่านี้จะเปลี่ยนไปได้เรื่อยๆตามแต่เครื่องมือที่เลือกใช้นั่นเอง  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201_2.png"&gt;&lt;img style="border-top-width:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="420" alt="รูปที่ 1" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%201_thumb.png" width="576" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 1 พื้นที่ทำงานของ Expression Design&lt;/em&gt;  &lt;p&gt;โปรแกรมจัดการกราฟิกมาตรฐานทั่วไปมักจะอนุญาตให้เราสามารถแบ่งเก็บวัตถุต่างๆที่ประกอบเป็นชิ้นงานของเราแยกกันไว้ได้เป็นชั้นๆ หรือที่เรียกกันว่าเลเยอร์ สำหรับ Expression Design ก็เช่นกัน เราสามารถแยกเก็บส่วนต่างๆของชิ้นงานไว้ในเลเยอร์ที่ต่างกันได้ ในแต่ละเลเยอร์เรายังสามารถแยกเก็บวัตถุต่างๆไว้เป็นกลุ่มได้อีกด้วย การจัดเก็บจึงเป็นระเบียบและง่ายต่อการโยกย้ายวัตถุไปมาระหว่างเลเยอร์ต่างๆ อีกทั้งเรายังสามารถกำหนดให้มีการป้องกันการแก้ไขวัตถุต่างๆในชิ้นงานได้จากการล๊อควัตถุต่างๆที่อยู่ภายในแต่ละเลเยอร์ และหากเราไม่ต้องการใช้งานเลเยอร์นั้นๆ เราก็สามารถซ่อนมันไว้ได้อย่างง่ายดายอีกด้วย  &lt;p&gt;และถ้าหากเราเลือกที่วัตถุชิ้นใดชิ้นหนึ่งใน Art Board แถบพื้นที่ที่เรียกว่า Action Bar จะปรากฎขึ้นที่ส่วนล่างของหน้าจอ Action Bar ทำหน้าที่เป็นเหมือนทางลัดเพื่อช่วยให้เราจัดการตำแหน่งหรือรูปทรงของชิ้นงานได้ง่ายและแม่นยำขึ้น ยกตัวอย่างเช่น เมื่อเราเลือกวัตถุหลายๆชิ้นพร้อมกันในชิ้นงานของเรา เราอาจเลือกตำแหน่งของมันจากพิกัด X, Y บน Art Board, กำหนดความกว้างและความสูงของวัตถุ, องศาที่ใช้ในการหมุน (Rotation), ลักษณะการบิด (Skewing) หรือแม้แต่การจัดเรียงวัตถุหลายๆชิ้นด้วยวิธีต่างๆ เช่น Align, Distribute และ Stack เป็นต้น  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%202_2.png"&gt;&lt;img style="border-top-width:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="112" alt="รูปที่ 2" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%202_thumb.png" width="567" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 2 ตัวเลือกในการปรับแต่งวัตถุต่างๆใน Action Bar&lt;/em&gt;  &lt;p&gt;เมื่อทำความรู้จักส่วนประกอบต่างๆบนพื้นที่ทำงานของ Expression Design กันไปแล้ว ต่อไปเรามาทำความรู้จักกับเครื่องไม้เครื่องมือต่างๆและคุณสมบัติในการทำงานของมันกันบ้างดีกว่า  &lt;h2&gt;เครื่องมือต่างๆใน Expression Design&lt;/h2&gt; &lt;p&gt;เมื่อลองสำรวจเข้าไปใน Toolbox ของ Expression Design เราจะพบเครื่องมือที่ใช้ในการเลือก, สร้าง, แก้ไขพาธ (Path) การหมุนหรือย่อขยายวัตถุ รวมถึงเครื่องมือที่ใช้ในการนำไปยังจุดต่างๆของชิ้นงาน เช่น แว่นขยายและมือ เป็นต้น เครื่องมือเหล่านี้อาจเป็นเครื่องมือเดี่ยวๆ หรือรวมกันอยู่เป็นกลุ่มได้โดยมีสัญลักษณ์สามเหลี่ยมเล็กๆตรงมุมล่างซ้ายของไอคอนเป็นที่สังเกต หากเรากดเมาส์ค้างไว้ที่บนไอคอนที่มีสัญลักษณ์นี้ เครื่องมือต่างๆที่อยู่ภายในก็จะปรากฏออกมาให้เลือกใช้งาน  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%203_2.png"&gt;&lt;img style="border-top-width:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="414" alt="รูปที่ 3" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%203_thumb.png" width="565" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 3 เครื่องมือต่างๆใน Toolbox&lt;/em&gt;  &lt;p&gt;เครื่องมือกลุ่มแรกที่เราเห็นใน Toolbox คือเครื่องมือกลุ่มที่ใช้ไว้เลือกวัตถุต่างๆ ซึ่งประกอบด้วย Selection Tool, Group Selection Tool, Direct Selection Tool และ Lasso Selection Tool เราใช้เครื่องมือเหล่านี้ไว้เลือกวัตถุด้วยวิธีต่างๆกัน Selection Tool จะเป็นเครื่องมือพื้นฐานที่ใช้เลือกวัตถุทั้งชิ้น เพื่อหมุน, ย่อ, ขยาย หรือย้ายตำแหน่ง ในขณะที่ Group Selection Tool จะใช้เลือกวัตถุที่อยู่ในกลุ่ม ส่วน Direct Selection Tool และ Lasso Selection Tool จะใช้เลือกเฉพาะจุดต่างๆที่ประกอบเป็นพาธหรือรูปทรง  &lt;p&gt;ถัดลงมาเป็นเครื่องมือที่ใช้วาดพาธและรูปทรงต่างๆ เช่น Paintbrush Tool, Pen Tool, Polyline Tool, B-spline Tool, Rectangle Tool, Ellipse Tool, Polygon Tool และ Line Tool เมื่อใช้งานเครื่องมือเหล่านี้จะทำให้เกิดวัตถุที่เป็นพาธหรือรูปทรงต่างๆขึ้นในชิ้นงาน เราสามารถปรับแต่งตำแหน่งและรูปทรงของพาธที่เกิดขึ้นได้ด้วย Direct Selection Tool และ Convert Anchor Point Tool รวมถึงสามารถเพิ่มและลดจุดในพาธได้ด้วย Add Anchor Point Tool และ Delete Anchor Point Tool  &lt;p&gt;เราใช้เครื่องมือ Text Tool ในการใส่ตัวอักษรลงในภาพ เราอาจเลือกป้อนตัวอักษรลงไปบนพื้นที่ว่างหรือกำหนดให้ไหลไปกับพาธโดยการวางบนตำแหน่งต่างๆใน Art Board และยังสามารถปรับแต่งชนิดอักษรและขนาดที่ใช้ ชนิดของเส้นกรอบ สี หรือน้ำหนักของตัวอักษรได้จาก Property Inspector หากต้องการปรับแต่งรูปร่างของตัวอักษรให้ดูมีเอกลักษณ์ยิ่งขึ้น เราก็สามารถแปลงชุดตัวอักษรให้เป็นพาธได้ด้วยคำสั่ง เช่น Convert Object to Path อีกด้วย  &lt;p&gt;เครื่องมืออื่นๆที่ยังไม่ได้กล่าวถึงใน Toolbox เช่น Fill Transform, Gradient Transform, Scissors, Reverse Path, Start Point, Color Dropper, Attribute Dropper, Pan หรือ Zoom ล้วนมีส่วนช่วยให้การทำงานใน Expression Design สะดวกสบายขึ้น เราสามารถใช้ Gradient Transform ในการปรับทิศทางของการไล่เฉดสี และใช้ Attribute Dropper ในการกำหนดแอททริบิวต์ของวัตถุหนึ่งให้เหมือนกับอีกวัตถุหนึ่ง เครื่องมือที่ใช้บ่อย เช่น Pan และ Zoom หรือแม้แต่เครื่องมือพื้นฐานทั่วไปล้วนเรียกมาใช้งานได้ด้วย Keyboard Shortcuts ซึ่งจะแสดงให้เห็นเมื่อเอาเมาส์ไปวางบนไอคอนของเครื่องมือแต่ละชนิด  &lt;h2&gt;Bezier Path และ B-spline Path&lt;/h2&gt; &lt;p&gt;พาธนั้นเกิดจากการกำหนดจุดมากกว่าหนึ่งจุดขึ้นไปลงใน Art Board พาธที่เกิดขึ้นจากเครื่องมือวาดภาพใน Expression Design แบ่งออกได้เป็นสองชนิด คือ Bezier Path และ B-spline Path โดยทั่วไปเราใช้เครื่องมือ เช่น Direct Selection Tool ในการจัดการพาธทั้งสองแบบนี้ได้เช่นเดียวกัน Bezier Path นั้นเกิดจากการใช้เครื่องมือประเภท Pen Tool, Polyline Tool หรือ Shape Tool ในการสร้าง ส่วน B-spline Path จะเกิดจากการใช้ B-spline Tool เป็นตัวกำหนดจุด ส่วนมากนักออกแบบมักคุ้นเคยกับ Bezier Path มากกว่า B-spline Path เนื่องจากมีการใช้งานอย่างแพร่หลายในกราฟิกแอพพลิเคชั่นทั่วไปอยู่แล้ว ส่วน B-spline Path มักพบใช้งานในกราฟิกแอพพลิเคชั่นประเภทสามมิติมากกว่า อย่างไรก็ดีมีนักออกแบบหลายท่านที่พบว่าการลากเส้นด้วย B-spline Tool นั้นถนัดมือมากกว่า เนื่องจากควบคุมส่วนโค้งต่างๆได้ง่ายกว่า ซึ่งเรื่องนี้ก็คงต้องขึ้นอยู่กับความถนัดของแต่ละคน  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%204_2.png"&gt;&lt;img style="border-top-width:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="171" alt="รูปที่ 4" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%204_thumb.png" width="299" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 4 Bezier Path และ B-spline Path&lt;/em&gt;  &lt;p&gt;Bezier Path จะประกอบด้วยจุด (Points / Nodes) และแขนที่กางออกไปจากจุดที่เราเรียกกันว่า Handle เราใช้ Direct Selection Tool และ Convert Anchor Point Tool ในการควบคุมส่วนโค้งของพาธ และกำหนดการหักมุมของพาธ ณ จุดที่ต้องการ หากเราต้องการให้พาธโค้งไปทางใด ให้ลาก Handle ไปในทางตรงกันข้าม ยิ่งแกนของ Handle มีความยาวมาก เส้นพาธก็จะยิ่งโค้งมากขึ้นตามไปด้วย ในทางกลับกัน B-spline Path จะประกอบขึ้นจากจุดต่างๆซึ่งมักอยู่ในตำแหน่งยอดของส่วนโค้ง หากต้องการให้โค้งมากขึ้น ก็ต้องใช้ Direct Selection Tool ในการย้ายตำแหน่งของจุดเพื่อให้เกิดความโค้งหรือเป็นรูปทรงตามต้องการ  &lt;h2&gt;ภาพแบบ Bitmap และ Pixel Preview&lt;/h2&gt; &lt;p&gt;เนื่องจาก Expression Design เน้นการตกแต่งกราฟิกในแบบเวกเตอร์เป็นหลัก ดังนั้นก่อนที่จะนำชิ้นงานไปใช้ในแอพพลิเคชั่นอื่น อาจจะต้องทำการส่งออกโดยแปลงกราฟิกให้กลายเป็นแบบบิตแมพเสียก่อน ซึ่งบางครั้งอาจต้องทำการปรับแต่งตัวเลือกที่ใช้ในการส่งออกเพื่อให้เหมาะสมกับคุณภาพของกราฟิกที่ต้องการ ในการทำงานจริงหากต้องทำการส่งออกแล้วเปิดไฟล์กราฟิกที่ได้ขึ้นมาตรวจสอบทุกครั้งอาจเป็นการเสียเวลามาก Expression Design จึงมาพร้อมกับฟีเจอร์ที่เรียกว่า Pixel Preview ที่จะช่วยให้เราสามารถตรวจสอบการแสดงผลกราฟิกของเราในแบบบิตแมพได้ ก่อนที่เราจะทำการส่งออกจริง Pixel Preview จะเสนอตัวเลือกในการส่งออกก่อนจะแสดงภาพตัวอย่างที่มีคุณภาพตามแบบตัวเลือกที่เราเลือกใน Art Board แทนที่ภาพต้นฉบับของเรา ทั้งนี้เพื่อให้เราได้ตรวจสอบและปรับค่าต่างๆให้เหมาะสมก่อนการส่งออกจริงนั่นเอง เราสามารถปรับเปลี่ยนค่าเหล่านี้กี่ครั้งก็ได้ตามความต้องการ โดยที่จะไม่มีผลกระทบใดๆกับภาพต้นฉบับของเราเลย  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%205_2.png"&gt;&lt;img style="border-top-width:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="279" alt="รูปที่ 5" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%205_thumb.png" width="395" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 5 การใช้งาน Pixel Preview&lt;/em&gt;  &lt;h2&gt;ตกแต่งกราฟิกให้เก๋ด้วย Live Effects&lt;/h2&gt; &lt;p&gt;เราสามารถกำหนด Effects หรือที่เรียกอีกอย่างว่า Filters ให้กับกราฟิกประเภทต่างๆได้ โดยการเพิ่ม Effect ต่างๆเข้าไปในช่อง Effects ใน Property Inspector ของวัตถุที่เลือก Effects เหล่านี้มีทั้งที่เป็นประเภทที่ใช้งานบ่อย เช่น Drop Shadow และ Gaussian Blur เป็นต้น และประเภทที่เป็นเชิงศิลป์ เช่น Film Grain, Bas Relief, Graphic Pen และ Note Paper เป็นต้น  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%206_2.png"&gt;&lt;img style="border-top-width:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="77" alt="รูปที่ 6" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%206_thumb.png" width="124" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 6 ปุ่มที่เกิดจากการใช้งาน Live Effects&lt;/em&gt;  &lt;p&gt;Effects ต่างๆที่กำหนดให้กับวัตถุใน Expression Design จะไม่ทำการเปลี่ยนแปลงต้นแบบ ดังนั้นมันจึงเรียกว่า Live Effects ซึ่งหมายความว่านักออกแบบสามารถที่จะปรับค่าของ Effect, ซ่อนมัน หรือเอามันออกได้ตลอดเวลา นักออกแบบยังสามารถจัดเรียงลำดับของการกำหนดค่า Effects และสลับลำดับของมันเพื่อให้เกิดการแสดงผลที่แตกต่างออกไปได้เช่นกัน  &lt;h2&gt;ส่งชิ้นงานออกไปใช้ในรูปแบบ XAML&lt;/h2&gt; &lt;p&gt;หลังจากสร้างหรือปรับปรุงกราฟิกเสร็จสิ้นแล้ว เราอาจเลือกส่งออกชิ้นงานของเราเพื่อไปใช้งานต่อในแอพพลิเคชั่นอื่นๆได้หลายรูปแบบ หากต้องการนำไปใช้ประกอบในแอพพลิเคชั่นที่สร้างด้วย Windows Presentation Foundation (WPF) หรือ Silverlight ก็สามารถทำได้โดยการเลือก Export จากเมนู File แล้วจึงเลือก Xaml เป็นชนิดของไฟล์ที่จะส่งออก จากนั้นเราจะสามารถเลือกชนิดของการส่งออกได้ว่าจะเก็บภาพเป็นแบบ Canvas, Resource Dictionary หรือ Silverlight อีกทั้งเรายังสามารถเลือกได้ด้วยว่าจะแปลง Live Effects ต่างๆให้เป็นบิตแมพด้วยหรือไม่ก่อนจะเริ่มต้นการส่งออก ภาพที่ส่งออกจะถูกแปลงให้เป็นโค้ด Xaml ซึ่งจะแสดงตัวอย่างให้ดูหากเรากดที่แท็ป Xaml ข้างหน้าต่างพรีวิว จากนั้นเราจึงเอาไฟล์ Xaml ที่ได้ไปเปิดใช้งานในแอพพลิเคชั่นอื่นๆ เช่น Expression Blend ต่อไป  &lt;p&gt;&lt;a href="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%207_2.png"&gt;&lt;img style="border-top-width:0px;border-left-width:0px;border-bottom-width:0px;border-right-width:0px;" height="297" alt="รูปที่ 7" src="http://coredeveloper.net/blogs/meekob/WindowsLiveWriter/MicrosoftExpressionDesign_BDD1/%E0%B8%A3%E0%B8%B9%E0%B8%9B%E0%B8%97%E0%B8%B5%E0%B9%88%207_thumb.png" width="404" border="0" /&gt;&lt;/a&gt;  &lt;p&gt;&lt;em&gt;รูปที่ 7 หลากหลายตัวเลือกในการส่งออกแบบ Xaml&lt;/em&gt;  &lt;h2&gt;บทสรุป&lt;/h2&gt; &lt;p&gt;Expression Design เป็นแอพพลิเคชั่นในชุด Microsoft Expression Studio ที่ใช้ในการจัดการกราฟิกสำหรับการสร้างแอพพลิเคชั่นทั้งในแบบเวกเตอร์และบิตแมพ ด้วยเครื่องมือที่เพียบพร้อมทำให้มันเป็นแอพพลิเคชั่นที่ใช้งานง่าย และจะช่วยประหยัดเวลาให้นักออกแบบ เมื่อปรับแต่งกราฟิกต่างๆเสร็จก็สามารถส่งออกไปในรูปแบบของไฟล์ Xaml ซึ่งเป็นรูปแบบมาตรฐานที่จะนำไปใช้ได้ทันทีในแอพพลิเคชั่นแวดล้อมอื่นๆ เช่น Expression Blend เป็นต้น ด้วยความสามารถอันโดดเด่นนี้ทำให้ Expression Design เป็นแอพพลิเคชั่นหนึ่งที่น่าติดตั้งไว้ใช้งาน หากคุณเป็นนักออกแบบหรือนักพัฒนาคนหนึ่งซึ่งต้องการผลิตชิ้นงานกราฟิกดีๆสำหรับ WPF หรือ Silverlight แอพพลิเคชั่นของคุณ  &lt;p&gt;&amp;nbsp; &lt;h2&gt;แหล่งข้อมูลเพิ่มเติม&lt;/h2&gt; &lt;h2&gt;&lt;a href="http://www.microsoft.com/Expression/products/overview.aspx?key=design"&gt;http://www.microsoft.com/Expression/products/overview.aspx?key=design&lt;/a&gt; &lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Raster_graphics"&gt;http://en.wikipedia.org/wiki/Raster_graphics&lt;/a&gt; &lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Vector_graphics"&gt;http://en.wikipedia.org/wiki/Vector_graphics&lt;/a&gt;&lt;/h2&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=278" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/Graphic+Design/default.aspx">Graphic Design</category><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/Expression+Design/default.aspx">Expression Design</category></item><item><title>สวัสดีชาวโลก</title><link>http://coredeveloper.net/blogs/meekob/archive/2008/03/12/277.aspx</link><pubDate>Wed, 12 Mar 2008 06:07:43 GMT</pubDate><guid isPermaLink="false">86cc649a-bf28-4d34-a9ef-d75c61f34293:277</guid><dc:creator>Suwitcha Chandhorn</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://coredeveloper.net/blogs/meekob/rsscomments.aspx?PostID=277</wfw:commentRss><comments>http://coredeveloper.net/blogs/meekob/archive/2008/03/12/277.aspx#comments</comments><description>&lt;p&gt;ผมได้รับโอกาสจาก CoreDeveloper.net ให้มาร่วมแบ่งปันความรู้เกี่ยวกับการใช้งาน ASP.Net, VB.Net, Expression Studio และ Silverlight ในเว็บแห่งนี้ รู้สึกยินดีมากครับ คงจะมีโอกาสได้เสนอบทความดีๆให้ทุกท่านเป็นระยะๆนะครับ&lt;/p&gt;&lt;img src="http://coredeveloper.net/aggbug.aspx?PostID=277" width="1" height="1"&gt;</description><category domain="http://coredeveloper.net/blogs/meekob/archive/tags/MISC/default.aspx">MISC</category></item></channel></rss>