読者です 読者をやめる 読者になる 読者になる

あたも開発ブログ

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

【Android】よく使う共通処理【Util】

Android java Util

個人的によく使う処理です。
Utilにまとめておくと意外と便利!

/**
 * クリップボードにコピー
 */
public static void copy(Context c, String label, String text) {
  android.content.ClipboardManager cm = (android.content.ClipboardManager)c.getSystemService(Context.CLIPBOARD_SERVICE);
  cm.setPrimaryClip(ClipData.newPlainText(label, text));
}

/**
 * 画面横サイズ取得
 */
public static int getDisplayWidth(Context context) {
  try {
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    return metrics.widthPixels;
  } catch (Exception e) {
    return 0;
  }
}

/**
 * 画面縦サイズ取得
 */
public static int getDisplayHeight(Context context) {
  try {
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    return metrics.heightPixels;
  } catch (Exception e) {
    return 0;
  }
}

/**
 * dip → px 変換
 */
public static int dipToPixel(Context context, int dp) {
  return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}

/**
 * 色を少し明るくする
 */
public static int lighter(int color, float factor) {
  int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255);
  int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255);
  int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255);
  return Color.argb(Color.alpha(color), red, green, blue);
}

/**
 * 色を少し暗くする
 */
public static int darker (int color, float factor) {
  if (factor < 0 || factor > 1) return color;
  int r = Color.red(color);
  int g = Color.green( color );
  int b = Color.blue( color );
  return Color.argb(Color.alpha(color), Math.max((int)(r * factor), 0), Math.max((int)(g * factor), 0), Math.max((int)(b * factor), 0));
}

/**
 * 現在時刻を取得
 */
public static String now() {
  return now("yyyy-MM-dd HH:mm:ss", TimeZone.getDefault().toString());
}
public static String now(String format) {
  return now(format, TimeZone.getDefault().toString());
}
public static String now(String format, String timeZone) {
  Date date = new Date();
  return FastDateFormat.getInstance(format, TimeZone.getTimeZone(timeZone)).format(date);
}

/**
 * data/data/パッケージ名/filesの下にフォルダを作成し、作成したフォルダのパスを返却
 */
public static String makePkgFilesDir(Context context, String folderPath) {
  String path = context.getFilesDir().getAbsolutePath() + "/" + folderPath;
  // ディレクトリがない場合は作成
  File makeDir = new File(path);
  if (makeDir.exists() == false) makeDir.mkdirs();
  return path;
}

/**
 * ディレクトリ、ファイルの削除
 */
public static boolean delete(File f) {
  if (f.exists() == false) return false;
  
  if (f.isFile()) {
    f.delete();
  } else if (f.isDirectory()) {
    File[] files = f.listFiles();
    for (int i = 0; i < files.length; i++) {
      delete(files[i]);
    }
    f.delete();
  }
  return true;
}

/**
 * キーボード表示
 */
public static void show(Activity activity) {
  if(null == activity) return;
  
  InputMethodManager imm = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
  View v = activity.getCurrentFocus();
  if(null != v) {
    imm.showSoftInput(v.findFocus(), 0);
  }
}

/**
 * キーボード非表示
 */
public static void hide(Activity activity) {
  if(null == activity) return;
  
  InputMethodManager imm = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
  View v = activity.getCurrentFocus();
  if(null != v) {
    imm.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
  }
}

【Swift】xmlで管理している色情報を簡単に扱う

Swift 3 Swift Xcode 8.1 extension iOS UIColor xml

はじめに

今回は、xmlで色を管理した場合に、簡単に扱えるようにしたいと思います。

xmlについて

以下のフォーマットでxmlを用意します。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="buttonBackGround">#285176</color>
    <color name="buttonText">#305c83</color>
    .
    .
    .
</resources>

コード

今回は、色に関してのみですが、xmlファイル1つで色々とまとめて管理することができるので、使い回しがしやすいように、クラスを分けています。

class XMLUtil: NSObject,  XMLParserDelegate {
    
    var parser: XMLParser!
    
