あたも技術ブログ

セットジャパンコーポレーションの社員が運営しています。

【C#】ユーザコントロールでテロップ表示

 今回もWindowsフォーム アプリケーション関連のメモです。
Windowsフォームアプリの要件の場合で、たまに出てくる文字列のテロップ表示を行うユーザコントロールを今回は作成したいと思います。

namespace TelopSample
{
  partial class TelopLabel
  {
    /// <summary> 
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region コンポーネント デザイナーで生成されたコード

    /// <summary> 
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を 
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.pnlTelop = new System.Windows.Forms.Panel();
      this.SuspendLayout();
      // 
      // pnlTelop
      // 
      this.pnlTelop.Dock = System.Windows.Forms.DockStyle.Fill;
      this.pnlTelop.Location = new System.Drawing.Point(0, 0);
      this.pnlTelop.Margin = new System.Windows.Forms.Padding(0);
      this.pnlTelop.Name = "pnlTelop";
      this.pnlTelop.Size = new System.Drawing.Size(300, 50);
      this.pnlTelop.TabIndex = 0;
      // 
      // TelopLabel
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(29F, 72F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.Controls.Add(this.pnlTelop);
      this.Margin = new System.Windows.Forms.Padding(0);
      this.Name = "TelopLabel";
      this.Size = new System.Drawing.Size(300, 50);
      this.Load += new System.EventHandler(this.TelopLabel_Load);
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.Panel pnlTelop;

  }
}

