I am new to WPF, so I thought this was simple. I have a form with a listbox and a button. In the click handler for the button I do something iteratively that generates strings, which I want to put in the listbox as I get them. The xaml for the list box is like
<ListBox Height="87" Margin="12,0,12,10" Name="lbxProgress" VerticalAlignment="Bottom">
<ListBox.BindingGroup>
<BindingGroup Name="{x:Null}" NotifyOnValidationError="False" />
</ListBox.BindingGroup>
</ListBox>
and the click handler is like
private void btn_Click(object sender, RoutedEventArgs e)
{
List<String> lstrFiles= new List<String>(System.IO.Directory.GetFiles ("C:\\temp", "*.tmp");
foreach(string strFile in lstrFiles)
lbxProgress.Items.Add(strFile);
}
Pretty straightforward. Since my real operation is lengthy, I want the listbox to update as I do each one - how do I get the box to dynamically update on each addition?
Create an ObservableCollection<string>
and set your ListBox.ItemsSource to that collection. Because the collection is observable, the ListBox will update as its contents change.
However, if your real operation is blocking the UI thread, this may prevent WPF from updating the UI until the operation completes (because the WPF data binding infrastructure doesn't get a chance to run). So you may need to run your lengthy operation on a background thread. In this case, you will not be able to update the ObservableCollection from the background thread due to WPF cross-threading restrictions (you can update properties, but not collections). To get around this, use Dispatcher.BeginInvoke() to update the collection on the UI thread while continuing your operation on the background thread.
don't use a List<>, use an ObservableCollection<>. Unlike a normal List, Observable collection fires events whenever an item is added or removed, this will cause any objects that are listening to act appropriately--such as your listbox refreshing to reflect the new/removed items.
If you require sorting, grouping, filtering, then consider using a CollectionView.
For a complete answer, here is the resulting code snippet, minus some error handling code:
namespace Whatever
{
public partial class MyWindow : Window
{
public delegate void CopyDelegate();
private string m_strSourceDir; // Source directory - set elsewhere.
private List<string> m_lstrFiles; // To hold the find result.
private string m_strTargetDir; // Destination directory - set elsewhere.
private int m_iFileIndex; // To keep track of where we are in the list.
private ObservableCollection<string> m_osstrProgress; // To attach to the listbox.
private void CopyFiles()
{
if(m_iFileIndex == m_lstrFiles.Count)
{
System.Windows.MessageBox.Show("Copy Complete");
return;
}
string strSource= m_lstrFiles[m_iFileIndex]; // Full path.
string strTarget= m_strTargetDir + strSource.Substring(strSource.LastIndexOf('\\'));
string strProgress= "Copied \"" + strSource + "\" to \"" + strTarget + '\"';
try
{
System.IO.File.Copy(strFile, strTarget, true);
}
catch(System.Exception exSys)
{
strProgress = "Error copying \"" + strSource + "\" to \"" + strTarget + "\" - " + exSys.Message;
}
m_osstrProgress.Add(strProgress);
++m_iFileIndex;
lbxProgress.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, new CopyDelegate(CopyFiles));
}
private void btnCopy_Click(object sender, RoutedEventArgs e)
{
m_lstrFiles= new List<String>(System.IO.Directory.GetFiles(m_strSourceDir, "*.exe"));
if (0 == m_lstrFiles.Count)
{
System.Windows.MessageBox.Show("No .exe files found in " + m_strSourceDir);
return;
}
if(!System.IO.Directory.Exists(m_strTargetDir))
{
try
{
System.IO.Directory.CreateDirectory(m_strTargetDir);
}
catch(System.Exception exSys)
{
System.Windows.MessageBox.Show("Unable to create " + m_strTargetDir + ": " + exSys.Message);
return;
}
}
m_iFileIndex= 0;
m_osstrProgress= new ObservableCollection<string>();
lbxProgress.ItemsSource= m_osstrProgress;
lbxProgress.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new CopyDelegate(CopyFiles));
}
}
}