    var compleationBlock: (([String : String]?)->Void) = {_ in}
    var element: String!
    var attribute: String!
    var tmpDic: [String : String]!
    var tmpKey: String!
    
    public func parse(fileName: String, element: String! = nil, attribute: String! = nil, compleation: @escaping (([String : String]?)->Void)) {
        self.element = element
        self.attribute = attribute
        compleationBlock = compleation
        
        guard let tmpData: Data = loadAssetFile(name: fileName) else {
            self.compleationBlock(nil)
            return
        }
        
        parser = XMLParser(data: tmpData)
        parser.delegate = self
        
        let _ = parser.parse()
        
    }
    
    private func loadAssetFile(name: String) -> Data! {
        var data: Data! = nil
        // Assetsから取得
        if #available(iOS 9.0, *) {
            if let tmpData = NSDataAsset(name: name) {
                data = tmpData.data
            }
        } 
        return data
    }
    
    // MARK: - NSXMLParserDelegate
    
   public func parserDidStartDocument(_ parser: XMLParser) {
        tmpDic = [ : ]
    }
    
    public func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        
        guard let _ = element else {
            tmpKey = elementName
            return
        }
        guard let _ = attribute else {
            tmpKey = element
            return
        }
        if elementName == element {
            guard let key = attributeDict[attribute] else {
                return
            }
            tmpKey = key
        }
    }
    
    public func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        tmpKey = nil
    }
    
    public func parser(_ parser: XMLParser, foundCharacters string: String) {
        guard let _ = tmpKey else {
            return
        }
        tmpDic[tmpKey] = string
    }
    
    public func parserDidEndDocument(_ parser: XMLParser) {
        self.compleationBlock(tmpDic)
    }
}
class XMLColorUtil: NSObject {
    private var colors: [String : String]! = nil
    
    static var shared: XMLColorUtil = {
        let instance = XMLColorUtil()
        let res = instance.loadColors()

        return instance
    }()
    private override init() {
    }
   
    internal func loadColors() {
        var finish = false
        XMLUtil().parse(fileName: "colors", element: "color", attribute: "name") {
            dic in
            if dic != nil {
                self.colors = dic
            }
            finish = true
        }
        
    }
    
    public func getColor(key: String = #function) -> UIColor {
        guard let _ = colors else {
            return .white
        }
        guard let hexColor = colors[key] else {
            return .white
        }
        return UIColor(hex: hexColor)
    }
}
extension UIColor {
    convenience init(hex: String) {
        let range = NSMakeRange(0, rgbString.characters.count)
        let hexStr = (rgbString as NSString).replacingOccurrences(of: "[^0-9a-fA-F]", with: "", options: .regularExpression, range: range)
        
        var r: Float = 1
        var g: Float = 1
        var b: Float = 1
        let a: Float = 1
        if hexStr.characters.count == 6 {
            if let num = Int(hexStr, radix: 16) {
                r = Float((num >> 16) & 0xFF) / 255.0
                g = Float((num >> 8) & 0xFF) / 255.0
                b = Float((num) & 0xFF) / 255.0
            }
        }
        self.init(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: CGFloat(a))
    }
}

色クラス

class AppColor: NSObject {
    private let util = XMLColorUtil.shared
    
    // xml上のnameと合わせる
    public var buttonBackGround: UIColor {
        return util.getColor()
    }
    public var buttonText: UIColor {
        return util.getColor()
    }
    .
    .
    .
}

このクラスに上にxmlでname属性に設定した値をプロパティとして定義し、XMLColorUtilクラスのgetColor()を呼びます

getColor()の引数の初期値は#functionなので、呼び出し元のメソッド名を取得するようになっています。

XMLColorUtil().getColor(key: "buttonBackGround")とすることもできます。

使い方

let appColor = AppColor()

let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 60)))

button.backgroundColor = appColor.buttonBackGround
button.setTitleColor(appColor.buttonText, for: .normal)

使用する色が増えた際には、xml編集後、AppColorクラスにプロパティを追加するだけで使えるようになります。

【C#】Windowsフォーム アプリケーションで拡大・縮小ラベルユーザコントロール

