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

あたも開発ブログ

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

【RxSwift】RxSwiftを触ってみる

RxSwiftとは

ReactiveX(Reactive Extensions)のSwift実装で非同期のイベントベースのプログラムを実装するためのライブラリです。

導入

CocoaPodsやCarthageで導入可能です。

今回はCocoaPodsを使います。

target 'RxSwiftTest' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!
  pod 'RxSwift',    '~> 3.0'
  pod 'RxCocoa',    '~> 3.0'
  # Pods for RxSwiftTest

  target 'RxSwiftTestTests' do
    inherit! :search_paths
    # Pods for testing
    pod 'RxBlocking', '~> 3.0'
    pod 'RxTest',     '~> 3.0'
  end

  target 'RxSwiftTestUITests' do
    inherit! :search_paths
    # Pods for testing
    pod 'RxBlocking', '~> 3.0'
    pod 'RxTest',     '~> 3.0'
  end

end

使ってみる

テキストフィールドの入力をラベルへリアルタイムに反映してみます。

RxSwiftを使わないで実装する場合はUITextFieldのDelegateを実装したりNotificationを使う必要があります。

f:id:atamo_dev:20170515200329g:plain:w300

Delegateでの実装

class ViewController: UIViewController, UITextFieldDelegate {
    
    var label: UILabel = UILabel()
    var field: UITextField = UITextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ラベルをViewへ追加
        label.frame.size = CGSize(width: UIScreen.main.bounds.size.width, height: 60)
        label.center = CGPoint(x: view.center.x, y: view.center.y - 100)
        view.addSubview(label)
        
        // テキストフィールドをViewへ追加
        field.frame.size = CGSize(width: UIScreen.main.bounds.size.width, height: 60)
        field.center = view.center
        field.borderStyle = .roundedRect
        field.delegate = self
        view.addSubview(field)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        
    }
    
    // MARK: - UITextFieldDelegate
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let text = textField.text as NSString? else {
            return false
        }
        label.text = text.replacingCharacters(in: range, with: string)
        return true
    }
}

RxSwiftでの実装

これをRxSwiftで実装するとこうなります。

import UIKit
import RxSwift      
import RxCocoa      

let disposeBag = DisposeBag()   

class ViewController: UIViewController {
    
    var label: UILabel = UILabel()
    var field: UITextField = UITextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ラベルをViewへ追加
        label.frame.size = CGSize(width: UIScreen.main.bounds.size.width, height: 60)
        label.center = CGPoint(x: view.center.x, y: view.center.y - 100)
        view.addSubview(label)
        
        // テキストフィールドをViewへ追加
        field.frame.size = CGSize(width: UIScreen.main.bounds.size.width, height: 60)
        field.center = view.center
        field.borderStyle = .roundedRect
        view.addSubview(field)
        
        // テキストフィールドのテキストをラベルへバインド
        field.rx.text.map {$0}.bindTo(label.rx.text).addDisposableTo(disposeBag)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        
    }
    
}

DelegateやNotificationを使用した場合は処理がいろいろな場所へ散乱してしまい非常に読みづらいですが、RxSwiftを使うとスッキリと書くことができます。

参考

http:// http://qiita.com/jollyjoester/items/c4013c60acd453ea7248

CG 背景イラスト 夏の海

f:id:atamo_dev:20170521155933p:plain

これからの季節、イラストに欠かせない夏の海を描いてみます。

 

使用ソフト:PhotoShop,SAI

ペンタブ:INTUOS PTH-851

 

今回作成したpsdファイルは下記URLに配置してあります。

https://drive.google.com/drive/folders/0B9dZ314mf8WoTGpYZVBqZmtzVUk?usp=sharing

再配布以外でしたら個人用途に限りご自由にお使いください。

報告も、配布元の表示も不要です。

 

1.PhotoShopで新規ファイルを作成

今回は横2000×縦1200でファイルを作成しました。

 

 

2.海と空のベースを作成

「グラデーションツール」を使い空と海の下地を描画します。

 f:id:atamo_dev:20170521164910p:plain

 

 

今回グラデーションに設定する色の値は、それぞれ下記の画像の通りです。

f:id:atamo_dev:20170518193623p:plain

 

1.新規レイヤーを作成 → 海面レイヤーと命名します

2.海面レイヤーに長方形選択ツールとグラデーションツールを使って海面を描く

3.続いて背景レイヤーを選択 → グラデーションツールを使い空を描く

f:id:atamo_dev:20170518194134p:plain

 

 

 3.砂浜のベースを作成

ボケ足の強いブラシツールを使って手前側に砂浜を描きます。

 f:id:atamo_dev:20170521164909p:plain

 

 

1.最上面に新規レイヤーを作成 → 砂浜レイヤーと命名

2.手前側に大ざっぱに砂浜の下地を描いていきます

f:id:atamo_dev:20170518194405p:plain 

