mikeo_410


Silverlightアプリケーションの例(2)


  左図をクリックするとSilverlightアプリケーションを実行します。下記で説明している手順で作成したものです。まずは動作を確認してください。 

「Silverlightアプリケーションの例(1)」は、文字列の表示に Glyphs を使用し、組み込みフォント odttf を使う例になっていました。
  今回は、文字列の表示に TextBlock を使用し、フォントファイルを指定しない方法です。

  正確に表示するためには、組み込みフォントを使う必要がありますが、多くの場合、一般のフォントで充分だと思います。
  フォントファイルを使わないことは、ブラウザでのHTMLの表示のように、ブラウザ側の環境依存になることで特に不都合はないと思います。プログラミング時には、管理項目が減る分、便利だと考えます。
  ブラウザ側の環境依存と書きましたが、Silverlightのランタイム依存で、「ポータブル ユーザーインターフェイス」フォントが使われると言うことのようです。これは、実際にフォントファイルが有るわけではなく、Silverlightのランタイムがブラウザ側の環境に応じて善きに計らってくれると言うことだと解釈しています。

1.ワードで表示画面を作る

  ワードを数式の記述とボタンの配置に利用します。
  ボタンになる図形と、グラフの表示に使う矩形などには、色を付けました。この色は、16進で切の良い値を指定して検索し易くします。長いXAML中から該当箇所を見つける目印にしようと言うもので、色そのものは使いません。
  Blendやデザイナで表示できるなら特にこの工夫はいりません。

  さらに、0次、1次、2次、n次の式を別々に書きます。ワードで空白のページを4ページ用意します。それぞれのページの先頭に1つずつ式を書きます。
  これは、どれか1つを選択して、最初のページの先頭に表示するのに使います。

  これを、XPS形式で保存します。特に指定をしなかったので、「ドキュメント」に「sinx.xps」と言うファイルが出来ました。

2.Xps2Xamlで変換


  XPSファイルをXAMLに変換します。
  「利用局面」には、「SILVERLIGHT」を選びます。

  「Glyphsを使用」をチェックを外します。

  この設定で、Glyphs は TextBlock に置き換えられます。

  フォントファイルは、出力されません。
  TextBlockには、Glyphsで指定されていたフォントファイルのフォントファミリ名を与えます。
  設定を確認したら、このウインドウの上にXPSファイルをドラッグ、ドロップして貼り付けます。
  出力フォルダを指定します。

この例では、ワードで画像を貼り付けていないので、画像はありません。sinx1P01.xaml、sinx1P02.xaml、sinx1P03.xaml、sinx1P04.xaml、sinx1P05.xaml.xaml の5つのXAMLファイルのみ作成されます。
  このファイルは、画像やフォントを参照していないので、ブラウザで直接開いて確認できます。

3.Visual Studio でプロジェクトを作る

  「ファイル」「新規作成」「プロジェクト」で「Silverlightアプリケーション」を作ります。

4.画面の作成

  自動的に作られる MainPage.xaml は、以下のようになっています。
  <Grid></Grid>間(6行目)に、sinx1P01.xaml の内容を貼り付けます。
  貼り付けるのは、sinx1P01.xamlの <Page>,</Page> を除いた <Canvas>...</Canvas>部分です。

  1. <UserControl x:Class="sla_xps_textblock2.MainPage"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  4.     Width="400" Height="300">
  5.     <Grid x:Name="LayoutRoot" Background="White">
  6.     </Grid>
  7. </UserControl>

  F5を押して実行し、表示を確認して置きます。

5.XAMLの修正

5.1.グラフを描く矩形に名前を付ける

  ワードで描いた矩形に、255,255,0 の塗りつぶしを指定を指定しておいたので、これを目印に、グラフ描画領域にする図形を探します。

  1. <Path Data="M 212.85,87.75 L 212.85,285 L 431.85,285 L 431.85,87.75 Z ">
  2.     <Path.Fill>
  3.         <SolidColorBrush Color="#FFFFFF00" />
  4.     </Path.Fill>
  5. </Path>

  名前を chart1 と付けました。
  塗りつぶしは不要なので削除しました。

  1. <Path Name="chart1" Data="M 212.85,87.75 L 212.85,285 L 431.85,285 L 431.85,87.75 Z ">
  2. </Path>

