WPF 可观察集合。刷新时计数 == 0 但有很多项目

发布于 2024-10-20 07:33:03 字数 5118 浏览 5 评论 0原文

早上好,

对我即将提供的大量文本表示歉意,但是...

我有一个 WPF ListView,其 ItemsSource 绑定到其各自 ViewModel 中的 ObservableCollection。当窗口加载时,可观察集合通过命令从 Web 服务填充。但是,当程序运行时,BackgroundWorker 线程会定期更新此集合,以便将新项目添加到 ObservableCollection。

这个机制运作良好。 ListView 在 UI 线程和后台线程上更新都没有问题。但是,当双击 ListView 中的某个项目时,会打开一个新窗口来显示上述 ObservableCollection 中包含的 Ticket 对象的详细信息。

我有一个私有方法,每次调用 ObservableCollection 的 set 方法时都会触发该方法,该方法用于从已在新窗口中打开的集合中查找 Ticket 项,并根据新更新的 ObservableCollection 中的项更新其属性。在进行此更新之前,我会检查以确保 ObservableCollection.Count 大于 1,如果没有任何内容可供更新,则进行更新是没有意义的!

我的问题是 ObservableCollection.Count 属性始终等于 0。但我知道这不是真的,因为 ListView 仍在使用添加到此集合的新 Ticket 对象来更新其项目,如果此集合的计数确实为 0,那么这将通过 ListView 反映出来,因为它绑定到该集合,所以其中也没有项目。

那么这是怎么回事呢?我想知道也许是因为BackgroundWorker正在调用;

myCollection = new ObservableCollection();

在与 UI 不同的线程上,当我检查 UI 线程上的计数时,实际上测试了错误的集合对象的“计数”。但这仍然不能解释为什么 ListView 毫无问题地反映 ObservableCollection 的内容。

再次对墙上的文字表示歉意,但我想完整地解释这个问题。 感谢您的宝贵时间以及您提供的任何意见。

编辑更多详细信息

用户控件的列表视图部分

<ListView x:Name="lvTicketSummaries" ItemsSource="{Binding Path=TicketSummaries}" Grid.Row="1" Width="Auto" Height="Auto" SizeChanged="lvTicketSummaries_SizeChanged" SelectionMode="Single" Foreground="Black" Background="#3BFFFFFF" ItemContainerStyle="{DynamicResource ListViewItem}">
        <ListView.View>
            <GridView AllowsColumnReorder="True">
                <GridViewColumn Header="ID"
                                DisplayMemberBinding="{Binding ID}"
                                Width="25"/>

                <GridViewColumn Header="Status"
                                DisplayMemberBinding="{Binding Status}"
                                Width="25"/>

                <GridViewColumn Header="Subject"
                                DisplayMemberBinding="{Binding Subject}"
                                Width="25"/>

                <GridViewColumn Header="Requester"
                                DisplayMemberBinding="{Binding Owner.Name}"
                                Width="25"/>
            </GridView>                
        </ListView.View>
    </ListView>

上述用户控件的视图模型

这里您可以看到列表视图的 TicketSummaries 集合绑定到以及refreshOpenTicket() 方法,该方法用于更新子视图模型中的 Ticket 属性,该子视图模型自身的新实例位于新刷新的集合中。

 public class MainWindowViewModel : ViewModelBase
{

    private DispatcherTimer timer;
    private BackgroundWorker worker_TicketLoader;

    private ObservableCollection<Ticket> ticketSummaries;
    public ObservableCollection<Ticket> TicketSummaries
    {
        get { return ticketSummaries; }
        set
        {
            ticketSummaries = value;
            this.RaisePropertyChanged(p => p.TicketSummaries);
            refreshOpenTicket();
        }
    }