ここまで描いたら一旦ファイルを保存し、PhotoShopを終了します。(保存形式はpsdを選択してください)

 

 

4.海面に明暗を描画

保存したファイルをSAIで開き、「水彩筆」を使って海面に明暗をつけます。

 f:id:atamo_dev:20170516202931p:plain

 

 

「水彩筆」の設定は下記の通りです。(今回使う水彩ブラシはこれのみ)

f:id:atamo_dev:20170515191956p:plain 

 

1.海面レイヤーを選択し、「水彩筆」を使って海面に明暗を描き込んでいきます。奥にいくほど暗く細かく、手前ほど明るく大きくなるように、ホライズンラインに沿って筆を動かします。飽くまで遠景なので細かく描く必要はありません。大雑把に描きます。

f:id:atamo_dev:20170518194625p:plain

 

 

5.波打ち際の作成

波打ち際に泡を描きます。この工程で一気に海っぽくなります。

 f:id:atamo_dev:20170521164911p:plain

 

 

1.砂浜レイヤーを選択 → 不当明度保護にチェックをいれる

2.「水彩筆」を使い海面との境界付近を暗く塗ります

f:id:atamo_dev:20170518194622p:plain

3.砂浜レイヤーの上に新規レイヤーを作成 → 泡レイヤーと命名

4.泡レイヤーに水彩ブラシを使い白色(R255 G255 B255)で泡を描きこみます。波打ち際にいくほど泡の密度を高くします。

f:id:atamo_dev:20170521141711p:plain

5.泡レイヤーの下に新規レイヤーを作成 → 泡影レイヤーと命名

6.泡影レイヤーにうっすらと泡の陰を落とします。

f:id:atamo_dev:20170521142121p:plain

 

 

6.砂浜の描き込み

砂浜に陰影をつけます。非常に地味な工程ですが、これをやっておくと完成度があがります。

 f:id:atamo_dev:20170521164912p:plain

 

 

1.砂浜レイヤーを選択 → 砂浜に濃い部分と淡い部分を描きこみます。細かく描く必要はまったく無く、表面をなでる程度でかまいません。

f:id:atamo_dev:20170521142851p:plain

ここで一旦ファイルを保存し、SAIを終了します。(保存形式は先ほどと同じpsd形式を選択してください)

 

 

7.砂テクスチャの作成

PhotoShopの「フィルター」を使って砂のテクスチャを作成します。

 f:id:atamo_dev:20170521164913p:plain

 

 

PhotoShopを立ち上げ、横2000pixel × 縦2000pixelで新規ファイルを作成します。

[フィルター] → [ノイズを加える] → [グレースケールノイズ]でざらついたテクスチャを作成します。 

f:id:atamo_dev:20170521144116p:plain

これを[選択範囲] → [全てを選択]し、[編集] → [コピー]でコピーしておきます。 

f:id:atamo_dev:20170521144117p:plain

 

 

8.砂テクスチャの貼り付けと合成

先ほど作成したテクスチャを砂浜に貼り付けます。

 f:id:atamo_dev:20170521164914p:plain

 

 

1.PhotoShopで先ほどまで描いていた海のファイルを展開。

2.砂浜レイヤーの上に砂テクスチャを貼り付けます。

3.[変形] → [遠近法]を使いテクスチャを変形。

f:id:atamo_dev:20170521144118p:plain

4.砂浜レイヤーと「クリッピングパス」を作成します。続いてレイヤーの合成モードを「焼き込みカラー」にし、透明度を調整(ここでは20%に設定)したうえで下の砂浜レイヤーと結合します。

f:id:atamo_dev:20170521144119p:plain

ここで再びファイルを保存し、PhotoShopを終了します。(保存形式はpsdを選択してください)

 

 

9.雲の描画

空に雲を描きます。今回は夏の海という設定なので夏らしく積乱雲を描いてみました。今回は海がメインなので簡単に。雲の描き方はまた別記事で紹介させてください。

 f:id:atamo_dev:20170521164915p:plain

 

 

1.SAIを開いて、背景レイヤーの上に新規レイヤーを作成 → 雲レイヤーと命名

f:id:atamo_dev:20170521144120p:plain

2.「筆」と「水彩筆」を使って雲のベースとなる形を描きます。

雲というと、ついついボケ足の強いブラシを使ってしまいがちですが、積乱雲の場合、ボカさずに輪郭を取っておいて、一部分のみをボカすことでメリハリがでます。

3.明るい部分と暗い部分を描き足し、それぞれ境界の一部分を周囲の色となじませていきます。

f:id:atamo_dev:20170521144121p:plain

 

 

10.仕上げ

海と空に諧調変化に差をつけて、遠近感を強調します。下記の手順で三つのレイヤーを作成し、それぞれに「エアブラシ」を使って色をおいていきます。

 f:id:atamo_dev:20170521164916p:plain

 

 

1.背景レイヤーの上に新規レイヤーの作成 → 諧調変化と命名

空の手前側を濃く鮮やかにします。

