Using Thai language with Silverlight 2.0

เมื่อคราว Silverlight 1 ออกมา เราก็ใช้ภาษาไทยกับ Silverlight ได้แบบพอถูไถกันไป พอคราว Silverlight 2 Beta 2 ออกมา ทาง GreatFriends ก็ได้แสดงวิธีการใช้งานภาษาไทยใน Silverligh 2 Beta 2 ไปแล้ว คราวนี้เมื่อตัวจริงออกมาเราก็จะมาดูว่าภาษาไทยได้รับการปรับปรุงไปขนาดไหนแล้ว โดยในครั้งนี้เราจะใช้แค่เพียง Expression Blend 2 Service Pack 1 เท่านั้น ถ้ายังไม่มีสามารถดาวน์โหลดได้ที่ http://silverlight.net/GetStarted/

หลังจากเปิด Blend ขึ้นมาแล้วก็ลองสร้างโปรเจคใหม่ชื่อ ThaiLang

createproject

สร้าง TextBox และ TextBlock ขึ้นมาอย่างละอัน ใครขี้เกียจจะใช้ XAML ข้างล่าง

<Grid x:Name="LayoutRoot" Background="#FFBDBDBD">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <TextBlock Text="TextBlock" TextWrapping="Wrap" Margin="50,10,50,10" Grid.Row="0"/>
    <TextBox HorizontalAlignment="Stretch" Margin="50,10,50,10" Width="Auto" Grid.Row="1" Text="TextBox" TextWrapping="Wrap"/>
</Grid>
2-textblock-textbox

ทีนี้ถ้าเราลองใส่ข้อความภาษาไทยลงไป

๏ เป็นมนุษย์สุดประเสริฐเลิศคุณค่า
กว่าบรรดาฝูงสัตว์เดรัจฉาน
จงฟันฝ่าพัฒนาวิชาการ
อย่าล้างผลาญฤๅเข่นฆ่าบีฑาใคร
ไม่ถือโทษโกรธแช่งซัดฮึดฮัดด่า
หัดอภัยเหมือนกีฬาอัชฌาสัย
ปฏิบัติประพฤติกฎกําหนดใจ
พูดจาให้จ๊ะๆ จ๋าๆ น่าฟั งเอยฯ

พ่อปู่พี่ปี่ฎฐุญุกิสมิํธมฺมํเกื็อกป่า

๐๑๒๓๔๕๖๗๘๙ กกๆ ๚ะ๛
A QUICK BROWN FOX JUMPS OVER THE LAZY DOG.
a quick brown fox jumps over the lazy dog.
0123456789

เละครับเละ

thai-fail

เพราะว่าฟอนท์ตั้งต้นที่ติดมากับ Silverlight ที่ชื่อ Portable User Interface ไม่สามารถแสดงภาษาไทยได้ก็เลยกลายเป็นสี่เหลี่ยมๆไปหมด แต่ถ้าฟอนท์แสดงภาษาไทยได้ก็สบายเรา

งั้นก็ลองปรับให้ใช้ฟอนท์ที่รองรับภาษาไทยอย่าง Tahoma ดูซิ

font1

เป็นภาษาไทยแล้วล่ะ แต่ Blend จะขึ้นข้อความมาเตือนอะไรบางอย่าง

The font, Tahoma, isn’t a built-in silverlight font. You must embed this font for it to display on your Silverlight Application

เมื่อเราติ๊กคำว่า Embed ในแท็บ Properties แล้วข้อความเมื่อกี้ก็หายไป

font2

แต่เราจะได้สิ่งนี้มาแทน

file-explorer

ถ้าเราขยับๆ จะเห็น Silverlight 2.0 รองรับการตัดคำภาษาไทยแล้ว เย่

result

แต่ก็มาตกม้าตายตอนท้ายเพราะว่าพอลองใช้ TextBox ว่าพอกดปุ่มสระปุ๊บก็เละครับ Silverlight Object หายไปเลย ทางแก้ง่ายมาก ก็อย่าเพิ่งใช้ Silverlight กับงานที่ต้องกรอกข้อมูลหรือไม่ก็ใช้วิธีเดียวกับ Silverlight 1.0 คือเอา HTML input textbox มาใช้แทน แล้วก็อย่าลืมเข้าไปดูที่บักนี้ แล้วเพิ่มลง watchlist ด้วย เค้าจะได้รู้ว่าเรารอให้เค้าแก้บักนี้กันหลายคนเหมือนกัน

