none
PanelコントロールのマウスによるScroll動作時の再描画 RRS feed

  • 質問

  • 皆様お世話になります。

    長年使ってきたC++Builderを諦めてVisualStudio C#.NETで再出発です。

    勉強がてら、昔C++Builderで作った簡単なプログラムをいくつか移植しており、Panel.AutoScrollMinsizeを設定してスクロールバーを表示し、Scrollイベントで内容をスクロール表示することはできました。

    しかし、マウスホイールにてAutoScrollPositionは変化し、描画した内容も勝手にスクロールするのにScrollイベントが発生しません。

    仕方がないので、MouseWheelイベントを拾ってPanel.Invalidate()し、Paint()イベントは発生しましたが、スクロールした新しい領域に描画されません。何が起きているのでしょうか?

    Visual Studio Community2019 Version16.8.3、.NET Frameworkのプロジェクト設定は4.7.2です。

    なお、Panelを継承したカスタムコントロールを作ってダブルバッファーを設定していましたが、とりあえずコンストラクタで何もしないようにしても変わらないことを確認したところです。

    そもそも、Panelを使いたいわけではなく、C++Builderでは、SetScrollInfo()、GetScrollInfo()を使ってカスタムコントロールを作っていましたが、C#でWin32APIを呼び出すのはかなり敷居が高くなっていますが、C#でもWin32APIを呼び出すスタイルは普通なのでしょうか?

    よろしくお願いします。

    2021年2月1日 21:26

回答

  • 長年C++でプログラムしていたならわかると思うのですが、マウスホイールのイベントは昔はありませんでいた。
    その後マウスホイール用のイベントWM_MOUSEWHEELが追加されました。
    ただしWM_MOUSEWHEELにどのように反応するかどうかは決められていません。
    さらに、Windows10まではアクティブではないウィンドウにはWM_MOUSEWHEELは送られませんでした。
    (Windows10->Windowsの設定->デバイス->マウス->ポイントしたときに非アクティブウィンドウをスクロールする)

    .NET FrameworkのFormsのPanelはWindows10よりも昔からあり、WM_VSCROLLとWM_HSCROLLに反応していれば十分だったのです。
    つまり、.NET FrameworkのForms.PanelはWM_MOUSEWHEELに反応してScrollイベントを発生するようにはなっていません。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
    
            Panel p = new Panel()
            {
                Width = 200,
                Height=200,
                Left=50,
                Top=50,
                AutoScroll=true,
                AutoScrollMinSize=new Size(500,500)
            };
            //typeof(Panel)
            //    .GetProperty("DoubleBuffered", System.Reflection.BindingFlags.NonPublic| System.Reflection.BindingFlags.Instance)
            //    .SetValue(p, true);
    
            p.Paint += p_Paint;
            p.Scroll += p_Scroll;
            this.ClientSize = new Size(300, 300);
            this.Controls.Clear();
            this.Controls.Add(p);
    
        }
    
        private void p_Scroll(object sender, ScrollEventArgs e)
        {
            var p=(Panel)sender;
            p.Invalidate();
        }
    
        private void p_Paint(object sender, PaintEventArgs e)
        {
            var p=(Panel)sender;
            var x = p.HorizontalScroll.Value;
            var y = p.VerticalScroll.Value;
            var text = $"{DateTime.Now:HH:mm:ss.fff} {x:000},{y:000}";
            e.Graphics.DrawString(text, this.Font, Brushes.Red, new Point(10, 100));
        }
    
    }


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2021年2月2日 8:46 PanelのWndProcのコードへのリンクを追加
    • 回答としてマーク M14Cluster 2021年2月2日 14:47
    2021年2月2日 4:07

すべての返信

  • 長年C++でプログラムしていたならわかると思うのですが、マウスホイールのイベントは昔はありませんでいた。
    その後マウスホイール用のイベントWM_MOUSEWHEELが追加されました。
    ただしWM_MOUSEWHEELにどのように反応するかどうかは決められていません。
    さらに、Windows10まではアクティブではないウィンドウにはWM_MOUSEWHEELは送られませんでした。
    (Windows10->Windowsの設定->デバイス->マウス->ポイントしたときに非アクティブウィンドウをスクロールする)

    .NET FrameworkのFormsのPanelはWindows10よりも昔からあり、WM_VSCROLLとWM_HSCROLLに反応していれば十分だったのです。
    つまり、.NET FrameworkのForms.PanelはWM_MOUSEWHEELに反応してScrollイベントを発生するようにはなっていません。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
    
            Panel p = new Panel()
            {
                Width = 200,
                Height=200,
                Left=50,
                Top=50,
                AutoScroll=true,
                AutoScrollMinSize=new Size(500,500)
            };
            //typeof(Panel)
            //    .GetProperty("DoubleBuffered", System.Reflection.BindingFlags.NonPublic| System.Reflection.BindingFlags.Instance)
            //    .SetValue(p, true);
    
            p.Paint += p_Paint;
            p.Scroll += p_Scroll;
            this.ClientSize = new Size(300, 300);
            this.Controls.Clear();
            this.Controls.Add(p);
    
        }
    
        private void p_Scroll(object sender, ScrollEventArgs e)
        {
            var p=(Panel)sender;
            p.Invalidate();
        }
    
        private void p_Paint(object sender, PaintEventArgs e)
        {
            var p=(Panel)sender;
            var x = p.HorizontalScroll.Value;
            var y = p.VerticalScroll.Value;
            var text = $"{DateTime.Now:HH:mm:ss.fff} {x:000},{y:000}";
            e.Graphics.DrawString(text, this.Font, Brushes.Red, new Point(10, 100));
        }
    
    }


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2021年2月2日 8:46 PanelのWndProcのコードへのリンクを追加
    • 回答としてマーク M14Cluster 2021年2月2日 14:47
    2021年2月2日 4:07
  • 補足すると、WM_MOUSEWHEELの以前の仕様では

     (1)キーボードフォーカスが無いと送られません (M$さんはホイールはキーボードに付くと確信???)。
     (2)トップレベルウィンドウにのみ送られます。

    ですね。
    https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel

    2021年2月2日 6:15
  • gekka様、仲澤@失業者様、済みません。

    描画されない原因は、AutoScrollPositionがスクロールバーの値だと勘違いしていたためでした。

    デバッグしていて負の値が出るので変だとは思っていたのですが、精度の悪いエンコーダでは起こり得ることですし、Panelのスクロールバーが何故負の値をとるのかは、それはまた質問しようと思って、とりあえず描画用のオフセット値をゼロに切り上げる処理だけ追加して原因を追及していませんでした。

    大変、失礼しました。

    2021年2月2日 14:47