2.雲レイヤーの下に新規レイヤーの作成 → 雲なじませと命名

空と雲の間を明るくして、両者をなじませます。

3.海面レイヤーの上にに新規レイヤーの作成 → 手前光レイヤーと命名

レイヤー合成モードを「発光」にし、海面の手前に光を入れて明るくします。

f:id:atamo_dev:20170521193846p:plain

 

 

何となくさみしかったので近景に石垣と緑を描きました。近景を描くと奥行き感が出る気がします。

f:id:atamo_dev:20170521164917p:plain

 

完成。

 最後まで読んでいただきありがとうございました。

 

By SetJapanCorporation,Inc. Designer : すんすん。

【C#】Windowsフォーム アプリケーションでDispatcherTimerを利用する

 今回はWindowsフォーム アプリケーションでThreading.DispatcherTimerを使用してみます。
 Windows.Forms.Timerはスレッドセーフではありますが、WindowメッセージのWM_TIMERで処理されるため遅いという欠点があります。
 その点、DispatcherTimerはスレッドセーフかつ、UIスレッド内での動作が基本となっており、Forms.Timerを使うくらいならThreading.DispatcherTimerの使用を推奨します。
 WPFへ移行する際もそのまま利用できますし、Timers.TimerはWPFではサポートされていないため非常にお勧めです。

 フォームにラベルを配置し、ボタンを押すとテロップのように移動するサンプルとなっています。

デザイナー側実装

namespace DispatcherTimerSample
{
  partial class Form1
  {
    /// <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 Windows フォーム デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.button1 = new System.Windows.Forms.Button();
      this.label1 = new System.Windows.Forms.Label();
      this.SuspendLayout();
      // 
      // button1
      // 
      this.button1.Location = new System.Drawing.Point(186, 12);
      this.button1.Name = "button1";
      this.button1.Size = new System.Drawing.Size(86, 42);
      this.button1.TabIndex = 0;
      this.button1.Text = "移動開始";
      this.button1.UseVisualStyleBackColor = true;
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // label1
      // 
      this.label1.Font = new System.Drawing.Font("メイリオ", 18F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128)));
      this.label1.Location = new System.Drawing.Point(169, 97);
      this.label1.Name = "label1";
      this.label1.Size = new System.Drawing.Size(116, 30);
      this.label1.TabIndex = 1;
      this.label1.Text = "移動する";
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(284, 262);
      this.Controls.Add(this.label1);
      this.Controls.Add(this.button1);
      this.Name = "Form1";
      this.Text = "Form1";
      this.Load += new System.EventHandler(this.Form1_Load);
      this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Label label1;
  }
}

 

 Visual Studioのプロジェクトの「参照の追加」→「アセンブリ」→「フレームワーク」と選択してWindowsBaseへの参照を追加してください。

コードビハインド側実装

using System;
using System.Windows.Forms;
using System.Windows.Threading;

namespace DispatcherTimerSample
{
  public partial class Form1 : Form
  {
    #region メンバ変数
    /// <summary>
    /// 画面更新制御タイマー
    /// </summary>
    DispatcherTimer _DispatcherTimer = new DispatcherTimer();
    #endregion

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

    #region イベント
    /// <summary>
    /// フォームが初めて表示される直前に発生します。
    /// </summary>
    /// <param name="sender">イベント発生オブジェクト</param>
    /// <param name="e">イベント引数</param>
    private void Form1_Load(object sender, EventArgs e)
    {
      this._DispatcherTimer.Tick += LabelMoveDispatcherTimer_Tick;
    }

    /// <summary>
    /// ボタンコントロールがクリックされたときに発生します。
    /// </summary>
    /// <param name="sender">イベント発生オブジェクト</param>
    /// <param name="e">イベント引数</param>
    private void button1_Click(object sender, EventArgs e)
    {
      this._DispatcherTimer.Interval = TimeSpan.FromMilliseconds(100);
      this._DispatcherTimer.Start();
    }

    /// <summary>
    /// タイマーの間隔が経過したときに発生します。
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void LabelMoveDispatcherTimer_Tick(object sender, EventArgs e)
    {
      if (this.label1.Left - 1  < -this.Left)
      {
        this.label1.Left = this.Width;
      }
      else
      {
        this.label1.Left -= 1;
      }
    }
    #endregion
  }
}

 わたしの担当時、今期はWindowsフォーム アプリケーションで色々とサンプルをご紹介してきましたが、まだまだ業務では使えるかな?という印象でした。
 来期もまた別のテーマで色々とナレッジを残せたらと思います。

【Android】よく使う共通処理【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で管理している色情報を簡単に扱う

はじめに

今回は、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フォーム アプリケーションで拡大・縮小ラベルユーザコントロール

 今回は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でボタンなどを押したときに出てくる波紋状のエフェクトですが、通常なら以下のように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に設定してあげるやり方でいいと思いますが、動的に色を変えたい時などにはお勧めです。