Blend to ASPX

ให้ตายสิไมโครซอฟท์ ออก Microsoft Expression Blend มาให้ใช้ แต่กลับไม่มีวิธีง่ายๆในการฝังไฟล์ Silverlight ลงใน ASP.NET !!!

เรื่องเริ่มต้นที่ว่าตอนหัดใช้ Silverlight ก็ต้องมานั่งแกะเองทีละขั้น ตอนนั้นก็คิดว่า ของใหม่เครื่องมือยังห่วยอยู่ ใช้งานยาก ก็น่าให้อภัย แต่ก็คิดว่า ซักวันมันต้องมีคนมาถามแน่ๆเลยว่าจะเอา XAML ไปใช้ยังไง

วันนี้เพื่อนมาถาม

นาย ม. says:
วีน ถามหน่อย สมมุติทำของใน blend เส็ดแล้ว จะใส่เข้าไปใน aspx ยังไง

โอ้…พระเจ้า ไมโครซอฟท์ทำสิ่งที่เราฝันไว้ได้จริงๆด้วย

อันนี้จะทำเฉพาะ 1.0 ไว้ละกันนะ

เริ่มต้นด้วยการเปิด Blend แล้วสร้าง Project เป็น Silverlight 1.0 Site อันนี้ขอใช้ Blend Preview 2.5 ละกัน

Project

จากนั้นสร้าง XAML ขึ้นมาให้เรียบร้อย

Blend2

แล้วลองเปิดเว็บไซต์ด้วย Visual Studio ซะ แล้วลองเปิดไฟล์ Default.html มาดู จะเห็นเป็นแบบนี้

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- saved from url=(0014)about:internet -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>SilverlightSite4</title>

    <script type="text/javascript" src="Silverlight.js"></script>
    <script type="text/javascript" src="Page.xaml.js"></script>
    <style type="text/css">
        #silverlightControlHost {
            height: 480px;
            width: 640px;
        }
        #errorLocation {
            font-size: small;
            color: Gray;
        }
    </style>
    <script type="text/javascript">
        function createSilverlight()
        {
            var scene = new SilverlightSite4.Page();
            Silverlight.createObjectEx({
                source: "Page.xaml",
                parentElement: document.getElementById("silverlightControlHost"),
                id: "SilverlightControl",
                properties: {
                    width: "100%",
                    height: "100%",
                    version: "1.0"
                },
                events: {
                    onLoad: Silverlight.createDelegate(scene, scene.handleLoad),
                    onError: function(sender, args) {
                        var errorDiv = document.getElementById("errorLocation");
                        if (errorDiv != null) {
                            var errorText = args.errorType + "- " + args.errorMessage;

                            if (args.ErrorType == "ParserError") {
                                errorText += "<br>File: " + args.xamlFile;
                                errorText += ", line " + args.lineNumber;
                                errorText += " character " + args.charPosition;
                            }
                            else if (args.ErrorType == "RuntimeError") {
                                errorText += "<br>line " + args.lineNumber;
                                errorText += " character " +  args.charPosition;
                            }
                            errorDiv.innerHTML = errorText;
                        }   
                    }
                }
            });
        }


        if (!window.Silverlight) 
            Silverlight = {};

        Silverlight.createDelegate = function(instance, method) {
            return function() {
                return method.apply(instance, arguments);
            }
        }
    </script>
</head>

<body>
    <div id="silverlightControlHost">
        <script type="text/javascript">
            createSilverlight();
        </script>
    </div>

    <!-- Runtime errors from Silverlight will be displayed here.
    This will contain debugging information and should be removed or hidden when debugging is completed -->
    <div id='errorLocation'></div>
</body>
</html>

เราจะลองมาชำแหละทีละส่วนดูว่า แต่ละอันมันคืออะไร

เริ่มจาก



SilverlightSite4

อันนี้ไม่มีอะไร แค่เป็นหัวของ HTML ไม่ต้องสนใจ

    <script type="text/javascript" src="Silverlight.js"></script>
    <script type="text/javascript" src="Page.xaml.js"></script>

