2010年10月20日

Design-Time Attributes for Components and Behaviors

使用 Expression Blend 設計 Silverlight 介面時,若希望設計人員能更清楚 Property 的含意,可以透過 Design-Time Attributes 來協助。

以下列出可能會使用到的項目:


可以在 class 上加入 Description, 來說明物件的功用 (上圖)
    [Description("可點選物件 - 可以設定為正確或錯誤點選")]
    public class ClickItemProcess : QuestionGroupBase<panel>
    {
       //..
    }


Description 也可以使用在 property 上, 並能以 Category 來分類 (上圖)
        [Category("遊戲功能"), Description("若此物件為錯誤答案,點選後所顯示的動畫(Storyboard)")]
        public string WrongAnswerAnimation
        {
            get { return (string)GetValue(WrongAnswerAnimationProperty); }
            set { SetValue(WrongAnswerAnimationProperty, value); }
        }

如果 property 不希望出現在 Expression Blend 的面板上, 可以將 Browsable 設為 false
        Result m_Result = Result.NoAnswer;
         [Browsable(false)]
        public Result Result
        {
            get { return m_Result; }
            set { m_Result = value; }
        }


如果 properties 屬於 state, element 或 storyboard, 並希望直接出現下拉選單,則可以用 CustomPropertyValueEditorAttribute 來設定。CustomPropertyValueEditor.StateName, CustomPropertyValueEditor.Element, CustomPropertyValueEditor.Storyboard 分別對應這三種不同類型的物件。

        [CustomPropertyValueEditorAttribute(CustomPropertyValueEditor.Storyboard)] 
        public Storyboard RightAnswerAnimation
        {
            get { return (Storyboard)GetValue(RightAnswerAnimationProperty); }
            set { SetValue(RightAnswerAnimationProperty, value); }
        }

另外,對於 TriggerAction, 可以利用 DefaultTriggerAttribute 預設 event。以及利用 TypeConstraintAttribute 來限制 TargetedTriggerAction 與 EventTriggerBase 的 AssociatedObject 型態。

    [DefaultTrigger(typeof(UIElement), typeof(EventTrigger), "MouseLeftButtonDown")]
    [DefaultTrigger(typeof(ButtonBase), typeof(EventTrigger), "Click")]
    public class WaveAction : TriggerAction<UIElement> { }

[reference]
MSDN: Design-Time Attributes for Components
State, Element, and Storyboard Pickers
Silverlight Hover Behavior

2010年10月12日

Free Silverlight Hosting

在部落格上探討 Silverlight 的技術與應用,如果不能顯示範例執行的樣貌,是一件令人苦惱的事情。當然,可以花錢用虛擬主機解決,只是還沒有收到 Google AdSense 的支票,卻要先支出,也太吃虧了。

先前微軟提供了 Microsoft Silverlight Streaming by Windows Live 免費的 hosting 服務,2010 已由 Azure 取代,也有 Introductory Special 免費方案 (Limited Amount at No Charge),但是台灣地區卻遲遲未上線。想亂編一個美國的地址去註冊,因為要輸入信用卡號,覺得不妥而作罷。

好在發現可以利用 Google App Engine application 來解決,雖然要多繞一圈,但還算值得,而且第二個 ap 之後,也會方便許多。步驟如下:

  1. 建立 Python 開發環境,並安裝 Google App Engine 上傳檔案的工具
  2. 利用 Google App Engine 建立帳號並新增 Application
  3. 撰寫 app.yaml 來設定上傳的檔案,而 .py (以 Python為例) 可以是一個空檔案
  4. 利用 appcfg.py 上傳 app.yaml, silverlight.js, .xap 至 Server
  5. 在部落格的網頁中,加入呼叫 .xap 的語法

app.yaml 範例:

application: dysonliu
version: 1
runtime: python
api_version: 1

handlers:

