首页 > 解决方案 > 我的 ListView 两次显示相同的项目。我如何解决它?

问题描述

我有一个ComboBox允许用户选择一个类别和一个ListView绑定到ObservableCollection所选类别中的项目的一个。当用户选择不同的类别时,集合中的项目会更新。有时这会按预期工作,但有时项目列表会被破坏。当应该有两个单独的项目时,它会显示一个重复的项目。

结果似乎取决于我从哪个类别切换。例如,如果我从没有项目的类别切换到有两个项目的类别,相同的项目会显示两次。但是,如果我从具有四个项目的类别切换到具有两个项目的同一类别,它们就会正确显示。

这是一个复制品:

主页.xaml

<Page
    x:Class="ListViewDuplicateItem_Binding.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ListViewDuplicateItem_Binding">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ComboBox
            Grid.Row="0"
            Grid.Column="0"
            ItemsSource="{Binding ViewModel.Groups}"
            SelectedItem="{Binding ViewModel.SelectedGroup, Mode=TwoWay}" />
        <ListView
            Grid.Row="1"
            Grid.Column="0"
            ItemsSource="{Binding ViewModel.Widgets}"
            SelectedItem="{Binding ViewModel.SelectedWidget, Mode=TwoWay}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Widget">
                    <TextBlock Text="{Binding Id}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <local:MyControl
            Grid.Row="1"
            Grid.Column="1"
            Text="{Binding ViewModel.SelectedWidget.Id, Mode=OneWay}" />
    </Grid>
</Page>

MainPage.xaml.cs

using Windows.UI.Xaml.Controls;

namespace ListViewDuplicateItem_Binding
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            InitializeComponent();
            DataContext = this;
        }

        public MainViewModel ViewModel { get; } = new MainViewModel();
    }
}

主视图模型.cs

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace ListViewDuplicateItem_Binding
{
    public class MainViewModel : INotifyPropertyChanged
    {
        private string _selectedGroup;
        private Widget _selectedWidget;

        public MainViewModel()
        {
            PropertyChanged += HomeViewModel_PropertyChanged;
            SelectedGroup = Groups.First();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public ObservableCollection<string> Groups { get; } = new ObservableCollection<string>(DataSource.AllGroups);

        public string SelectedGroup
        {
            get => _selectedGroup;
            set
            {
                if (_selectedGroup != value)
                {
                    _selectedGroup = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedGroup)));
                }
            }
        }

        public Widget SelectedWidget
        {
            get => _selectedWidget;
            set
            {
                if (_selectedWidget != value)
                {
                    _selectedWidget = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedWidget)));
                }
            }
        }

        public ObservableCollection<Widget> Widgets { get; } = new ObservableCollection<Widget>();

        private void HomeViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(SelectedGroup))
            {
                var widgetsToLoad = DataSource.GetWidgetsForGroup(SelectedGroup);
                // Add widgets in this group
                widgetsToLoad.Except(Widgets).ToList().ForEach(w => Widgets.Add(w));
                // Remove widgets not in this group
                Widgets.Except(widgetsToLoad).ToList().ForEach(w => Widgets.Remove(w));
                // Select the first widget
                if (SelectedWidget == null && Widgets.Any())
                {
                    SelectedWidget = Widgets.First();
                }
            }
        }
    }
}

数据源.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace ListViewDuplicateItem_Binding
{
    public static class DataSource
    {
        public static ObservableCollection<string> AllGroups { get; } = new ObservableCollection<string>
        {
            "First Widget",
            "First Two Widgets",
            "Last Two Widgets",
            "All Widgets",
            "None"
        };

        public static List<Widget> AllWidgets { get; } = new List<Widget>
        {
            new Widget()
            {
                Id = 1,
            },
            new Widget()
            {
                Id = 2,
            },
            new Widget()
            {
                Id = 3,
            },
            new Widget()
            {
                Id = 4,
            }
        };

        public static List<Widget> GetWidgetsForGroup(string group)
        {
            switch (group)
            {
                case "First Widget":
                    return new List<Widget> { AllWidgets[0] };

                case "First Two Widgets":
                    return new List<Widget> { AllWidgets[0], AllWidgets[1] };

                case "Last Two Widgets":
                    return new List<Widget> { AllWidgets[2], AllWidgets[3] };

                case "All Widgets":
                    return new List<Widget>(AllWidgets);

                default:
                    return new List<Widget>();
            }
        }
    }
}

小部件.cs

namespace ListViewDuplicateItem_Binding
{
    public class Widget
    {
        public int Id { get; set; }
    }
}

MyControl.xaml

<UserControl
    x:Class="ListViewDuplicateItem_Binding.MyControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <TextBox Text="{x:Bind Text, Mode=TwoWay}" />
</UserControl>

MyControl.xaml.cs

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace ListViewDuplicateItem_Binding
{
    public sealed partial class MyControl : UserControl
    {
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(MyControl), new PropertyMetadata(null));

        public MyControl()
        {
            InitializeComponent();
        }

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
    }
}

标签: c#xamllistviewdata-bindinguwp

解决方案


这似乎仅在项目包含使用绑定标记的自定义控件时发生。

在上面的示例中,如果MyControl从 中删除MainPage.xaml,它会按预期工作。

同样,如果<local:MyControl Text="{Binding ViewModel.SelectedWidget.Id}" />更改为<local:MyControl Text="{x:Bind ViewModel.SelectedWidget.Id}" />,则示例按预期工作

这似乎是ListView控件中的一个错误,但您可以通过使用{x:Bind} 编译的绑定来解决它。

编辑:经过进一步调查,自定义控件可能是一个红鲱鱼。将自定义控件更改为标准 TextBox 并不能解决我之前认为的问题。该问题可以在没有自定义控件的情况下重现。尽管如此,使用 {x:Bind} 或完全删除控件确​​实可以解决这种情况下的问题。


推荐阅读