WinFormsでスクロール処理を行うと言うと、ScrollableControlの派生クラスを作ってAutoScrollMinSizeでコンテンツのサイズを指定するのが定石だと思うのですが、このAutoScrollMinSizeが上手いこと反映されない事がたまにあります。

以下、無理矢理再現させたコードです。

namespace ScrollTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);

            if (ClientSize == Size.Empty)
            {
                AutoScrollMinSize = new Size(8000, 8000);
            }
            else
            {
                AutoScrollMinSize = new Size(10, 10);
            }
            Debug.WriteLine("AutoScrollMinSize= {0}", AutoScrollMinSize);
        }
    }
}

見たとおりですがフォームを最小化した瞬間に、AutoScrollMinSizeを{8000, 8000}にし、元のサイズに戻したときには{10, 10}に設定します。
実行して最小化→元のサイズに戻すと、AutoScrollMinSizeが{8000, 8000}だった頃のスクロールバー状態のままになっています。

Edge2

この条件だけでしたら、ClientSize == Size.EmptyのときにはAutoScrollMinSizeを変更しない事で回避できますが、他にも何パターンか有るような気がします。
この症状について何か情報をお持ちの方がいらっしゃいましたらご連絡ください。

  • ある画像をマルチスレッド(分割統治法など)で事前にレンダリングしておき、キャッシュする。
  • OnPaintでそのキャッシュを使う。

なんて事をしようと思いまして、スレッドプールを使って処理をさせ、レンダリングが終わったらワーカースレッド側でManualResetEventのSet()を呼び出し、メインスレッド側ではWaitOneで各ワーカースレッドが終わるのを待つ・・・・というような処理を書きました。

で、たまに描画結果が期待したものにならない事がありまして調べてみると、
どうやらWaitOneで待っている間にOnPaintが呼ばれてしまう事があるようなのです。
(当然その段階ではキャッシュは作り終わっていないので、描画がおかしくなるというわけです。)

このような挙動は過去にMFCなどでネイティブアプリを作っている時には起こり得なかったため、特定に時間がかかりました。

WinFormsはそういう作りになっているそうです。
参考:
http://stackoverflow.com/questions/4540244/how-is-this-possible-onpaint-processed-while-in-waitone

ただ、WaitOneだけならともかく、他にもメインスレッドを待つ処理だったり、.NETのフレームワークの一部の機能を使ったとき(例えばクリップボードに格納されているデータを確認するとき)のような意図しない時にもOnPaintが呼ばれるのが厄介です。
(ツールバーの「貼り付け」ボタンの状態を更新しようとしたらOnPaintが呼ばれる、なんて事もありえます。)

こうした場合、

  • マルチスレッドで画像をレンダリングするのを止める(^^;
  • キャッシュを使わないようにする
  • OnPaintの中でキャッシュを作るようにする
  • フラグでなんとか回避する

などなど、注意してプログラムする必要がありそうです。

何か良いアイディアをご存知の方がいらっしゃいましたら、ご連絡くださいm(_ _)m
(先の参考サイトだとMutexなら起こらないとか書かれていますが、今回のケースだと使えないような?)

カスタムコントロールを作ってデザイナへ貼り付けても、InitializeComponent()内でnewされず、実行するとエラーになるケースが頻発しました。

これどうやら、カスタムコントロールのコンストラクタがpublicでないと起こるみたいです。
http://stackoverflow.com/questions/5684784/custom-user-control-not-initialized-in-auto-generated-code

interfaceの実装もpublic固定ですし、デザイナで表示させるプロパティもpublicである必要がありますし(うろ覚え)、データバインディングを行わせるためのプロパティもpublicでないといけません。

外部に公開する気は無いのでinternalをよく使っているのですが、結構こういう制限があって統一しきれないのが残念です。

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

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

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;
}

記事検索

アーカイブ