5.2.sin x ボタンを作る

  これも色を目印に探します。

  1. <Path Data="M 67.125,260.25 C 64.847,260.25 63,262.1 63,264.38 L 63,280.88 C 63,283.15 64.847,285 67.125,285 L 139.12,285 C 141.4,285 143.25,283.15 143.25,280.88 L 143.25,264.38 C 143.25,262.1 141.4,260.25 139.12,260.25 Z ">
  2.     <Path.Fill>
  3.         <SolidColorBrush Color="#FFFFFF20" />
  4.     </Path.Fill>
  5. </Path>

  Path の座標を生かして、前後をボタンの設定で挟みました。
  クリックされた時のイベントハンドラを指定します。(右ボタンメニューの「イベントハンドラへ移動」で、ビハインドコードにハンドラも作って置きます。)

  1. <Button Click="Sin_Button_Click">
  2.     <Button.Template>
  3.         <ControlTemplate>
  4.             <Path Data="M 67.125,260.25 C 64.847,260.25 63,262.1 63,264.38 L 63,280.88 C 63,283.15 64.847,285 67.125,285 L 139.12,285 C 141.4,285 143.25,283.15 143.25,280.88 L 143.25,264.38 C 143.25,262.1 141.4,260.25 139.12,260.25 Z ">
  5.                 <Path.Fill>
  6.                     <LinearGradientBrush x:Name="lgb1"  StartPoint="0.5 0" EndPoint="0.5 1">
  7.                         <LinearGradientBrush.GradientStops>
  8.                             <GradientStop Offset="0" Color="Gray"/>
  9.                             <GradientStop Offset="0.5" Color="AntiqueWhite"/>
  10.                             <GradientStop Offset="1" Color="Gray"/>
  11.                         </LinearGradientBrush.GradientStops>
  12.                     </LinearGradientBrush>
  13.                 </Path.Fill>
  14.                 <VisualStateManager.VisualStateGroups>
  15.                     <VisualStateGroup>
  16.                         <VisualState x:Name="Normal"/>
  17.                         <VisualState x:Name="MouseOver">
  18.                             <Storyboard>
  19.                                 <DoubleAnimation Duration="0" Storyboard.TargetName="lgb1"  
  20.                                      Storyboard.TargetProperty="Opacity" To="0.7"/>
  21.                             </Storyboard>
  22.                         </VisualState>
  23.                     </VisualStateGroup>
  24.                 </VisualStateManager.VisualStateGroups>
  25.             </Path>
  26.         </ControlTemplate>
  27.     </Button.Template>
  28. </Button>

5.3.スライダとTextBlockの追加

  次数を変えるためのスライダと、次数を表示する TextBlock を追加して置きます。
  名前は、それぞれ、slider_n、textBlock_n としました。
  MainPage.xaml の最後尾の部分です。

  1.             <Slider x:Name="slider_n" Height="30" Width="109" Canvas.Left="96" Canvas.Top="145"/>
  2.             <TextBlock x:Name="textBlock_n" Height="30" Width="54" Canvas.Left="35" Canvas.Top="154" Text="TextBlock" TextWrapping="Wrap"/>
  3.         </Canvas>
  4.     </Grid>
  5. </UserControl>

