MikeGeek MikeGeek - 2 months ago 25
C# Question

How To Avoid Horizontal Scrollbar When Proportionally Resizing WPF ListView Columns

Goal: In a WPF Grid, programmatically resize the columns in a child ListView when the window size is changed, maintaining relative column sizes, without ever displaying a horizontal scrollbar.

Currently, the proportional resizing is working very well except that when I reduce the width of the window, I will get a horizontal scrollbar that has only a tiny amount of space it scrolls. I am wondering if this is due to the Width property not accounting for the graphic dividers between columns?.. or..?

The part that is causing the problem is the last section where I extend the width of the last column to fill the rest of the space. I don't want to subtract some magic number that I produce from trial & error (which might work to a certain extent).

..and yes, eventually I will account for the presence (or not) of a vertical scrollbar, but right now I just want to avoid ever seeing a horizontal scrollbar.

Here is the code that resizes the ListView columns:

LV_FileList.SizeChanged += this.onLV_FileList_SizeChanged;


...

/// <summary>
/// Proportionally resize listview columns when listview size changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void onLV_FileList_SizeChanged(object sender, SizeChangedEventArgs e)
{
if ((sender is ListView) &&
(e.PreviousSize.Width > 0))
{
double total_width = 0;
GridViewColumnCollection gvcc = ((GridView)(sender as ListView).View).Columns;
foreach (GridViewColumn gvc in gvcc)
{
gvc.Width = (gvc.Width / e.PreviousSize.Width) * e.NewSize.Width;
total_width += gvc.Width;
}

//Increase width of last column to fit width of listview if integer division made the total width to small
if (total_width < e.NewSize.Width)
{
gvcc[gvcc.Count - 1].Width += (e.NewSize.Width - total_width);
}
}
}


I added a While loop to the problematic section, but it has the unfortunate effect of not working. The ComputedHorizontalScrollBarVisibilityProperty value never changes as the width of the last column is decremented, so it just goes to 0 and throws an invalid value exception for the width of the column. I even tried throwing a call to LV_FileList.UpdateLayout() in the loop, thinking that maybe the display of the listview control needs to be refreshed or something before the horizontal scrollbar goes away. No dice.

//Increase width of last column to fit width of listview if integer division made the total width to small
if (total_width < e.NewSize.Width)
{
gvcc[gvcc.Count - 1].Width += (e.NewSize.Width - total_width);
while ((Visibility)LV_FileList.GetValue(ScrollViewer.ComputedHorizontalScrollBarVisibilityProperty) == Visibility.Visible)
{
gvcc[gvcc.Count - 1].Width--;
//LV_FileList.UpdateLayout(); <-- doesn't help
}
}

Answer

Here's what I ended up with. It handles a vertical scrollbar as well. the one drawback I've found so far is that the horizontal scrollbar will sometimes flash briefly while the user is resizing the window. If anyone knows a better way, please post! Thanks!

        /// <summary>
    /// Proportionally resize listview columns when listview size changes
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void onLV_FileList_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if ((sender is ListView) && 
            (e.PreviousSize.Width > 0))
        {
            double total_width = 0;
            GridViewColumnCollection gvcc = ((GridView)(sender as ListView).View).Columns;
            foreach (GridViewColumn gvc in gvcc)
            {
                gvc.Width = (gvc.Width / e.PreviousSize.Width) * e.NewSize.Width;
                total_width += gvc.Width;
            }

            //Increase width of last column to fit width of listview if integer division made the total width to small
            if (total_width < e.NewSize.Width)
            {
                gvcc[gvcc.Count - 1].Width += (e.NewSize.Width - total_width);
            }

            //Render changes to ListView before checking for horizontal scrollbar
            this.AllowUIToUpdate();

            //Decrease width of last column to eliminate scrollbar if it is displayed now
            ScrollViewer svFileList = this.FindVisualChild<ScrollViewer>(LV_FileList);
            while ((svFileList.ComputedHorizontalScrollBarVisibility != Visibility.Collapsed) &&  (gvcc[gvcc.Count - 1].Width > 1))
            {
                gvcc[gvcc.Count - 1].Width--;
                this.AllowUIToUpdate();
            }
        }
    }


    /// <summary>
    /// Threaded invocation to handle updating UI in resize loop
    /// </summary>
    private void AllowUIToUpdate()
    {
        DispatcherFrame dFrame = new DispatcherFrame();

        Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new DispatcherOperationCallback(delegate(object parameter)
        {
            dFrame.Continue = false;
            return null;

        }), null);

        Dispatcher.PushFrame(dFrame);
    }