【问题标题】:WPF Grid row height Auto with maximum of Star (*)WPF 网格行高自动,最大星号 (*)
【发布时间】:2020-10-13 18:52:44
【问题描述】:

我有一个带有以下代码的 UserControl(为了便于阅读而进行了简化):

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TextBlock Grid.Row="0" />
    <TextBlock Grid.Row="1" />
    <DataGrid Grid.Row="2" />
    <TextBlock Grid.Row="3" />
</Grid>

现在我希望所有控件都显示在堆栈中,但限于窗口大小。

问题是当我在 DataGrid 中有大量数据时,它会超出边界并且最后一个 TextBlock 不可见 - 也不显示 DataGrid 滚动条。

当我为 Star (*) 设置第三行定义时,DataGrid 的大小适合大量项目,但如果 DataGrid 中只有少数项目,最后一个 TextBlock 会出现在屏幕底部(不需要直接在 DataGrid 之后)。

当我使用 StackPanel 而不是 Grid 时,它看起来与上面的代码相同。如果我使用 DockPanel,DataGrid 可以正常滚动,但最后一个 TextBlock 根本不可见。

我会想象将第三行定义为 Height="Auto"MaxHeight="*" 的解决方案,但这显然是不可能的。

你能帮忙吗?

【问题讨论】:

  • 我不太确定我是否理解您想要实现的目标。您想要: 1. 始终显示 4 个控件。 2.最后一个TextBlock正好在DataGrid的底部,但不一定在父网格的底部?

标签: c# .net wpf datagrid


【解决方案1】:

您需要以编程方式执行此操作,而不是在 xaml 中。这是因为您希望它做两件不同的事情:

  1. 如果只有几个项目,则将最后一个 TextBlock 靠近 DataGrid。
  2. 如果 DataGrid 有大量项目,则保持最后一个 TextBlock 可见。

这样做需要您在代码隐藏中挂钩事件,确定最后一个 TextBlock 是否消失,然后在 RowDefinition 上相应地调整 Height="Auto" 或 Height="*",然后是 UpdateLayout。

这是一个示例项目。为了简单起见,我用 TextBlock 替换了您的 DataGrid。

XAML:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Content="Make Grid.Row=2 Long, But Keep Text 3 Visible" Click="Button_Click" HorizontalAlignment="Center" Margin="5" Padding="5,10"/>
        <Grid Grid.Row="1" x:Name="myGrid">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition x:Name="myRowDefinition" Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <TextBlock Grid.Row="0" Text="This is Text 1" Background="Red"/>
            <TextBlock Grid.Row="1" Text="This is Text 2" Background="Green"/>
            <TextBlock Grid.Row="2" x:Name="myDataGrid" FontSize="64" Text="{Binding Output}" TextWrapping="Wrap" Background="Blue"/>
            <TextBlock Grid.Row="3" x:Name="lastTextBlock" Text="This is Text 3" Background="Violet"/>
        </Grid>
    </Grid>

代码隐藏:

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string output;

        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += OnLoaded;
            this.DataContext = this;
        }

        /// <summary>
        /// Handles the SizeChanged event of your data grid.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MyDataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (!IsUserVisible(lastTextBlock, this))
            {
                if (this.myRowDefinition.Height == GridLength.Auto)
                {
                    // Edit the row definition and redraw it
                    this.myRowDefinition.Height = new GridLength(1, GridUnitType.Star);
                }
            }
            else
            {
                if (this.myRowDefinition.Height != GridLength.Auto && CanDataGridBeSmaller(this.myRowDefinition.ActualHeight))
                {
                    // If the datagrid can be smaller, change the row definition back to Auto
                    this.myRowDefinition.Height = GridLength.Auto;
                }
            }
            this.UpdateLayout();
        }

        /// <summary>
        /// It is possible for the DataGrid to take on more space than it actually needs.  This can happen if you are messing with the window resizing.
        /// So always check to make sure that if you can make the DataGrid smaller, that it stays small.
        /// </summary>
        /// <param name="actualHeight">the actual height of the DataGrid's row definition</param>
        /// <returns></returns>
        private bool CanDataGridBeSmaller(double actualHeight)
        {
            // Create a dummy control that is equivalent to your datagrid (for my purposes, I used a Textblock for simplicity, so I will recreate it fully here.
            TextBlock dummy = new TextBlock() { TextWrapping = TextWrapping.Wrap, FontSize = 64, Text = this.Output };
            dummy.Measure(new Size(this.myGrid.ActualWidth, this.myGrid.ActualHeight));

            // Get the dummy height and compare it to the actual height
            if (dummy.DesiredSize.Height < myRowDefinition.ActualHeight)
                return true;
            return false;
        }

        /// <summary>
        /// This method determines if the control is fully visible to the user or not.
        /// </summary>
        private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
        {
            if (!element.IsVisible)
                return false;

            Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
            Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
            return rect.Contains(bounds);
        }

        /// <summary>
        /// This is purely for setup.
        /// </summary>
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            this.myDataGrid.SizeChanged += MyDataGrid_SizeChanged;
            this.Output = "This row is short, so Text 3 below me should be flush with my bottom.";
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public string Output { get => this.output; set { this.output = value; this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Output))); } }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.Output = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
        }
    }

