schoetbi / XPTable

sourceforge.net XPTable clone with my patches
Other
53 stars 28 forks source link

Rendering of NoItemsText is broken when hscrolling due to overzealous filtering of Invalidate() #41

Open antiduh opened 7 years ago

antiduh commented 7 years ago

The rendering of the NoItemText property, or similar outputs (ie, no table model, no column model) is broken due to the way that calls to Invalidate() are filtered in the out OnHorizontalScroll handler.

The paint code for NoItemText always attempts to paint the text in the center of the client rectangle of the control, that is, it's always supposed to be center, irrespective of scroll position. Hint number 1 that something is broken is that in a control that has wide enough columns to have a horizontal scrollbar, the text moves with the 'background' of the control.

If you shrink the window down, and then scroll such that some portion of text ends up off screen, it never repaints correctly when the text comes back on screen. Usually, the background is white, or contains 'stuttering' paints.

The OnPaint handler is definitely trying to repaint the text every time its called, and it's definitely being called whenever scrolling happens - the problem is that the control is double buffered and our Invalidate() filtering logic is a little too overzealous. Since we're double buffered, the .Net code that handles the double buffering never copies from our painted-to buffer for this portions of the UI, because we never invalidate it.

Here is the current implementation of the scroll handler - note, that it does not have any handling for invalidating the region where we're painting the NoItemText:

protected void OnHorizontalScroll( object sender, ScrollEventArgs e )
{
    // stop editing as the editor doesn't move while
    // the table scrolls
    if( this.IsEditing )
    {
        this.StopEditing();
    }

    if( this.CanRaiseEvents )
    {
        // non-solid row lines develop artifacts while scrolling
        // with the thumb so we invalidate the table once thumb
        // scrolling has finished to make them look nice again
        if( e.Type == ScrollEventType.ThumbPosition )
        {

            if( this.GridLineStyle != GridLineStyle.Solid )
            {
                if( this.GridLines == GridLines.Rows || this.GridLines == GridLines.Both )
                {
                    this.Invalidate( this.CellDataRect, false );
                }
            }

            // same with the focus rect
            if( this.FocusedCell != CellPos.Empty )
            {
                this.Invalidate( this.CellRect( this.FocusedCell ), false );
            }
        }
        else
        {
            this.HorizontalScroll( e.NewValue );
        }
    }
}

If you replace that method with the following (hereforth called "The Cheap Fix"), it works:

protected void OnHorizontalScroll( object sender, ScrollEventArgs e )
{
    // stop editing as the editor doesn't move while
    // the table scrolls
    if( this.IsEditing )
    {
        this.StopEditing();
    }

    if( this.CanRaiseEvents )
    {
        this.Invalidate( this.CellDataRect, false );

        if( e.Type != ScrollEventType.ThumbPosition )
        {
            this.HorizontalScroll( e.NewValue );
        }
    }
}

I've attached a video of the broken behavior and a video showing the behavior with The Cheap Fix:

XPTable NoItemsText.zip

antiduh commented 7 years ago

The more complicated fix would to extract the logic from OnPaintEmptyTableText() to know when the NoItemsText needs to be painted, then use that logic in both OnPaintEmptyTableText() and OnHoritizontalScroll()'s invalidation logic.