GridSplitter的MinWidth与固定大小

5

我有一个包含两列的网格,通过以下XAML代码使用GridSplitter进行分隔:

<Grid>
    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="100" MinWidth="20" />
    <ColumnDefinition Width="10" />
    <ColumnDefinition Width="*" MinWidth="100" />
    </Grid.ColumnDefinitions>

    <Rectangle Fill="Blue" />
    <GridSplitter Grid.Column="1" Background="LightGray" HorizontalAlignment="Stretch" />
    <Rectangle Fill="Yellow" Grid.Column="2" />
</Grid>

问题:右侧列的MinWidth被忽略。
  • 页面加载时,第一列的宽度必须为“100px”,因此不能使用*大小。
  • 我不想在第一列上设置MaxWidth。

*我知道这个问题之前已经解决了,但它总是建议将列大小设置为*或在第一列上设置maxWidth...我不想要那个。


找到了一个解决方案,但它很丑!:p,有人有更简洁的方法来实现我想要的东西...无需编码(如果可能的话)?

private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
   var g = (Grid)sender;

   Double maxW = e.NewSize.Width - g.ColumnDefinitions[2].MinWidth - g.ColumnDefinitions[1].ActualWidth;
   g.ColumnDefinitions[0].MaxWidth = maxW;
}

你是否尝试将列的宽度设置为“自动”而不是“*”? - mlemay
不,将第一列的宽度设置为自动并不能解决问题。另外,我希望在打开时我的第一列宽度为100像素(我也无法将其内容宽度设置为100,因为它不会重新调整大小)。 - danbord
你想要的是:第一列在启动时需要为100像素,不能小于20像素,而最后一列将占据窗口上剩余的所有空间,但至少应该为100像素? - mlemay
准确地说,在启动后(左列设置为100px),我希望GridSplitter正常工作,左列最小值为20px,右列最小值为100px。 - danbord
为什么您不想在第一列设置最大宽度? - Shawn Kendrot
我不想在第一列设置最大宽度,因为我事先不知道网格的大小。我可以在GridSizeChanged事件中计算并设置第一列的MaxWidth,但这样做并不干净利落。我希望有更好的解决方案。 - danbord
2个回答

6
基本问题在于网格分隔器通过调整左列的宽度来工作,仅假定右列将star-size以适应剩余空间。这意味着你要解决的问题实际上是“如何限制左列的最大宽度,以使右列不会太小?”。这基本上就是你的代码示例所做的事情。
如果你想要一个更便携的解决方案,在XAML中实现,可以创建一个Silverlight behavior,可以应用于网格(如下所示)。它将附加到父网格的SizeChanged事件,并几乎完全执行你的代码片段的操作,但作为行为,你可以在Blend中拖放它们或在XAML中附加它们。
这是一个我为你准备的示例行为(基于你自己的代码):

MinWidthSplitterBehavior.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace GridSplitterMinWidth
{
    public class MinWidthSplitterBehavior : Behavior<Grid>
    {
        public Grid ParentGrid { get; set; }

        protected override void OnAttached()
        {
            base.OnAttached();
            ParentGrid = this.AssociatedObject as Grid;
            ParentGrid.SizeChanged += parent_SizeChanged;
        }

        void parent_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (ParentGrid.ColumnDefinitions.Count == 3)
            {
                Double maxW = e.NewSize.Width - ParentGrid.ColumnDefinitions[2].MinWidth -
                              ParentGrid.ColumnDefinitions[1].ActualWidth;
                ParentGrid.ColumnDefinitions[0].MaxWidth = maxW;
            }
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            if (ParentGrid != null)
            {
                ParentGrid.SizeChanged -= parent_SizeChanged;
            }
        }
    }
}

并像这样使用:

<UserControl x:Class="GridSplitterMinWidthApp.MainPage"
    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"
    xmlns:GridSplitterMinWidth="clr-namespace:GridSplitterMinWidth"
    xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" MinWidth="20" />
                <ColumnDefinition Width="10" />
                <ColumnDefinition Width="*" MinWidth="100" />
            </Grid.ColumnDefinitions>
            <interactivity:Interaction.Behaviors>
                <GridSplitterMinWidth:MinWidthSplitterBehavior/>
            </interactivity:Interaction.Behaviors>
            <Rectangle Fill="Blue" />
            <Controls:GridSplitter Grid.Column="1" Background="LightGray" HorizontalAlignment="Stretch">
            </Controls:GridSplitter>
            <Rectangle Fill="Yellow" Grid.Column="2" />
        </Grid>
    </Grid>
</UserControl>

我不认为这是普遍适用的,因为如果您将所有内容都设置为星号大小,则右侧列的MinWidth将受到尊重。有关更多详细信息,请参见我的答案。 - PatrickV
@PatrickV:鉴于现在已经是2014年了,而我已经好几年没有使用Silverlight了,你很有可能是正确的 :) - iCollect.it Ltd

4
这在我看来是WPF GridSplitter中的一个bug。问题的根源是在Width列中使用了不同类型的值。
这样是行不通的:
<Grid.ColumnDefinitions>
    <ColumnDefinition Width="100" MinWidth="20" />
    <ColumnDefinition Width="10" />
    <ColumnDefinition Width="*" MinWidth="100" />
</Grid.ColumnDefinitions>

由于[0]列宽为“100”,是一个明确的数字类型,而[1]列宽为“*”,但以下代码可以正常工作:
<Grid.ColumnDefinitions>
    <ColumnDefinition Width="*" MinWidth="20" />
    <ColumnDefinition Width="10" />
    <ColumnDefinition Width="*" MinWidth="100" />
</Grid.ColumnDefinitions>

当您这样做时,GridSplitter将同时尊重左侧和右侧的"MinWidth"值。在我看来,这意味着WPF中存在错误。
在我的情况下,我需要将左列设置为带有最小宽度的“Auto”,将右列设置为带有“*”和最小宽度,但貌似没有任何方法可以在不添加一些代码后台的情况下完成这个操作。我猜最简单的解决方案是将所有大小设置为“*”,并使用自定义行为替换列的“auto”功能(因为这是一个单列、一次性的修复),但我还没有这样做。希望XAML看起来像这样:
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" MinWidth="20" />
        <ColumnDefinition Width="10" />
        <ColumnDefinition Width="*" MinWidth="100" />
    </Grid.ColumnDefinitions>
    <interactivity:Interaction.Behaviors>
        <MyBehaviors:GridColumnStarSizedToAutoBehavior Column="0"/>
    </interactivity:Interaction.Behaviors>
    ...
</Grid>        

更新*: 我按照上述帖子中的建议实现了一种行为。 由于网格限制,它并不像我希望的那么简单。 我无法发布解决方案,因为公司有限制,但我可以给出一些提示。 我遇到的最大问题是,即使使用行为代码,也无法摆脱星号大小,因为它会使星号大小列的MinWidth停止工作。
为了弥补这一点,需要处理网格的所有列。 您必须获取要自动调整大小的列,通过获取每列的UIElement并在其上使用Measure来确定其子元素的所需大小,然后将其星号大小值更改为总可用星号大小间距的百分比。 您还必须相应地调整非自动星号大小列的星号大小值。
例如,考虑具有3个列的网格:第一个星号大小的MinWidth我们希望自动,第二个自动调整大小而没有MinWidth,第三个星号大小的MinWidth。 如果控件宽度为100个单位,并且第一列的内容需要25个像素,第三列的内容需要5个像素,则代码必须将第一列和最后一列的星号大小设置为“25 *”和“70 *”(或任何其他数字以获得正确的比例)。
这很不容易,但最终确实起作用。 我没有考虑列跨度。
抱歉我不能发布代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接