 ポイントは文字を動かすのではなく、ラベル自体をタイマーで移動させることです。
コントロールの幅と文字列を実際に描画した幅を算出、連結表示したい場合なども考慮し複数のラベルを生成します。生成したラベルの位置をタイマーでずらしながら表示しているだけです。
もっと軽くしたいのでしたら文字を自力レンダリングするのが良いでしょう。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace TelopSample
{
  /// <summary>
  /// テロップユーザコントロール
  /// </summary>
  public partial class TelopLabel : UserControl
  {
    #region メンバ変数
    /// <summary>
    /// テロップの実行を制御するタイマー
    /// </summary>
    private Timer _Timer;

    /// <summary>
    /// テロップ表示するテキスト
    /// </summary>
    private string _TelopText = String.Empty;

    /// <summary>
    /// テロップ表示するテキストの配置
    /// </summary>
    private ContentAlignment _TelopTextAlign = ContentAlignment.MiddleCenter;

    /// <summary>
    /// スクロールの実行可否
    /// </summary>
    private bool _DoScroll = false;

    /// <summary>
    /// 連結表示
    /// </summary>
    private bool _ConnectedDisplay = false;

    /// <summary>
    /// タイマーの間隔
    /// </summary>
    private int _Interval = 1;

    /// <summary>
    /// コントロールの表示間隔
    /// </summary>
    private int _NextControlIntervalWidth = 0;

    /// <summary>
    /// テロップ表示するラベルオブジェクトのコレクション
    /// </summary>
    private List<Label> _LabelCaptions;
    #endregion

    #region プロパティ
    /// <summary>
    /// テロップに表示する文字列を取得または設定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("テロップに表示する文字列を取得または設定します。")]
    public string TelopText
    {
      get
      {
        return this._TelopText;
      }
      set
      {
        if (this._TelopText != value)
        {
          this._TelopText = value;
        }
      }
    }

    /// <summary>
    /// テロップに表示するテキストの配置を取得または設定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("テロップに表示するテキストの配置を取得または設定します。")]
    public ContentAlignment TelopTextAlign
    {
      get
      {
        return this._TelopTextAlign;
      }
      set
      {
        if (this._TelopTextAlign != value)
        {
          this._TelopTextAlign = value;
        }
      }
    }

    /// <summary>
    /// テロップの実行可否を取得または設定します。
    /// </summary>
    [Browsable(true)]
    [Category("動作")]
    [Description("スクロールの実行可否を取得または設定します。")]
    public bool DoScroll
    {
      get
      {
        return this._DoScroll;
      }
      set
      {
        if (this._DoScroll != value)
        {
          this._DoScroll = value;
        }
      }
    }

    /// <summary>
    /// テロップする文字を連結表示するかを示す値を取得または設定します。
    /// </summary>
    [Browsable(true)]
    [Category("動作")]
    [Description("テロップする文字を連結表示するかを示す値を取得または設定します。")]
    public bool ConnectedDisplay
    {
      get
      {
        return this._ConnectedDisplay;
      }
      set
      {
        if (this._ConnectedDisplay != value)
        {
          this._ConnectedDisplay = value;
        }
      }
    }

    /// <summary>
    /// タイマーの間隔を取得または設定します。
    /// </summary>
    /// <remarks>ミリ秒で設定します。1ミリ秒がデフォルト値です。
    /// 早い:1ミリ秒、普通:25ミリ秒、遅い:50ミリ秒でToolからは設定を行う。
    /// </remarks>
    [Browsable(true)]
    [Category("動作")]
    [Description("タイマーの間隔を取得または設定します。")]
    public int Interval
    {
      get
      {
        return this._Interval;
      }
      set
      {
        if (this._Interval != value)
        {
          this._Interval = value;
        }
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    public TelopLabel()
    {
      InitializeComponent();
      this.DoubleBuffered = true;
    }
    #endregion  

    #region イベント
    /// <summary>
    /// コントロールが初めて表示される前に発生します。
    /// </summary>
    /// <param name="sender">イベントの送り手</param>
    /// <param name="e">イベントの引数</param>
    private void TelopLabel_Load(object sender, EventArgs e)
    {
      InitializeControl();
    }

    /// <summary>
    /// 指定したタイマーの間隔が経過し、タイマーが有効である場合に発生します。
    /// </summary>
    /// <param name="sender">イベントの送り手</param>
    /// <param name="e">イベントの引数</param>
    private void TelopTimer_Tick(object sender, EventArgs e)
    {
      Animation();
    }
    #endregion

    #region override
    /// <summary>
    /// Resize イベントを発生させます。
    /// </summary>
    /// <param name="e">イベントの引数</param>
    protected override void OnResize(EventArgs e)
    {
      if (this._Timer != null) { this._Timer.Enabled = false; }

      InitializeControl();

      base.OnResize(e);
    }
    #endregion

    #region Privateメソッド
    /// <summary>
    /// コントロールを構築します。
    /// </summary>
    private void InitializeControl()
    {
      this.pnlTelop.BackColor = this.BackColor;

      // テロップ表示するラベルコントロールのコレクションを作成
      CreateLabelList();

      // スクロール開始
      if (this._DoScroll)
      {
        this._Timer = new Timer();
        this._Timer.Interval = this._Interval;
        this._Timer.Tick += new EventHandler(TelopTimer_Tick);
        this._Timer.Enabled = true;
      }
    }

    /// <summary>
    /// テキストとフォントサイズから適切なコントロールサイズを算出します。
    /// </summary>
    private Size CalcTelopLabelSize()
    {
      // 仮のキャンバスへ文字列を描画して、コントロールのサイズを算出する
      using (var canvas = new Bitmap(this.Width, this.Height))
      using (var g = Graphics.FromImage(canvas))
      {
        TextRenderer.DrawText(g, this._TelopText, this.Font, new Point(0, 0), Color.Black);
        // 文字列を描画するときの大きさを計測する
        var size = TextRenderer.MeasureText(g, this._TelopText, this.Font);

        return size;
      }
    }

    /// <summary>
    /// テロップ表示を行うためのラベルのコレクションを生成します。
    /// </summary>
    private void CreateLabelList()
    {
      this._LabelCaptions = new List<Label>();

      // ラベルコントロールのサイズ算出
      var size = CalcTelopLabelSize();

      if (size.Height <= 0 || size.Width <= 0) { return; }

      // ラベルとキャンバスのサイズからコントロールのコレクション数を算出する
      // 文字列のサイズより表示領域の幅が大きいときはラベルを複数生成して連結表示するようにコレクション数を求める
      // それ以外の場合、2固定で連結表示する
      var num = 2;
      if (this._ConnectedDisplay)
      {
        num = (int)(size.Width < this.Width ? 
          System.Math.Ceiling((double)(this.Width + size.Width) / (double)size.Width) : 
          System.Math.Ceiling((double)size.Width / (double)this.Width));
      }

      for (int i = 0; i < num; i++)
      {
        var control = new Label();
        control.AutoSize = false;
        control.Text = this._TelopText;
        control.TextAlign = this._TelopTextAlign;
        control.Font = this.Font;
        control.ForeColor = this.ForeColor;
        control.BackColor = Color.Transparent;
        control.Size = size;
        control.Height = this.pnlTelop.Height;
        control.Top = 0;
        control.Left = this.Width + (size.Width * i);
        control.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom);
        control.BringToFront();
        this._LabelCaptions.Add(control);

        // 連結しない場合、一件のみ生成する
        if (!this._ConnectedDisplay) { break; }
      }
      this.pnlTelop.Controls.Clear();
      this.pnlTelop.Controls.AddRange(this._LabelCaptions.ToArray());

      // コントロールの表示間隔を求める
      if (this._ConnectedDisplay && this.Width < size.Width * this._LabelCaptions.Count)
      {
        this._NextControlIntervalWidth = size.Width * (this._LabelCaptions.Count - 1);
      }
      else
      {
        this._NextControlIntervalWidth = this.Width;
      }
    }

    /// <summary>
    /// テロップのアニメーションを行います。
    /// </summary>
    private void Animation()
    {
      var max = this._LabelCaptions.Count;

      for (int i = 0; i < max; i++)
      {
        this._LabelCaptions[i].Top = 0;
        this._LabelCaptions[i].Left = this._LabelCaptions[i].Left - 1 != -this._LabelCaptions[i].Width ?
          this._LabelCaptions[i].Left - 1 : this._NextControlIntervalWidth;
      }
    }
    #endregion
  }
}

 今回は横移動のテロップを紹介しましたが、縦移動がしたい場合は1文字ごとにラベルを生成してポイントをずらすことで可能です。