.NET C# Microsoft WindowsForm

 今回はWindowsフォーム アプリケーションで前回に作成した角丸コーナーラベルを使用して拡大、縮小するラベルっぽいものを作りたいと思います。
 プロパティで指定した基準点(左上、左下、中央、右上、右下)から拡大、縮小します。
 TableLayoutPanelを利用してそれっぽくというか力技で拡大、縮小しています。速度を求める場合はしっかり計算してレンダリングし直した方が良いでしょう。

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

namespace ZoomingSample
{
  /// <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
  }
}

 角丸コーナーラベルの実装コードです。

namespace ZoomingSample
{
  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 ZoomingSample
{
  /// <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
  }
}

 拡大、縮小ラベルの実装コードです。拡大、縮小時に割込みをブロックしていません。実際に利用する場合は拡大中、縮小中か判定して処理の割込みはしないよう考慮した方が良いでしょう。

namespace ZoomingSample
{
  partial class ZoomingLabel
  {
    /// <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.tableLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
      this.SuspendLayout();
      // 
      // tableLayoutPanel
      // 
      this.tableLayoutPanel.BackColor = System.Drawing.Color.Transparent;
      this.tableLayoutPanel.ColumnCount = 3;
      this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
      this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
      this.tableLayoutPanel.Font = new System.Drawing.Font("メイリオ", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
      this.tableLayoutPanel.Location = new System.Drawing.Point(0, 0);
      this.tableLayoutPanel.Margin = new System.Windows.Forms.Padding(0);
      this.tableLayoutPanel.Name = "tableLayoutPanel";
      this.tableLayoutPanel.RowCount = 3;
      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
      this.tableLayoutPanel.Size = new System.Drawing.Size(400, 300);
      this.tableLayoutPanel.TabIndex = 0;
      // 
      // ZoomingLabel
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 28F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.BackColor = System.Drawing.Color.Transparent;
      this.Controls.Add(this.tableLayoutPanel);
      this.Font = new System.Drawing.Font("メイリオ", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
      this.Margin = new System.Windows.Forms.Padding(0);
      this.Name = "ZoomingLabel";
      this.Size = new System.Drawing.Size(400, 300);
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.TableLayoutPanel tableLayoutPanel;
  }
}
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace ZoomingSample
{
  /// <summary>
  /// 拡大縮小ラベルユーザコントロール
  /// </summary>
  public partial class ZoomingLabel : UserControl
  {
    #region Enum定義
    /// <summary>
    /// 拡大するときの方向を示す定義
    /// </summary>
    public enum ZoomDirection
    {
      /// <summary>
      /// 右下方向(左上を基準点にして拡大します)
      /// </summary>
      RightDown = 0,
      /// <summary>
      /// 右上方向(左下を基準点にして拡大します)
      /// </summary>
      RightUp = 1,
      /// <summary>
      /// 左下方向(右上を基準点にして拡大します)
      /// </summary>
      LeftDown = 2,
      /// <summary>
      /// 左上方向(右下を基準点にして拡大します)
      /// </summary>
      LeftUp = 3,
      /// <summary>
      /// 全方向(中心点を基準点にして拡大します)
      /// </summary>
      AllSide = 4
    }
    #endregion

    #region メンバ変数
    /// <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;

    /// <summary>
    /// 拡大を行う方向
    /// </summary>
    /// <remarks>デフォルトは中心点から全方向拡大</remarks>
    private ZoomDirection _Direction = ZoomDirection.LeftDown;

    /// <summary>
    /// テーブルレイアウトパネルの縮小・拡大時の刻み幅
    /// </summary>
    private float _PitchWidth = 0.0F;

    /// <summary>
    /// テーブルレイアウトパネルの縮小・拡大時の刻み高さ
    /// </summary>
    private float _PitchHeight = 0.0F;

    /// <summary>
    /// テーブルレイアウトパネルの元の幅
    /// </summary>
    private float _OriginalWidth = 0.0F;

    /// <summary>
    /// テーブルレイアウトパネルの元の高さ
    /// </summary>
    private float _OriginalHeight = 0.0F;

    /// <summary>
    /// 拡大・縮小時のアニメーションの更新間隔
    /// </summary>
    private int _AnimationInterval = 1;

    /// <summary>
    /// 拡大・縮小で利用するタイマー
    /// </summary>
    private Timer _ZoomTimer;
    #endregion

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

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

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

    /// <summary>
    /// 角の丸さ
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("角の丸さを指定します。(半径)")]
    public int CornerR
    {
      get
      {
        return this._CornerR;
      }
      set
      {
        this._CornerR = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

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

    /// <summary>
    /// 枠線の幅を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("枠線の太さを指定します。")]
    public int PenWidth
    {
      get
      {
        return this._PenWidth;
      }
      set
      {
        this._PenWidth = value;
        SetRoundCornerLabel();
        this.Refresh();
      }
    }

    /// <summary>
    /// 拡大時の方向を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("表示")]
    [Description("拡大時の方向を指定します。")]
    public ZoomDirection Direction
    {
      get
      {
        return this._Direction;
      }
      set
      {
        this._Direction = value;
        CreateTebleLayout();
        this.Refresh();
      }
    }

    /// <summary>
    /// 拡大・縮小時のアニメーションの更新間隔を指定します。
    /// </summary>
    [Browsable(true)]
    [Category("動作")]
    [Description("拡大・縮小時のアニメーションの更新間隔を指定します。")]
    public int AnimationInterval
    {
      get
      {
        return this._AnimationInterval;
      }
      set
      {
        this._AnimationInterval = value;
      }
    }
    #endregion

    #region コンストラクタ
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ZoomingLabel()
    {
      InitializeComponent();
      this.BackColor = Color.Transparent;
      this.tableLayoutPanel.BackColor = SystemColors.Control;
      this.DoubleBuffered = true;

      // 元の角丸ラベルユーザコントロールのサイズを保持しておく
      this._OriginalWidth = this.Width / 2;
      this._OriginalHeight = this.Height / 2;
      CreateTebleLayout();
      CalcPitch();
    }
    #endregion

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

    /// <summary>
    /// FontChanged イベントを発生させます。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnFontChanged(EventArgs e)
    {
      base.OnFontChanged(e);
      SetRoundCornerLabel();
      this.Refresh();
    }

    /// <summary>
    /// BackColorChanged イベントを発生させます。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnBackColorChanged(EventArgs e)
    {
      base.OnBackColorChanged(e);
      this.tableLayoutPanel.BackColor = this.BackColor;
      this.Refresh();
    }

    /// <summary>
    /// Resize イベントを発生させます。
    /// </summary>
    /// <param name="e"></param>
    protected override void OnResize(EventArgs e)
    {
      base.OnResize(e);
      // 元の角丸ラベルユーザコントロールのサイズを保持しておく
      this._OriginalWidth = this.Width / 2;
      this._OriginalHeight = this.Height / 2;
      CreateTebleLayout();
      CalcPitch();
    }
    #endregion

    #region public
    /// <summary>
    /// 指定された方向へ拡大処理を行います。
    /// </summary>
    /// <remarks>拡大済の場合、それ以上大きくはならない</remarks>
    public void ZoomIn()
    {
      this._ZoomTimer = new Timer();
      this._ZoomTimer.Interval = _AnimationInterval;
      this._ZoomTimer.Tick += new EventHandler(this.ZoomInTimer_Tick);
      this._ZoomTimer.Enabled = true;
    }

    /// <summary>
    /// 拡大時の処理を行います。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ZoomInTimer_Tick(object sender, EventArgs e)
    {
      var control = this.tableLayoutPanel.Controls[0] as RoundCornerLabel;
      var h1 = 0.0F;
      var w1 = 0.0F;
      var h2 = 0.0F;
      var w2 = 0.0F;
      var col1 = 0;
      var row1 = 0;
      var col2 = 0;
      var row2 = 0;

      if (this._Direction == ZoomDirection.RightDown)
      {
        // 左上基準、右下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        row1 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.RightUp)
      {
        // 左下基準、右上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        row2 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftDown)
      {
        // 右上基準、左下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        row1 = 1;
        col2 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftUp)
      {
        // 右下基準、左上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        row2 = 1;
        col2 = 1;
      }
      else
      {
        h1 = this.tableLayoutPanel.RowStyles[0].Height - 0.5F;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width - 0.5F;
        h2 = this.tableLayoutPanel.RowStyles[1].Height + 0.5F;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width + 0.5F;
      }


      // コントロールのサイズ以上となった場合、Timerを停止し拡大を終える
      if ((this.Width <= w2 || this.Height <= h2) || (w1 <= 0 || h1 <= 0))
      {
        this._ZoomTimer.Enabled = false;
        this._ZoomTimer.Tick -= this.ZoomOutTimer_Tick;
        return;
      }

      if (this._Direction != ZoomDirection.AllSide)
      {
        this.tableLayoutPanel.RowStyles[row1].Height = h1;
        this.tableLayoutPanel.ColumnStyles[col1].Width = w1;
        this.tableLayoutPanel.RowStyles[row2].Height = h2;
        this.tableLayoutPanel.ColumnStyles[col2].Width = w2;
      }
      else
      {
        this.tableLayoutPanel.RowStyles[0].Height = h1;
        this.tableLayoutPanel.RowStyles[1].Height = h2;
        this.tableLayoutPanel.RowStyles[2].Height = h1;
        this.tableLayoutPanel.ColumnStyles[0].Width = w1;
        this.tableLayoutPanel.ColumnStyles[1].Width = w2;
        this.tableLayoutPanel.ColumnStyles[2].Width = w1;
      }
    }

    /// <summary>
    /// 指定された逆方向へ縮小処理を行います。
    /// </summary>
    /// <remarks>拡大時のみ縮小される。元のコントロールのサイズより小さくはならない</remarks>
    public void ZoomOut()
    {
      this._ZoomTimer = new Timer();
      this._ZoomTimer.Interval = _AnimationInterval;
      this._ZoomTimer.Tick += new EventHandler(this.ZoomOutTimer_Tick);
      this._ZoomTimer.Enabled = true;
    }

    /// <summary>
    /// 縮小時の処理を行います。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ZoomOutTimer_Tick(object sender, EventArgs e)
    {
      var control = this.tableLayoutPanel.Controls[0] as RoundCornerLabel;
      var h1 = 0.0F;
      var w1 = 0.0F;
      var h2 = 0.0F;
      var w2 = 0.0F;
      var col1 = 0;
      var row1 = 0;
      var col2 = 0;
      var row2 = 0;

      if (this._Direction == ZoomDirection.RightDown)
      {
        // 左上基準、右下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        row1 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.RightUp)
      {
        // 左下基準、右上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[1].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[0].Width - this._PitchWidth;
        row2 = 1;
        col1 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftDown)
      {
        // 右上基準、左下方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[1].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[0].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        row1 = 1;
        col2 = 1;
      }
      else if (this._Direction == ZoomDirection.LeftUp)
      {
        // 右下基準、左上方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height + this._PitchHeight;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width + this._PitchWidth;
        h2 = this.tableLayoutPanel.RowStyles[1].Height - this._PitchHeight;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width - this._PitchWidth;
        row2 = 1;
        col2 = 1;
      }
      else
      {
        // 中央基準点、全方向拡大の場合
        h1 = this.tableLayoutPanel.RowStyles[0].Height + 0.5F;
        w1 = this.tableLayoutPanel.ColumnStyles[0].Width + 0.5F;
        h2 = this.tableLayoutPanel.RowStyles[1].Height - 0.5F;
        w2 = this.tableLayoutPanel.ColumnStyles[1].Width - 0.5F;
      }


      // コントロールの元のサイズ以下となった場合、Timerを停止し縮小を終える
      if (control.Width <= this._OriginalWidth || control.Height <= this._OriginalHeight)
      {
        this._ZoomTimer.Enabled = false;
        this._ZoomTimer.Tick -= this.ZoomInTimer_Tick;
        return;
      }

      if (this._Direction != ZoomDirection.AllSide)
      {
        this.tableLayoutPanel.RowStyles[row1].Height = h1;
        this.tableLayoutPanel.ColumnStyles[col1].Width = w1;
        this.tableLayoutPanel.RowStyles[row2].Height = h2;
        this.tableLayoutPanel.ColumnStyles[col2].Width = w2;
      }
      else
      {
        this.tableLayoutPanel.RowStyles[0].Height = h1;
        this.tableLayoutPanel.RowStyles[1].Height = h2;
        this.tableLayoutPanel.RowStyles[2].Height = h1;
        this.tableLayoutPanel.ColumnStyles[0].Width = w1;
        this.tableLayoutPanel.ColumnStyles[1].Width = w2;
        this.tableLayoutPanel.ColumnStyles[2].Width = w1;
      }
    }
    #endregion

    #region private
    /// <summary>
    /// 拡大縮小を行うためのテーブルレイアウトを構築します。
    /// </summary>
    private void CreateTebleLayout()
    {
      this.tableLayoutPanel.SuspendLayout();
      this.tableLayoutPanel.Controls.Clear();
      this.tableLayoutPanel.RowStyles.Clear();
      this.tableLayoutPanel.ColumnStyles.Clear();

      var control = CreateRoundCornerLabel();

      if (this._Direction == ZoomDirection.RightDown)
      {
        // 左上基準点、右下方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 0, 0);
      }
      else if (this._Direction == ZoomDirection.RightUp)
      {
        // 左下基準点、右上方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 0, 1);
      }
      else if (this._Direction == ZoomDirection.LeftDown)
      {
        // 右上基準点、左下方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 1, 0);
      }
      else if (this._Direction == ZoomDirection.LeftUp)
      {
        // 右下基準点、左上方向拡大の場合
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, this._OriginalWidth));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, this._OriginalHeight));
        this.tableLayoutPanel.Controls.Add(control, 1, 1);
      }
      else
      {
        // 中央基準点、全方向拡大の場合
        var width = 25F;
        var height = 25F;
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, width));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
        this.tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, width));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, height));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
        this.tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, height));
        control.Dock = DockStyle.Fill;
        this.tableLayoutPanel.Controls.Add(control, 1, 1);
      }
      if (this._Direction != ZoomDirection.AllSide)
      {
        control.Dock = DockStyle.Fill;
      }

      this.tableLayoutPanel.ResumeLayout();
    }

    /// <summary>
    /// 角丸ラベルユーザコントロールを生成します。
    /// </summary>
    private RoundCornerLabel CreateRoundCornerLabel()
    {
      var control = new RoundCornerLabel();
      control.DisplayText = this._DisplayText;
      control.CornerColor = this._CornerColor;
      control.CornerR = this._CornerR;
      control.ForeColor = this.ForeColor;
      control.Font = this.Font;
      control.BackColor = this.tableLayoutPanel.BackColor;
      control.InnerColor = this._InnerColor;
      control.PenWidth = this._PenWidth;
      control.TextAlign = this._TextAlign;
      control.Width = (int)this._OriginalWidth;
      control.Height = (int)this._OriginalHeight;
      control.Padding = new Padding(0);

      return control;
    }

    /// <summary>
    /// 角丸ラベルユーザコントロールのプロパティを変更します。
    /// </summary>
    private void SetRoundCornerLabel()
    {
      if (this.tableLayoutPanel.Controls.Count == 0) { return; }

      var control = this.tableLayoutPanel.Controls[0] as RoundCornerLabel;

      if (control == null) { return; }

      control.DisplayText = this._DisplayText;
      control.CornerColor = this._CornerColor;
      control.CornerR = this._CornerR;
      control.ForeColor = this.ForeColor;
      control.Font = this.Font;
      control.BackColor = this.tableLayoutPanel.BackColor;
      control.InnerColor = this._InnerColor;
      control.PenWidth = this._PenWidth;
      control.TextAlign = this._TextAlign;
      control.Width = (int)this._OriginalWidth;
      control.Height = (int)this._OriginalHeight;
    }

    /// <summary>
    /// 拡大縮小時のピッチの計算を行います。
    /// </summary>
    private void CalcPitch()
    {
      // 拡大縮小を行う単位を算出する
      var gcd = Gcd(this.Width, this.Height);
      var size = CalcAspect(this.Width, this.Height);
      this._PitchHeight = size.Height;
      this._PitchWidth = size.Width;
    }

    /// <summary>
    /// 引数のサイズからアスペクト比を算出します。
    /// </summary>
    /// <param name="w">横の解像度</param>
    /// <param name="h">縦の解像度</param>
    /// <returns>アスペクト比</returns>
    private SizeF CalcAspect(int w, int h)
    {
      // 最大公約数を求める
      var gcd = Gcd(w, h);
      if (gcd == 1) { gcd = 100; }

      var result = new SizeF();
      result.Width = (float)this.Width / (float)gcd;
      result.Height = (float)this.Height / (float)gcd;
      return result;
    }

    /// <summary>
    /// 最大公約数を求めます。
    /// </summary>
    /// <param name="w">横の解像度</param>
    /// <param name="h">縦の解像度</param>
    /// <returns></returns>
    private int Gcd(int w, int h)
    {
      if (w < h)
      {
        return Gcd(h, w);
      }
      while (h != 0)
      {
        var remainder = w % h;
        w = h;
        h = remainder;
      }
      return w;
    }
    #endregion
  }
}

 Windowsフォーム アプリケーションでも工夫と頑張りでアニメーションっぽい処理もそれなりにできますが、やっぱりWPFの方が楽ですね…。
 枯れてきたWindowsフォーム アプリケーションを使いたいって要件もまだそこそこありますので、参考になればと思います。

