あたも技術ブログ

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

【C#】Windowsフォーム アプリケーションで角丸コーナーのラベル

 今回はWindowsフォーム アプリケーションで角丸コーナーラベルを作成します。
 ピクチャーボックス上にレンダリングして角丸コーナーのラベルっぽいものにしています。ピクチャーボックスではControl.DoubleBuffered プロパティが使えなかったため、DoubleBufferクラスを実装してダブルバッファリングを行っています。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace RoundCornerLabelSample
{
  /// <summary>
  /// ダブルバッファクラス
  /// </summary>
  public class DoubleBuffer : IDisposable
  {
    #region メンバ変数
    /// <summary>
    /// ダブルバッファリングオブジェクト
    /// </summary>
    private BufferedGraphics _Buffer = null;

    /// <summary>
    /// バッファリングを行うコントロール
    /// </summary>
    private Control _Control = null;
    #endregion

    #region プロパティ
    /// <summary>
    /// グラフィックオブジェクト
    /// </summary>
    public Graphics Graphics
    {
      get
      {
        return this._Buffer.Graphics;
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="control">バッファリングを行うコントロール</param>
    public DoubleBuffer(Control control)
    {
      this.Dispose();

      var currentContext = BufferedGraphicsManager.Current;
      this._Buffer = currentContext.Allocate(control.CreateGraphics(), control.DisplayRectangle);

      if (this._Control != null)
      {
        this._Control.Paint += new System.Windows.Forms.PaintEventHandler(this.Paint);
      }
    }
    #endregion

    #region デストラクタ
    /// <summary>
    /// デストラクタ
    /// </summary>
    ~DoubleBuffer()
    {
      Dispose();
    }
    #endregion

    #region publicメソッド
    /// <summary>
    /// 解放処理を行います。
    /// </summary>
    public void Dispose()
    {
      if (this._Buffer != null)
      {
        this._Buffer = null;
      }
      if (this._Control != null)
      {
        this._Control.Paint -= new System.Windows.Forms.PaintEventHandler(this.Paint);
      }
    }

    /// <summary>
    /// 更新を行います。
    /// </summary>
    public void Refresh()
    {
      if (this._Buffer != null)
      {
        this._Buffer.Render();
      }
    }
    #endregion

    #region privateメソッド
    /// <summary>
    /// 描画処理を行います。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Paint(object sender, PaintEventArgs e)
    {
      Refresh();
    }
    #endregion
  }
}

 角丸コーナーラベルの実装コードです。ラベル内の文字が右方向にずれるためレンダリング時に-3位置をずらしています。

namespace RoundCornerLabelSample
{
  partial class RoundCornerLabel
  {
    /// <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.pic = new System.Windows.Forms.PictureBox();
      ((System.ComponentModel.ISupportInitialize)(this.pic)).BeginInit();
      this.SuspendLayout();
      // 
      // pic
      // 
      this.pic.BackColor = System.Drawing.Color.Transparent;
      this.pic.Dock = System.Windows.Forms.DockStyle.Fill;
      this.pic.Location = new System.Drawing.Point(0, 0);
      this.pic.Margin = new System.Windows.Forms.Padding(0);
      this.pic.Name = "pic";
      this.pic.Size = new System.Drawing.Size(200, 150);
      this.pic.TabIndex = 0;
      this.pic.TabStop = false;
      // 
      // RoundCornerLabel
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.Controls.Add(this.pic);
      this.Margin = new System.Windows.Forms.Padding(0);
      this.Name = "RoundCornerLabel";
      this.Size = new System.Drawing.Size(200, 150);
      ((System.ComponentModel.ISupportInitialize)(this.pic)).EndInit();
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.PictureBox pic;
  }
}
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace RoundCornerLabelSample
{
  /// <summary>
  /// 角丸コーナーラベルユーザコントロール
  /// </summary>
  public partial class RoundCornerLabel : UserControl
  {
    #region メンバ変数
    /// <summary>
    /// ダブルバッファクラス
    /// </summary>
    private DoubleBuffer _DoubleBuffer = null;

    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    private string _DisplayText = "";

    /// <summary>
    /// 文字列の配置
    /// </summary>
    private ContentAlignment _TextAlign = ContentAlignment.MiddleCenter;

    /// <summary>
    /// コーナーの角丸のサイズ(直径)
    /// </summary>
    private int _CornerR = 10;

    /// <summary>
    /// コーナーの線の太さ
    /// </summary>
    private int _PenWidth = 5;

    /// <summary>
    /// コーナーの線の色
    /// </summary>
    private Color _CornerColor = Color.Blue;

    /// <summary>
    /// 矩形内の背景色
    /// </summary>
    private Color _InnerColor = SystemColors.Window;
    #endregion

    #region プロパティ
    /// <summary>
    /// コントロールに関連付けられたテキスト
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに関連付けられたテキストです。")]
    public string DisplayText
    {
      get
      {
        return this._DisplayText;
      }
      set
      {
        this._DisplayText = value;
        Refresh();
      }
    }

    /// <summary>
    /// コントロールに表示される文字列の配置
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールに表示される文字列の配置です。")]
    public ContentAlignment TextAlign
    {
      get
      {
        return this._TextAlign;
      }
      set
      {
        this._TextAlign = value;
        Refresh();
      }
    }

    /// <summary>
    /// 角の丸さ
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("角の丸さを指定します。(半径)")]
    public int CornerR
    {
      get
      {
        return (int)(this._CornerR / 2);
      }
      set
      {
        if (0 < value)
        {
          this._CornerR = value * 2;
        }
        else
        {
          // 0以下が設定された場合、強制的に1にする
          this._CornerR = 1;
        }

        //RenewPadding();
        Refresh();
      }
    }

    /// <summary>
    /// 枠線の色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("外枠の色を指定します。")]
    public Color CornerColor
    {
      get
      {
        return this._CornerColor;
      }
      set
      {
        this._CornerColor = value;
        Refresh();
      }
    }

    /// <summary>
    /// 枠線の幅を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("枠線の太さを指定します。")]
    public int PenWidth
    {
      get
      {
        return this._PenWidth;
      }
      set
      {
        if (0 <= value)
        {
          this._PenWidth = value;
        }
        else
        {
          // 0以下が指定された場合、強制的に0にする
          this._PenWidth = 0;
        }

        //RenewPadding();
        Refresh();
      }
    }

    /// <summary>
    /// コントロールの矩形内の背景色
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("コントロールの矩形内の背景色を指定します。")]
    public Color InnerColor
    {
      get
      {
        return this._InnerColor;
      }
      set
      {
        this._InnerColor = value;
        Refresh();
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// クラスの新しいインスタンスを初期化します。
    /// </summary>
    public RoundCornerLabel()
    {
      InitializeComponent();

      // コントロールのサイズが変更された時にPaintイベントを発生させる
      SetStyle(ControlStyles.ResizeRedraw, true);
      SetStyle(ControlStyles.SupportsTransparentBackColor, true);
      // ピクチャボックスでダブルバッファを行うようにインスタンス生成
      this._DoubleBuffer = new DoubleBuffer(this.pic);

      this.BackColor = Color.Transparent;
    }
    #endregion

    #region override
    /// <summary>
    /// PaintBackground イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnPaintBackground(PaintEventArgs e)
    {
      // バックグランドの描画を行うとWhiteの挿入がされるため
      // 何もしないようにする
    }

    /// <summary>
    /// Load イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);
    }

    /// <summary>
    /// Paint イベントを発生させます。
    /// </summary>
    /// <param name="e">イベント引数</param>
    protected override void OnPaint(PaintEventArgs e)
    {
      Draw(e.Graphics);
    }
    #endregion

    #region private
    /// <summary>
    /// 描画を行います。
    /// </summary>
    /// <param name="g">グラフィックオブジェクト</param>
    private void Draw(Graphics g)
    {
      // 描画品質設定
      g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
      g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
      g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;

      // 背景用のImageオブジェクトを作成する
      var backrect = new Rectangle(0, 0, this.Width, this.Height);
      var backColor = this.Parent != null ? this.Parent.BackColor : SystemColors.Control;
      using (var brush = new SolidBrush(backColor))
      {
        g.FillRectangle(brush, backrect);
      }

      using (var cornerPen = new Pen(this._CornerColor, this._PenWidth))
      {
        // 変数初期化
        var harfPenWidth = GetHarfPenWidth();

        // ペンとブラシ初期化
        var offset = this._PenWidth;
        var rect = new Rectangle(offset, offset, this.Width, this.Height);
        using (var brush = new SolidBrush(this._InnerColor))
        using (var graphPath = new GraphicsPath())
        {
          graphPath.AddArc(harfPenWidth, harfPenWidth, this._CornerR, this._CornerR, 180, 90);
          graphPath.AddArc(this.Width - this._CornerR - harfPenWidth, harfPenWidth, this._CornerR, this._CornerR, 270, 90);
          graphPath.AddArc(this.Width - this._CornerR - harfPenWidth, this.Height - this._CornerR - harfPenWidth, this._CornerR, this._CornerR, 0, 90);
          graphPath.AddArc(harfPenWidth, this.Height - this._CornerR - harfPenWidth, this._CornerR, this._CornerR, 90, 90);
          graphPath.CloseFigure();

          // パス塗り
          g.FillPath(brush, graphPath);

          // ペンの太さが1以上なら外枠描画
          if (0 < this._PenWidth)
          {
            g.DrawPath(cornerPen, graphPath);
          }
        }

        // テキストが設定されている場合は文字を書く
        var size = new Size(this.Width - offset * 2, this.Height - offset * 2);
        var fontSize = CalcFontSize(this._DisplayText, size, g);
        if (fontSize <= 0) { fontSize = 0.1F; }

        var font = new Font(this.Font.Name, fontSize);
        var stringFormat = new StringFormat();
        var num = (Int32)System.Math.Log((Double)this._TextAlign, 2);
        stringFormat.LineAlignment = (StringAlignment)(num / 4);
        stringFormat.Alignment = (StringAlignment)(num % 4);

        // 文字描画用のブラシを生成する
        using (var foreColorBrush = new SolidBrush(this.ForeColor))
        {
          // 微妙に右にずれるため-3して補正する
          rect.X = rect.X - 3;
          g.DrawString(this._DisplayText, font, foreColorBrush, rect, stringFormat);
        }
      }
    }

    /// <summary>
    /// Paddingサイズを更新します。
    /// </summary>
    private void RenewPadding()
    {
      var harfCornerR = (int)(this._CornerR / 2);
      var adjust = (int)(System.Math.Cos(45 * System.Math.PI / 180) * harfCornerR);
      this.Padding = new Padding(harfCornerR + this._PenWidth + 2 - adjust);
    }

    /// <summary>
    /// コーナー線の幅の1/2の値を取得します。
    /// </summary>
    /// <returns>コーナー線の幅の1/2の値</returns>
    private int GetHarfPenWidth()
    {
      return (int)System.Math.Floor((double)(this._PenWidth) / 2 + 0.5);
    }

    /// <summary>
    /// 指定されたグラフィックオブジェクトに描画する最適なフォントサイズを算出します。
    /// </summary>
    /// <param name="str">出力する文字列</param>
    /// <param name="size">サイズ</param>
    /// <param name="g">描画するグラフィックオブジェクト</param>
    /// <returns>フォントサイズ</returns>
    private float CalcFontSize(string str, Size size, Graphics g)
    {
      var s = new SizeF(0.1F, 0.1F);
      var a = 0.1F;
      var b = 0.1F;

      if (!string.IsNullOrEmpty(str))
      {
        s = g.MeasureString(str, new Font(this.Font.Name, 1));
        a = (size.Width / s.Width);
        b = (size.Height / s.Height);
      }
      return (a < b) ? a : a;
    }
    #endregion
  }
}

 WPFの場合、簡単に実装できますがWindowsフォーム アプリケーションだとかなり面倒ですね…。