2013-02-14 7 views
5

Sto lavorando a uno strumento di supporto che visualizza più TabItem s in un TabControl. Ogni TabItem rappresenta un dipendente e all'interno di ciascuno di questi dipendenti Tab s vi è un altro TabControl che contiene ulteriori TabItem s. Questi numeri TabItem rappresentano cartelle di Outlook per quel dipendente (come "Lavoro", "Completato", ecc.). Ciascuna di queste cartelle TabItem s contiene un numero ListBox associato a uno ObservableCollection di MailItem s relativo a tale cartella di Outlook. Queste non sono raccolte enormi - solo una dozzina di articoli per ListBox. Sebbene, in totale, tra tutti gli TabItem s possano essere presumibilmente 100 articoli circa.Qual è il trucco per creare un'interfaccia utente WPF reattiva quando si popolano più ListBox?

Il modo in cui attualmente ho creato l'applicazione è che l'applicazione si attiva e popola lo schermo con le schede e sottoschede dipendenti appropriate. Questo processo è abbastanza veloce e sono felice. Ho creato uno statico Global.System.Timer con cui il code-behind di tutte le cartelle è sincronizzato con. Quindi ogni 5 minuti l'applicazione cancellerà tutti i ObserverableCollection e riscriverà le cartelle di Outlook.

Il problema è che il processo di scansione interrompe l'applicazione. Ho provato con un BackgroundWorker per raccogliere la posta da Outlook come un processo in background, quindi passare un oggetto List<MailItem> a un metodo RunWorkerCompleted che poi esegue un processo this.Dispatcher.BeginInvoke che cancella il rispettivo ObservableCollection poi aggiunge gli oggetti dal List<MailItem> indietro al ObservableCollection. Ho persino impostato questo Dispatcher sulla priorità più bassa.

Nonostante ciò, l'applicazione è molto fastidiosa durante il processo scan/populate ListBox. Non sono chiaro su come progettare meglio questo e ammetto di essere un po 'nuovo in questo. Mi rendo conto che eliminare tutti gli ObservableCollection s è inefficiente, ma gli eventi di modifica delle cartelle di Outlook non sono particolarmente affidabili, quindi è necessario eseguire una nuova scansione brute force ogni tanto per assicurare che tutti gli MailItem s siano rappresentati.

Di seguito è riportato il mio codice per il controllo WPF che contiene ListBox. Tieni presente che vi sono circa 10 di questi controlli ListBox attivi contemporaneamente.

// 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); 
      } 
     } 
    } 
+0

Sembra un po 'strano che si stanno avendo questi problemi grandi prestazioni con tali dati poco, ho un'applicazione simile, mailreader prospettive a schede che include le cartelle con gli elementi FINO 5000+ e questo non si blocca l'interfaccia utente , Che tipo di 'Timer' è' GlobalStatics.Timer', io uso un 'Threading.Timer' che elimina la necessità di un' BackgroundWorker', ci sono anche le immagini associate a questi elementi di posta? –

+0

Il gestore RunWorkerCompleted non viene eseguito sul thread in cui è stato creato BackgroundWorker? In questo caso non è necessario utilizzare un Dispatcher per eseguire il codice sul thread dell'interfaccia utente poiché si è già sul thread dell'interfaccia utente. – Andy

risposta

1

per il vostro requisito, specialmente in WPF, non si deve utilizzare il timer o l'operatore in background per mantenere la vista reattiva. Invece dovresti progettare la tua app con pattern MVVM. MVVM è Model, View e View Model, dove se c'è una modifica nel modello, il modello aggiorna il View Model e il modello di vista aggiorna la vista. Ciò avviene ereditando semplicemente un'interfaccia "INotifyPropertyChanged".

Ecco un semplice esempio

Xaml parte:

<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> 

e la parte 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); 
      } 
     } 
    } 

} 

Non immaginare che il pulsante di scatto è l'aggiornamento innescato da scheduler, è il modello si aggiorna per primo che attiva un evento modificato proprietà per aggiornare la vista.

Utilizzando questo modello il codice sarà molto affidabile.

Spero che questo aiuti.

saluti Jegan

+0

Ehh. Dov'è il tuo viewmodel? Vedo solo la vista e il modello. –

+0

Codice modificato come hai fatto notare! – Jegan

+0

Il tuo codice va bene, ma non sono sicuro che tu possa chiamarlo MVMC. Non ho mai visto la VM ereditare da M –