สำหรับสองบรรทัดนี้ บรรทัดแรกจะ้เป็นการเพิ่ม reference ของ silverlight.js ที่เป็นไฟล์ที่เก็บโค้ดที่ใช้ตรวจสอบว่ามีการติดตั้ง Silverlight ไว้หรือยัง และประกาศเมธอดช่วยต่างๆเช่น Silverlight.createObjectEx เป็นต้น
สำหรับบรรทัดที่สองเป็นการโหลดไฟล์ Page.xaml.js ซึ่งเป็นไฟล์ที่เก็บสคริปต์ที่ใช้ควบคุม ตัว XAML ของเรา

    <style type="text/css">
        #silverlightControlHost {
            height: 480px;
            width: 640px;
        }
        #errorLocation {
            font-size: small;
            color: Gray;
        }
    </style>

ต่อมา ก็เป็น css ที่บอกว่า ให้ element ที่มี id silverlightControlHost มีขนาดกว้างยาวเป็น 640*480 พิกเซล

function createSilverlight()
{
    var scene = new SilverlightSite4.Page();
    Silverlight.createObjectEx({
        source: "Page.xaml",
        parentElement: document.getElementById("silverlightControlHost"),
        id: "SilverlightControl",
        properties: {
            width: "100%",
            height: "100%",
            version: "1.0"
        },
        events: {
            onLoad: Silverlight.createDelegate(scene, scene.handleLoad),
            onError: function(sender, args) {
                var errorDiv = document.getElementById("errorLocation");
                if (errorDiv != null) {
                    var errorText = args.errorType + "- " + args.errorMessage;

                    if (args.ErrorType == "ParserError") {
                        errorText += "<br>File: " + args.xamlFile;
                        errorText += ", line " + args.lineNumber;
                        errorText += " character " + args.charPosition;
                    }
                    else if (args.ErrorType == "RuntimeError") {
                        errorText += "<br>line " + args.lineNumber;
                        errorText += " character " +  args.charPosition;
                    }
                    errorDiv.innerHTML = errorText;
                }   
            }
        }
    });
}

ฟังก์ชันต่อมา ยาวนิดนึง คือฟังก์ชัน createSilverlight() ฟังก์ชันนี้แหละที่เป็นหัวใจสำคัญในการเรียกใช้ Silverlight โดยตอนแรกก็จะสร้าง object ของ Silverlight4.Page ขึ้นมา โดยคลาสนี้จะอยู่ในไฟล์ Page.Xaml.js

หลังจากนั้นก็จะโหลด xaml ด้วยคำสั่ง Silverlight.createObjectEx โดยพารามิเตอร์ที่ใส่เข้าไปจะเป็น hash ที่มีพารามิเตอร์ดังนี้

  • source อันนี้ระบุว่าจะให้โหลดไฟล์ขึ้นมา
  • parentElement ระบุเพื่อบอกว่าให้ใส่ลงไปใน element ไหน
  • id ตั้งชื่อว่าอะไร
  • properties อันนี้กำหนดความกว้าง สูง กำหนดเป็นเปอร์เซนต์ได้ หรือจะกำหนดเป็นพิกเซลก็ได้ จริงๆแล้วก็ตั้งตรงนี้เป็น 100 เปอร์เซนต์ แล้วก็ไปกำหนดตรง CSS ข้างบนเอาดีกว่า
  • events สำหรับอันนี้จะเป็นผูก event เข้ากับ method ต่างๆ ซึ่งในที่นี้ก็คือจะไปผูกกับ method handleLoad

        if (!window.Silverlight) 
            Silverlight = {};
    
        Silverlight.createDelegate = function(instance, method) {
            return function() {
                return method.apply(instance, arguments);
            }
        }
    

ตรงนี้เป็นการสร้าง class Silverlight ขึ้นมาพร้อม static method ชื่อ createDelegate เพื่อจะได้เอาใช้ต่อ

    <div id="silverlightControlHost">
        <script type="text/javascript">
            createSilverlight();
        </script>
    </div>

