20010. 5.25.
石立 喬

Visual C++ 2010 Express の易しい使い方(6)

――― Graphicsを用いて、画面上にグラフを描く ―――

 ここでは、フォーム上にグラフを描く方法を紹介する。いままで、パソコンを用いて計算をし、計算結果を数値でフォーム上に出力する方法を述べてきたが、やはり、結果をグラフで画面に表示しないと実感が湧かない。すでに紹介した「易しい使い方」で説明した部分は省略されている場合があるので注意して欲しい。
 英語版Beta2では、コードエディタで、プログラムの定数(数字)が赤茶色で表示されていた(" "で囲まれた文字列と同様に)のに、それが無くなり、2008版と同様になったのは残念である。

概要
 電気技術者にとって興味のあるのは、各種の波形を画面上に表示することである。電気技術者が波形と言えば交流(正弦波)を思い浮かべるが、単なる交流波形では面白くないので、振幅変調波形、周波数変調波形を表示してみることにした。これにより変調の仕組みが理解でき、色々なパラメータを変化させて、どう変わるかを確認することができる。

変調に使用する波形の式

    

 ただし、AC は搬送波(carrier)の振幅(amplitude)、AS は信号波(signal)の振幅、ωC は搬送波の角周波数(angular frequency)、ωS は信号波の角周波数、MAM は振幅変調(amplitude modulation)の変調度(modulation factor)、MFM は周波数変調(frequency modulation)の変調度である。

グラフを描くためのGraphicsの使い方
 Graphicsクラスには、画面上に描画するのに便利なメソッドが多数ある。ここでは、グラフを描くのに必要なメソッドを紹介する。
◎グラフの背景
 グラフの背景などに色を付けるには、矩形範囲を塗りつぶす必要がある。これには、
   g->FillRectangle(Pens::Black,x,y,width,height);
などを使用する。塗りつぶす色は、Pens::Black などのペンで指定し、x,yは矩形範囲の左上の座標である。
◎直線を引く
 グラフの座標軸や目盛には、直線を引く必要がある。これには、
   g->DrawLine(Pens::Gray,x1,y1,x2,y1);
などを使用する。x1,y1は直線を引き始める座標、x2,y1は直線を引き終わる座標である。Y座標は同じなので、これは横(水平)線を引くことになる。
 線幅が2ピクセル以上の線を引く場合には、
   Pen^ pen1=gcnew Pen(Color::Blue,2);
などでペンを指定する。
◎点と点を結ぶ
 これも、結局は短い直線を引くことになるので、一般的には、二点をP1(x1,y1)とP2(x2,y2)として、
   g->DrawLine(Pens::Black,x1,y1,x2,y2);
でよい。
 しかし、グラフを描く場合のように、点を順次結んでゆく場合には、それらの点を、x[0]、y[0]、… …、x[i]、y[i]、… … として、
 1)線の開始点では、
   old_x=x[0];
   old_y=y[0];
を用いて位置の設定のみを行い、
 2)二回目からは、
   g->DrawLine(pen1,old_x,old_y,x[i],y[i]);
   old_x=x[i];
   old_y=y[i];
を繰り返して用いると良い。

フォームの設定
 すでに示した方法に従って、
1)タイトルバーに「振幅変調と周波数変調」のキャプションを入れる。
2)フォームの背景色を「Window」にする。
3)使用するフォントを「MS ゴシック,9.75pt」に設定する(MSゴシックで10ポイントを指定しても、なぜか9.75ポイントになる)。
4)プログラム実行時に開くウインドウのサイズを決める
 「Form1」の「プロパティ」ウインドウの「配置」欄で、「Size」の「300,300」を「528,444」に書き換える。このように設定すると、ユーザが使用できる領域(Client Size)は、横方向に左右4ピクセルずつ合計8ピクセル引き、縦方向に上30ピクセル、下4ピクセル引いた、「520,410」になる。横幅は、グラフの幅が500ピクセルなので、両側に10ピクセル余白を取ってある。縦方向は、グラフの高さが90ピクセルあり、それが4個で90 x 4=360ピクセルと、上下の余白を含めて10 x 5=50ピクセルから成るので、合計410ピクセルを確保する。なお、グラフのサイズや配置を決めて実際に実行させてみて、最終的に設定すると良い。

プログラムの構成
 プログラムは、Form1_Paint()メソッドに記述する。
 主な内容は以下の通りである。
1)4種類のグラフを表示するために、4個の黒い背景(矩形)と、その中心に4本の明るいグレイの基準線を引く。
2) 時間を t=0 から t=499 まで変化させ、搬送波、変調信号などをグラフで描画する。
3) 各グラフの右下に、説明の文字列を表示する。

