eye_am_groot
eye_am_groot

Reputation: 682

DataGrid Need A Horizontal ScrollBar and Resizing Capability

I have a DataGrid in a ScrollViewer. I have one of two problems:

  1. When the width is shrunk and the item text is cut off (the H2 column isn't wide enough), the scroll bar doesn't appear. However, the two columns that I have are sized appropriately and can't be resized off-screen. The XAML is (basically):

    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <DataGrid Width="{Binding ActualWidth, 
                      RelativeSource={RelativeSource AncestorType={x:Type ScrollViewer}}}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="H1" Width="40" MinWidth="35"/>
                <DataGridTextColumn Header="H2" Width="*" MinWidth="47"/>
            </DataGrid.Columns>
        </DataGrid>
    </ScrollViewer>
    
  2. When I have a working scrollbar, the columns can be resized so that the right one can be out of view (the center divider can be moved far enough right that the entire column is out of view). XAML:

    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <Grid>
            <DataGrid Width="{Binding ActualWidth, 
                          RelativeSource={RelativeSource AncestorType={x:Type Grid}}}">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="H1" Width="40" MinWidth="35"/>
                    <DataGridTextColumn Header="H2" Width="*" MinWidth="47"/>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </ScrollViewer>
    

(Note: in both cases, I have excluded the itemsource as I don't believe it is necessary for the question, I can add that, as well as the backend code, if needed).

I imagine that this has to do with the element that the DataGrid is bound to (maybe the Grid element doesn't resize appropriately, I have tried binding the width of the Grid to the ScrollViewer but the problem is the same as (1)). I have also noticed that the scroll bar does appear (both instances) when the vertical content is cut off and the vertical scroll bar appears (both appear at the same time). Any suggestions to have the scrollbar and ability to ensure that the columns doesn't go off-screen?

Upvotes: 1

Views: 1828

Answers (2)

manic_coder
manic_coder

Reputation: 176

Here's what I came up with by making use of VisualTreeHelper.

Create a method that can search the Visual Tree for a child of a particular type. I found a method for looking for a parent (the reverse direction) that I was able to modify. https://stackoverflow.com/a/636456/1640271.

public static T FindVisualChild<T>(DependencyObject parent)
    where T : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
        DependencyObject childObject = VisualTreeHelper.GetChild(parent, i);

        if (childObject == null) return null;

        if (childObject is T obj)
        {
            return obj;
        }
        else
        {
            return FindVisualChild<T>(childObject);
        }
    }

    return null;
}

My code behind included creating one constant, one class variable, and using two Window event triggers.

Constant

(imposes a constraint on the Window width to ensure that at least some portion of the second columns can always be seen).

private const double _item2MinViewableWidth = 100;

Variable

(used to capture the Grid from the Visual Tree that is inside the ScrollView of the DataGrid).

private Grid _innerGrid;

Events

private void Window_ContentRendered(object sender, EventArgs e)
{
    _innerGrid = UIHelper.FindVisualChild<Grid>(dataGrid);

    item2Column.MinWidth = _item2MinViewableWidth;

    item1Column.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
    item2Column.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);

    MaxWidth = ActualWidth;

    _innerGrid.MinWidth = item1Column.ActualWidth + _item2MinViewableWidth;
    _innerGrid.Width = item1Column.ActualWidth + _item2MinViewableWidth;

    UpdateLayout();

    var mainGridInitWidth = mainGrid.ActualWidth;
    MinWidth = ActualWidth;
    _innerGrid.Width = Double.NaN;

    mainGrid.Width = mainGridInitWidth;
}

private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (e.WidthChanged && _innerGrid != null)
    {
        var widthDetla = e.NewSize.Width - e.PreviousSize.Width;
        mainGrid.Width += widthDetla;
    }

    if (Math.Abs(ActualWidth - MaxWidth) < 1)
    {
        dataGrid.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
    }
    else
    {
        dataGrid.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
    }
}

Here's my XAML for good measure:

<Window
x:Class="WPFSandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFSandbox"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Height="450"
ContentRendered="Window_ContentRendered"
ResizeMode="CanResize"
SizeChanged="Window_SizeChanged"
SizeToContent="Width">

<Grid x:Name="mainGrid">
    <DataGrid x:Name="dataGrid"
        AutoGenerateColumns="False"
        CanUserResizeColumns="False"
        HeadersVisibility="Column"
        IsReadOnly="True"
        ItemsSource="{Binding MyItems}">
        <DataGrid.ColumnHeaderStyle>
            <Style TargetType="{x:Type DataGridColumnHeader}">
                <Setter
                    Property="SeparatorVisibility"
                    Value="Hidden" />
            </Style>
        </DataGrid.ColumnHeaderStyle>
        <DataGrid.Columns>
            <DataGridTextColumn x:Name="item1Column"
                Binding="{Binding Item1}"
                Header="Item1" />
            <DataGridTextColumn x:Name="item2Column"
                Binding="{Binding Item2}"
                Header="Item2" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

Hopefully, this is in line with what you need.

Upvotes: 1

Matt Norrie
Matt Norrie

Reputation: 666

The problem is that the width of the DataGrid is bound to the width of the ScrollViewer

<DataGrid Width={Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}">

The DataGrid is never going to be wider than the ScrollViewer so the scrollbar will not be enabled.

Setting HorizontalAlignment to 'Stretch' will achieve the layout that I think you are looking for and allow scrolling. So the DataGrid element ought to be:

 <DataGrid HorizontalAlignment="Stretch">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="H1" Width="40" MinWidth="35"/>
                    <DataGridTextColumn Header="H2" Width="*" MinWidth="47"/>
                </DataGrid.Columns>
            </DataGrid>

Upvotes: 1

Related Questions