5.4.式の表示領域の作成

  ワードでデザインしたときの、上の黄色い矩形は、次数に応じた式を表示に使います。
  n=0,1,2,n の4つの式のうち1つを同じ場所に表示します。

  1. <Path Data="M 36.75,15 L 36.75,54 L 298.65,54 L 298.65,15 Z ">
  2.     <Path.Fill>
  3.         <SolidColorBrush Color="#FFFFFF80" />
  4.     </Path.Fill>
  5. </Path>

  まず、ワードで描いた矩形(Path)を Canvas に置き換えます。
  矩形の隅の座標を見て、位置と大きさを同じにします。

  1. <Canvas Name="f_x" Canvas.Left="36.75" Canvas.Top="15" Width="261.9" Height="50.15">
  2. </Canvas>

  このキャンバスに、sinx1P02.xaml、sinx1P03.xaml、sinx1P04.xaml、sinx1P05.xaml.xaml の内容を貼り付けます。
  各ファイルの先頭は下記のようになっています。
  <Page>、</Page>を除いて、<Canvas>...</Canvas>間を貼り付けます。
  このとき、<Canvas.RenderTransform>...</Canvas.RenderTransform> も、除いてください。

  1. <Page xml:lang="ja-JP" ShowsNavigationUI="False" xmlns="http://schemas.microsoft.com/xps/2005/06">
  2.   <!--Width=794 Height1123-->
  3.   <Canvas>
  4.     <Canvas.RenderTransform>
  5.       <MatrixTransform Matrix="1.333333333,0,0,1.333333333,0,0" />
  6.     </Canvas.RenderTransform>
  7.     <TextBlock Foreground="#FF000000" FontFamily="Century" Text=" " FontSize="10.560" Canvas.Left="0.000" Canvas.Top="2.667" />

   4つのXAMLファイルの分を順次貼り付けます。
  それぞれの先頭の<Canvas>には、名前を付けます。f0、f1、f2、fn としました。
   (f_xと名前を付けたキャンバスに、f0、f1、f2、fn を並列に子として追加します。)

  fn のキャンバス中にある、TextBlockの文字 n は、次数に応じて数字に変えて表示します。
  このために、3つある n を探して、名前を付けて置きます。n_0、n_1、n_2 としました。
  色も赤に換えて置きます。

  1. <Canvas Clip="M 0,0.72 L 261.89,0.72 L 261.89,38.28 L 0,38.28 Z ">
  2.     <TextBlock Foreground="#FF000000" FontFamily="Cambria Math" Text=")" FontSize="14.040" Canvas.Left="187.340" Canvas.Top="0.099" />
  3. </Canvas>
  4. <Canvas Clip="M 0,0.72 L 261.89,0.72 L 261.89,38.28 L 0,38.28 Z ">
  5.     <TextBlock Name="n_0" Foreground="Red" FontFamily="Cambria Math" Text="n" FontSize="9.960" Canvas.Left="193.220" Canvas.Top="-1.184" />
  6. </Canvas>

5.5.先頭のGridにLoadedイベントハンドラを追加

  先頭のGridにLoadedイベントハンドラを追加して、初期化の処理に使います。

5.6.先頭のCanvasに名前を付ける

  先頭の Canvas に rootCanvas と名前を付けました。

6.n次の計算

  展開式を計算するクラスをプロジェクトに追加しました。

  1. using System;
  2. using System.Collections.Generic;
  3. namespace sla_xps_textblock2
  4. {
  5.     public class TaylorExpansionSin
  6.     {
  7.        public TaylorExpansionSin(int n) 
  8.         { 
  9.             List<double> list = new List<double>(); 
  10.             double deno = 1; 
  11.             list.Add(deno); 
  12.             double d = 1; 
  13.             for (int i = 1; i <= n; i++) 
  14.             { 
  15.                 deno *= ++d; 
  16.                 deno *= ++d; 
  17.                 list.Add(deno); 
  18.             } 
  19.             denominator = list.ToArray(); 
  20.         } 
  21.   
  22.         readonly double[] denominator; 
  23.         readonly int[] sign = new int[] { 1, -1 }; 
  24.   
  25.         public double Sin(double x) 
  26.         { 
  27.             double result = 0; 
  28.             for (int i = 0; i < denominator.Length; i++) 
  29.             { 
  30.                 result += sign[i % 2] * Math.Pow(x, i * 2+1) / denominator[i]; 
  31.             } 
  32.             return result; 
  33.         } 
  34.     }
  35. }

