I am working on a support tool that displays multiple TabItem
s in a TabControl
. Each TabItem
represents an employee, and within each of these employee Tab
s there is another TabControl
which contains additional TabItem
s. These TabItem
s represent Outlook folders for that employee (like "Working", "Completed", etc). Each of these folder TabItem
s contains a ListBox
that is bound to an ObservableCollection
of MailItem
s that pertain to that Outlook folder. These aren't huge collections - only a dozen or so items per ListBox
. Although, in total, across all TabItem
s there could conceivably be 100 items or so.
The way I have currently built the application is that the app fires up and populates the screen with the appropriate employee tabs and sub-tabs. This process is fairly quick and I'm happy. I have created a Static Global.System.Timer
that all folder TabItem
's code-behind is synchronized with. So every 5 minutes the application will clear all ObserverableCollection
s and re-scan the Outlook folders.
The problem is that the scan process brings the application to a halt. I have tried using a BackgroundWorker
to gather mail from Outlook as a background process, then pass a List<MailItem>
object to a RunWorkerCompleted
method that then runs a this.Dispatcher.BeginInvoke
process that clears the respective ObservableCollection
then adds the items from the List<MailItem>
back to the ObservableCollection
. I've even set this Dispatcher
to the lower priority.
Despite this, the application is very clunky feeling during the scan/populate ListBox
process. I am unclear on how to better design this and I admit I am somewhat new to this. I realize that clearing out each of the ObservableCollection
s is inefficient, but Outlook folder change events aren't particularly reliable, so I need to do a brute force re-scan every once in a while to insure all MailItem
s are represented.
Below is my code for the WPF control that contains the ListBox
. Keep in mind that there are about 10 of these ListBox
controls active at once.
// 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);
}
}
}
for your requirement, specially in WPF, you shouldn't be using timer or background worker to keep the view responsive. Instead you should be designing your app with MVVM pattern. MVVM is Model, View and View Model, where if there is a change in the model, the model updates the View Model, and the view model updates the view. This is done by just inheriting a "INotifyPropertyChanged" Interface.
Here is a simple example
Xaml part:
<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>
And The .cs Part
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);
}
}
}
}
No imagine that the button click is the update triggered by scheduler, you model get updated first that trigger a property changed event to update the view.
Using this pattern your code will be much reliable.
I hope this helps.
Regards Jegan
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With