- url: /Silverlight3D.xap
static_files: Silverlight3D.xap
upload: Silverlight3D.xap
mime_type: application/x-silverlight-app

- url: /Silverlight.js
static_files: Silverlight.js
upload: Silverlight.js

- url: .*
script: Silverlight3D.py


插入部落格 HTML 的語法,可以由 Silverlight 的專案抄過來,然後把 Silverlight.js 與 .xap 的位置改掉,並加入寬度與高度的設定。

[reference]
Google 應用服務引擎 - 入門指南
How to embed a silverlight control on a Google Sites webpage?

2010年9月28日

Jigsaw Puzzle by Silverlight

我打算使用 Silverlight 來寫拼圖遊戲,並以網友自己的照片,來做為圖案。為了要完成這項工作,需要將複雜的過程,分成幾個步驟,並找到對應的解決方法。
  1. 首先,我們可以想像將照片切分成大小相同的矩形。
  2. 接著將矩形的四個邊,轉換成拼圖所特有的曲線。
  3. 由於曲線的兩端固定在矩形的頂點上,利用亂數改變曲線的線條,便可以產生每個形狀都有些微差異的拼塊了。

執行上列的步驟,我們需要了解 Path 的語法,以及切割照片的方法。

Cubic Bezier Curve

利用 Cubic Beizer Curver 可以很容易設計出我們所要的曲線。上面的解說圖,說明 Cubic Bezier Curve 的語法,如果是直接寫在 xaml 上,結果如下:
<Path Data="Mx1,y1 Cx2,y2 x3,y3 x4,y4 C....", ..>

如果用程式來產生,程式碼如下:
            Path path = new Path();
            PathGeometry pathGeometry = new PathGeometry(); 

            path.Data = pathGeometry; 
            pathGeometry.Figures = new PathFigureCollection(); 

            PathFigure figur = new PathFigure(); 
            pathGeometry.Figures.Add(figur); 
            figur.StartPoint = new Point(x1, y1);

            BezierSegment seg1 = new BezierSegment();
            seg1.Point1 = new Point(x2, y2);
            seg1.Point2 = new Point(x3, y3);
            seg1.Point3 = new Point(x4, y4);
            figur.Segments.Add(seg1); 
            :            
            :            
            path.Stroke = new SolidColorBrush(Colors.Blue);
            path.StrokeThickness = 3.0;
            this.LayoutRoot.Children.Add(path);


Piece Sharp

接下來,可以利用 Expression Blend 設計一段邊線的模板 (PathNodes),並調整數值,使左右能夠對稱,並利用旋轉、鏡射,產出完整的拼塊圖案。
        
Point[] PathNodes = new Point[]
        {
            new Point( 33,8),  // C1-1
            new Point(69,9),   // C1-2
            new Point(77,-8),   // C1-3

            new Point( 85,-23),  // C2-1
            new Point(54,-39),   // C2-2 
            new Point(65,-58),   // C2-3

            new Point( 77,-76),          // C3-1
            new Point(200-77,-76),    // C3-2
            new Point(200-65,-58),   // C3-3

            new Point(200-54,-39),   // C4-1
            new Point(200-85,-23),   // C4-2
            new Point(200-77,-8),     // C4-3

            new Point(200-69,9),  // C5-1 
            new Point(200-33,8),  // C5-2
            new Point(200,0)        // C5-3
        };

        private enum EdgeOrientation
        {
            Horizontal = 0,
            Vertical = 1
        }

       private PathGeometry CreateJigsawPuzzlePathGrometry(Point startPoint, Size pieceSize, EdgeOrientation orientation)
        {            
            PathGeometry pathGeometry = new PathGeometry();
            pathGeometry.Figures = new PathFigureCollection();

            PathFigure figur = new PathFigure();
            figur.StartPoint = startPoint;
            pathGeometry.Figures.Add(figur);

            Point lastPoint = startPoint;

            for (int edge = 0; edge < 4; edge++)
            {
                TransformGroup group = new TransformGroup();
                double scaleX = (edge % 2 == 0) ? pieceSize.Width / 200 : pieceSize.Height / 200;
                double scaleY = (edge % 2 == 0) ? pieceSize.Height / 200 : pieceSize.Width / 200;
                scaleY = (edge % 2 == (int)orientation) ? -scaleY : scaleY;

                ScaleTransform st = new ScaleTransform() { ScaleX = scaleX, ScaleY = scaleY };
                group.Children.Add(st);

                TranslateTransform tt = new TranslateTransform() { X = lastPoint.X, Y = lastPoint.Y };
                group.Children.Add(tt);

                RotateTransform rt = new RotateTransform() { Angle = edge * 90, CenterX = lastPoint.X, CenterY = lastPoint.Y };
                group.Children.Add(rt);

                for (int i = 0; i < PathNodes.Length / 3; i++)
                {
                    BezierSegment seg = new BezierSegment();
                    seg.Point1 = group.Transform(PathNodes[i * 3]);
                    seg.Point2 = group.Transform(PathNodes[i * 3 + 1]);
                    seg.Point3 = group.Transform(PathNodes[i * 3 + 2]);

                    figur.Segments.Add(seg);
                    lastPoint = seg.Point3;
                }
            }
            return pathGeometry;
        }


