13

.NET UserControl(から派生ScrollableControl)は、水平および垂直スクロールバーを表示する機能を備えている必要があります。

呼び出し元は、これらの水平および垂直スクロールバーの可視性と範囲を設定できます。

UserControl.AutoScroll = true;
UserControl.AutoScrollMinSize = new Size(1000, 4000); //1000x4000 scroll area

注:UserControlie )は、ウィンドウスタイルをScrollableControl指定するWindows標準メカニズムを使用して、スクロールバーを表示します。つまり、個別のWindowsまたは.NETスクロールコントロールを作成せず、ウィンドウの右/下に配置します。Windowsには、一方または両方のスクロールバーを表示するための標準メカニズムがあります。WS_HSCROLLWS_VSCROLL

ユーザーがコントロールをスクロールすると、またはメッセージUserControlが送信されます。これらのメッセージに応答して、ScrollableControlがクライアント領域を無効にすることを望みます。これはネイティブWin32で発生することです。WM_HSCROLLWM_VSCROLL

switch (uMsg) 
{ 
   case WM_VSCROLL:
       ...
       GetScrollInfo(...);
       ...
       SetScrollInfo(...);
       ...

       InvalidateRect(g_hWnd, 
              null, //erase entire client area
              true, //background needs erasing too (trigger WM_ERASEBKGND));
       break;
 }

クライアントエリア全体を無効にする必要があります。問題は、UserControl(つまりScrollableControl)がAPI関数を呼び出すことです。ScrollWindow

protected void SetDisplayRectLocation(int x, int y)
{
    ...
    if ((nXAmount != 0) || ((nYAmount != 0) && base.IsHandleCreated))
    {
        ...
        SafeNativeMethods.ScrollWindowEx(new HandleRef(this, base.Handle), nXAmount, nYAmount, null, ref rectClip, NativeMethods.NullHandleRef, ref prcUpdate, 7);
    }
    ...
}

ScrollableControlは、クライアントの四角形全体でInvalidateRectをトリガーするのではなく、クライアント領域の既存のコンテンツを「サルベージ」しようとします。たとえば、ユーザーが上にスクロールすると、現在のクライアントコンテンツがによって押し下げられScrollWindowEx新しくカバーされていない領域のみが無効になり、WM_PAINT:がトリガーされます。

ここに画像の説明を入力してください

上の図では、チェッカーボード領域は無効なコンテンツであり、次のWM_PAINT中にペイントする必要があります。

私の場合、これは良くありません。コントロールの上部には「ヘッダー」(リストビューの列ヘッダーなど)が含まれています。このコンテンツをさらに下にスクロールするのは正しくありません。

ここに画像の説明を入力してください

そしてそれは視覚的な腐敗を引き起こします。

ScrollableControlでを使用せScrollWindowEx、代わりにクライアント領域全体を無効にします。

OnScroll保護されたメソッドをオーバーライドしてみました:

protected override void OnScroll(ScrollEventArgs se)
{
   base.OnScroll(se);

   this.Invalidate();
}

しかし、それはダブルドローを引き起こします。

注:ダブルバッファリングを使用して問題を隠すことはできますが、それは実際の解決策ではありません

  • リモートデスクトップ/ターミナルセッションではダブルバッファリングを使用しないでください
  • CPUリソースの無駄です
  • それは私が尋ねている質問ではありません

Control代わりにUserControl(つまり、継承チェーンの前に)を使用して、ScrollableControlHScrollまたはVScroll .NETコントロールを手動で追加することを検討しましたが、これも望ましくありません。

  • Windowsは、スクロールバーの位置の標準的な外観をすでに提供しています(複製するのは簡単ではありません)
  • これは、 ScrollWindowExではなくInvalidateRectのみを実行したい場合に、最初から再現する必要のある多くの機能です。

を確認して投稿できるので、内部のコードは、ScrollableControlの使用を無効にするプロパティがないことを知っていますが、?ScrollWindowの使用を無効にするプロパティはありScrollWindowますか?


アップデート:

問題のあるメソッドをオーバーライドし、リフレクターを使用してすべてのコードを盗もうとしました。

protected override void SetDisplayRectLocation(int x, int y)
{
    ...
    Rectangle displayRect = this.displayRect;
    ...
    this.displayRect.X = x;
    this.displayRect.Y = y;
    if ((nXAmount != 0) || ((nYAmount != 0) && base.IsHandleCreated))
    {
        ...
        SafeNativeMethods.ScrollWindowEx(new HandleRef(this, base.Handle), nXAmount, nYAmount, null, ref rectClip, NativeMethods.NullHandleRef, ref prcUpdate, 7);
    }
    ...
}

問題は、SetDisplayRectLocationがプライベートメンバー変数()の読み取りと書き込みを行うことdisplayRectです。MicrosoftがC#を変更して、子孫がプライベートメンバーにアクセスできるようにしない限り、私はそれを行うことができません。


アップデート2

の実装をコピーアンドペーストし、1つの問題ScrollableControlを修正すると、継承チェーン全体をコピーアンドペーストする必要があることに気付きました。UserControl

...
   ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
      ContainerControl2 : ScrollableControl2, IContainerControl
         UserControl2 : ContainerControl2

私は、オブジェクト指向のデザインに反対するのではなく、それ を使って作業することを本当に望んでいます。

4

3 に答える 3

7

これを投稿してくれてありがとう、私は同じ問題を抱えていました。私はあなたの問題の解決策を見つけたかもしれません。私の解決策は、スクロールメッセージを処理するためにWndProcをオーバーロードし、基本クラスハンドラーの呼び出し中に再描画をオフにして、メッセージが処理された後にウィンドウ全体の再描画を強制することです。このソリューションは問題なく機能しているようです。

    private void sendRedrawMessage( bool redrawFlag )
    {
        const int WM_SETREDRAW = 0x000B;

        IntPtr wparam = new IntPtr( redrawFlag ? 1 : 0 );
        Message msg = Message.Create( Handle, WM_SETREDRAW, wparam, IntPtr.Zero );
        NativeWindow.FromHandle( Handle ).DefWndProc( ref msg );
    }

    protected override void WndProc( ref Message m )
    {
        switch ( m.Msg )
        {
            case 276: // WM_HSCROLL
            case 277: // WM_VSCROLL
                sendRedrawMessage( false );
                base.WndProc( ref m );
                sendRedrawMessage( true );
                Refresh(); // Invalidate all
                return;
        }

        base.WndProc( ref m );
    }

WndProcをオーバーロードするという提案と、SetDisplayRectLocationをオーバーロードできないという観察結果を組み合わせて、これを試してみようと思いました。UserControlによるスクロールイベントの処理中にWM_PAINTを無効にするとうまくいくかもしれないと思いました。

お役に立てれば。

トム

于 2013-07-06T02:56:10.307 に答える
1

マイクロソフトのプログラマーと連絡を取ってみましたか?あなたがマイクロソフトに連絡すれば、あなたは彼らにあなたの質問を投稿することができ、おそらく電話サポートを受けることさえできると確信しています。

.NETFrameworkをサポートするためのリンクは次のとおりです。ここをクリックしてください。電子メール、電話、またはオンラインで.NETサポートの専門家と連絡を取ることができると記載されています。

于 2012-03-03T19:51:23.183 に答える
0

トムのソリューションは素晴らしいですが、小さな最適化の機会があると思います。トムの2つの方法がないと、たとえばスクロールバーの端点をクリックしてスクロールを発生させると、onPaintに1回の呼び出しが表示されます。Tomの2つのメソッドを追加すると、onPaintは同じスクロールバーの位置に対して2つの呼び出しを取得し始めます。私にとっての解決策は、発生する最後のSB_ENDSCROLLを無視することであるように思われました。それはスクロール操作です。これで、同じスクロール位置に重複するペイントが表示されなくなりました。

private void sendRedrawMessage(bool redrawFlag)
{
    const int WM_SETREDRAW = 0x000B;

    IntPtr wparam = new IntPtr(redrawFlag ? 1 : 0);
    Message msg = Message.Create(Handle, WM_SETREDRAW, wparam, IntPtr.Zero);
    NativeWindow.FromHandle(Handle).DefWndProc(ref msg);
}

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case 276: // WM_HSCROLL
        case 277: // WM_VSCROLL
            if ((ushort)m.WParam == 8) // SB_ENDSCROLL ignore scroll bar release
                break;
            sendRedrawMessage(false);
            base.WndProc(ref m);
            sendRedrawMessage(true);
            Refresh(); // Invalidate all
            return;
    }

    base.WndProc(ref m);
}
于 2015-08-01T21:28:59.867 に答える