通过 ListCollectionView 的 ObservableCollection 显示不正确的项目列表

发布于 2024-12-26 10:41:44 字数 1724 浏览 2 评论 0原文

C#:

public partial class MainWindow : Window
{
    private readonly ViewModel vm;

    public MainWindow()
    {
        InitializeComponent();
        vm = new ViewModel();

        DataContext = vm;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        vm.Models.RemoveAt(0);
    }
}

public class ViewModel
{
    public ObservableCollection<Model> Models { get; set; }
    public ListCollectionView View { get; set; }

    public ViewModel()
    {
        Models = new ObservableCollection<Model>()
        {
            new Model() { Name = "Gordon Freeman" },
            new Model() { Name = "Isaac Kleiner" },
            new Model() { Name = "Eli Vance" },
            new Model() { Name = "Alyx Vance" },
        };

        Models.CollectionChanged += (s, e) => View.Refresh();
        View = new ListCollectionView(Models);
    }
}

public class Model
{
    public string Name { get; set; }

    public override string ToString()
    {
        return Name;
    }
}

XAML:

<StackPanel>
    <ListBox ItemsSource="{Binding Path=View}" />
    <Button Click="Button_Click">Click</Button>
</StackPanel>

ObservableCollection 包含 4 个元素,并且 ListBox 按预期显示所有 4 个元素。单击该按钮时,ObservableCollection 的第一个元素将被删除。然而,列表框现在仅显示第二个和第三个。看来第 1 条和第 4 条已被删除。

如果行 Models.CollectionChanged += (s, e) =>; View.Refresh(); 被移动之后 View = new ListCollectionView(Models); (或完全注释掉),事情按预期工作。

为什么?

PS 这是一个更大拼图的一个简单部分。在这个小例子中,我意识到我不需要在 CollectionChanged 上调用 View.Refresh(); 来让 ListBox 自行更新。

C#:

public partial class MainWindow : Window
{
    private readonly ViewModel vm;

    public MainWindow()
    {
        InitializeComponent();
        vm = new ViewModel();

        DataContext = vm;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        vm.Models.RemoveAt(0);
    }
}

public class ViewModel
{
    public ObservableCollection<Model> Models { get; set; }
    public ListCollectionView View { get; set; }

    public ViewModel()
    {
        Models = new ObservableCollection<Model>()
        {
            new Model() { Name = "Gordon Freeman" },
            new Model() { Name = "Isaac Kleiner" },
            new Model() { Name = "Eli Vance" },
            new Model() { Name = "Alyx Vance" },
        };

        Models.CollectionChanged += (s, e) => View.Refresh();
        View = new ListCollectionView(Models);
    }
}

public class Model
{
    public string Name { get; set; }

    public override string ToString()
    {
        return Name;
    }
}

XAML:

<StackPanel>
    <ListBox ItemsSource="{Binding Path=View}" />
    <Button Click="Button_Click">Click</Button>
</StackPanel>

The ObservableCollection contains 4 elements, and the ListBox is displaying all 4, as expected. When the button is clicked, the 1st element of the ObservableCollection is removed. The ListBox, however, is now displaying only the 2nd and 3rd. It would appear the 1st and 4th have been removed.

If the line Models.CollectionChanged += (s, e) => View.Refresh(); is moved after View = new ListCollectionView(Models); (or commented out entirely) things work as expected.

Why?

P.S. This is a simple piece of a larger puzzle. In this small example, I realize I don't need to call View.Refresh(); on CollectionChanged for the ListBox to update itself.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

笙痞 2025-01-02 10:41:44

我的猜测是刷新会干扰视图的自动更新。据推测,视图也在构造函数中订阅了CollectionChanged,因此,如果您还在视图订阅事件并调用刷新之前订阅了该事件,那么您会在集合更改和视图自己的更新之间获得不需要的更新。

例如,

项目 0 被删除 ->通知事件侦听器
=>您的处理程序:Refresh() ->重建视图=>项目被删除。
=>查看处理程序:事件参数显示:项目 X 已删除 ->删除项目 X

这仍然不能解释为什么第一个和最后一个项目被删除,但对我来说似乎是合理的。

如果订阅是在视图实例化之后:

Item 0被移除->通知事件侦听器
=>查看处理程序:事件参数显示:项目 X 已删除 ->删除项目 X
=>您的处理程序:Refresh() ->重建视图=>一切都没有改变。

My guess would be that the refresh interferes with the automatic update by the view. Presumably the view subscribes to CollectionChanged as well in the constructor, so if you also subscribe to the event before the view does and call refresh you get an unwanted update in between the collection change and the view's own update.

e.g.

Item 0 is removed -> Notify event listeners
=> Your handler: Refresh() -> Rebuild view => Item gets removed.
=> View handler: Event args say: Item X was removed -> Remove item X

This still does not explain why the first and the last items get removed but it seems reasonable to me.

If the subscription is after the view instantiation:

Item 0 is removed -> Notify event listeners
=> View handler: Event args say: Item X was removed -> Remove item X
=> Your handler: Refresh() -> Rebuild view => Nothing changed.

誰ツ都不明白 2025-01-02 10:41:44

看来是ListView刷新的问题。如果将 labe/TextBlock 绑定到 ListView.Items.Count,您将看到列表仍然有 3 个项目(第一次删除后)。

Seems to be a problem with ListView refresh. If you bind a labe/TextBlock to ListView.Items.Count, you'll see that the list still has 3 items (after first delete).

凯凯我们等你回来 2025-01-02 10:41:44

即使您将 Model 类包装在 ObservableCollection 中,您仍然需要为其实现 INotifyPropertyChanged。我已经多次用头脑去解决这个问题了,现在我记得从最低的构建块(即模型)开始进行故障排除。

public class Model : INotifyPropertyChanged
{
    #region INotify Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
    #endregion

    public string _Name;
    public string Name {
        get { return _Name;  }
        set { _Name = value; NotifyPropertyChanged("Name"); }

    }
    public override string ToString()
    {
        return Name;
    }
}

另外,在您的 ViewModel 中,当正确实现 INotifyPropertyChanged 时,您不需要以下行。 INotifyPropertyChanged 将为您更新您的 UI。

    Models.CollectionChanged += (s, e) => View.Refresh();
    View = new ListCollectionView(Models);

Even though you're wrapping your Model class in an ObservableCollection, you still need to implement INotifyPropertyChanged for it. I've banged my head against this problem enough times that now I remember to start troubleshooting at the lowest building block, i.e. Model.

public class Model : INotifyPropertyChanged
{
    #region INotify Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
    #endregion

    public string _Name;
    public string Name {
        get { return _Name;  }
        set { _Name = value; NotifyPropertyChanged("Name"); }

    }
    public override string ToString()
    {
        return Name;
    }
}

Also, in your ViewModel, when INotifyPropertyChanged is implemented correctly you don't need the below lines. INotifyPropertyChanged will update your UI for you.

    Models.CollectionChanged += (s, e) => View.Refresh();
    View = new ListCollectionView(Models);
墨落画卷 2025-01-02 10:41:44

尽量不要使用 new 关键字来定义 listCollectionView 的新实例。请改用以下内容。

var sortedCities  = CollectionViewSource.GetDefaultView(Models);

Try not to use the new keyword to define your new instance of listCollectionView. Use the following instead.

var sortedCities  = CollectionViewSource.GetDefaultView(Models);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文