グラフの描き方
 すでに説明した方法によれば、以下に示す「最も素直な方法」が一番分かり易いが、if文を500回も繰り返す無駄がある。t=0の場合のみをforループの外に出したのが次の「最初のt=0だけ別に計算する方法」であり、if文の繰り返しはなくなったが、同じoutの計算を外と中でやるのはスマートでない。最後の「t=0の時の値をold_yに直接入れる方法」は、t=0の時のoutの値を別途計算して、直接old_yに入れる方法で、outの計算が簡単な場合には好ましい(可読性はやや悪くなるが…)。

◎最も素直な方法

    //搬送波

    for(int t=1;t<500;t++){
        out=AMP_C*Math::Sin(OMEGA_C*t);
        y=Y0-(int)out;
        if(t==0)
            old_y=y;
        else{
            g->DrawLine(Pens::LightGreen,X0+(t-1),old_y,X0+t,y);
            old_y=y;
        }
    }

◎最初のt=0だけ別に計算する方法

    //搬送波
    int t=0;
    old=AMP_C*Math::Sin(OMEGA_C*t);
    old_y=Y0-(int)out;
    for( t=1;t<500;t++){
       out=AMP_C*Math::Sin(OMEGA_C*t);
       y=Y0-(int)out;
       g->DrawLine(Pens::LightGreen,X0+(t-1),old_y,X0+t,y);
       old_y=y;
    }

◎t=0の時の値をold_yに直接入れる方法

    //搬送波
    old_y=Y0;    //t=0でout=0が分かっているので
    for(int t=1;t<500;t++){
        out=AMP_C*Math::Sin(OMEGA_C*t);
        y=Y0-(int)out;
        g->DrawLine(Pens::LightGreen,X0+(t-1),old_y,X0+t,y);
        old_y=y+
    }


プログラムの実際

private: System::Void Form1_Paint(System::Object^ sender,
                       System::Windows::Forms::PaintEventArgs^ e) {

   Graphics^ g=e->Graphics;

   int X0=10;
   int Y0=55,Y1=Y0+100,Y2=Y0+200,Y3=Y0+300;

   double AMP_C=20.0;             //搬送波(被変調波)振幅
   double FREQ_C=1/12.5;            //搬送波周波数(時間500に40サイクル)
   double OMEGA_C=2*Math::PI*FREQ_C;     //搬送波角周波数
   double AMP_S=1.0;             //信号波(変調波)振幅
   double FREQ_S=1/250.0;           //信号波周波数(時間500に2サイクル)
   double OMEGA_S=2*Math::PI*FREQ_S;     //信号波角周波数
   double MOD_A=0.5;             //振幅変調変調度
   double MOD_F=0.025;            //周波数変調変調度

   double out;
   int y,old_y;

   for(int i=0;i<4;i++){
      //黒で背景を描く
      g->FillRectangle(Brushes::Black,Rectangle(X0,Y0-45+100*i,500,90));
      //明るいグレイで横線を引く
      g->DrawLine(Pens::LightGray,X0,Y0+100*i,X0+500,Y0+100*i);
   }

   //搬送波
   old_y=Y0;
   for(int t=1;t<500;t++){
      out=AMP_C*Math::Sin(OMEGA_C*t);
      y=Y0-(int)out;
      g->DrawLine(Pens::LightGreen,X0+(t-1),old_y,X0+t,y);
      old_y=y;
   }
   g->DrawString("搬送波(被変調波)",Font,Brushes::White,380,Y0+27);

   //変調信号
   old_y=Y1;
   for(int t=1;t<500;t++){
      out=20.0*AMP_S*Math::Sin(OMEGA_S*t);    //表示のために20倍に拡大
      y=Y1-(int)out;
      g->DrawLine(Pens::LightGreen,X0+(t-1),old_y,X0+t,y);
      old_y=y;
   }
   g->DrawString("信号波(変調波)",Font,Brushes::White,380,Y1+27);

   //振幅変調
   old_y=Y2;
   for(int t=1;t<500;t++){
      out=AMP_C*(1+MOD_A*AMP_S*Math::Sin(OMEGA_S*t))*Math::Sin(OMEGA_C*t);
      y=Y2-(int)out;
      g->DrawLine(Pens::Blue,X0+(t-1),old_y,X0+t,y);
      old_y=y;
   }
   g->DrawString("振幅変調波",Font,Brushes::White,380,Y2+27);

   //周波数変調
   old_y=Y3;
   for(int t=1;t<500;t++){
      out=AMP_C*Math::Sin(OMEGA_C*(1+MOD_F*AMP_S*Math::Sin(OMEGA_S*t))*t);
      y=Y3-(int)out;
      g->DrawLine(Pens::Red,X0+(t-1),old_y,X0+t,y);
      old_y=y;
   }
   g->DrawString("周波数変調波",Font,Brushes::White,380,Y3+27);

}

得られた画面
 得られた結果を下図に示す。


図 得られた結果



「Visual C++ の勉強部屋」(目次)へ