7.MainPage.xaml.cs の修正

  MainPage.xaml.cs は、XAMLの修正作業で、イベントハンドラが追加されています。

  1. namespace sla_xps_textblock2
  2. {
  3.     public partial class MainPage : UserControl
  4.     {
  5.         public MainPage()
  6.         {
  7.             InitializeComponent();
  8.         }
  9.         private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
  10.         {
  11.         }
  12.         private void Sin_Button_Click(object sender, RoutedEventArgs e)
  13.         {
  14.         }
  15.     }
  16. }

7.1.グラフを描くキャンバスを作る

  下記は、先頭の Grid の Loaded イベント処理 LayoutRoot_Loaded() です。
  この前半で、キャンバス(canvas1)を作り、もっとも外側のキャンバス(rootCanvas)に追加しています。
  ワードでデザインした矩形に付けた名前 Chart1 から位置やサイズを取り、canvas1を作ります。

  1. Canvas canvas1; 
  2. private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
  3. {
  4.     canvas1 = new Canvas();
  5.     //キャンバスのサイズ 
  6.     canvas1.Width = chart1.Data.Bounds.Width;
  7.     canvas1.Height = chart1.Data.Bounds.Height;
  8.     //クリップ設定 
  9.     RectangleGeometry rg = new RectangleGeometry();
  10.     rg.Rect = new Rect(0, 0, canvas1.Width, canvas1.Height);
  11.     canvas1.Clip = rg;
  12.     //最上位のキャンバスへ追加し配置 
  13.     rootCanvas.Children.Add(canvas1);
  14.     Canvas.SetLeft(canvas1, chart1.Data.Bounds.Left);
  15.     Canvas.SetTop(canvas1, chart1.Data.Bounds.Top);
  16.     //軸の描画 
  17.     DrawLines(new Point[] { new Point(-5, 0), new Point(5, 0) }, Colors.Gray);
  18.     DrawLines(new Point[] { new Point(0, 5), new Point(0, -5) }, Colors.Gray);
  19.     //スライダーの設定
  20.     slider_n.Minimum = 0;
  21.     slider_n.Maximum = 9;
  22.     slider_n.ValueChanged += new RoutedPropertyChangedEventHandler<double>(slider_n_ValueChanged);
  23.     //最初の表示
  24.     slider_n.Value = 0;
  25.     nDegDraw(0);
  26. }

7.2.軸を描く

  LayoutRoot_Loaded() では、後述する線メソッドを使って軸を引いています。

7.3.スライダーを設定する

  LayoutRoot_Loaded() では、スライダーの値の範囲(0-9)と、値が変わった時のイベントハンドラを登録しています。
  イベントハンドラは、slider_n.ValueChanged += まで入力すると、Tabキーで補完してくれます。さらに、ハンドラのひな型も作ります。

7.4.スライダーのハンドラ

  1. void slider_n_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
  2. {
  3.     nDegDraw((int)Math.Round(e.NewValue));
  4. }

7.5.線を引くメソッドを追加

  1. //chart1の矩形に線を引く。中央が原点で、X、Yとも-5から5を描画する。 
  2. Polyline DrawLines(Point[] points, Color color)
  3. {
  4.     double offset_x = chart1.Data.Bounds.Width / 2;
  5.     double offset_y = chart1.Data.Bounds.Height / 2;
  6.     double r_x = chart1.Data.Bounds.Width / 10;
  7.     double r_y = -chart1.Data.Bounds.Height / 10;
  8.     Polyline pl = new Polyline();
  9.     pl.Stroke = new SolidColorBrush(color);
  10.     pl.StrokeThickness = 1;
  11.     foreach (Point p in points)
  12.     {
  13.         double x = p.X * r_x + offset_x;
  14.         double y = p.Y * r_y + offset_y;
  15.         pl.Points.Add(new Point(x, y));
  16.     }
  17.     canvas1.Children.Add(pl);
  18.     return pl;
  19. }