    private void refreshOpenTicket()
    {
        // Check there are actually some tickets to refresh
        if (TicketSummaries.Count < 1)
            return;

        // Check we have created the view model
        if (TicketDetailsViewModel != null)
        {
            // Check the ticket loaded correctly
            if (TicketDetailsViewModel.Ticket != null)
            {
                // Find a ticket in the collection with the same id
                Ticket openTicket = TicketSummaries.Where(
                    ticket => ticket.ID == TicketDetailsViewModel.Ticket.ID
                    ).First();

                // Make sure we are not going to overrite with a null reference
                if (openTicket != null)
                    TicketDetailsViewModel.Ticket = openTicket;
            }
        }
    }

该集合是通过以下命令从各种来源更新的

private void Execute_GetAgentsTickets(object agent)
    {
        TicketSummaries = new ObservableCollection<Ticket>();
        var agentsTickets = ticketService.GetAgentsTickets((Agent)agent);
        agentsTickets.ForEach(
            ticket => TicketSummaries.Add(ticket)
            );

        AppSettings.LoggedAgent = (Agent)agent;
        RequeryCommands();

    }

,但有时后台工作人员会在线程外修改该集合,

void worker_TicketLoader_DoWork(object sender, DoWorkEventArgs e)
    {
        State = "Loading Tickets";
        IsLoadingTickets = true;
        var agentsTickets = ticketService.GetAgentsTickets(AppSettings.LoggedAgent);

        TicketSummaries = new ObservableCollection<Ticket>();
        foreach (Ticket ticket in agentsTickets)
        {
            TicketSummaries.AddOnUIThread<Ticket>(ticket);
        }
        refreshOpenTicket();
        lastRefresh = DateTime.Now;
    }

以防万一发生变化,TicketSummaries.AddOnUIThread(ticket);是我在 StackOverflow 上找到的一个解决方案,用于尝试将项目添加到集合中,该集合是线程外 UI 控件的绑定源,如下所示:

public static void AddOnUIThread<T>(this ICollection<T> collection, T item)
{
    Action<T> addMethod = collection.Add;
    Application.Current.Dispatcher.BeginInvoke(addMethod, item);
}

我希望这有助于进一步了解情况。 再次感谢您抽出时间。

Good morning,

Apologies for the mass of text im about to provide but...

I have a WPF ListView with its ItemsSource bound to an ObservableCollection in it's respective ViewModel. When the window loads, the observable collection is populated from a web service by means of a Command. However, as the program is running, this collection is periodically updated by a BackgroundWorker thread in order to add new items to the ObservableCollection.

This mechanism works fine. The ListView is updated without issue on both the UI thread and the background thread. However, when an item in the ListView is double clicked, a new window is opened to display details of the Ticket object contained within the aforementioned ObservableCollection.

I have a private method which fires each time the ObservableCollection's set method is called which serves to find the Ticket item from within the collection which has been opened in the new window and update its properties according to the items in the newly updated ObservableCollection. Before doing this update, I check to ensure the ObservableCollection.Count is greater than 1, there is no point doing an update if there is nothing to update from!

My issue is that the ObservableCollection.Count property ALWAYS equates to 0. But I know this not to be true as the ListView is still updating its items with new Ticket objects added to this collection, if the count of this collection really was 0, then this would be reflected by the ListView also having no items in it as it is bound to this collection.

So what is going on here ? Im wondering maybe because the BackgroundWorker is calling;

myCollection = new ObservableCollection();

on a different thread to the UI that when I check the count on the UI thread, the wrong collection object is actually tested for 'Count'. But this still doesn't explain why the ListView reflects the contents of the ObservableCollection without issue.

Again, apologies for the wall-o-text but I wanted to explain this issue fully.
Thank you for your time and any input you may give.

EDIT FOR MORE DETAIL

The list view section of user control

<ListView x:Name="lvTicketSummaries" ItemsSource="{Binding Path=TicketSummaries}" Grid.Row="1" Width="Auto" Height="Auto" SizeChanged="lvTicketSummaries_SizeChanged" SelectionMode="Single" Foreground="Black" Background="#3BFFFFFF" ItemContainerStyle="{DynamicResource ListViewItem}">
        <ListView.View>
            <GridView AllowsColumnReorder="True">
                <GridViewColumn Header="ID"
                                DisplayMemberBinding="{Binding ID}"
                                Width="25"/>

