【问题标题】:ItemsControl wrapping issueItemsControl 包装问题
【发布时间】:2019-04-01 21:00:49
【问题描述】:

我做了小的自定义 ItemsControl 以将一些列表显示为项目行。而且效果几乎完美。

<ItemsControl.ItemTemplate>
    <DataTemplate>
       <WrapPanel Orientation="Horizontal">
           <TextBlock x:Name="delimiter" Text=";" Margin="0 0 5 0"/>
           <TextBlock Text="{Binding LinkId}" />
       </WrapPanel>
       <DataTemplate.Triggers>
          <DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
              <Setter Property="Visibility" TargetName="delimiter" Value="Collapsed"/>
          </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
</ItemsControl.ItemTemplate>

我的问题在于定界符:当 ItemsConteol 换行到下一行时,定界符字符会从下一行开始。

我知道问题出在哪里,但我不知道如何解决。

感谢您的建议。

【问题讨论】:

  • 您实际上可以使用来自here 的基于 AlternationCount 的解决方案(但您需要定位最后一项,而不是第一项)
  • @Vlad,谢谢。不幸的是,这种方法laso不适用于我:第一个和第二个元素之间没有分隔符。但我决定完全避免使用触发器,在最后一个元素之后的任务分隔符中并不是一个关键问题。

标签: c# wpf


【解决方案1】:

我有一个简单的解决方案,DataTemplateSelector。它是这样的:

你定义一个选择器,用来区分最后一项。

class LastElementSelector : DataTemplateSelector
{
    public DataTemplate NormalTemplate { get; set; }
    public DataTemplate LastTemplate { get; set; }
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return IsLast(container) ? LastTemplate : NormalTemplate;
    }

    bool IsLast(DependencyObject container)
    {
        var itemsControl = FindParentOrSelf<ItemsControl>(container);
        var idx = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
        var count = itemsControl.Items.Count;
        return idx == count - 1;
    }

    T FindParentOrSelf<T>(DependencyObject from) where T : DependencyObject
    {
        for (var curr = from; curr != null; curr = VisualTreeHelper.GetParent(curr))
            if (curr is T t)
                return t;
        return null;
    }
}

有了这个,你可以为普通和最后的项目使用不同的模板:

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplateSelector>
        <local:LastElementSelector>
            <local:LastElementSelector.NormalTemplate>
                <DataTemplate>
                    <TextBlock>
                        <Run Text="{Binding Mode=OneWay}"/><!-- no space between runs
                        --><Run Text="; " Foreground="Red" FontWeight="Bold"/>
                    </TextBlock>
                </DataTemplate>
            </local:LastElementSelector.NormalTemplate>
            <local:LastElementSelector.LastTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"/>
                </DataTemplate>
            </local:LastElementSelector.LastTemplate>
        </local:LastElementSelector>
    </ItemsControl.ItemTemplateSelector>
</ItemsControl>

结果:

【讨论】:

    【解决方案2】:

    您的定界符在下一行的前面,因为它位于 LinkId TextBox 的前面,并且它们都在 WrapPanel 中绑定在一起。整个 WrapPanel 流向下一行。

    WrapPanels 上方的父面板不知道也不关心其中的内容。

    顺便说一句,我不知道这是否是您的意图,但在您编写的代码中,您会为集合中的每个项目生成不同的 WrapPanel。就目前而言,您的测试列表中有 20 个 WrapPanel。

    如果如您所说,最后一个分隔符不是问题,那么这是一个足够好的解决方案:

        <ItemsControl
            ItemsSource="{Binding YourItems}"
            >
            <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel
                    Orientation="Horizontal"
                    ></WrapPanel>
            </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel
                        Orientation="Horizontal"
                        >
                        <TextBlock
                            Text="{Binding LinkId}"
                            ></TextBlock>
                        <TextBlock
                            Text="; "
                            ></TextBlock>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    

    如果这只是用于数据显示的目的,请考虑那些在行中的 texbox 很可能是 TextBox 内的单个字符串。也许您可以使用单个格式化字符串并将相关部分制作成链接。这会将操作的字符串分隔符部分移至 ViewModel,并会大大简化您的 XAML 代码。

    【讨论】:

      【解决方案3】:

      您可以尝试将分隔符 置于文本之后,并通过将其 Text 属性设置为空字符串来隐藏最后一个 Run 元素:

      <ItemsControl AlternationCount="{Binding RelativeSource={RelativeSource Self}, Path=Items.Count}">
          <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate>
                  <WrapPanel Orientation="Horizontal" />
              </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.ItemTemplate>
              <DataTemplate>
                  <TextBlock>
                      <Run Text="{Binding Path=., Mode=OneWay}" /><Run x:Name="delimiter" Text=";    "/>
                  </TextBlock>
                  <DataTemplate.Triggers>
                      <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                          <Setter Property="Text" TargetName="delimiter" Value=""/>
                      </Trigger>
                  </DataTemplate.Triggers>
              </DataTemplate>
          </ItemsControl.ItemTemplate>
      </ItemsControl>
      

      这应该将分隔符和文本保持在同一行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-09-14
        • 2011-08-08
        • 2011-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-08
        相关资源
        最近更新 更多