C# 将按钮放置在最后一个listviewitem之后

C# 将按钮放置在最后一个listviewitem之后,c#,wpf,xaml,listview,C#,Wpf,Xaml,Listview,我的ListView绑定到一个ObservableCollection,有没有办法在最后一个listviewitem之后定位一个按钮?我所做的是在DataTemplate中定义按钮,如下所示: <DataTemplate x:Key="TestDataTemplate"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/&

我的ListView绑定到一个ObservableCollection,有没有办法在最后一个listviewitem之后定位一个按钮?我所做的是在DataTemplate中定义按钮,如下所示:

    <DataTemplate x:Key="TestDataTemplate">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>            
            <TextBlock x:Name="SeletedFilterText" Text="{Binding}" />
            <Button Command="{Binding DataContext.TestCommand,ElementName=TestListView}"                    
                Content="Test"                    
                Visibility="{Binding Converter={StaticResource testConverter}}"
                Grid.Column="1"/>        
    </Grid>      
</DataTemplate>


在ViewModel中,我定义了一个字符串变量来存储最后一项。每次我将集合的最后一个设置为LastItem变量时,ItemSource(一个可观察的)可以添加或删除项。在转换器中,将绑定内容与LastItem进行比较,如果值为true,则显示按钮,如果为false,则隐藏按钮。但是转换器永远不会被触发。有人可以帮忙吗?