开始时的示例输出:

点击顶部按钮后的示例输出:

【讨论】:

    【解决方案2】:

    @Tam Bui --- 很抱歉没有在评论部分回复,但我的字符用完了:/

    您的解决方案有效,谢谢。但如果 DataGrid 中有大量数据,它似乎效率不高 - 看起来它一次加载所有行(如自动设置)。

    根据您的解决方案,我想出了更高效、更简单的解决方案:

    private void OnSizeChanged(object sender, RoutedEventArgs e)
    {
        if (!IsLoaded) return;
    
        AdjustGridSize();
    }
    
    private void AdjustGridSize()
    {
        GridRowDefinition.Height = new GridLength(1, GridUnitType.Star);
        UpdateLayout();
    
        ExpensesGrid.MaxHeight = GridRowDefinition.ActualHeight;
        GridRowDefinition.Height = GridLength.Auto;
    }
    

    GridRowDefinition 是 DataGrid 所在行的定义,ExpensesGrid 是我的 DataGrid。

    此外,还应该在 Loaded 事件调用中调用 AdjustGridSize 方法来初始调整大小。

    如果您发现此解决方案有任何缺点,请告诉我。

    【讨论】:

    • 看起来不错!很高兴我的建议帮助您找到更好的解决方案。
    【解决方案3】:

    我建议您使用DockPanel,其中DataGrid 是填充的最后一个孩子。将DockPanel.MaxHeight设置为父ActualHeight作为约束,但不要设置Height,那么当列表中的item很少时,整个DockPanel会缩小。

    完整示例:

    <Window x:Class="WpfApplication1.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Window.Resources>
            <!-- TEST data for demonstration -->
            <XmlDataProvider x:Key="MockList" XPath="/MockObjects/*" >
                <x:XData >
                    <MockObjects xmlns="">
                        <MockObject Name="Test 1" />
                        <MockObject Name="Test 2" />
                        <MockObject Name="Test 3" />
                        <MockObject Name="Test 4" />
                        <MockObject Name="Test 5" />
                        <MockObject Name="Test 6" />
                        <MockObject Name="Test 7" />
                        <MockObject Name="Test 8" />
                        <MockObject Name="Test 9" />
                    </MockObjects>
                </x:XData>
            </XmlDataProvider>
        </Window.Resources>
        <!-- Stretching Parent-->
        <Grid Name="parentGrid">
            <DockPanel Width="200" MaxHeight="{Binding ElementName=parentGrid,Path=ActualHeight}" VerticalAlignment="Top" HorizontalAlignment="Left">
                <TextBlock DockPanel.Dock="Top">Test</TextBlock>
                <TextBlock DockPanel.Dock="Top">Test</TextBlock>
                <!-- Notice change of order here -->
                <TextBlock DockPanel.Dock="Bottom" Background="LightBlue">Test</TextBlock>
                
                <DataGrid ItemsSource="{Binding Source={StaticResource MockList}, XPath=/MockObjects/MockObject}" AutoGenerateColumns="False">
                    <DataGrid.Columns>
                        <DataGridTextColumn Header="Text" Binding="{Binding XPath=@Name}"/>
                    </DataGrid.Columns>
                </DataGrid>
            </DockPanel>
        </Grid>
    </Window>
    

    【讨论】:

      猜你喜欢
      • 2012-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-14
      • 1970-01-01
      • 2018-09-03
      • 2013-01-05
      • 2012-07-03
      • 2011-07-27
      相关资源
      最近更新 更多