背景
LabelのTextを自力でDrawしたかったんです。
OnPaintの中でDrawStringすればオッケです。
簡単でしょ?
詳細
なるべく、Labelコントロールのプロパティを尊重したいです。
特にTextAlignプロパティは重要でした。
ところで、Label.TextAlign は ContentAlignment 型のプロパティです。
えーと、こんな感じ
しかし、DrawStringで表示位置を制御するにはStringAlignment型のアトリビュート二つ、つまり
- Alignment
- LineAlignment
の組み合わせで、縦横の位置を指定しなければなりません。
つまりAlignmentは、この位置を表していて
LineAlignmentは、この位置を表していて
この組み合わせで、ContentAlignmentと同じ位置指定ができるようになっている。
ちゅー訳です。
NearとFarの位置関係は日本語ではの話、と思ってね
分析
ところで、ContentAlignment型はenumで以下の値を持っています。
TopLeft = 1
TopCenter = 2
TopRight = 4
MiddleLeft = 16
MiddleCenter = 32
MiddleRight = 64
BottomLeft = 256
BottomCenter = 512
BottomRight =1024
StringAlignment型もenumで以下の値。
Near = 0
Center = 1
Far = 2
因みに、ContentAlignmentを図示すると以下の形。
enumの値は実際に立ったビットが2進数値としてどう評価されるかの値。
一方、StringAlignmentは下図。
enum値はただの、値を持った識別子。
変換 その1(あっち ⇒ こっち)
えーと、まずは分かり易そうな方から…
StringAlignmentはただの値、と云いながらもよ~く見てみると以下の対応関係が成り立つようになっています。
Left = Near なのでContentAlignment(の一部)とStringAlignmentの位置関係は、上の図のようになります。
で、着目すべきは赤い数字。
つまり、$2^{StringAlignment}$とすると、その結果が1だったり2だったり4だったりして、ContentAlignmentの対応位置にビットを立てる事ができるって訳です。
次は垂直方向の位置合わせです。
サクッと図にしてしまうと以下のような感じです。
Top / Middle / Bottom其々を4ビットのブロックとして考えると、StringAlignmentの値とブロックの位置関係を対応付ける事が出来ます。
つまり、水平位置の変換結果に垂直位置の変換値を掛け合わせる事で、ContentAlignmentに変換されます。
こう云う事―
ContentAlignment = Math.Pow(2, Alignment) * Math.Pow(16, LineAlignment);
わっかり難ーい!
と云う時には以下の様に考えても可。
$2^{Alignment}$ で求めた水平配置情報を―
- 0ブロック左シフトすると、垂直方向にはTopに配置される
- 1ブロック左シフトすると、垂直方向にはMiddleに配置される
- 2ブロック左シフトすると、垂直方向にはBottomに配置される
ContentAlignment = Math.Pow(2, Alignment) << (4 * LineAlignment);
4ビット左シフトすると云う事は、(2の4乗)=16を掛けるってのと同義だからね。
さて―
簡単な方は片付いたけど、本来やりたかったのはContentAlignmentを二つのStringAlignemntに分割する方でした。
変換 その2(こっち ⇒ あっち)
あっちからこっちへの変換は数式でできたので、こっちからあっちへの逆変換も数式で出来る筈です。
数ⅡBを思い出しましょう!
冪乗の逆関数は対数になります。
$2^n = x$ なら、$n = log_2 x$ と云う事です。
配置 | 冪乗 | 対数 |
---|---|---|
left | $2^0 = 1$ | $0 = log_2 1$ |
center | $2^1 = 2$ | $1 = log_2 2$ |
right | $2^2 = 4$ | $2 = log_2 4$ |
さて、もう一回この図を持ってきて逆の見方で読み解くと-
もし ContentAlignment の値が $1$ ならば、$log_2 1 = 0$ で、(Topの)Nearです。
もし ContentAlignment の値が $2$ ならば、$log_2 2 = 1$ で、(Topの)Middleになります。
もし ContentAlignment の値が $4$ ならば、$log_2 4 = 2$ で、(Topの)Farに変換できます。
じゃぁ、垂直方向がTop以外の場合はどうなるかと云うと、この場合も先ほどとは逆に4ビット単位で右シフトしてあげれば良いですね。
ここも例に依って対数です。
一つのブロックが4ビット($=2^4=16$)単位ですので $log_{16} ContentAlignment$ とすれば、ビットがどのブロックに含まれているかを知る事が出来ます。
えーと、ここにきて更に公式ですけど…
C#のMathライブラリにおいて扱えるのは、常用対数か自然対数のどちらか1です。(対数の底が $10$ か $e$ かって事)
上に出てきた様に底を $2$とか $16$ にしたければ以下の変換公式を使う必要があります。
$log_x n = log_{10} n / log_{10} x$
ここら辺を考慮すると縦位置はー
LineAlignment = Math.Floor(Math.Log10(ContentAlignment) / Math.Log10(16))
横位置の方は縦位置に従って右シフトした上で対数変換すれば…
Alignment = Math.Log10(ContentAlignment >> (4 * LineAlignment)) / Math.log10(2)
で、縦横のStringAlignmentに変換できました。
じゃぁ、C#で表現してみましょう
多分、Extensionにした方が使い易い様な気がする…
ちょっと癖が出てしまいましたが、双方向ともStringFormatに対する拡張メソッドになります。
public static class ExtClass {
public static ContentAlignment ToContentAlignment(this StringFormat Me) {
return (ContentAlignment)((int)Math.Pow(2, (int)Me.Alignment) << (4 * (int)Me.LineAlignment));
}
public static void SetStringAlignment(this StringFormat Me, ContentAlignment ca) {
int Valignment = (int)Math.Floor(Math.Log10((int)ca) / Math.Log10(16));
int Halignment = (int)(Math.Log10((int)ca >> (4 * Valignment)) / Math.Log10(2));
Me.LineAlignment = (StringAlignment)Valignment;
Me.Alignment = (StringAlignment)Halignment;
}
}
StringFormat sf = new StringFormat();
Console.WriteLine(sf.ToContentAlignment()); // sfのAlignmentをContentAlignmentで取得
sf.SetStringAlignment(ContentAlignment.BottomCenter); // ContentAlignemnetからsfにAlignmentをコピー
Console.WriteLine(sf.ToContentAlignment());
てな感じで…
-
.NET Frmeworkの場合 ↩