                <GridViewColumn Header="Status"
                                DisplayMemberBinding="{Binding Status}"
                                Width="25"/>

                <GridViewColumn Header="Subject"
                                DisplayMemberBinding="{Binding Subject}"
                                Width="25"/>

                <GridViewColumn Header="Requester"
                                DisplayMemberBinding="{Binding Owner.Name}"
                                Width="25"/>
            </GridView>                
        </ListView.View>
    </ListView>

The view model of the above user control

Here you see the TicketSummaries collection which the list view is bound to as well as the refreshOpenTicket() method used to update the Ticket property in a child view model which the new instance of itself in the newly refreshed collection.

 public class MainWindowViewModel : ViewModelBase
{

    private DispatcherTimer timer;
    private BackgroundWorker worker_TicketLoader;

    private ObservableCollection<Ticket> ticketSummaries;
    public ObservableCollection<Ticket> TicketSummaries
    {
        get { return ticketSummaries; }
        set
        {
            ticketSummaries = value;
            this.RaisePropertyChanged(p => p.TicketSummaries);
            refreshOpenTicket();
        }
    }

    private void refreshOpenTicket()
    {
        // Check there are actually some tickets to refresh
        if (TicketSummaries.Count < 1)
            return;

        // Check we have created the view model
        if (TicketDetailsViewModel != null)
        {
            // Check the ticket loaded correctly
            if (TicketDetailsViewModel.Ticket != null)
            {
                // Find a ticket in the collection with the same id
                Ticket openTicket = TicketSummaries.Where(
                    ticket => ticket.ID == TicketDetailsViewModel.Ticket.ID
                    ).First();

                // Make sure we are not going to overrite with a null reference
                if (openTicket != null)
                    TicketDetailsViewModel.Ticket = openTicket;
            }
        }
    }

This collection is updated from various sources via the following command

private void Execute_GetAgentsTickets(object agent)
    {
        TicketSummaries = new ObservableCollection<Ticket>();
        var agentsTickets = ticketService.GetAgentsTickets((Agent)agent);
        agentsTickets.ForEach(
            ticket => TicketSummaries.Add(ticket)
            );

        AppSettings.LoggedAgent = (Agent)agent;
        RequeryCommands();

    }

But occasionally this collection will be modified off-thread by the background worker

void worker_TicketLoader_DoWork(object sender, DoWorkEventArgs e)
    {
        State = "Loading Tickets";
        IsLoadingTickets = true;
        var agentsTickets = ticketService.GetAgentsTickets(AppSettings.LoggedAgent);

        TicketSummaries = new ObservableCollection<Ticket>();
        foreach (Ticket ticket in agentsTickets)
        {
            TicketSummaries.AddOnUIThread<Ticket>(ticket);
        }
        refreshOpenTicket();
        lastRefresh = DateTime.Now;
    }

Just in case it makes a difference, the TicketSummaries.AddOnUIThread(ticket); is a solution I found on StackOverflow to trying to add items to a collection which is a binding source to UI controls off-thread and is as;

public static void AddOnUIThread<T>(this ICollection<T> collection, T item)
{
    Action<T> addMethod = collection.Add;
    Application.Current.Dispatcher.BeginInvoke(addMethod, item);
}

I hope this helps shed some more light on the situation.
Thanks again for your time.

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

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

发布评论

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

评论(1

橘亓 2024-10-27 07:33:03

您没有提供足够的信息来诊断,但我猜测 ListView 绑定到一个属性,当您替换 ObservableCollection 时,您没有更新该属性。因此,ListView 仍然附加到原始集合,而您的 VM 代码正在使用新集合。

为什么要更换 OC?为什么不直接在项目来自中间层时更新它呢?如果您确实必须更换它,请务必通过属性并发出该属性的更改通知。

You haven't provided enough information to diagnose, but I'm guessing the ListView is bound to a property and when you replace the ObservableCollection, you're not updating that property. Hence, the ListView is still attached to the original collection whilst your VM code is working with a new one.

Why replace the OC at all? Why not just update it as items come through from the middle tier? If you really must replace it, be sure to go via a property and raise change notification for that property.

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