在填充多个ListBox时创建响应式WPF UI的技巧是什么?

Jak*_*rth 6 c# wpf multithreading dispatcher backgroundworker

我正在开发一个显示多个TabItems 的支持工具TabControl.每个TabItem代表一个员工,在这些员工Tab的每个人中都有另一个TabControl包含额外TabItem的员工.这些TabItem代表该员工的Outlook文件夹(如"正在工作","已完成"等).这些文件夹中TabItem的每一个都包含一个ListBox绑定到与该Outlook文件夹相关ObservableCollectionMailItems的文件夹.这些不是巨大的收藏 - 每个只有十几件ListBox.虽然总的来说,所有人都TabItem可以想到100件左右.

我目前构建应用程序的方式是应用程序启动并使用相应的员工选项卡和子选项卡填充屏幕.这个过程相当快,我很高兴.我创建了一个静态Global.System.Timer文件,所有文件夹TabItem的代码隐藏都与之同步.因此,应用程序每5分钟清除一次ObserverableCollection并重新扫描Outlook文件夹.

问题是扫描过程使应用程序停止.我尝试使用a BackgroundWorker从Outlook收集邮件作为后台进程,然后将一个List<MailItem>对象传递给一个RunWorkerCompleted方法,然后运行一个this.Dispatcher.BeginInvoke清除相应的进程,ObservableCollection然后将项目从List<MailItem>后面添加到ObservableCollection.我甚Dispatcher至将此设置为较低优先级.

尽管如此,在扫描/填充ListBox过程中应用程序非常笨重.我不清楚如何更好地设计这个,我承认我对此有些新意.我意识到清除每个ObservableCollections都是低效的,但Outlook文件夹更改事件并不是特别可靠,所以我需要每隔一段时间重新扫描一次以确保所有MailItems都被表示.

下面是我的WPF控件的代码,其中包含ListBox.请记住,这些ListBox控件中大约有10个同时处于活动状态.

// This entire UserControl is essentially a ListBox control
public partial class TicketListView : UserControl
    {
        private TicketList _ticketList; //this is the ObservableCollection object
        private Folder _folder;         // Outlook Folder

        public TicketListView(Folder folder)
        {
            InitializeComponent();

            _ticketList = this.FindResource("TicketList") as TicketList; 
            _folder = folder; 

            GlobalStatics.Timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
        }

        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Refresh();
        }

        private void Refresh()
        {
            BackgroundWorker worker = new BackgroundWorker();

            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.RunWorkerAsync();
        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            List<MailItem> tickets = new List<MailItem>();
            string filter = TicketMonitorStatics.TicketFilter(14);
            Items items = _folder.Items.Restrict(filter);

            try
            {
                foreach (MailItem mi in items.OfType<MailItem>())
                {
                    tickets.Add(mi);
                }
            }
            catch (System.Exception) { }

            e.Result = tickets;
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            List<MailItem> tickets = e.Result as List<MailItem>;

            this.Dispatcher.BeginInvoke(new System.Action(delegate
                {
                    _ticketList.Clear();
                    PopulateList(tickets);
                }));

            BackgroundWorker worker = sender as BackgroundWorker;
            worker.Dispose();
        }

        private void PopulateList(List<MailItem> ticketList)
        {
            foreach (MailItem mi in ticketList)
            {
                this.Dispatcher.BeginInvoke(new System.Action(delegate
                    {
                        _ticketList.Add(mi);
                    }), System.Windows.Threading.DispatcherPriority.SystemIdle);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Jeg*_*gan 1

根据您的要求,特别是在 WPF 中,您不应该使用计时器或后台工作程序来保持视图响应。相反,您应该使用 MVVM 模式设计您的应用程序。MVVM 是模型、视图和视图模型,如果模型发生变化,模型会更新视图模型,视图模型也会更新视图。这是通过继承“INotifyPropertyChanged”接口来完成的。

这是一个简单的例子

Xaml部分:

<Window x:Class="SimpleMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="259" Width="445">
    <Grid Margin="0,0,2,-3">
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="10,33,0,0" VerticalAlignment="Top" Width="75"/>
        <Label x:Name="label" Content="{Binding Name}" HorizontalAlignment="Left" Margin="103,23,0,0" VerticalAlignment="Top" Width="220" BorderBrush="Black" BorderThickness="1" Height="32" Padding="0"/>

    </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

和 .cs 部分

using System.ComponentModel;
using System.Windows;

namespace SimpleMVVM
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private AnimalViewModel _animal= new AnimalViewModel ();

        public MainWindow()
        {
            InitializeComponent();

            DataContext = _animal;
            button.Click += (sender, e) => _animal.Name = "Taylor" ;
        }
    }

    public class AnimalViewModel : AnimalModel 
    {
        public AnimalViewModel ()
        {
        }        
    }

    public class AnimalModel : INotifyPropertyChanged
    {
        private string _name;

        public event PropertyChangedEventHandler PropertyChanged;

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
                PropertyChanged(this, args);
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

不要想象按钮单击是由调度程序触发的更新,您的模型首先得到更新,触发属性更改事件来更新视图。

使用这种模式,您的代码将更加可靠。

我希望这有帮助。

问候杰根