2011年1月26日

A Distributable (.exe) Silverlight OOB Application

Silverlight 的 Out of Browser 可以很方便讓你由網站,直接將程式安裝到桌面上;但是,若想把 Silverlight 包裝成可安裝的套裝軟體,反而變得比較麻煩。

下面這篇文章可以告訴你如何處理:

A Distributable (.exe) Silverlight OOB Application
How to distribute a Silverlight OOB Application?

2010年11月26日

ASP.NET Dynamic Image by WPF

[ Download Source Code ]

WPF 是微軟目前最主要的繪圖系統,應用在 ASP.NET Dynamic Image,可以產生許多特效與變化,例如文字特效、相片合成與特效等。



以下範例,我分別建立了 Web Site 與 WPF User Control 兩個專案,並在 Web Site 上,加入 WPF User Control 的參考。建立 Web Site 後,記得先加入下列四組 .dll 參考

  • PresentationCore
  • PresentationFramework
  • System.Xaml
  • WindowsBase

在 Web 上,新增 ImageBuilderHandler.ashx 來處理 Dynamic Image,並在網頁上加入 Image 元件,並設定 ImageUrl 連結 ImageBuilderHandler.ashx



而 ImageBuilderHander.ashx 的程式碼如下:
    public class ImageBuilderHandler : IHttpHandler
    {
        HttpContext m_context;
        byte[] m_imageBuffer = null;

        public void ProcessRequest(HttpContext context)
        {
            m_context = context;
            CreateImage();

            if (m_imageBuffer != null)
            {
                context.Response.ContentType = "image/jpeg";
                context.Response.BinaryWrite(m_imageBuffer);
            }
        }

        void CreateImage()
        {
            Thread worker = new Thread(new ThreadStart(this.CreateImageWorker));
            worker.SetApartmentState(ApartmentState.STA);
            worker.Name = "CreateImageWorker";
            worker.Start();
            worker.Join();
        }

        void CreateImageWorker()
        {
            UserControl control = new UserControl1() { TimeText = DateTime.Now.ToString("HH:mm:ss.f") };

            control.Arrange(new System.Windows.Rect(new System.Windows.Size(control.Width, control.Height)));
            control.UpdateLayout();

            RenderTargetBitmap bitmapRenderer = new RenderTargetBitmap((int)control.ActualWidth, (int)control.ActualHeight, 96, 96, PixelFormats.Default);
            bitmapRenderer.Render(control);

            JpegBitmapEncoder jpeg = new JpegBitmapEncoder();
            jpeg.Frames.Add(BitmapFrame.Create(bitmapRenderer));
            MemoryStream memoryStream = new MemoryStream();
            jpeg.Save(memoryStream);
            m_imageBuffer = memoryStream.ToArray();

            if (bitmapRenderer.Dispatcher.Thread.IsAlive)
                bitmapRenderer.Dispatcher.InvokeShutdown();
        }    

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

程式碼中,UserControl1 即是由 WPF 所產出的,可以利用 XAML 來整合美術設計與程式。
雖然 WPF 的優勢在於互動的介面上,但是用來取代 GDI+ 也是個不錯的選擇。

2010年11月10日

Using WPF to Create Images Dynamically (instead of GDI+)

如果想在 Asp.net 上,利用 WPF 來產生圖片,下列連結是個很好的參考資料。
雖然詢問的標題是如何利用 dll 來處理,但答覆提出了一個直接產出的方式。
Using WPF on a DLL to create Images Dynamically (instead of GDI+)

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);
            }
        }

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...