ตรงนี้จะเป็น element ที่จะเป็นที่วาง Silverlight ลงไป สังเกตว่าจะไปเรียกฟังก์ชัน createSilverlight ข้างบนด้วย

พอเรารู้แบบนี้ เราก็น่าจะพอเดาได้ว่าจะต้องเอาอะไรไปแปะตรงไหนแล้วใช่ไหม
หลักๆที่ต้องเอาไปก็คือ ส่วน script, css, แล้วก็ จุดวาง

ทีนี้ลองสร้าง aspx ขึ้นมาอันนึง

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    </div>
    </form>
</body>
</html>

แล้วก็ทำแบบเมื่้อกี้ สำคัญสุดคือ script เอาไปไว้ใน tag head แบบอันเก่าก็ได้ หรือถ้าใครใช้ ASP.NET Ajax ก็เอาไปไว้ใน ScriptManager ก็ได้ไม่ว่ากัน

<form id="form1" runat="server">
<div>
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/Silverlight.js" />
        <asp:ScriptReference Path="~/Page.xaml.js" />
    </Scripts>
    </asp:ScriptManager>
</div>
</form>

สำหรับใครที่ทำแบบมีหลายๆไฟล์สคริปต์ต้องใส่ให้ครบทุกไฟล์ จุดนี้จะต่างจาก xaml ที่เราเอาไปไว้ใน zip file แล้วใช้ downloader ดึงมาทีหลังได้

ทีนี้ก็เพิ่มฟังก์ชัน createSilverlight , css แล้วก็เพิ่มจุดวางเข้าไป

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>

<%@ Register Assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
    Namespace="System.Web.UI" TagPrefix="asp" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    <script type="text/javascript">
        function createSilverlight()
        {
            var scene = new SilverlightSite4.Page();
            Silverlight.createObjectEx({
                source: "Page.xaml",
                parentElement: document.getElementById("silverlightControlHost"),
                id: "SilverlightControl",
                properties: {
                    width: "100%",
                    height: "100%",
                    version: "1.0"
                },
                events: {
                    onLoad: Silverlight.createDelegate(scene, scene.handleLoad),
                    onError: function(sender, args) {
                        var errorDiv = document.getElementById("errorLocation");
                        if (errorDiv != null) {
                            var errorText = args.errorType + "- " + args.errorMessage;

                            if (args.ErrorType == "ParserError") {
                                errorText += "<br>File: " + args.xamlFile;
                                errorText += ", line " + args.lineNumber;
                                errorText += " character " + args.charPosition;
                            }
                            else if (args.ErrorType == "RuntimeError") {
                                errorText += "<br>line " + args.lineNumber;
                                errorText += " character " +  args.charPosition;
                            }
                            errorDiv.innerHTML = errorText;
                        }   
                    }
                }
            });
        }


        if (!window.Silverlight) 
            Silverlight = {};

        Silverlight.createDelegate = function(instance, method) {
            return function() {
                return method.apply(instance, arguments);
            }
        }
    </script>
    <style type="text/css">
        #silverlightControlHost {
            height: 480px;
            width: 640px;
        }
        #errorLocation {
            font-size: small;
            color: Gray;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/Silverlight.js" />
        <asp:ScriptReference Path="~/Page.xaml.js" />
    </Scripts>
    </asp:ScriptManager>
    <div id="silverlightControlHost">
        <script type="text/javascript">
            createSilverlight();
        </script>
    </div>    
    </form>
</body>
</html>

ลองเปิดดูก็จะได้ผลลัพธ์เหมือนรันใน HTML แล้วล่ะ

SilverlightResult

แต่แบบนี้มันไม่เท่ ถ้าใครใช้ ASP.NET 3.5 แล้วมันจะมี Silverlight Control เพิ่มเข้ามา แต่จากเท่าที่ลองเล่นดูพบว่า ถ้าใช้กับ Silverlight 1.0 มันใช้ยากยิ่งนัก แถมยังไปใช้เทคนิคของ ASP.NET AJAX ที่ไม่ค่อยน่าอภิรมย์ซักเท่าใดนัก แต่สำหรับคนที่ใช้ Silverlight 2.0 ดูท่าว่าจะทำให้ชีวิตสบายขึ้นอย่างมากมาย

ลองดูแล้วกัน ได้ผลยังไงวานบอก

