I am currently having an issue with my code where the focus on a particular item row will not hold. I created the int focusReference to keep track of the currently focused row, but it appears to reset to 0 after every couple of seconds, i.e. once the user selects a row, in a couple of seconds, it "forgets" the users selection. I've included an example below:
As you can see from my code below, I have a timer doing something every few seconds. I've already been told that refreshing my list every few seconds is leading to the loss in focus every few seconds.
How would I programmatically set focus on an item, to retain that focus a user has? I've tried to implement a FocusItem method but it doesn't seem to be working. This is a very important aspect of my program, and I need it to work properly otherwise other features (like the right click context menu I am implementing), will also not work:
public MainWindow()
{
InitializeComponent();
int focusReference = 0;
PlotListView.SelectionChanged += (s, ee) => { PlotListView_SelectionChanged(s, ee, focusReference); };
var dbObject = new DbConnect();
dbObject.OpenConnection();
RefreshPlotTimer(filterReference, focusReference);
}
public void PlotListView_SelectionChanged(object sender, SelectionChangedEventArgs e, int focusReference)
{
if (PlotListView.SelectedItems.Count == 0) return;
var selectedItem = (DbConnect.PlotList)PlotListView.SelectedItems[0];
focusReference = Convert.ToInt32(selectedItem.PlotId);
FocusItem(focusReference);
}
private void FocusItem(int focusReference)
{
if (PlotListView.SelectedItems.Count != 0)
{
DbConnect.PlotList plotList =
PlotListView.Items.OfType<DbConnect.PlotList>()
.FirstOrDefault(p => Convert.ToInt32(p.PlotId) == focusReference);
if (plotList != null)
{
//get visual container
var container = PlotListView.ItemContainerGenerator.ContainerFromItem(plotList) as ListViewItem;
if (container != null)
{
container.IsSelected = true;
container.Focus();
}
}
}
}
public void RefreshPlotTimer(int filterReference, int focusReference)
{
var refreshTimer = new Timer();
refreshTimer.Elapsed += (sender, e) => RefreshPlot(sender, e, filterReference, focusReference);
refreshTimer.Interval = 2500;
refreshTimer.Enabled = true;
}
public void RefreshPlot(object source, ElapsedEventArgs e, int filterReference, int focusReference)
{
var dbObject = new DbConnect();
dbObject.OpenConnection();
dbObject.RefreshPlot();
Dispatcher.Invoke(() =>
{
FocusItem(focusReference);
if (!string.IsNullOrWhiteSpace(FilterTextBox.Text) &&
(!Regex.IsMatch(FilterTextBox.Text, "[^0-9]")))
{
filterReference = Convert.ToInt32(FilterTextBox.Text);
}
});
ResetPlot(filterReference);
}
public void ResetPlot(int filterReference)
{
var dbObject = new DbConnect();
dbObject.OpenConnection();
List<DbConnect.PlotList> plotList = dbObject.SelectPlotLists(filterReference);
Dispatcher.BeginInvoke(
new ThreadStart(() => PlotListView.ItemsSource = plotList));
int jobSum = 0;
int bidSum = 0;
foreach (DbConnect.PlotList item in PlotListView.Items)
{
jobSum += Convert.ToInt32(item.Jobs);
bidSum += Convert.ToInt32(item.Bids);
}
Dispatcher.BeginInvoke(
new ThreadStart(() => JobBidRatioTextBlock.Text = jobSum + " jobs - " + bidSum + " bids"));
}
UPDATE 2
I reverted back to the code I was using initially, could it be that the FocusItem method is not retaining the listview item focus?
private void FocusItem(int focusReference)
{
Dispatcher.Invoke(() =>
{
if (PlotListView.SelectedItems.Count != 0)
{
DbConnect.PlotList plotList =
PlotListView.Items.OfType<DbConnect.PlotList>()
.FirstOrDefault(p => Convert.ToInt32(p.PlotId) == focusReference);
if (plotList != null)
{
//get visual container
var container = PlotListView.ItemContainerGenerator.ContainerFromItem(plotList) as ListViewItem;
if (container != null)
{
container.IsSelected = true;
container.Focus();
}
}
}
}
}
I'm calling it at the right places and the index is definitely the right one, according to what is written in the console.
PlotListView_SelectionChanged 4
Before refresh 4
After refresh 4
PlotListView_SelectionChanged 7
Before refresh 7
After refresh 7
Before refresh 7
After refresh 7
Both in the refresh plot method...
public void RefreshPlot(object source, ElapsedEventArgs e)
{
var dbObject = new DbConnect();
dbObject.OpenConnection();
dbObject.RefreshPlot();
Console.WriteLine("\rBefore refresh " + focusReference);
Dispatcher.Invoke(() =>
{
if (!string.IsNullOrWhiteSpace(FilterTextBox.Text) &&
(!Regex.IsMatch(FilterTextBox.Text, "[^0-9]")))
{
filterReference = Convert.ToInt32(FilterTextBox.Text);
}
});
ResetPlot(filterReference);
Console.WriteLine("After refresh " + focusReference);
FocusItem(focusReference);
}
And the reset plot method...
public void ResetPlot(int filterReference)
{
// Establish MySQL connection
var dbObject = new DbConnect();
dbObject.OpenConnection();
// Fill plot list view
List<DbConnect.PlotList> plotList = dbObject.SelectPlotLists(filterReference);
Dispatcher.BeginInvoke(
new ThreadStart(() => PlotListView.ItemsSource = plotList));
int jobSum = 0;
int bidSum = 0;
foreach (DbConnect.PlotList item in PlotListView.Items)
{
jobSum += Convert.ToInt32(item.Jobs);
bidSum += Convert.ToInt32(item.Bids);
}
FocusItem(focusReference);
// Determine job/bid list ratio
Dispatcher.BeginInvoke(
new ThreadStart(() => JobBidRatioTextBlock.Text = jobSum + " jobs - " + bidSum + " bids"));
}
Before doing the refresh you need to remember the SelectedItem and set it agian after the refresh. That's the theory.
In case of objects you'll need to find which one is now the same as the one before (because it's not the same instance anymore) so you'll have to search for it in the new results list and assign it to the SelectedItem.
The easiest way would be to just re-set the SelectedIndex but because the list probably changes it would also sooner or later re-select the wrong item.
// Gets current selection.
public DbConnect.PlotList SelectedPlotList
{
get
{
return PlotListView.SelectedItem as DbConnect.PlotList;
}
}
public void ResetPlot(int filterReference)
{
// Get current plot number;
int? plotNumber = SelectedPlotList == null ? (int?)null : SelectedPlotList.PlotNumber;
var dbObject = new DbConnect();
dbObject.OpenConnection();
List<DbConnect.PlotList> plotList = dbObject.SelectPlotLists(filterReference);
// Find the plot list in the new list.
DbConnect.PlotList selectPlotList =
plotNumber.HasValue
? plotList.Where(x => x.PlotNumber == plotNumber.Value).FirstOrDefault();
: null;
Dispatcher.BeginInvoke(new ThreadStart(() => PlotListView.ItemsSource = plotList));
// Re-select plot list if found in the new list.
if (selectPlotList != null)
{
PlotListView.SelectedItem = selectPlotList;
}
int jobSum = 0;
int bidSum = 0;
foreach (DbConnect.PlotList item in PlotListView.Items)
{
jobSum += Convert.ToInt32(item.Jobs);
bidSum += Convert.ToInt32(item.Bids);
}
Dispatcher.BeginInvoke(
new ThreadStart(() => JobBidRatioTextBlock.Text = jobSum + " jobs - " + bidSum + " bids"));
}
EDIT:
I've just tested a quick & dirty demo and it works. There must be a bug somewhere else.
This generates 7 items with unique ids and re-selects the last selected item if it is drawn again:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
List<ListViewItem> items = new List<ListViewItem>();
Random rnd = new Random(DateTime.Now.Millisecond);
HashSet<int> ids = new HashSet<int>();
for (int i = 0; i < 7; i++)
{
int id = 0;
do
{
id = rnd.Next(0, 10);
} while (ids.Contains(id));
ids.Add(id);
items.Add(new ListViewItem() { Id = id, Name = "Item-" + i });
}
int? selectedId = listView1.SelectedItem != null ? (listView1.SelectedItem as ListViewItem).Id : (int?)null;
listView1.ItemsSource = items;
if (selectedId.HasValue)
{
listView1.SelectedItem = items.Where(x => x.Id == selectedId).FirstOrDefault();
listView1.Focus();
}
}
}
class ListViewItem
{
public int Id { get; set; }
public string Name { get; set; }
}
Related
I want to add a dynamic micro chart to my application but it doesn't work. After a call from a method a value gets added and it makes a completely new micro chart for my chart to have the new values, but the change isn't visible in the app. So the old Values stayed and there is no new one. Thanks for helping me.
WeightList = new List<float>();
WeightList.Add(0);
WeightList.Add((float)74.3);
entries = new ChartEntry[30];
SyncArray();
private void SyncArray()
{
if (WeightList.Count != entries.Length)
{
entries = new ChartEntry[WeightList.Count];
}
for (int i = 0; i <= WeightList.Count - 1; i++)
{
if (i == WeightList.Count - 1 || i == 0)
{
entries[i] = new ChartEntry(WeightList[i]) { Label = "" + i, ValueLabel = "" + WeightList[i] };
}
else
{
entries[i] = new ChartEntry(WeightList[i]) { Label = "" + i };
}
}
chart = new LineChart() { Entries = entries, BackgroundColor = SKColors.Transparent };
Chart = chart;
}
public LineChart Chart
{
get => chart;
set => SetProperty(ref chart, value);
}
public float Weight
{
get => weight;
set
{
weight = value;
WeightList.Add(weight);
SyncArray();
}
}
Credits: #Jason
What to change:
private void SyncArray()
{
if (WeightList.Count != entries.Length)
{
entries = new ChartEntry[WeightList.Count];
}
for (int i = 0; i <= WeightList.Count - 1; i++)
{
if (i == WeightList.Count - 1 || i == 0)
{
entries[i] = new ChartEntry(WeightList[i]) { Label = "" + i, ValueLabel = "" + WeightList[i] };
}
else
{
entries[i] = new ChartEntry(WeightList[i]) { Label = "" + i };
}
}
Chart = new LineChart() { Entries = entries, BackgroundColor = SKColors.Transparent };
}
in this program, when the Recall button (recallBtn_Click()) is clicked, it calls a method (that calculates directions) from another class which should then call the showPath() method. the show path method should then display its output in a textBox. But the values don't show even though i can see from debugging that the values are being sent to the text box. can anybody tell me where i went wrong?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
storeRetSelect.SelectedIndex = 0;
PrioritySelect.SelectedIndex = 0;
}
public void showPath(List<PathFinderNode> mPath)
{
var T = new Form1().directionsTextBox;
foreach (PathFinderNode node in mPath)
{
if ((node.X - node.PX) > 0) { T.Text += "Right" + System.Environment.NewLine ; }
if ((node.X - node.PX) < 0) { T.Text += "Left" + System.Environment.NewLine; }
if ((node.Y - node.PY) > 0) { T.Text += "UP" + System.Environment.NewLine; }
if ((node.Y - node.PY) < 0) { T.Text += "Down" + System.Environment.NewLine; }
}
}
private void recallBtn_Click(object sender, EventArgs e)
{
var path = new pathPlan();
string desigString = inputTextBox.Text;
int[] desig = new int[3];
for (int i = 0; i < desigString.Length; i++) { desig[i] = (int)char.GetNumericValue(desigString[i]); }
path.Recall(desig[1], desig[2], (-1) * desig[0]);
}
}
With this line you are initialising a new object and get the reference of the textbox there.
var T = new Form1().directionsTextBox;
But I assume you want to use the textbox of the form which is allready open. Change the line to the following to access the textbox of the current object.
var T = this.directionsTextBox;
I have bound one list from web service. First time only 5 items are loaded and then when user scroll the list view other 5 items are loaded. Every thing is working fine but I am facing issue in one scenario.
First time when List is bound (don't do scrolling) and if immediately I clicked on some list item then "ItemTepped" event is fired and page should be navigated to other page but problem is it showing me error as below image.
I have put Active indicator for loading.
This code is in xamarin.forms (Portable library). This is not xamarin.Android project.
public partial class NewsGalleryListPage : ContentPage
{
int totalItems = 0;
decimal MaxIndex = 0;
int index = 0;
List<Newslist> NewsList;
public NewsGalleryListPage()
{
InitializeComponent();
NewsGalleryLists.ItemTapped += NewsGalleryLists_ItemTapped;
NewsGalleryLists.ItemAppearing += NewsGalleryLists_ItemAppearing1;
loadingIndicator.IsVisible = true;
loading.IsRunning = true;
loading.IsVisible = true;
CallWebServiceForNewsGalleryList(index);
}
private void NewsGalleryLists_ItemAppearing1(object sender, ItemVisibilityEventArgs e)
{
try
{
if (totalItems != 0)
{
MaxIndex = Math.Ceiling(((decimal)totalItems) / 5);
if (index < MaxIndex)
{
if (NewsList != null && e.Item != null && e.Item == NewsList[NewsList.Count - 1])
{
index++;
if (index != MaxIndex)
{
loadingIndicator.IsVisible = true;
loading.IsVisible = true;
loading.IsRunning = true;
CallWebServiceForNewsGalleryList(index);
}
}
}
}
}
catch(Exception ex)
{
}
}
private void NewsGalleryLists_ItemTapped(object sender, ItemTappedEventArgs e)
{
var selectedNewsGalleryListItem = sender as Xamarin.Forms.ListView;
var obj = selectedNewsGalleryListItem.SelectedItem as CMO.ServicesClasses.Newslist;
Navigation.PushAsync(new CMO.Gallery.NewsGalleryDetail(obj));
}
public async void CallWebServiceForNewsGalleryList(int index)
{
try
{
string lang = "en";
if (Application.Current.Properties.ContainsKey("Language"))
{
lang = Application.Current.Properties["Language"] as string;
// do something with i
}
List<KeyValuePair<string, string>> values = new List<KeyValuePair<string, string>>();
values.Add(new KeyValuePair<string, string>("lang",lang));
values.Add(new KeyValuePair<string, string>("title", ""));
values.Add(new KeyValuePair<string, string>("index", Convert.ToString(index)));
values.Add(new KeyValuePair<string, string>("limit", "5"));
var response = await GeneralClass.GetResponse<CMO.ServicesClasses.RootObjectNewsGalleryList>("http://14.141.36.212/maharastracmo/api/getnewslist", values);
if (response != null)
{
if (NewsList == null || index==0)
{
NewsList = new List<Newslist>();
}
for(int i = 0; i < response.newslist.Count; i++)
{
var ObjectNewslist = new Newslist();
ObjectNewslist.page_id = response.newslist[i].page_id;
ObjectNewslist.title = response.newslist[i].title;
ObjectNewslist.date = response.newslist[i].date;
ObjectNewslist.news_photo = response.newslist[i].news_photo;
ObjectNewslist.content = response.newslist[i].content;
NewsList.Add(ObjectNewslist);
}
totalItems = response.total_results;
NewsGalleryLists.RowHeight = 100;
var x = NewsGalleryLists.RowHeight;
this.Title = AppResources.LNewsGallery;
NewsGalleryLists.ItemsSource = NewsList;
}
}
catch (WebException exception)
{
}
loading.IsVisible = false;
loading.IsRunning = false;
loadingIndicator.IsVisible = false;
}
}
I have gone through following link
https://forums.xamarin.com/discussion/23124/proper-way-to-update-the-tableviews-sections-content
https://forums.xamarin.com/discussion/21994/threading-in-xamarin-forms
I have tried
Xamarin.Forms.Device.BeginInvokeOnMainThread (() =>
{});
Task.Run(() => { });
I don't know how to handle thread in xamarin forms as beginner level.
It usually happens when you use TemplateSelector, but creating a new one for each row. Create one instance of TemplateSelector and use it.
I store in dictionary all the values of listView in dictionary-value and store its index in dictionary-keys at form load.
I am using dictionary as a medium of index storage for corresponding transferred listitems.
Now i transfer from a to b on a button click (a listview is full and b is empty) then i do again transfer the same element from b to a on another button click. It is now appended at last when i do back to b->a transfer. But i want it to append on same index on which it was before transferring to a->b.
I mean when i do b->a transfer then it must go to same index where it used to stay before
My code is this (please correct me where i am wrong and please give me a solution)
private void Form1_Load(object sender, EventArgs e)
{
//storing data in dictionary
}
private void button1_Click(object sender, EventArgs e)
{
MoveSelectedItems(listView1, listView2,0);
}
private void button2_Click(object sender, EventArgs e)
{
MoveSelectedItems(listView2, listView1,1);
}
private void MoveSelectedItems(ListView source, ListView target, int flag)
{
ListViewItem temp = source.SelectedItems[0];
source.Items.Remove(temp);
if(flag==0)//called when i do a->b
{
target.Items.Add(temp);
}
else
{
int index=getIndex(temp);
target.Items.Insert(index, temp);
}
}
private int getIndex(ListViewItem temp)
{
int index = 0;
if (dictList.ContainsValue(temp.Text))
{
foreach (KeyValuePair<int, string> pair in dictList)
if (temp.Text.Equals(pair.Value))
{
index=Convert.ToInt32(pair.Key);
return index;
}
}
return index;
}
What you actually need is, after taking the desired index from the dictionary, to search the items currently in the list view and find the actual insert position.
There are different way you can do that, here is one using binary search:
else
{
foreach (ListViewItem item in source.SelectedItems)
{
ListViewItem lvItem = item.Clone() as ListViewItem;
int index = dictList[item.Text];
// Insert at appropriate position based on index value
if (index == 0) // Always first
target.Items.Insert(0, lvItem);
else if (index == dictList.Count - 1) // Always last
target.Items.Add(lvItem);
else
{
// Binary search the current target items
int lo = 0, hi = target.Items.Count - 1;
while (lo <= hi)
{
int mid = lo + (hi - lo) / 2;
if (index < dictList[target.Items[mid].Text])
hi = mid - 1;
else
lo = mid + 1;
}
// Here lo variable contains the insert position
target.Items.Insert(lo, lvItem);
}
source.Items.Remove(item);
}
}
EDIT: Here is a [mcve] proving that it works:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace Samples
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var splitView = new SplitContainer { Dock = DockStyle.Fill, Parent = form, Orientation = Orientation.Vertical };
var listView1 = new ListView { Dock = DockStyle.Fill, Parent = splitView.Panel1, View = View.List };
var listView2 = new ListView { Dock = DockStyle.Fill, Parent = splitView.Panel2, View = View.List };
var buttonPanel = new Panel { Dock = DockStyle.Bottom, Parent = form };
var button1 = new Button { Parent = buttonPanel, Left = 16, Top = 8, Text = ">" };
var button2 = new Button { Parent = buttonPanel, Left = button1.Right + 16, Top = 8, Text = "<" };
buttonPanel.Height = button1.Height + 16;
var dictList = new Dictionary<string, int>
{
{ "first", 0 },
{ "second", 1 },
{ "third", 2 },
{ "fourth", 3 },
{ "fifth", 4 },
{ "sixth", 5 },
{ "seventh", 6 },
};
foreach (var item in dictList)
listView1.Items.Insert(item.Value, item.Key);
Action<ListView, ListView, int> MoveSelectedItems = (ListView source, ListView target, int flag) =>
{
while (source.SelectedItems.Count > 0)
{
var item = source.SelectedItems[0];
source.Items.Remove(item);
if (flag == 0)
{
target.Items.Add(item);
}
else
{
int index = dictList[item.Text];
// Insert at appropriate position based on index value
if (index == 0) // Always first
target.Items.Insert(0, item);
else if (index == dictList.Count - 1) // Always last
target.Items.Add(item);
else
{
// Binary search the current target items
int lo = 0, hi = target.Items.Count - 1;
while (lo <= hi)
{
int mid = lo + (hi - lo) / 2;
if (index < dictList[target.Items[mid].Text])
hi = mid - 1;
else
lo = mid + 1;
}
// Here lo variable contains the insert position
target.Items.Insert(lo, item);
}
}
}
};
button1.Click += (sender, e) => MoveSelectedItems(listView1, listView2, 0);
button2.Click += (sender, e) => MoveSelectedItems(listView2, listView1, 1);
Application.Run(form);
}
}
}
You can add a ListViewItemComparer to the list view. Like this.
public class ListViewItemComparer : IComparer
{
public int Compare(object x, object y)
{
return (((ListViewItem)x).Index > ((ListViewItem)y).Index ? 1 : -1);
}
}
And then in MoveSelectedItems:
this.listView1.ListViewItemSorter = new ListViewItemComparer();
Good luck.
Okay, I have a loading animation that runs while a large DataTable is populated to let the user know that the program has not frozen. I have the animation working fine, but it freezes while the DataTable is updatingv as well. Is there some way to have multiple UI threads, so that the animation will continue to run while the DataTable is loading information?
EDIT: Current code is below.
private void CreateFileTable()
{
file_data = new DataSet();
data_table = new DataTable();
file_data.Tables.Add(data_table);
DataColumn tempCol = new DataColumn("File Name", typeof(string));
data_table.Columns.Add(tempCol);
tempCol = new DataColumn("Ext", typeof(string));
data_table.Columns.Add(tempCol);
tempCol = new DataColumn("Size", typeof(string));
data_table.Columns.Add(tempCol);
tempCol = new DataColumn("Created", typeof(Label));
data_table.Columns.Add(tempCol);
tempCol = new DataColumn("Modified", typeof(Label));
data_table.Columns.Add(tempCol);
tempCol = new DataColumn("Accessed", typeof(Label));
data_table.Columns.Add(tempCol);
tempCol = new DataColumn("Location", typeof(string));
data_table.Columns.Add(tempCol);
File_List.ItemsSource = file_data.Tables[0].DefaultView;
}
private void PopulateDirectories(string[] directories)
{
for (int i = 0; i < directories.Length; i++)
{
DirectoryInfo tempDirInfo = new DirectoryInfo(directories[i]);
bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);
if (!isSystem)
{
DataRow tempRow = data_table.NewRow();
tempRow["File Name"] = tempDirInfo.Name;
tempRow["Ext"] = "";
tempRow["Size"] = "";
tempLabel = new Label();
tempLabel.Padding = new Thickness(2, 0, 2, 0);
tempLabel.Content = tempDirInfo.CreationTime.ToLongDateString() + ", " + tempDirInfo.CreationTime.ToLongTimeString();
tempRow["Created"] = tempLabel;
tempLabel = new Label();
tempLabel.Padding = new Thickness(2, 0, 2, 0);
tempLabel.Content = tempDirInfo.LastWriteTime.ToLongDateString() + ", " + tempDirInfo.LastWriteTime.ToLongTimeString();
tempRow["Modified"] = tempLabel;
tempLabel = new Label();
tempLabel.Padding = new Thickness(2, 0, 2, 0);
tempLabel.Content = tempDirInfo.LastAccessTime.ToLongDateString() + ", " + tempDirInfo.LastAccessTime.ToLongTimeString();
tempRow["Accessed"] = tempLabel;
tempRow["Location"] = tempDirInfo.FullName;
data_table.Rows.Add(tempRow);
}
}
}
private void PopulateFiles(string[] files)
{
for (int i = 0; i < files.Length; i++)
{
FileInfo tempFileInfo = new FileInfo(files[i]);
bool isSystem = ((File.GetAttributes(files[i]) & FileAttributes.System) == FileAttributes.System);
if (!isSystem)
{
DataRow tempRow = data_table.NewRow();
tempRow["File Name"] = tempFileInfo.Name;
tempRow["Ext"] = tempFileInfo.Extension;
int fileSize = (int)tempFileInfo.Length;
if (fileSize > 1048576)
{
tempRow["Size"] = "" + fileSize / 1048576 + " MB";
}
else if (fileSize > 1024)
{
tempRow["Size"] = "" + fileSize / 1024 + " KB";
}
else
{
tempRow["Size"] = "" + fileSize + " B";
}
tempLabel = new Label();
tempLabel.Padding = new Thickness(2, 0, 2, 0);
tempLabel.Content = tempFileInfo.CreationTime.ToLongDateString() + ", " + tempFileInfo.CreationTime.ToLongTimeString();
tempRow["Created"] = tempLabel;
tempLabel = new Label();
tempLabel.Padding = new Thickness(2, 0, 2, 0);
tempLabel.Content = tempFileInfo.LastWriteTime.ToLongDateString() + ", " + tempFileInfo.LastWriteTime.ToLongTimeString();
tempRow["Modified"] = tempLabel;
tempLabel = new Label();
tempLabel.Padding = new Thickness(2, 0, 2, 0);
tempLabel.Content = tempFileInfo.LastAccessTime.ToLongDateString() + ", " + tempFileInfo.LastAccessTime.ToLongTimeString();
tempRow["Accessed"] = tempLabel;
tempRow["Location"] = tempFileInfo.DirectoryName;
data_table.Rows.Add(tempRow);
}
}
}
private string GetSelectedPath(TreeViewItem selectedNode)
{
return selectedNode.Tag as string;
}
private void PopulateFileList()
{
PopulateDirectories(Directory.GetDirectories(GetSelectedPath((TreeViewItem)Dir_Tree.SelectedItem)));
PopulateFiles(Directory.GetFiles(GetSelectedPath((TreeViewItem)Dir_Tree.SelectedItem)));
}
private void UpdateFileList()
{
LoadingWheel.Visibility = System.Windows.Visibility.Visible;
CreateFileTable();
PopulateFileList();
TxtFoundCount.Text = "Files/Folders Found: " + File_List.Items.Count;
LoadingWheel.Visibility = System.Windows.Visibility.Hidden;
}
I have tried using the BackgroundWorker and calling the UpdateFileList() method inside it, but I'm not having any luck.
EDIT: Below is my code for the BackgroundWorker.
private BackgroundWorker bgWorker1;
private void InitializeBackgroundWorker()
{
bgWorker1 = new BackgroundWorker();
bgWorker1.DoWork += new DoWorkEventHandler(bgWorker1_DoWork);
bgWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker1_RunWorkerCompleted);
}
private void bgWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (Dispatcher.CheckAccess())
{
PopulateFileList();
}
else
{
Dispatcher.Invoke(new Action(() => PopulateFileList()));
}
}
private void bgWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
TxtFoundCount.Text = "Files/Folders Found: " + File_List.Items.Count;
LoadingWheel.Visibility = System.Windows.Visibility.Hidden;
}
private void UpdateFileList()
{
LoadingWheel.Visibility = System.Windows.Visibility.Visible;
CreateFileTable();
bgWorker1.RunWorkerAsync();
}
I have a SelectionChanged event on a TreeView that calls InitializeBackgroundWorker() and UpdateFileList(). The list loads still, and I get no errors, but the loading animation never becomes visible.
Any help would be greatly appreciated.
Thanks.
There is only one UI thread. What you need to do is to load the data in the DataTable on a different thread.
If you want to show progress to the DataTable loading along the way (either directly, or through a ProgressBar or some other mechanism), the BackgroundWorker is a fairly straight-forward way to do that.
UPDATE: Very Simple Background Worker example
Here is a fairly simple example. It adds 100 random numbers to a collection, pausing the thread for a short time between each to simulate a long loading process. You can simply cut and paste this into a test project of your own to see it work.
The thing to notice is that the heavy lifting (the stuff that takes a while) is done in the DoWork, while all UI updates are done in ProgressChanged and RunWorkerCompleted. In fact, a separate list (numbers) is created in the DoWork handler because the global mNumbers collection is on the UI thread, and can't interact in the DoWork handler.
XAML
<Button x:Name="btnGenerateNumbers"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Generate Numbers" />
C# Code-Behind
BackgroundWorker bgWorker = new BackgroundWorker();
ObservableCollection<int> mNumbers = new ObservableCollection<int>();
public Window1()
{
InitializeComponent();
bgWorker.DoWork +=
new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.ProgressChanged +=
new ProgressChangedEventHandler(bgWorker_ProgressChanged);
bgWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
bgWorker.WorkerReportsProgress = true;
btnGenerateNumbers.Click += (s, e) => UpdateNumbers();
this.DataContext = this;
}
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progress.Visibility = Visibility.Collapsed;
lstItems.Opacity = 1d;
btnGenerateNumbers.IsEnabled = true;
}
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
List<int> numbers = (List<int>)e.UserState;
foreach (int number in numbers)
{
mNumbers.Add(number);
}
progress.Value = e.ProgressPercentage;
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
Random rnd = new Random();
List<int> numbers = new List<int>(10);
for (int i = 1; i <= 100; i++)
{
// Add a random number
numbers.Add(rnd.Next());
// Sleep from 1/8 of a second to 1 second
Thread.Sleep(rnd.Next(125, 1000));
// Every 10 iterations, report progress
if ((i % 10) == 0)
{
bgWorker.ReportProgress(i, numbers.ToList<int>());
numbers.Clear();
}
}
}
public ObservableCollection<int> NumberItems
{
get { return mNumbers; }
}
private void UpdateNumbers()
{
btnGenerateNumbers.IsEnabled = false;
mNumbers.Clear();
progress.Value = 0;
progress.Visibility = Visibility.Visible;
lstItems.Opacity = 0.5;
bgWorker.RunWorkerAsync();
}
I wrote a little test program which shows the use of the Dispatcher class. It just requires a WPF-Window and a ListBox with Name "listBox". Should be easy to apply this solution to your problem.
public void Populate() {
// for comparison, freezing the ui thread
for (int i = 0; i < 1000000; i++) {
listBox.Items.Add(i);
}
}
private delegate void AddItemDelegate(int item);
public void PopulateAsync() {
// create a new thread which is iterating the elements
new System.Threading.Thread(new System.Threading.ThreadStart(delegate() {
// inside the new thread: iterate the elements
for (int i = 0; i < 1000000; i++) {
// use the dispatcher to "queue" the insertion of elements into the UI-Thread
// DispatcherPriority.Background ensures Animations have a higher Priority and the UI does not freeze
// possible enhancement: group the "jobs" to small units to enhance the performance
listBox.Dispatcher.Invoke(new AddItemDelegate(delegate(int item) {
listBox.Items.Add(item);
}), System.Windows.Threading.DispatcherPriority.Background, i);
}
})).Start();
}