7.6.n次の線を引くメソッド

  1. Polyline[] polyline = new Polyline[10];
  2. void nDegDraw(int n)
  3. {
  4.     f0.Visibility = Visibility.Collapsed;
  5.     f1.Visibility = Visibility.Collapsed;
  6.     f2.Visibility = Visibility.Collapsed;
  7.     fn.Visibility = Visibility.Collapsed;
  8.     switch (n)
  9.     {
  10.         case 0:
  11.             f0.Visibility = Visibility.Visible;
  12.             break;
  13.         case 1:
  14.             f1.Visibility = Visibility.Visible;
  15.             break;
  16.         case 2:
  17.             f2.Visibility = Visibility.Visible;
  18.             break;
  19.         default:
  20.             fn.Visibility = Visibility.Visible;
  21.             n_0.Text = n_1.Text = n_2.Text = n.ToString();
  22.             break;
  23.     }
  24.     textBlock_n.Text = " n = " + n.ToString();
  25.     if (polyline[n] == null)
  26.     {
  27.         int count = 100;
  28.         double x = -5;
  29.         double dx = 10.0 / (count - 1);
  30.         Point[] points = new Point[count];
  31.         TaylorExpansionSin tes = new TaylorExpansionSin(n);
  32.         for (int i = 0; i < count; i++)
  33.         {
  34.             points[i] = new Point(x, tes.Sin(x));
  35.             x += dx;
  36.         }
  37.         polyline[n] = DrawLines(points, Colors.Green);
  38.     }
  39.     for (int i = 0; i < 10; i++)
  40.     {
  41.         if (polyline[i] != null)
  42.         {
  43.             polyline[i].Visibility = i == n ? Visibility.Visible : Visibility.Collapsed;
  44.         }
  45.     }
  46. }

7.7.sin x ボタンの処理

  1. Polyline sin_polyline = null;
  2. private void Sin_Button_Click(object sender, RoutedEventArgs e)
  3. {
  4.     if (sin_polyline == null)
  5.     {
  6.         int count = 100;
  7.         double x = -5;
  8.         double dx = 10.0 / (count - 1);
  9.         Point[] points = new Point[count];
  10.         for (int i = 0; i < count; i++)
  11.         {
  12.             points[i] = new Point(x, Math.Sin(x));
  13.             x += dx;
  14.         }
  15.         sin_polyline = DrawLines(points, Colors.Black);
  16.         sin_polyline.Visibility = Visibility.Collapsed;
  17.         sin_polyline.Opacity = 0.6;
  18.     }
  19.     sin_polyline.Visibility = sin_polyline.Visibility==Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
  20. }

8.表示の差異

  Glyphs を TextBlock に置き換えた影響を見ます。

  Glyphs を使用:

  TextBlock に置き換え:

  ワードで作成した数式は、Cambria Math フォントが使われているようです。
  (「ポータブル ユーザーインターフェイス」がSilverlightのフォントのようですが、ファミリ指定は有効なようです。)

  シグマ記号は特別です。これは、変換プログラムが細工をした結果です。
  Glyphsの場合は、大きさの異なるフォントを使い分けています。TextBlockでは、普通に文字コードが示すフォントしか使えないので、それを拡大しました。結果として、太くなっています。
  変換プログラムでは、以下のような処理をしています。

  1. XPS上で、文字コード(unicode)を持たない Glyphs が出現します。これは、文字コードではなく、フォントファイルのインデクスで文字の字形を示しています。
    括弧やシグマなどが該当します。
  2. このインデクスで文字の字形を示す目的は、同じポイント数の設定で大きさの異なる字形を表示することにあるようです。
    本来のポイント数より大きなフォントを指すことで、表示と処理の行数を崩さないようにしているものと想像します
  3. TextBlock を使う場合、文字には文字コード(unicode)が必要です。
    Cambria Math フォントの インデクス->unicode テーブルを用意しました。
  4. インデクスは、文字コードだけでなく、大きさも表しています。指定されるポイント数と実際のサイズの率もテーブルに入れました。
  5. 文字のサイズは、縦横独立にはできません。実際のCambria Math フォントの該当部分は、主に高さを変えているわけですが、これは諦めました。
  6. 基準線を中心に移動して表示を前後に合わせました。


mikeo_410@hotmail.com