ภาษาไทยกับ Silverlight

หลังจากที่ Silverlight 1.0 ออกมาแล้ว 11 เดือนก็ยังไม่ได้เคยลองเอามาใช้แบบจริงๆจังๆซักที จะมีลองเล่นๆก็คือตอน BarCamp ครั้งนู้น แต่ว่าช่วงนี้ได้งานมางานหนึ่งซึ่งต้องใช้ Silverlight 1.0 ในการพัฒนาเลยได้ลองใช้ของจริงเลย

ปัญหาอย่างหนึ่งที่พบตั้งแต่เมื่อ 11 เดือนก่อนแล้วไม่คิดว่ามันจะแก้ไขได้ง่ายแบบนี้คือเรื่องการแสดงผลภาษาไทย

จากปกติ ถ้าเราใส่โค้ด XAML สร้าง TextBlock แล้วกำหนดข้อความเป็นภาษาไทยดังด้านล่าง

<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="640" Height="480"
    Background="White"
    x:Name="Page">
    <TextBlock Canvas.Left="8" Canvas.Top="8" Text="สวัสดีครับ" TextWrapping="Wrap" FontFamily="Tahoma" x:Name="myTextBlock"/>    
</Canvas>

ถึงแม้ว่าเราจะกำหนดตรงฟ้อนท์ให้เป็น Tahoma และลองดูใน Expression Blend ได้ผลดูดีแล้วก็เถอะ

Thai support in Silverlight

แต่เมื่อนำไปแสดงผลในบราวเซอร์ กลับได้ผลลัพธ์ที่ไม่ค่อยน่าพอใจซักเท่าไหร่

Thai support in Silverlight

ตรงนี้จากความเข้าใจ อาจจะเป็นเพราะว่าตัว Silverlight เองรองรับฟ้อนท์อยู่ไม่กี่แบบ ถ้าเราไม่ได้ตั้งค่าของ Font Family ให้กับ TextBlock ค่าปกติก็จะเป็น “Portable User Interface” ซึ่งก็คือ “Lucida Sans Unicode, Lucida Grande” นั่นเอง

Font support in Silverlight
รูปจาก [Silverlight SDK](http://blogs.msdn.com/silverlight_sdk/archive/2007/05/02/downloading-fonts-using-the-downloader-object.aspx)

ถ้าลองเปิดเข้าไปดูที่นี่ จะเห็นว่าเราสามารถนำ Downloader ซึ่งเป็นตัวช่วยดาวน์โหลดใน Silverlight มาใช้ในการโหลดฟ้อนท์อื่นๆเพิ่มมาใช้กับ Silverlight ของเราได้

ก็เลยลองซะ


if (!window.SilverlightSite2)
    SilverlightSite2 = {};

SilverlightSite2.Page = function() 
{
}

SilverlightSite2.Page.prototype =
{
    handleLoad: function(control, userContext, rootElement) 
    {
        this.control = control;
        var downloader = control.createObject('Downloader');
        // Add Completed event.
        downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.downloadCompleted));

         // Initialize the Downloader request.       
        downloader.open("GET", "font.zip");
         // Execute the Downloader request.
        downloader.send();
        
    },  
    downloadCompleted : function(sender, eventArgs){
         var myTextBlock = sender.findName('myTextBlock');

         // Add the fonts to the typeface collection.
         myTextBlock.setFontSource(sender);

         // Specify the desired font.
         myTextBlock.fontFamily = "Tahoma";
    }
}

ในกรณีนี้จะเก็บฟ้อนท์ Tahoma.ttf ไว้ในไฟล์ชื่อ font.zip เมื่อ Silverlight โหลด font.zip เสร็จก็จะไปตั้งค่า FontSource และ FontFamily ให้กับ myTextBlock ลองรันดูก็ได้ผลลัพธ์ตามต้องการ แสดงผลเป็นภาษาไทยด้วยฟ้อนท์ Tahoma แล้วล่ะ

Thai support in Silverlight

แล้วถ้าเกิดมีจำนวน TextBlock เยอะมาก แล้วต้องมานั่งตั้งแบบนี้ทุกอันเหรอ

คำตอบคือ ถูกต้อง ต้องทำแบบนี้แหละ

