草庐IT

c# - 为什么在嵌套网格中调整星号大小不起作用?

coder 2024-05-21 原文

考虑以下 XAML:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>

以上,所有ColumnDefinition除一个之外的值使用 Width 的默认值, 即 "*" ,即“星级”。一个异常(exception)是包含嵌套 Grid 的列。控件,设置为 "Auto" .

我希望它的工作方式如下:
  • Grid根据其内容的需要调整第二列的大小,然后将控件的剩余宽度分配给第一列。
  • 内部Grid将其可用空间平均分配给两列。毕竟,它们都设置为使用star sizing,而star sizing 应该设置GridLength属性(宽度,在这种情况下)到可用空间的加权分布。此内部的最小布局大小 Grid (外部 Grid 需要计算其第二列的宽度)是均匀分布宽度的星形列的总和(即在这种情况下,是内容最宽的列宽度的两倍)。

  • 但是,嵌套网格的列宽是根据计算出的每个按钮的最小尺寸设置的,两个星形列之间没有明显的加权关系(为了清晰起见,显示了网格线):



    如果我没有外部网格,它会按预期工作,即只是让内部网格成为窗口中唯一的网格:



    两列被强制大小相同,然后当然左手按钮被拉伸(stretch)以适应其包含单元格的大小(这是我想要的......最终目标是让这两个按钮具有相同的宽度,网格列提供了实现这一点的布局)。

    在这个特定的例子中,我可以使用 UniformGrid作为一种解决方法,强制均匀分布列宽。这就是我想要它的实际外观( UniformGrid 没有 ShowGridLines 属性,所以你只需要想象两个最右边的按钮之间的假想线):



    但我真的很想更广泛地了解如何实现这一点,以便在更复杂的场景中我能够在嵌套的 Grid 中使用 star-sizing。控制。

    似乎不知何故,被包含在另一个 Grid 的单元格中控件正在改变为内部 Grid 计算星号大小的方式控制(或完全防止星级调整产生任何影响)。但为什么会这样呢?我是否(又一次)错过了 WPF 的一些深奥的布局规则,将其解释为“按设计”行为?或者这只是框架中的一个错误?

    更新:

    我理解 Ben 的回答意味着在考虑了每列的最小尺寸后,star-sizing 应该只分配剩余的空间。但这不是人们在其他场景中看到的。

    例如,如果包含内部网格的列已明确调整大小,那么对内部网格的列使用星型大小会导致列的大小均匀,正如我所期望的那样。

    IE。这个 XAML:

    <Grid ShowGridLines="True">
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <!--
        <ColumnDefinition Width="Auto"/>
        -->
        <ColumnDefinition Width="60"/>
      </Grid.ColumnDefinitions>
      <Button Content="Button" HorizontalAlignment="Left"/>
      <Grid Grid.Column="1" ShowGridLines="True">
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Content="B" Margin="5"/>
        <Button Content="Button" Grid.Column="1" Margin="5"/>
      </Grid>
    </Grid>
    

    产生这个输出:



    换句话说,在最坏的情况下,我希望 WPF 首先计算内部网格的最小尺寸而不考虑星号大小(例如,如果短按钮需要 10 像素,长按钮需要 70,那么总宽度将是80),然后仍然均匀分布列宽(即在 10/70 示例中,每列将结束为 40 像素,截断较长的按钮,类似于上图)。

    为什么star-sizing 有时会在列之间均匀分布宽度,有时则不是?

    更新 #2:

    这是一个简单的示例,它清楚而生动地展示了 WPF 如何根据是否是计算 Grid 的人来不同地处理星号大小。宽度或者你是:

    <Window x:Class="TestGridLayout2.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            SizeToContent="WidthAndHeight"
            Title="MainWindow">
      <Window.Resources>
        <Style TargetType="Border">
          <Setter Property="BorderBrush" Value="Black"/>
          <Setter Property="BorderThickness" Value="1"/>
        </Style>
        <Style TargetType="TextBlock">
          <Setter Property="FontSize" Value="24"/>
        </Style>
      </Window.Resources>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
    
        <Border Grid.Column="0"/>
        <Border Grid.Column="1"/>
    
        <StackPanel>
          <TextBlock Text="Some text -- one"/>
          <TextBlock Text="Some text -- two"/>
          <TextBlock Text="Some text -- three"/>
        </StackPanel>
        <StackPanel Grid.Column="1">
          <TextBlock Text="one"/>
          <TextBlock Text="two"/>
          <TextBlock Text="three"/>
        </StackPanel>
      </Grid>
    </Window>
    

    当你运行程序时,你会看到:



    WPF 已忽略星号大小,将每列宽度设置为最小值。如果您只是单击窗口边框,就像调整窗口大小一样(您甚至不必将边框实际拖动到任何地方),Grid布局被重做,你会得到这个:



    此时,将应用星号大小(正如我所期望的),并且列根据 XAML 声明进行比例调整。

    最佳答案

    我同意 Ben 的回答强烈暗示了这里封面下可能发生的事情:

    正如 Ben 指出的那样,WPF 在计算内部 Grid 时忽略了星级大小。对象的最小宽度(也许合理......我认为有诚实辩论的空间,但显然这是一种可能且合法的设计)。

    不清楚(以及 Ben 没有回答)是为什么这意味着在计算该内部 Grid 内的列宽时也忽略了星号大小。 .由于在外部施加宽度时,比例宽度将导致内容在必要时被截断以保留这些比例,为什么当根据内容的最小所需大小自动计算宽度时不会发生同样的事情。

    IE。我仍在寻找我的问题的答案。

    与此同时,恕我直言,有用的答案包括解决该问题的方法。虽然不是我问题本身的实际答案,但它们显然对可能遇到问题的任何人都有帮助。所以我写这个答案是为了整合所有已知的解决方法(无论好坏,WPF 的一个重要“特性”是似乎总是至少有几种不同的方法来实现相同的结果:))。

    解决方法 #1:

    使用 UniformGrid而不是 Grid对于内部网格对象。此对象不具有与 Grid 相同的所有功能当然,不允许任何列的宽度不同。所以它可能不适用于所有场景。但它确实很容易解决这里的简单问题:

    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition Width="Auto"/>
      </Grid.ColumnDefinitions>
      <Button Content="Button" HorizontalAlignment="Left"/>
      <UniformGrid Rows="1" Columns="2" Grid.Column="1">
        <Button Content="B" Margin="5"/>
        <Button Content="Button" Grid.Column="1" Margin="5"/>
      </UniformGrid>
    </Grid>
    

    解决方法 #2:

    绑定(bind)MinWidth较小内容对象的属性(例如,这里是网格中的第一个 Button)到 ActualWidth较大者的属性(property)。
    这当然需要知道哪个对象的宽度最大。在本地化方案中,这可能会出现问题,因为除了文本资源之外,还必须对 XAML 进行本地化。但无论如何这有时是必要的,所以...... :)

    这看起来像这样(基本上是回答者 dub stylee 提供的 an answer here ):

    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition Width="Auto"/>
      </Grid.ColumnDefinitions>
      <Button Content="Button" HorizontalAlignment="Left"/>
      <Grid Grid.Column="1">
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Content="B" Margin="5"
                MinWidth="{Binding ElementName=button2, Path=ActualWidth}"/>
        <Button x:Name="button2" Content="Button" Grid.Column="1" Margin="5"/>
      </Grid>
    </Grid>
    

    一个变体(为简洁起见,我不会在这里包括)将使用 MultiBinding它将所有相关控件作为输入并返回最大的 MinWidth的集合。当然,此绑定(bind)将用于 Width每个ColumnDefinition ,以便所有列都显式设置为最大的 MinWidth .

    绑定(bind)方案还有其他变化,具体取决于您要使用和/或设置的宽度。这些都不是理想的,不仅因为潜在的本地化问题,还因为它在 XAML 中嵌入了更明确的关系。但在很多场景下,它都能完美运行。

    解决方法 #3:

    通过使用 SharedSizeGroup ColumnDefinition 的属性(property)值,可以明确强制一组列具有相同的宽度。在这种方法中,内部Grid然后在此基础上计算对象的最小宽度,当然宽度也相同。

    例如:

    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition Width="Auto"/>
      </Grid.ColumnDefinitions>
      <Button Content="Button" HorizontalAlignment="Left"/>
      <Grid Grid.Column="1" IsSharedSizeScope="True">
        <Grid.ColumnDefinitions>
          <ColumnDefinition SharedSizeGroup="buttonWidthGroup"/>
          <ColumnDefinition SharedSizeGroup="buttonWidthGroup"/>
        </Grid.ColumnDefinitions>
        <Button Content="B" Margin="5"/>
        <Button Content="Button" Grid.Column="1" Margin="5"/>
      </Grid>
    </Grid>
    

    这种方法允许使用 Grid ,从而获得该对象的所有正常特征,同时解决所关注的特定行为。我希望它不会干扰 SharedSizeGroup 的任何其他合法使用。 .

    但是,它确实意味着 "Auto"调整大小,并排除 "*" (星)尺码。在这种特殊情况下,这不是问题,但就像 UniformGrid 的情况一样。变通方法,当尝试将其与其他尺寸组合时,它确实限制了一个人的选择。例如。有第三列使用 "Auto"并想要 SharedSizeGroup列占用 Grid 的剩余空间.

    尽管如此,这在许多情况下都可以正常工作,没有任何问题。

    解决方法 #4:

    我最终重新审视了这个问题,因为我遇到了主题的变化。在这种情况下,我正在处理一种情况,我希望调整大小的列具有不同的比例。上述所有解决方法都假定列大小相等。但我希望(例如)一列拥有 25% 的空间,另一列拥有 75% 的空间。和以前一样,我想要 Grid 的总大小以适应所有列所需的最小宽度。

    这种解决方法涉及简单地明确地自己做我认为 WPF 应该做的计算。 IE。取每列内容的最小宽度,以及指定的比例大小,计算 Grid 的实际宽度控制。

    这是一个可以做到这一点的方法:
    private static double ComputeGridSizeForStarWidths(Grid grid)
    {
        double maxTargetWidth = double.MinValue, otherWidth = 0;
        double starTotal = grid.ColumnDefinitions
            .Where(d => d.Width.IsStar).Sum(d => d.Width.Value);
    
        foreach (ColumnDefinition definition in grid.ColumnDefinitions)
        {
            if (!definition.Width.IsStar)
            {
                otherWidth += definition.ActualWidth;
                continue;
            }
    
            double targetWidth = definition.ActualWidth / (definition.Width.Value / starTotal);
    
            if (maxTargetWidth < targetWidth)
            {
                maxTargetWidth = targetWidth;
            }
        }
    
        return otherWidth + maxTargetWidth;
    }
    

    此代码查找仍可容纳每列最小宽度和比例大小的每个星形大小的列的最小宽度,以及未使用星形大小的其余列。

    您可以在适当的时间调用此方法(例如在 Grid.Loaded 事件处理程序中),然后将其返回值分配给 Grid.Width属性强制宽度为正确的大小,以适应所有列所需的最小宽度,同时保持指定的比例。

    类似的方法会对行高做同样的事情。

    解决方法 #5:

    我想值得指出的是:可以简单地指定 Grid大小明确。这仅适用于预先知道大小的内容,但同样,在许多情况下这会很好。 (在其他情况下,它会截断内容,因为即使指定的大小太小,当它是明确的时,WPF 会继续并应用星形大小比例)。

    我鼓励其他人在这个答案中添加额外的变通方法,如果他们意识到与已经展示的那些变通方法有实质性不同的好的变通方法。或者,您可以随意在您的解决方法中发布另一个答案,我会(在我方便的时候尽早 :) )自己将其添加到此处。

    关于c# - 为什么在嵌套网格中调整星号大小不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30719453/

    有关c# - 为什么在嵌套网格中调整星号大小不起作用?的更多相关文章

    1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    2. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

      我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

    4. ruby-on-rails - Rails 编辑表单不显示嵌套项 - 2

      我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib

    5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

      我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

    6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    7. ruby - 将散列转换为嵌套散列 - 2

      这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

    8. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

      为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

    9. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

      它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

    10. ruby - Infinity 和 NaN 的类型是什么? - 2

      我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

    随机推荐