CreateJigsawPuzzlePathGrometry 有三個參數:
startPoint - 我們將一張圖,以矩形劃分,每個矩形的左上角,距離圖片左上角的位置,即是每一片小拼塊開始進行切割的 startPoint
pieceSize - 指劃分圖片矩形大小
orientation - 由於鄰近的小拼塊,凹凸位置剛好相反,orientation 應交互變換



Clip Image

我們利用上列方法所產生的 PathGrometry 來進行圖片切割的工作,並讓切割後的圖案成為獨立的 element,並有一致的原始點位置,方便使用者移動與操作。
Silverlight 並沒有 sprite 的功能,ImageBrush.ImageSource 也沒辦法設定位移與範圍,所以我利用了 WriteableBitmap 來達成這項工作。

   
        private Canvas CreatePiece(BitmapImage bitmap, Point startPoint, Size pieceSize, EdgeOrientation orientation)
        {
            Image imageTemp = new Image();
            imageTemp.Source = bitmap;
            imageTemp.Clip = CreateJigsawPuzzlePathGrometry(startPoint, pieceSize, orientation);

            Size panelSize = new Size(pieceSize.Width * 2, pieceSize.Height * 2);
            WriteableBitmap wb = new WriteableBitmap((int)panelSize.Width, (int)panelSize.Height);
            TranslateTransform translate = new TranslateTransform() { X = -startPoint.X + pieceSize.Width / 2, Y = -startPoint.Y + pieceSize.Height / 2 };
            wb.Render(imageTemp, translate);
            wb.Invalidate();

            Image image = new Image();
            image.Source = wb;
            Canvas.SetLeft(image, -pieceSize.Width / 2);
            Canvas.SetTop(image, -pieceSize.Height / 2);

            Canvas canvas = new Canvas();
            canvas.Children.Add(image);

            Path path = new Path() { StrokeThickness = 2, Stroke = new SolidColorBrush(Colors.Black) };
            path.Data = CreateJigsawPuzzlePathGrometry(startPoint, pieceSize, orientation);
            Canvas.SetLeft(path, -startPoint.X);
            Canvas.SetTop(path, -startPoint.Y);
            canvas.Children.Add(path);
            
            return canvas;
        }
     


不過,後續的事情還很多,只是技術上困難的部分大概都搞定了。切割時,還要考慮最外圍邊緣必須改為直線,而操作介面則是另一項複雜的學問,但我就不再談下去了。

2010年9月27日

Enumerate HierarchicalDataSource