แต่!!!

ในเมื่อ Silverlight เก็บข้อมูลของ Element ต่างๆในลักษณะของต้นไม้ เราก็แค่วิ่งไปตามกิ่งต่างๆเพื่อตั้งค่าได้นี่

if (!window.SilverlightSite2)
    SilverlightSite2 = {};

SilverlightSite2.Page = function() 
{
}

SilverlightSite2.Page.prototype =
{
    handleLoad: function(control, userContext, rootElement) 
    {
        this.control = control;
        this.root = rootElement;
        var downloader = control.createObject('Downloader');
        // Add Completed event.
        downloader.addEventListener("Completed", Silverlight.createDelegate(this, this.downloadCompleted));

         // Initialize the Downloader request.
         // Zip file contains: Britanic.ttf, Erasbd.ttf, Showg.ttf
        downloader.open("GET", "font.zip"    );
         // Execute the Downloader request.
        downloader.send();

    },  
    downloadCompleted : function(sender, eventArgs){
        this.setupFont(sender, this.root);
    },
    setupFont : function (sender, element){
        // Check element type to detect child node
        if (element.toString() != "Canvas")
            return;
        var children = element.children;
        for(var i = 0; i < children.count; i++)
        {
            var child = children.getItem(i);
            this.setupFont(sender, child);
            setFont(child, sender);
        }       
    }
}


function setFont(textElement, fontSource)
{
  if (textElement.toString() != "TextBlock")
    return;
  textElement.setFontSource(fontSource);
}

ตรงการตรวจสอบ Type ตรงนี้มีลูกเล่นนิดนึงคือใช้วิธีแปลงเป็นสตริงออกมาก่อนแล้วค่อยดูว่าเป็น Canvas หรือไม่ เหตุผลที่ตรวจสอบว่าเป็น Canvas รึเปล่าเพราะใน Silverlight 1.0 มีคอนโทรลประเภท Container ตัวเดียวคือ Canvas ซึ่งหกมีหลายตัวก็คือดูจากคุณสมบัติ Children ได้เช่นกัน แต่ปัญหาก็คือใน IE ไม่มี เมธอด hasProperty เลยต้องใช้วิธีนี้แทน

โค้ดนี้จะทำการวิ่งไล่จาก rootElement ซึ่งเป็นรากของต้นไม้ ไปตามกิ่งต่างๆและเมื่อเจอปมที่เป็น TextBlock ก็จะทำการตั้งค่า FontSource เป็น downloader ที่เรากำหนด ซึ่งเราจะกำหนดฟ้อนท์ที่เราอยากใช้เอาไว้ใน XAML แล้ว เมื่อนำไปแสดงผล ก็จะได้ตัวหนังสือตรงกับที่ตั้งเอาไว้ใน XAML ทันที

ลองเอามาใช้ร่วมกับฟ้อนท์อื่นดูบ้างอย่างฟ้อนท์ 2005_iannnnnTKO ดู โดยการใส่ฟ้อนท์นี้เพิ่มลงไปใน font.zip แล้วใช้โค้ด XAML เป็น