【Android】Ripple Effectをコードで適応【Material Design】

Android Material Design java デザイン 小技

今回はちょっとした小ネタになります。

Androidでボタンなどを押したときに出てくる波紋状のエフェクトですが、通常なら以下のようにxmlで記述し、backgroundに設定してあげることで実現することができます。

/values/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="btn_color">#ffffaab3</color>
    <color name="btn_highlight_color">#ff4081</color>
</resources>


/drawable/btn_ripple.xml

<?xml version="1.0" encoding="utf-8"?>
    <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/btn_highlight_color">
    <item android:drawable="@color/btn_color" />
</ripple>

/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="@drawable/btn_ripple"
        android:text="Hello World!" />
</RelativeLayout>

今回はxmlを使用せずにコードでRipple Effectを実現する方法を書いていきます。
コード自体は簡単で、以下のようにコードを書いてeffectを適用させたいViewを渡してあげるだけです。

RippleEffect.java

public class RippleEffect {
    private static ColorStateList getPressedColorSelector(int normalColor, int pressedColor) {
        return new ColorStateList(new int[][] {
                new int[]{android.R.attr.state_pressed},
                new int[]{android.R.attr.state_focused},
                new int[]{android.R.attr.state_activated},
                new int[]{}
        }, new int[] {
                pressedColor,
                pressedColor,
                pressedColor,
                normalColor
        });
    }

