如何在 UWP 中使用可隐藏内容正确自定义 ListViewItem 并将其绑定到模型?

How to customize ListViewItem properly with hideable content and bind it to a model, in UWP?

我想创建一个 ListView,其中的项目具有 "hover" 和 "selected" 状态,在每个状态下显示不同的内容。

Whosebug 上有一些类似的问题,但 none 对我的特殊情况有所帮助。

假设我有一个 模型:

public class TagFile : BaseBind // A class with INotifyProperyChanged interface
{
    private string path;
    public String Path
    {
        get { return path; }
        set
        {
            SetProperty(ref path, value);
        }
    }

    public void SelectButtonClick() 
    {
        // Do something
    }

    public void HoverButtonClick() 
    {
        // Do something
    }
}

...然后我有一个主要的 ViewModel:

public class AppViewModel : BaseBind
{
    public ObservableCollection<TagFile> ItemsList { get; set; }

    // Other things
}

... 然后 Page:

<Grid>
     <ListView ItemsSource="ItemsList">
          <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:TagFile">
                     <Grid>
                          <Grid.RowDefinitions>
                              <RowDefinition/>
                              <RowDefinition/>
                              <RowDefinition/>
                          </Grid.RowDefinitions>
                          <TextBlock Text="{x:Bind Path, mode=OneWay}"/>
                          <Button Content="Select Button" Click="{x:Bind SelectButtonClick}"/> 
                          <Button Content="Hover Button" Click="{x:Bind HoverbuttonClick}"/>
                     </Grid>
                </DataTemplate>
          </ListView.ItemTemplate>
     </ListView>
</Grid>

我想自定义 ListViewItem 样式,以使 SelectButton 仅在 ListViewItem 被选中时出现,而 HoverButton 仅在光标位于其上时出现。

我已经知道我需要使用 ItemTemplate 和 ItemContainerStyle,但这似乎比我一开始想的要难,因为我可以通过这种方式为 ItemContainerStyle 创建自定义样式(使用 IsSelected 属性 来自 ListViewItem):

<Style TargetType="ListViewItem" x:Key="TestContainerStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListViewItem">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <ContentPresenter x:Name="Presenter" Content="{TemplateBinding Content}"/>
                        <Button Grid.Row="1" Content="Select Button" Visibility="{Binding IsSelected, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    </Style>

...但是这样: 1)我松开所有默认画笔; 2) 我不知道如何将 SelectButton 绑定到模型的命令; 3)我必须为 "hover" 状态找到一种方法,以便显示 HoverButton; 4) 最后但同样重要的是......我想将所有自定义包含在 App.xaml 文件中(或在相关的 ResourceDictionary 文件中,因为我在整个应用程序中需要它们)。

就是这样...我尝试了 Whosebug 答案中的各种模式,但我的案例包括许多不同的东西(在 App.xaml 文件中编译绑定,创建 "hover" 状态,使用绑定没有数据类型的样式等),仅在此处单独回答。

我需要...把它们放在一起,正如我所说,这似乎比我想象的要棘手。

感谢您的帮助,非常感谢。

编辑:行为应该类似于 Groove App 列表项:一些按钮(播放和删除项)仅在项被选中或处于悬停状态时出现。

正常状态:

...和 ​​Hover/selected 状态:

有两种方法可以达到你想要的效果:

第一个:我不喜欢它因为你需要复制整个样式并将您的项目模板嵌入样式中。如果您希望应用的多个部分具有相同的行为,它也不会扩展。

您可以复制 ListViewItem style,它使用 UIElement 树和视觉状态而不是 ListViewItemPresenter。这样你就不会丢失默认画笔。

注意:在上面 link 中查找带有 x:Key="ListViewItemExpanded" 的模板。

<!-- Style for Windows.UI.Xaml.Controls.ListViewItem -->
<Style TargetType="ListViewItem" x:Key="ListViewItemExpanded">

第二个注意事项:

These styles and resources are based on the Windows Software Development Kit (SDK) for Windows 10, Version 1511 (Windows SDK version 10.0.10586.0). Styles and resources from other versions of the SDK might have different values. For design purposes, generic.xaml is available in the (Program Files)\Windows Kits\DesignTime\CommonConfiguration\Neutral\UAP.0.10586.0\Generic folder from a Windows SDK installation.

现在您可以使用 pointeroverselected 状态来显示和隐藏不同的元素。

第二种方法:使用自定义控件[推荐]

将您的问题分解为两件事:

  1. 您可以在 model/viewmodel 中为每个项目定义 IsSelected 属性,并在 select 命令中更新它。现在您可以通过绑定到 IsSelected 来控制第一个按钮的可见性。

  2. 其次是悬停部分,您创建一个公开依赖项 属性 IsPointerOver 的自定义控件,您可以将 'Hover Button' 的可见性绑定到它。

示例:

public class ExtendedControl : ContentControl
    {
        public static readonly DependencyProperty IsPointerOverProperty =
            DependencyProperty.Register("IsPointerOver", typeof(bool), typeof(ExtendedControl),
                new PropertyMetadata(false));

        public bool IsPointerOver
        {
            get => (bool)GetValue(IsPointerOverProperty);
            protected set => SetValue(IsPointerOverProperty, value);
        }
        protected override void OnPointerEntered(PointerRoutedEventArgs e)
        {
            base.OnPointerEntered(e);
            IsPointerOver = true;
        }
        protected override void OnPointerCanceled(PointerRoutedEventArgs e)
        {
            base.OnPointerCanceled(e);
            IsPointerOver = false;
        }
        protected override void OnPointerExited(PointerRoutedEventArgs e)
        {
            base.OnPointerExited(e);
            IsPointerOver = false;
        }
    }

Xaml:

  <ListView.ItemTemplate>
            <DataTemplate >
                <local:ExtendedControl x:Name="ExtendedControl">
                    <StackPanel>
                        <Button Content="Always Visible"/>
                        <Button Content="Only on hover" Visibility="{x:Bind ExtendedControl.IsPointerOver,Mode=OneWay,Converter={StaticResource BoolToVisibilityConverter}}"/>
                    </StackPanel>
                </local:ExtendedControl>
            </DataTemplate>
        </ListView.ItemTemplate>