1

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?

Funk
  • 10,976
  • 1
  • 17
  • 33
eye_am_groot
  • 682
  • 6
  • 19
  • 1
    Why not use the datagrid's built in horizontalscrollbar? – Andy Jan 17 '19 at 16:32
  • Same issue as (1) – eye_am_groot Jan 17 '19 at 16:35
  • Why shouldn't the columns go off-screen? You can use the scrollbar to reach them. Or vice versa, why the scrollbar if all columns should remain on screen? – Funk Jan 25 '19 at 17:27
  • @Funk, I'd like for the user to be able to see all (both) columns at all time and be able to scroll if the content in the columns goes off screen – eye_am_groot Jan 25 '19 at 17:42
  • So, you want the all-columns-in-sight behavior both when resizing the container (window) and resizing the columns? Does that mean resizing should be stopped when the last column is about to go out of sight? – Funk Jan 25 '19 at 17:52
  • Yes, **both** columns should always be visible. The DataGrid can be resized (there will be a `MinWidth` and `MaxWidth` for the DataGrid) but the columns should fill the DataGrid. If the content of a row is too large to fit in the available space, the scroll should be present so that the user can scroll the content in the available space – eye_am_groot Jan 25 '19 at 17:58
  • 1. If the first column is so wide that it fills the entire viewable area, is that okay as long as the user can scroll? Or must the user always see some portion of both columns? 2. Must the entire viewable area be filled up? in other words, you don't want empty space between the right bound of the second columns and the right bound of the datagrid? 3. Would using code behind be an acceptable solution? – manic_coder Jan 30 '19 at 05:28

2 Answers2

1

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>
Matt Norrie
  • 646
  • 4
  • 15
1

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.

manic_coder
  • 176
  • 7
  • This seems like the answer. The only issue occurs if the header is longer than any of the data. Once I figure that out, the issue will be solved. – eye_am_groot Jan 30 '19 at 15:47
  • I think you can use DataGridLength(1, DataGridLengthUnitType.SizeToHeader) to figure out the width for the header. You can use this result as a third component in determining your minimum widths. – manic_coder Jan 30 '19 at 16:16
  • Actually, just noticed that it gives option to increase (to the right) the column off-screen. – eye_am_groot Jan 30 '19 at 19:59
  • @Greg Can you be more specific so I can be helpful? Is this not the behavior you desired? Is the issue that the second column can be widened to exist beyond the viewable area? Perhaps you could update your question with some visual aids to facilitate your description of the activity you need and how it is distinguished from my suggested solution? From your written description, I get the sense that i'm missing a nuance; i just can't pinpoint what it is. – manic_coder Jan 30 '19 at 20:36
  • "Is the issue that the second column can be widened to exist beyond the viewable area?" Yes, with your solution, that is the (I believe only) issue I have. Ideally, the user wouldn't be able to resize the columns so that they disappear from view. – eye_am_groot Jan 31 '19 at 12:16
  • @Greg Okay, but if the second column can never be widened enough to have any part of it exist beyond the right bound of the viewable area, why would you ever need horizontal scroll bars? Wouldn’t that just mean that some of your text might not be viewable in the second column? – manic_coder Jan 31 '19 at 12:52
  • That's what happens in (1) from my question. I'd like to be able to horizontally scroll to make all text viewable. If H2 contains a cell with "MyData1" in it, but is only wide enough to display "MyDa" I want to be able to scroll so that you see "yDat," "Data," "ata1," etc. – eye_am_groot Jan 31 '19 at 13:22
  • But in your example, wouldn’t H2 need to exist, at least some part of it, outside the viewable area? How else could you see all the text in that column, even if you have to scroll? – manic_coder Jan 31 '19 at 13:29
  • Yes, sorry, I think I understand the confusion. Both columns should be at least **partially** visible. The whole column need not be visible. If part of the column is cut off, the scroll bar should allow the user to scroll to see cut off parts. – eye_am_groot Jan 31 '19 at 13:32
  • That helps. What should happen if the text of H1 is greater than the viewable area? – manic_coder Jan 31 '19 at 13:33
  • Good question. Let's assume that all of H1 is always visible (there's a min width on both the column as well as the area, in your example the grid, that H1 and H2 take up) to ensure this. – eye_am_groot Jan 31 '19 at 13:37
  • In that case, what is the difference between what you are asking for, and just setting both column width properties to SizeToCells? – manic_coder Jan 31 '19 at 13:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187662/discussion-between-greg-and-manic-coder). – eye_am_groot Jan 31 '19 at 13:42
  • I think just click the link and you should be in – eye_am_groot Jan 31 '19 at 13:52
  • Since this is indirectly helpful to me on a project i'm working on, I've been doing more research. Looks like VisualTreeHelper can solve a lot of the "nastiness" of my proposed solution. I'm playing around with it now and if I come up with something better I'll replace my answer. – manic_coder Jan 31 '19 at 23:40
  • This is close. The only problem that I'm having is that if you shrink the H1 column, you can see the third column. (I also probably don't want the `MaxWidth` feature, but that's secondary). I appreciate your work, unless someone jumps in last minute with the **_perfect_** answer, I'll award the bounty to you in a bit – eye_am_groot Feb 01 '19 at 12:41
  • Thanks - that's why i set it up to prevent users from resizing the columns. Instead, they just automatically size to the contents. This prevents the "third column" from appearing, but might not be suitable to your needs. – manic_coder Feb 01 '19 at 14:18
  • I think it's probably a good enough baseline that I can suit it for my needs – eye_am_groot Feb 01 '19 at 14:24