是的,而且很简单:

  • 使用泛型类型

  • 将自定义对象添加到集合的末尾。(如果要向其添加命令或其他内容,它可以是类似于按钮的ViewModel)

  • 不要设置ListView(或ItemsControl等)的
    ItemTemplate

  • 相反,在参考资料中定义两个不带
    x:Key
    数据模板
    ,并将其数据类型设置为所需类型。它应该类似于
    “{x:Type local:ButtonVm}”
    “{x:Type vm:ListViewItemType}”

  • 现在,每个项目的模板将自动设置为与该项目类型匹配的数据模板

    例子: (请注意,如果模板可以在其他地方重用,则可以将ListView.Resources移动到Window.Resources)

    MainWindow.xaml:

    <ListView ItemsSource="{Binding Items}">
        <ListView.Resources>
            <DataTemplate DataType="{x:Type vm:ListItemVm}">
                <TextBlock Text="{Binding ItemText}"/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type vm:ButtonVm}">
                <Button Command="{Binding ButtonCommand}">
                    <TextBlock Text="{Binding ButtonText}"/>
                </Button>
            </DataTemplate>
        </ListView.Resources>
    </ListView>
    

    我建议您为您的用例创建一个自定义控件。像这样:

    public class ButtonListView : ListView
    {
        static ButtonListView()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ButtonListView), new FrameworkPropertyMetadata(typeof(ButtonListView)));
        }
    
        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
            "Command", typeof (ICommand), typeof (ButtonListView), new PropertyMetadata(default(ICommand)));
    
        public ICommand Command
        {
            get { return (ICommand) GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }
    
        public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(
            "ButtonContent", typeof (object), typeof (ButtonListView), new PropertyMetadata(default(object)));
    
        public object ButtonContent
        {
            get { return (object) GetValue(ButtonContentProperty); }
            set { SetValue(ButtonContentProperty, value); }
        }
    }
    
    并使用此样式:

    <SolidColorBrush x:Key="ListBox.Disabled.Background" Color="#FFFFFFFF" />
    <SolidColorBrush x:Key="ListBox.Disabled.Border" Color="#FFD9D9D9" />
    <Style TargetType="{x:Type local:ButtonListView}" BasedOn="{StaticResource {x:Type ListBox}}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ButtonListView}">
                    <Border Name="Bd"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            SnapsToDevicePixels="true"
                            Padding="1">
                        <ScrollViewer Padding="{TemplateBinding Padding}"
                                      Focusable="false">
                            <StackPanel>
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                <Button Content="{TemplateBinding ButtonContent}" Command="{TemplateBinding Command}"></Button>
                            </StackPanel>
                        </ScrollViewer>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBox.Disabled.Background}" />
                            <Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBox.Disabled.Border}" />
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsGrouping" Value="true" />
                                <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
                            </MultiTrigger.Conditions>
                            <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    
    
    然后您可以这样使用它:

    <wpfSandbox:ButtonListView ButtonContent="Press" Command="{Binding ...}"/>
    
    
    

    这样,您就不需要在
    ObservableCollection

    中跟踪订单,我建议不要在ViewModel中使用备份字段来跟踪集合中的最后一项

    只有
    转换器
    就位时才能执行此操作,如果传递的
    列表视图项
    列表视图
    中的最后一项,或未传递,转换器将返回true或false

    如果您想在底层
    observateCollection
    add/remove项时调用转换器,我建议
    Count
    属性传递给转换器,以便在从集合中添加/移除项时触发转换器。


    转换器代码:

    public class IsLastItemInContainerConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter,
                              CultureInfo culture)
        {
            DependencyObject item = (DependencyObject)values[0];
            ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
    
            if (ic != null)
            {
                return ic.ItemContainerGenerator.IndexFromContainer(item)
                          == ic.Items.Count - 1;
            }
            else
                return false;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes,
                                    object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    <Button Content="Test">
       <Button.Style>
         <Style TargetType="Button">
           <Setter Property="Visibility" Value="Collapsed"/>
           <Style.Triggers>
             <DataTrigger Value="True">
               <DataTrigger.Binding>
                 <MultiBinding
                      Converter="{StaticResource IsLastItemInContainerConverter}">
                    <Binding Path="."
                             RelativeSource="{RelativeSource Mode=FindAncestor, 
                                               AncestorType=ListViewItem}"/>
                    <Binding Path="DataContext.SourceCollection.Count"
                             RelativeSource="{RelativeSource Mode=FindAncestor, 
                                                        AncestorType=ListView}"/>
                 </MultiBinding>
               </DataTrigger.Binding>
               <Setter Property="Visibility" Value="Visible"/>
             </DataTrigger>
           </Style.Triggers>
         </Style>
       </Button.Style>
    </Button>
    
    XAML:

    public class IsLastItemInContainerConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter,
                              CultureInfo culture)
        {
            DependencyObject item = (DependencyObject)values[0];
            ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
    
            if (ic != null)
            {
                return ic.ItemContainerGenerator.IndexFromContainer(item)
                          == ic.Items.Count - 1;
            }
            else
                return false;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes,
                                    object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    <Button Content="Test">
       <Button.Style>
         <Style TargetType="Button">
           <Setter Property="Visibility" Value="Collapsed"/>
           <Style.Triggers>
             <DataTrigger Value="True">
               <DataTrigger.Binding>
                 <MultiBinding
                      Converter="{StaticResource IsLastItemInContainerConverter}">
                    <Binding Path="."
                             RelativeSource="{RelativeSource Mode=FindAncestor, 
                                               AncestorType=ListViewItem}"/>
                    <Binding Path="DataContext.SourceCollection.Count"
                             RelativeSource="{RelativeSource Mode=FindAncestor, 
                                                        AncestorType=ListView}"/>
                 </MultiBinding>
               </DataTrigger.Binding>
               <Setter Property="Visibility" Value="Visible"/>
             </DataTrigger>
           </Style.Triggers>
         </Style>
       </Button.Style>
    </Button>
    
    
    

    在dataTrigger中将
    SourceCollection
    替换为您的
    ObservableCollection名称

    感谢您的快速回复!是否可以向我提供一个示例?这样,如果我向集合中添加一个项目,我必须先删除按钮,然后在添加项目后,再次添加按钮。在我的场景中,按钮必须保留为集合的最后一项。谢谢您可以使用Items.Insert(Items.Count-1,newItem)当我从集合中删除一个项目时,该按钮也会折叠EDIS转换器被触发?不,当我添加一个项目时,转换器被触发,但当我删除一个项目时,它不抱歉!我错了!它工作得很好!真是一个很棒的解决方案!嗨,当我删除最后一个过滤器时,转换器将以无休止的循环启动:(您好,谢谢您的回复!我已经尝试过了,而且效果很好!但是我的ListView有一个复杂的样式,您能告诉我在您的代码中我可以将样式的哪一部分导入到我的样式中吗?谢谢!这一部分:这样,按钮总是在ListView下面,而不是在最后一个列表后面。)viewitem。你确定吗?这可能是你的风格有问题吗?如果你正确使用我的示例代码,它会起作用。我的ListViewItem水平排列在一个包装框中,如果第一行已满,请转到第二行。