ASP.NET 提供了很好的 SiteMap 機制,不過想以二維的方式來呈現 SiteMap,例如下列兩組 menu,分別以章節及索引不同的方式來條列,便需要利用到巡覽 DataSource 的技術了。

Home
Chapter One
Topic 1-1
Topic 1-2
Topic 1-3
Chapter Two
Topic 2-1
Topic 3-2
Index
A
Topic 1-2
C
Topic 1-1
Topic 2-2
N
Topic 1-3
Topic 2-1


我幫 Web.sitmap 增加了 index 的屬性,使巡覽時,可以依照 index 的第一個字母來分類。


    
      
      
        
        
        
        
      

      
        
        
      

    



下列程式,即是巡覽 XmlDataSource 的程式片段。
        public IEnumerable〈IGrouping〈char, XmlElement〉〉 CreateGroupingList(IDataSource dataSource)
        {
            List〈XmlElement〉 elementList = new List〈XmlElement〉();

            IHierarchicalDataSource hSource = dataSource as IHierarchicalDataSource;
            if (hSource != null)
            {
                HierarchicalDataSourceView hView = hSource.GetHierarchicalView(String.Empty);
                if (hView != null)
                {
                    IHierarchicalEnumerable hEnumerable = hView.Select();
                    ReadRecursive(hEnumerable, elementList);
                }
            }

            return elementList.OrderBy(p => p.Attributes["index"].Value).GroupBy(p => p.Attributes["index"].Value[0]);
        }


        private void ReadRecursive(IHierarchicalEnumerable enumerable, List〈XmlElement〉 elementList)
        {
            foreach (IHierarchyData item in enumerable)
            {
                if (item.Item is XmlElement)
                {
                    XmlElement element = item.Item as XmlElement;
                    if (element.Attributes["title"] != null && element.Attributes["index"] != null &&
                        element.Attributes["index"].Value.Length > 0)
                        elementList.Add(item.Item as XmlElement);
                }

                if (item.HasChildren)
                    ReadRecursive(item.GetChildren(), elementList);
            }
        }

2010年9月26日

Best Practices for ASP.NET MVC

這是微軟 msdn 上的一篇文章,分別就 model, view, controller, routing, extensibility, testability, security, localization and globalization, performance, 建議應採取的設計方式。內容對於 mode, view, controller 的分工方式, 有很好的說明, 相信對於採用 MVC 的設計師, 會有很大的幫助。

以下,我簡單列出一些要點:

Model: 用來定義商業、資料、驗證與記錄使用者狀態 session 等邏輯,對於大型的系統,可以拆成不同的 assembly。

View: 不要把商業邏輯寫在 View 中,要有可讀性。可以利用 partial HTML view(.ascx)來建立階層結構。要利用 ViewData 方式讀取資料,以 server 端註解 <%-- --%>增加安全性,採用 HTML Helper 產生 form、checkbox、textbox..。

Controller: 針對 routing system、HTML resuest 來回應。使用 model binding 取代手動,以 Post/Redirect/Get 處理 submitting form。並要記得管理 HandleUnknownAction 與 HandleError等狀況。

[Reference]
Best Practices for ASP.NET MVC

2010年9月21日

Google AdSense 申請獲准

這個部落格才剛開始時,就馬上申請 AdSense,但是沒有通過。 記得回覆信件中提要,希望我能多增加一些內容,再來送件。 好長一段時間後,前兩天管理網站時,看到先前未獲准的訊息, 隨手再送出申請,沒想到過了! 只是以目前的流量,不知道十年後能不能領到第一張Google的支票。

2010年9月20日

Free Website Templates

這真的是造福大眾的網站,Free Website Templates 提供了免費的樣板,
包含圖檔、HTML 與 CSS,想設計網頁的人一定不能錯過。

Deploying Vue & .NET with Google OAuth on GCP Cloud Run

Deploying Vue & .NET with Google OAuth on GCP Cloud Run Deploying Vue & .NET with Google OAuth on GCP Cloud Run...