dragan10

The Silverlight Geek - コンセプトを理解することと・・・

2008-10-25 02:42:43

身につけることとの違いは、本の執筆を予定通りに終わらせることと・・・えーと・・・その・・・遅れることの違いといえるかも知れない。

これまで少なくとも1ダース以上の章をカスタムコントロールについて書いてきましたし、カスタムコントロールの書き方は100回以上も説明しました。もう眠りながらでも説明くらいになっているんじゃないかと、みなさんは思うかも知れません。この、Silverlightのカスタムコントロールに関する最初の一連のBlogのエントリでは、カスタムコントロールをつくるのが難しくない理由と、説明するのが難しい理由、少なくともうまくうまく説明するのが難しい理由をお伝えしようと思っています。

ASP.NETやWindows Formsや、その他多くのGUIプラットフォームにもカスタムコントロールがありましたが、それらは同じ発想に基づいていました。典型的には、選択肢が三つ用意されていました。

第一の選択肢

カスタムコントロールをつくる最初の方法は、既存のコントロールを二つ以上集めてきて新しい型にして、何か新しい(あるいは巨大な)ものを作り上げます。私の好きな例で言うと、ボタンをドロップダウンと組み合わせて、動作を変えることのできるボタンにすることです。

第二の選択肢

第二のアプローチは、既存のコントロールから導出して、小さな新しい機能を追加する方法です。私がいつも使う例はClickable buttonで、これは自分が何回クリックされたか知っているボタンです。

以下のコードは、Programming ASP.NETから取ったものですが、考え方は多くのGUIフレームワークで共通です。

 using System;
 using System.ComponentModel;
 using System.Web.UI;

 namespace CustomControls
 {
     [DefaultProperty("Text")]
     [ToolboxData("<{0}:CountedButton2 runat=server>")]
     public class CountedButton : System.Web.UI.WebControls.Button
     {
       private string displayString;

       public CountedButton(  )
       {
          displayString = "clicks";
          InitValues(  );
       }

       private void InitValues(  )
       {
          if (ViewState["Count"] == null)
             ViewState["Count"] = 0;
          this.Text = "Click me";
       }

       public int Count
       {
          get { return (int) ViewState["Count"]; }
          set { ViewState["Count"] = value; }
       }

       protected override void OnClick(EventArgs e)
       {
          ViewState["Count"] =  ((int)ViewState["Count"]) + 1;
          this.Text = ViewState["Count"] + " " + displayString;
          base.OnClick(e);
       }
     }
 } 

第三の選択肢

そして第三の、典型的にはもっとも作業量が必要になる方法は、ルートのコントロールオブジェクトから導出して、(典型的には)コントロールの描画と、ロジックのすべてを作り込む方法です。私が何度も繰り返し使っている例はカスタムクロックの表示画面で、日付が観覧車のように回ります。作者は数字と長針・短針と秒を示す位置を、基本的には「手作業で」描かなければなりません。

この種のコントロールには、以下にコードの一部を示すように(完全な、注釈付きのコードはProgramming WinFormsに掲載されています)、環境によっては非常に多くのコーディングが必要になります。

 public class ClockFaceCtrl : System.Windows.Forms.Control
 {
     public ClockFaceCtrl()
     {    }

     public void OnTimer(Object source, ElapsedEventArgs e)
     {
         Graphics g = this.CreateGraphics();
         SetScale(g);
         DrawFace(g);
         DrawTime(g,false);
         DrawDate(g);
         g.Dispose();
     }

     protected override void OnPaint ( PaintEventArgs e )
     {     }

     private void SetScale(Graphics g)
     {
         g.TranslateTransform(Width / 2, Height / 2);
         float inches = Math.Min(Width / g.DpiX, Height / g.DpiX);
         g.ScaleTransform(inches * g.DpiX / 2000, inches * g.DpiY / 2000);
     }

     private static float GetSin(float degAngle)
     { return (float) Math.Sin(Math.PI * degAngle / 180f);     }

     private static float GetCos(float degAngle)
     { return (float) Math.Cos(Math.PI * degAngle / 180f);     }

     private void DrawFace(Graphics g)
     {
         for (int i = 1; i <= numHours; i++)
         {
             SizeF stringSize = g.MeasureString(i.ToString(),font);
             x = GetCos(i*deg + 90) * FaceRadius;
             y = GetSin(i*deg + 90) * FaceRadius;
             if ( currentTime.Second  == i * 5)
                 g.DrawString(i.ToString(), font, greenBrush, -x, -y,format);
             else
                 g.DrawString(i.ToString(), font, brush, -x, -y,format);
         }
     }

     private void DrawTime(Graphics g, bool forceDraw)
     {
         rotation = GetSecondRotation();
         state = g.Save();
         g.RotateTransform(rotation);
         g.FillEllipse(blankBrush,-25,-secondLength,50,50);
         g.Restore(state);
     }

     private float GetHourRotation()
     { }
     private float GetMinuteRotation()
     { return( 360f * currentTime.Minute / 60f );  }

     private float GetSecondRotation()
     { return(360f * currentTime.Second / 60f);  }

     private class LtrDraw
     {    }

     private class StringDraw
     {
         public StringDraw(string s, ClockFaceCtrl theControl)
         {           }

         public void DrawString(Graphics g, Brush brush)
         {
             foreach (LtrDraw theLtr in theString)
             {
                 theLtr.DrawString(g,brush,theControl);
             }
             ClockFaceCtrl.offset += 1;
         }
     }

     private void DrawDate(Graphics g)
     {
         Brush brush = new SolidBrush(ForeColor);
         sdToday.DrawString(g,brush);
     }
 }

このGUIフレームワークは、どこが他のどんなGUIフレームワークとも異なっているのか?

テンプレート化が可能なカスタムコントロールをSilverlightで書くのも全く同じことなのですが、ただし全体としては全く異なる点があります。つまり、アーキテクチャからみたビルド/利用/意志決定は同じですし、導出を決定するのも同じですが、新しく用意された仕組みとして、契約(コントラクト)によるロジックとビジュアルの分離があるのです。この契約は、パーツ・ステートモデルによって強制され、Visual State Machineが生かします。

 

ここまできて、起きるのに寝返りを打とうとしましたが、今午前2:20だということに気がつきました。寝てる時間です。絶対。

 (原文はこちら

訳注:現在、Jesse LibertyさんのBlogとは異なる順番で訳出していっています。カスタムコントロールやVSMに関する情報は重要だと思いますので、すこし前のポストを訳しています。


※このエントリは ブロガーにより投稿されたものです。朝日インタラクティブ および ZDNet Japan編集部の見解・意向を示すものではありません。
  • 新着記事
  • 特集
  • ブログ
このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]