iPhone/iPad用ドット絵エディタ「EDGE touch」

最近のエントリー

MFCどっぷり時代からC#のWinFormsをいじりだして半年ぐらい経った気がしますが、ラジオボタンの上手な使い方がどこを調べてもいまいち解りませんでした。

MFCならラジオボタンのどこにチェックが入っているかインデックス値を取得・設定出来るのですが、WinFormsはその辺のサポート機能が一切無いんですよね。
コンボボックスならデータバインディング等も使えていろんなサイトでも紹介されているのですが、ラジオボタンの記事はほとんど見かけた事がありません。

いろいろ試した結果、なんとか着地できたようなので、晒してみたいと思います。

まず、MFCと違い、WinFomrsでは「ラジオボタンのグループ化=どっかのコンテナにまとめて乗せる」というのが大前提です。
(これ最初は違和感有ったのですが。)

逆に言えば、コンテナに対してラジオボタンの便利関数を実装出来れば良さそうです。
C#は拡張メソッドが使えますから、以下のような関数を実装してみました。

/// <summary>
/// ラジオボタンを抽出
/// </summary>
/// <param name="parent"></param>
/// <returns></returns>
internal static IEnumerable<RadioButton> GetRadioButtons(this Control parent)
{
    return parent.Controls.Cast<Control>().Select(x => x as RadioButton).Where(x => x != null);
}

/// <summary>
/// Tagを元にラジオボタンにチェックを入れる
/// </summary>
/// <param name="parent"></param>
/// <param name="tag"></param>
internal static void CheckRadioButtonByTag(this Control parent, object tag)
{
    foreach (var radio in parent.GetRadioButtons())
    {
        if (radio.Tag.Equals(tag))
        {
            radio.Checked = true;
            break;
        }
    }
}

/// <summary>
/// チェックが入ったラジオボタンのタグを取得する
/// </summary>
/// <param name="parent"></param>
/// <returns></returns>
internal static object GetCheckedRadioButtonTag(this Control parent)
{
    foreach (var radio in parent.GetRadioButtons())
    {
        if (radio.Checked)
        {
            return radio.Tag;
        }
    }
    return null;
}

/// <summary>
/// ラジオボタンのチェックイベントを設定
/// </summary>
/// <param name="parent"></param>
/// <param name="action"></param>
internal static void SetRadioButtonCheckedEvent(this Control parent, Action<RadioButton> action)
{
    foreach (var radio in parent.GetRadioButtons())
    {
        radio.CheckedChanged += (sender, _) =>
        {
            RadioButton r = (RadioButton)sender;
            if (r.Checked)
            {
                action(r);
            }
        };
    }
}

あとはFormのLoadイベント辺りで

sizeB5RadioButton.Tag = PaperKind.B5;
sizeA4RadioButton.Tag = PaperKind.A4;
sizeB4RadioButton.Tag = PaperKind.B4;
sizeA3RadioButton.Tag = PaperKind.A3;
sizeGroupBox.CheckRadioButtonByTag([デフォルトの用紙サイズ]);
sizeGroupBox.SetRadioButtonCheckedEvent(radio => [ラジオボタンにチェックが入った時の処理]);

などと呼べば、良いかと思います。
この例は、フォーム上のグループボックスに用紙サイズのラジオボタンがいくつか並んでいるものです。

ラジオボタンのTagにコードであらかじめEnum値などを入れておき、それをやりくりするのがミソです(Enumで無くとも数値でも文字列でも何でも良いのですが)。
イベントに関してもデザイナで一つ一つに割り当てるのは面倒くさいので、コードで一気にやってしまいます。

後からチェックの入った情報を取り出したい時は

(PaperKind)sizeGroupBox.GetCheckedRadioButtonTag()

などとすれば良いです。

タイプセーフ的にあんまりなコードですが、いちいちフォームでラジオボタンのチェックプロパティを見に行ってチェックされていたら云々、と書くよりは良いと思うのですがどうでしょうかね。

ラジオボタンの定石をご存知の片がいらっしゃいましたら、こっそり教えてもらえると助かりますm(_ _)m

Activity(またはView)の状態保存・復元はonSaveInstanceStateとonRestoreInstanceStateとで行われますが、これのテストが困難だったりします。

(ちょっと憶測も入っていますが)Activityを呼び出したり戻したりすればonSaveInstanceStateとonRestoreInstanceStateが呼ばれるので、コードは一応通るのですが、大抵の場合はストレージ(?)への書き出し・読み込み処理は走らず、メモリ内のキャッシュデータを読み書きしているだけです。

ですが、いざという時(メモリ不足時?)には実際にストレージへの書き出し・読み込み処理が走り、いままで何の問題も無かった箇所で落ちる、何てことが起こりえます。

特に、カスタムViewを作り、Parcelオブジェクトを使って読み書きしている部分は、普段は処理が走らないので厄介です。
参考:
Viewの状態を保存する
http://ym02.blogspot.com/2010/08/view.html

iPhoneだとシミュレータでメモリ不足時のシミュレートが出来るようになっていますが、Androidはそのような事が出来るのでしょうか?
(知っている人いたら教えて下さい)

そこで、擬似的にこの辺りのテストを行うコードを書いてみました。