    public static void setRippleDrawable(View v, int normalColor, int pressedColor) {
        RippleDrawable ripple =  new RippleDrawable(getPressedColorSelector(normalColor, pressedColor), new ColorDrawable(normalColor), null);
        ripple.setColor(ColorStateList.valueOf(pressedColor));
        v.setBackground(ripple);
    }
}



MainActivity.java

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = (Button)findViewById(R.id.btn1);
        RippleEffect.setRippleDrawable(btn, getResources().getColor(R.color.btn_color), getResources().getColor(R.color.btn_highlight_color));
    }
}

基本的にはxmlで記述し、backgroundに設定してあげるやり方でいいと思いますが、動的に色を変えたい時などにはお勧めです。

【Swift 3】煩わしいString操作を簡単にする

Swift 3 Swift extension iOS iOS 10 小技 Xcode 8.1 Xcode String Range

Stringの操作についてObj-C時代から使い慣れているNSStringと比べ、随分使い勝手が悪いなーと感じることが多いです。

Qiitaになぜそうなっているのかという詳しい解説がありますが、使う上で不便なことには変わりないので、Extensionを書きました。

qiita.com

コード

extension String {
    func substring(to index: Int) -> String {
        return substring(to: self.index(self.startIndex, offsetBy: abs(index)))
    }
    func substring(from index: Int) -> String {
        return substring(from: self.index(self.endIndex, offsetBy: -abs(index)))
    }
    func substring(with range: NSRange) -> String {
        return substring(with: self.range(from: range)!)
    }
    func replacingCharacters(in range: NSRange, with text: String) -> String {
        return self.replacingCharacters(in: self.range(from: range)!, with: text)
    }
}