font set
    <Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="640" Height="480"
    Background="White"
    x:Name="Page">
    <TextBlock Canvas.Left="8" Canvas.Top="8" Text="สวัสดีครับ" TextWrapping="Wrap" FontFamily="Tahoma" x:Name="myTextBlock"/>
    <TextBlock x:Name="myTextBlock_Copy" Canvas.Left="8" Canvas.Top="19" FontFamily="Tahoma" Text="สวัสดีครับ" TextWrapping="Wrap"/>
    <TextBlock x:Name="myTextBlock_Copy1" Canvas.Left="134.52" Canvas.Top="33" FontFamily="Tahoma" Text="สวัสดีครับ" TextWrapping="Wrap"/>
    <TextBlock x:Name="myTextBlock_Copy2" Canvas.Left="8" Canvas.Top="33" FontFamily="Tahoma" Text="สวัสดีครับ" TextWrapping="Wrap"/>
    <TextBlock x:Name="myTextBlock_Copy3" Canvas.Left="72" Canvas.Top="8" FontFamily="Tahoma" Text="สวัสดีครับ" TextWrapping="Wrap"/>
    <Canvas Width="151.48" Height="93.594" Canvas.Left="8" Canvas.Top="54.703" Background="#BDD8D8D8">
        <TextBlock x:Name="myTextBlock_Copy4" FontFamily="2005_iannnnnTKO" Text="สวัสดีครับ" TextWrapping="Wrap" FontSize="24"/>
        <TextBlock x:Name="myTextBlock_Copy5" FontFamily="2005_iannnnnTKO" Text="สวัสดีครับ" TextWrapping="Wrap" Canvas.Top="75.891" FontSize="24"/>
        <TextBlock x:Name="myTextBlock_Copy6" FontFamily="2005_iannnnnTKO" Text="สวัสดีครับ" TextWrapping="Wrap" Canvas.Top="4" Canvas.Left="84.96" FontSize="24"/>
        <TextBlock x:Name="myTextBlock_Copy7" FontFamily="2005_iannnnnTKO" Text="สวัสดีครับ" TextWrapping="Wrap" Canvas.Top="67.891" Canvas.Left="84.96" FontSize="24"/>
        <TextBlock x:Name="myTextBlock_Copy8" FontFamily="2005_iannnnnTKO" Text="สวัสดีครับ" TextWrapping="Wrap" Canvas.Top="43.406" FontSize="24"/>
        <TextBlock x:Name="myTextBlock_Copy9" FontFamily="2005_iannnnnTKO" Text="สวัสดีครับ" TextWrapping="Wrap" Canvas.Top="21.703" Canvas.Left="52" FontSize="24"/>
    </Canvas>
</Canvas>
Thai support in Silverlight

เย่ ใช้ภาษาไทยได้แล้ว
ปล. ยังไม่ได้ลองกับ Silverlight 2.0
ปล.2 Silverlight ยังตัดคำไม่ค่อยดี รวมไปถึงการขึ้นบรรทัดใหม่ยังยุ่งยากด้วย
ปล.3 Flash รองรับเรื่องฟ้อนท์ได้ดีกว่าเพราะฝังลงไปในไฟล์ swf เลย

Silverlight 1.0 and Expression Blend 2.5 March Preview compatibility

Recently, I have involved in a small silverlight project. As the rest of team has no experience with Silverlight but have played with WPF for a while, I suggest them to use shiny new Expression 2.5 with Silverlight 2.0 runtime at first. Unfortunately, using beta software is unbearable by the project owner aka client. Therefore we fall backed to the released Silverlight 1.0 runtime.

The development was progressed smoothly and all bugs we had found was fixed. With the help of Expression 2.5, the development was easier than earlier version. However, when the user acceptance testing had begun, a very serious bug was introduced in some user machines. No one in development team had seen this bug before . The only error message I had at that time was

ParserError- Unknown attribute Ignorable on element Canvas.
File: LoadingPage.xaml, line 8 character 3

This bug is obviously related to XAML parsing. So I fired up notepad and went to the problem line. Then I realized the attribute was added by Expression Blend 2.5. But I didn’t sure enough to conclude this was the cause as we, the development team, could run it perfectly both in development machines and my computer at home.

In order to reproduce the bug, I decided to uninstall my Silverlight runtime from the computer and found out the root of problem. Silverlight 1.0 and 1.1 runtime do not handle XAML file as a normal XML file. They parse XAML file as XAML file. If you introduce any new namespace to the file, parser will struck and you will got parser error. Though you can use Expression Blend to create and design Silverlight 1.0, the XAML code still cannot be utilized by Silverlight 1.0 runtime. Furthermore, if I correct understand, Silverlight 2.0 javascript API has been changed and Rectangle has the actualWidth attribute as TextBlock element. Because my javascript also caused an error after I fixed the XAML namespace bug.

Since this new namespace was introduced by Expression Blend 2.5 Preview, this can be concluded that Expression Blend 2.5 Preview is not fully backward compatible with Silverlight 1.0. Although in the system requirement page states Silverlight 2.0 beta 1 is required, I think it is for development machine and should not require the 2.0 runtime to be installed in the client box. As this version is just preview, I don’t think this bug is Microsoft fault. I blog this for everyone who wanna try the software to be concern.

PS. This issue has been reported to Microsoft.