private void test()
{
	Parcel p1 = Parcel.obtain();
	Bundle b1 = new Bundle();
	onSaveInstanceState(b1);
	p1.writeBundle(b1);
	
	byte[] bytes = p1.marshall();
	
	Parcel p2 = Parcel.obtain();
	p2.unmarshall(bytes, 0, bytes.length);
	p2.setDataPosition(0);
	Bundle b2 = p2.readBundle();
	b2.setClassLoader(getClassLoader());
	onRestoreInstanceState(b2);
}

ただ、このコードによる保存・復元処理が正常時と同じかどうかは保障できません(^^;
また、私自身javaの経験が浅く、ClassLoader辺りはよく理解していません・・・。
(でもsetClassLoaderしないと、うまくparcelが取り出せないんですよね)

最近挙動シリーズが多い気がしますが(^^;

Rectangle rc = new Rectangle(10, 10, 10, 10);
Debug.WriteLine(Rectangle.Union(rc, Rectangle.Empty));

出力結果:
{X=0,Y=0,Width=20,Height=20}

矩形の合成って結構頻繁にやると思うのですが、上のコードのようにEmptyとUnionしたとき、期待する結果になりません。
Emptyは矩形が何もないという意味合いで使いたいのですが、合成するとEmptyのLeft, Top, Right, Bottomも影響を与えてしまっています。

特にMFCのCRectの感覚で使っていると痛い目を見ます(CRectはEmptyと合成したときには、Emptyは合成結果に影響を与えない)。

なので、これを回避するには、以下のような処理をする必要があるかと思います。
(このコード実行してないので、うまく動かないかもしれませんが(^^;)

public static void Union(ref Rectangle rc1, Rectangle rc2)
{
    if (rc1.IsEmpty)
    {
        rc1 = rc2;
    }
    else if (!rc2.IsEmpty)
    {
        rc1 = Rectangle.Union(rc1, rc2);
    }
}

// 拡張メソッド版
public static Rectangle Union(this Rectangle rc1, Rectangle rc2)
{
    if (rc1.IsEmpty)
    {
        return rc2;
    }
    else if (!rc2.IsEmpty)
    {
        return Rectangle.Union(rc1, rc2);
    }
    return rc1;
}

Visual Studioにもツールバーにコンボボックスが乗っていますが、そこへフォーカスを当てた状態で他のボタンの上へマウスカーソルを持っていけば、枠が表示されて選択状態となります。
Edge2

これと同じことをWinFormsのToolStripでやろうとすると、何故か挙動が違い、選択状態になりません。
Edge4

しかも同じツールバーに乗ってるコントロール上にフォーカスが有るときだけ、選択状態にならず、別のツールバー上にマウスカーソルを持っていけば、そちらではちゃんと選択状態になるという中途半端っぷりです。

こうなっているのは何かしら理由があるのかもしれませんが、気持ち悪いのでVisual Studioのような挙動にしたいところです。

.NET frameworkのソースを見た感じだと切り替えられるようにはなっていないようなので、こんな感じでどうでしょう。

private void Form6_Load(object sender, EventArgs e)
{
    Bugfix(toolStrip1);
}

private static void Bugfix(ToolStrip toolStrip)
{
    foreach (var item in toolStrip.Items)
    {
        if (item is ToolStripButton || item is ToolStripSplitButton || item is ToolStripDropDownButton)
        {
            ((ToolStripItem)item).MouseEnter += (sender, _) =>
            {
                var i = (ToolStripItem)sender;
                if (i.Enabled)
                {
                    i.Select();
                }
            };
        }
    }
}

副作用とかはあるかもしれません。

パスのバウンズを取得するときに、GraphicsPath.GetBoundsを利用することがありますが、引数に行列とペンを引き渡してバウンズを取得できる物も用意されています。

しかし実際に使ってみると、希望していたバウンズとは違う結果が取得されてしまいました。

Edge1

using (Pen pen = new Pen(Color.Black, 20))
using (Pen redPen = new Pen(Color.Red))
using (Pen bluePen = new Pen(Color.Blue))
using (GraphicsPath path = new GraphicsPath())
{
    path.AddLine(30, 30, 120, 120);
    e.Graphics.DrawPath(pen, path);

    RectangleF rc = path.GetBounds(null, pen);
    e.Graphics.DrawRectangle(redPen, rc.X, rc.Y, rc.Width, rc.Height);

    path.Widen(pen);
    rc = path.GetBounds();
    e.Graphics.DrawRectangle(bluePen, rc.X, rc.Y, rc.Width, rc.Height);
}

ソースを見てもらえれば判ると思いますが、赤で囲った矩形がGetBounds(Matrix, Pen)で取得したバウンズ、青で囲った矩形がWidenでペンをパス化した結果に対してGetBounds() したバウンズです。
結構余白が大きく入りますね。
ペンの太さが太かったり、矢印等のアンカーが付いていたりするとより顕著です。

より正確にバウンズを取得したい場合はGetBounds(Matrix, Pen)は使わないほうが良さそうです。
(動作速度もそこそこ差がありそうですが、今回は計っていません)

記事検索

コミュニティ

Banner designed by よし様
ドット絵掲示板
ドット絵掲示板
自作の「ドット絵」を投稿することができます。ドッターさん同士の交流の場としてご利用ください。
TakaboSoft Wiki
TakaboSoft Wiki
ソフトウェアに関する質問・不具合報告・要望などを書き込むことができます。