NSRangeRange<String.Index>の変換を行っている部分ですがStack Overflowから頂きました。

stackoverflow.com

extension String {
    func range(from nsRange: NSRange) -> Range<String.Index>? {
        guard let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
            let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
            let from = from16.samePosition(in: self),
            let to = to16.samePosition(in: self)
            else { return nil }
        return from ..< to
    }
}

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

.NET C# Microsoft WindowsForm

 今回は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フォーム アプリケーションだとかなり面倒ですね…。

【Xamarin.Android】XamarinプロジェクトのFabric登録【クラッシュレポート】

.NET Android C# Microsoft Visual Studio for Mac Xamarin Xamarin Studio

最近Javaのコード見るとうわっって思うくらいXamarinばっかり触っています。
今回もXamarinネタを頑張って書いていきます。

クラッシュレポート

アプリを開発しているとクラッシュレポートがほしくなってきますよね!
AndroidStudioで開発しているときはFabricを使用していたので、Xamarinでもそれでいいかなと思っていました。
しかし、なんとXamarinはFabric対応していないのです!!!

ちょっと調べてみたところ、Xamarinでクラッシュレポートを行う際はHockeyApp(元Xamarin Insightsという製品が使われていたよう)を使うのがいいみたいです。
だけど、今まで作ってきたアプリはFabricで管理してて、今回作るアプリだけHockeyAppかー、、、
どうせなら一緒にまとめたいなー、、、

と思い、XamarinでもFabricが使えるように頑張ってみました!笑
今回はその手順のご紹介です。(特にこだわりがなければHockeyAppにしましょう!)

Fabric利用手順

XamarinでFabricを利用するために、大きく以下のことを行います。

  1. Xamarinプロジェクト作成(パッケージ名設定)
  2. AndroidStudioでXamarinと同じパッケージ名でプロジェクトを作成
  3. AndroidStudioからアプリをFabricに登録
  4. Xamarin側でFabricの設定

以下詳細に記載

  1. 通常通りXamarinプロジェクトを作成

  2. プロジェクトの設定からパッケージ名を設定

  3. AndroidStudioでXamarinと同じパッケージ名でプロジェクトを作成

  4. AndroidStudioでFabric(Crashlytics)の登録
    Fabricの登録は以下のページを参考にしてください。
    www.dentare.net
    dev.classmethod.jp

  5. 一通りAndroidStudio側でFabricの設定を行ったら、一度アプリを実行してFabricにプロジェクトを登録
    (登録するだけなので、Hello worldとかでいいです)
    その後Fabricのサイトにログインし、先ほど実行したアプリが登録されていることを確認してください。

  6. Fabric公式サイトまたはFabricに登録するために作成したAndroidStudioのプロジェクトからAPIKEYを取得
    (Xamarin側で使用します)

  7. XamarinのNugetでhttps://github.com/azchohfi/Crashlytics.Droidを導入
    READMEに書いてある通りに、AndroidMajnifestとString.xml、ActivityのOnCreateを修正します!

  8. 修正後アプリを実行して、クラッシュしたときにFabricに送られていれば成功です!
    (クラッシュ用のコードを書いて確認すると楽です。)



正直ここまで面倒な手順を踏んでまでFabricに登録するメリットは少ないかもしれません、、、

今回はXamarinからFabricに登録する手段が分からなかったので、AndroidStudioでFabricに登録だけして、その後はXamarinから使用する方法を試してみました。
どうしてもFabricを使いたいって場合は試してみてください。