I am having a problem with getting data from db and showing in UI asynchronously.
I am using MVVM light, when I click the button, action is triggered in ViewModel:
private void SearchQuery(string query)
{
_redisModel.GetFriendsListAsync(query);
}
At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done.
At this point I need to update ListBox ItemSource. But when I try to update is I get
“The calling thread cannot access this object because a different thread owns it”
I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() and different magic, but it still doesn’t work.
I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.
private string filterText = string.Empty;
public string FilterText
{
get { return filterText; }
set
{
filterText = value;
this.RaisePropertyChanged(() => this.FilterText);
this.FriendsList.View.Refresh(); // Here where exception is happening.
}
}
I tried to change this line to
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>this.FriendsList.View.Refresh())); - still the same.
I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods.
Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.
I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.
A bit more details on how _redisModel.GetFriendsListAsync works:
In the end(after all chain of calls) it ends up here:
public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
_cbMethod = cbMethod;
_state = state;
QueueWorkOnThreadPool(workToBeDone);
}
ThreadPool.QueueUserWorkItem(state =>
{
try
{
_result = workToBeDone();
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
UpdateStatusToComplete(); //1 and 2
NotifyCallbackWhenAvailable(); //3 callback invocation
}
});
In viewmodel I have method:
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => this.FriendsList.View.Refresh()));
}
}
Can anybody please help me with this ?
Thank you
You are creating CollectionViewSource in one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompleted to
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => {
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
this.FriendsList.View.Refresh();
}));
}
}
}
You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.
If you include the relevant code it would be easier to say for sure.
Related
I have followed this blog by Stephen Cleary and I was wondering what would be the best approach for update the existing collection that is bounded to the UI which updates every 15 seconds?
For e.g. do I Clear the list then add a new collection to create a new object?
I am asking this question because when I added this Task taskA = Task.Run(() => UpdateManifest(_ManifestToken.Token)); line of code my CPU increase rapidly.
C#:
// Ctor.
public ManifestViewModel()
{
_ManifestItems = new NotifyTaskCompletion<ObservableCollection<ManifestItem>>(FetchData());
Task taskA = Task.Run(() => UpdateManifest(_ManifestToken.Token));
}
private NotifyTaskCompletion<ObservableCollection<ManifestItem>> _ManifestItems;
public NotifyTaskCompletion<ObservableCollection<ManifestItem>> ManifestItems
{
get => _ManifestItems;
set
{
if (_ManifestItems != value)
{
_ManifestItems = value;
OnPropertyChanged();
}
}
}
public static Task UpdateManifest(CancellationToken token)
{
while (true)
{
_ManifestItems = new NotifyTaskCompletion<ObservableCollection<ManifestItem>>(FetchData());
Task.Delay(15000);
}
}
You should fetch the data on a background thread and update the source collection on the UI thread. You could also use the BindingOperations.EnableCollectionSynchronization method enable the data-bound collection to be updated from a background thread.
A better way than calling Updatemanifest from your constructor and use a while (true) loop would be to use a Timer that fetches the data at given intervals, e.g.:
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var data = FetchData();
//either just set a source property to the fetched data collection
SourceCollectionProperty = data;
//...or update the collection
}
I have a program that searches the given directory and adds all the files to a list view. My problem is that the ui thread gets stuck while the search is busy. I have tried using tasks but can’t get it to work in async. The list view must be updated after each file has been found.
I have done a lot of reading about the TPL and how to use it but can’t get it to work in this case. I got it to work where the processing of data is in one method that create a task to process it. Can any one tel me what is wrong in the code below and how to fix it?
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
WalkDirectory(new DirectoryInfo(drive));
});
}
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
public bool WalkDirectory(DirectoryInfo directory)
{
if (directory == null)
{
throw new ArgumentNullException("directory");
}
return this.WalkDirectories(directory);
}
private bool WalkDirectories(DirectoryInfo directory)
{
bool continueScan = true;
continueScan = WalkFilesInDirectory(directory);
if (continueScan)
{
DirectoryInfo[] subDirectories = directory.GetDirectories();
foreach (DirectoryInfo subDirectory in subDirectories)
{
try
{
if ((subDirectory.Attributes & FileAttributes.ReparsePoint) != 0)
{
continue;
}
if (!(continueScan = WalkDirectory(subDirectory)))
{
break;
}
}
catch (UnauthorizedAccessException)
{
continue;
}
}
}
if (continueScan)
{
testTaskUpdateLabel(directory.FullName);
}
return continueScan;
}
private bool WalkFilesInDirectory(DirectoryInfo directory)
{
bool continueScan = true;
// Break up the search pattern in separate patterns
string[] searchPatterns = _searchPattern.Split(';');
// Try to find files for each search pattern
foreach (string searchPattern in searchPatterns)
{
if (!continueScan)
{
break;
}
// Scan all files in the current path
foreach (FileInfo file in directory.GetFiles(searchPattern))
{
try
{
testTaskUpdate(file.FullName);
}
catch (UnauthorizedAccessException)
{
continue;
}
}
}
return continueScan;
}
If you use a BackgroundWorker class, the UI will work and progress can be updated in the ProgressChanged event handler.
MSDN Reference
Can any one tel me what is wrong in the code below and how to fix it?
The problem is here
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
You should not use TPL to update the UI. TPL tasks are for doing non UI work and UI should only be updated on the UI thread. You already moved the work on a thread pool thread (via Task.Run), so the only problem you need to solve is how to update the UI from inside the worker. There are many ways to do that - using Control.Invoke/BeginInvoke, SynchronizationContext etc, but the preferred approach for TPL is to pass and use IProgress<T> interface. Don't be fooled by the name - the interface is an abstraction of a callback with some data. There is a standard BCL provided implementation - Progress<T> class with the following behavior, according to the documentation
Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed.
i.e. perfectly fits in UI update scenarios.
With all that being said, here is how you can apply that to your code. We'll use IProgress<string> and will call Report method and pass the full name for each file/directory we find - a direct replacement of your testTaskUpdateLabel calls.
private void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<string>(text => label4.Text = text);
Task.Run(() =>
{
WalkDirectory(new DirectoryInfo(drive), progress);
});
}
public bool WalkDirectory(DirectoryInfo directory, IProgress<string> progress)
{
if (directory == null) throw new ArgumentNullException("directory");
if (progress == null) throw new ArgumentNullException("progress");
return WalkDirectories(directory, progress);
}
bool WalkDirectories(DirectoryInfo directory, IProgress<string> progress)
{
// ...
if (!(continueScan = WalkDirectories(subDirectory, progress)))
// ...
if (continueScan)
progress.Report(directory.FullName);
// ...
}
bool WalkFilesInDirectory(DirectoryInfo directory, IProgress<string> progress)
{
// ...
try
{
progress.Report(file.FullName);
}
// ...
}
I got it to work by making the walkDirectory, walkDirectories and WalkFiles methods async. Thus using the await keyword before I call the testUpdate and testUpdateLabel methods. This way the listview is updated with the search results while the search is running without blocking the UI thread. I.E. the user can cancel the search when the file he was searching for has been found.
I have a ViewModel with an Observable Collection Property
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get
{
return currentSensorAreasList;
}
set
{
if (currentSensorAreasList != value)
{
currentSensorAreasList = value;
OnPropertyChanged(PROPERTY_NAME_CURRENT_SENSOR_AREAS_LIST);
}
}
}
Then in my xaml i have a binding
ItemsSource="{Binding CurrentSensorAreasList}">
This Observable Collection is updated trought a method that can be call in the viewModel constructor or when a collectionchanged handler from another list gets called.
I just clear the list and then add a fewer new items. While debugging i see all my new items updated on the list. But the UI does not get updated.
When i regenerate the viewModel and then this update method gets call in the constructor the list gets updated in the UI.
Any ideas?? I don't know if the problem comes when i call the method from a handler.....
UPDATE #1
As requested i'm going the code when I update the list
I have tested two ways to do this update
private void UpdateList1()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList.Clear();
CurrentSensorAreasList.AddRange(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList));
//AddRange is an extension method.
}
}
private void UpdateList2()
{
if (globalAreaManagerList != null && OperationEntity != null)
{
CurrentSensorAreasList = new ObservableCollection<GeographicArea>(globalAreaManagerList.Where(x => x != (OperationEntity as AreaManager)).SelectMany(areaRenderer => areaRenderer.AreaList))
}
}
Both cases works when i call it from the constructor. Then I have Other Lists where the Areas changes, and i get notified via CollectionChanged Handlers.
private void globalAreaManagerList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (AreaManager newItem in e.NewItems)
{
newItem.AreaList.CollectionChanged += AreaList_CollectionChanged;
}
}
if (e.OldItems != null)
{
foreach (AreaManager oldItem in e.OldItems)
{
oldItem.AreaList.CollectionChanged -= AreaList_CollectionChanged;
}
}
UpdateList();
}
private void AreaList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateList();
}
So when i use UpdateList1 seems to work more times but suddenly the Binding is broken and then this update does not show in the UI.
If you wish change exactly an instance of collection I recomend using DependecyProperty for that case.
Here is:
public ObservableCollection<GeographicArea> CurrentSensorAreasList
{
get { return (ObservableCollection<GeographicArea>)GetValue(CurrentSensorAreasListProperty); }
set { SetValue(CurrentSensorAreasListProperty, value); }
}
public static readonly DependencyProperty CurrentSensorAreasListProperty =
DependencyProperty.Register("CurrentSensorAreasList", typeof(ObservableCollection<GeographicArea>), typeof(ownerclass));
Where ownerclass - a name of class where you put this property.
But the better way is create only one instance of ObservaleCollection and then just change its items. I mean Add, Remove, and Clear methods.
I'm using the SimpleMVVM Toolkit.
I have a view (manage_view) with multiple buttons that will navigate (set a frame's source) to new views (manage_import_view, manage_scanners_view, etc). Each view has it's own VM.
For each of the views I set the datacontext to the VM by using a locator. The locator injects a ServiceAgent into the VM.
The problem is that when I navigate to the other views, the state of the previous view is lost. For instance I'd do an import on the Manage_Import_View and bind to properties on the VM. When I navigate to Manage_Scanners_View and then back to the Manage_Import_View, the properties that I bound to is lost.
I understand what is happening but Im not sure how to resolve it. How do I keep the state of the views when switching between them?
Looking forward to your thoughts on this.
(I've searched Switching between views according to state but it's not exactly what I need.)
Edit
My locator
public ImportViewModel ImportViewModel
{
get
{
IIntegrationServiceAgent sa = new IntegrationServiceAgent();
return new ImportViewModel(sa);
}
}
In my view's XAML I set the datacontext
DataContext="{Binding Source={StaticResource Locator}, Path=ImportViewModel}"
Navigation is like so
private void Navigate(string pageName)
{
Uri pageUri = new Uri("/Views/" + pageName + ".xaml", UriKind.Relative);
this.SelectedPage = pageUri;
this.SelectedPageName = pageName;
}
I have a completion callback once import is complete. This sets the props that my view binds to - these are the ones that are reset after switching views.
private void ImportCompleted(IntegrationResult intresult, Exception error)
{
if (error == null)
{
_errorCount = intresult.Errors.Count;
ErrorList = intresult.Errors;
ResultMessage = intresult.Message;
ErrorMessage = (errorList.Count == 1 ? "1 error" : errorList.Count.ToString() + " errors");
Notify(ImportCompleteNotice, null); // Tell the view we're done
ShowErrorDialog(importType);
}
else
NotifyError(error.Message, error);
IsImportBusy = false;
}
This seems clunky to me. I am not entirely sure why this is happening but I can guess... You are loading the SelectedPage from Uris each time they are requested, this will set and parse the XAML each time they are loaded which will effect your bindings. Here's what I would do:
First on the application start-up, load all of the Views into a view list
private Dictionary<string, Uri> viewUriDict;
private List<string> viewNameList = new List<string>()
{
"ViewA",
"ViewB"
};
// The main View Model constructor.
public MainViewModel()
{
viewUriDict = new Dictionary<string, Uri>();
foreach (string s in viewNameList)
viewUriDict.Add(s, new Uri("/Views/" + s + ".xaml", UriKind.Relative);
this.SelectedPageName = viewNameList[0];
}
private string selectedPageName;
public string SelectedPageName
{
get { return this.selectedPageName; }
set
{
if (this.selectedPageName == value)
return;
this.selectedPageName = value;
this.SelectedPage = this.viewUriDict[this.selectedPageName];
OnPropertyChanged("SelectedPageName"); // For INotifyPropertyChanged.
}
}
private Uri selectedPage;
private Uri selectedPageName
{
get { return this.selectedPage; }
set
{
if (this.selectedPage == value)
return;
this.selectedPage = value;
OnPropertyChanged("SelectedPage"); // For INotifyPropertyChanged.
}
}
So now the Uri list is cached in your main window/app. Navigation would then become
private void Navigate(string pageName)
{
this.SelectedPageName = pageName;
}
or by merely setting this.SelectedPageName = "PageX".
The second thing I would do is lazily instantiate the InportViewModel service agent. I am not sure how this is called, but I would not re-create the service agent on every call...
private IIntegrationServiceAgent sa;
public ImportViewModel ImportViewModel
{
get
{
if (sa == null)
sa = new IntegrationServiceAgent();
return new ImportViewModel(sa);
}
}
This may resolve your issue, or might not. Either way I hope it is of some value. If I were you, I would look at using Prism to do this type of thing, although it might be overkill if this is a small project.
I hope this helps.
For anyone who've experience the same puzzle, I've found the answer here and here .
tonysneed's reply on the second link explains it.
I have the synfusion tile view as below.
Maximized Item template for these Items is TreeView. the treeview items Source is bind to the Observable Collection. When I Maximize one of these Items, it will load the data from ViewModel as below. It's in the MaximizedItemChanged Event.
private void tileViewControl_Exchanges_MaximizedItemChanged(object sender, TileViewEventArgs args)
{
if (args.Source != null)
{
TileViewControl tileViewControl = (TileViewControl)sender;
TileViewItem tvi = (TileViewItem)args.Source;
PanelViewModel panelViewModel = (PanelViewModel)tileViewControl.ItemContainerGenerator.ItemFromContainer(tvi);
String currentSelectedPanelID = GetSelectedPanelID(tileViewControl);
// below function will load all the treeview items.
SetSelectedExchangeID(tileViewControl, exchangePanelViewModel.ExchangeID);
}
}
But treeview has over thousands of items. So after clicking on Maximize, It will take a while and the program hang. Is there a way to maximize the Item smoothly first and load the Treeview Item at the background? What I'd like to do is I will show the loading animation while the treeview is loading but now, when it maximized (after hanging for 8 or 9 secs) , the treeview is already loaded.
Edit: I added the code fore SetSelectedExchangeID.
public static readonly DependencyProperty SelectedExchangeIDProperty =
DependencyProperty.RegisterAttached("SelectedExchangeID",
typeof(String),
typeof(UC_Contract_List),
new UIPropertyMetadata(new PropertyChangedCallback(SelectedExchangeIDPropertyChanged)));
static void SelectedExchangeIDPropertyChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs eventArgs)
{
TileViewControl tileViewControl = (TileViewControl)depObj;
ItemContainerGenerator itemContainerGenerator = tileViewControl.ItemContainerGenerator;
String newPanelID = (String)eventArgs.NewValue;
if (newPanelID != null)
{
if (tileViewControl.Visibility == Visibility.Visible)
{
foreach (PanelViewModel exchangePanel in tileViewControl.Items)
{
if (exchangePanel.ExchangeID.Equals(newExchangeID))
{
TileViewItem tvi = (TileViewItem)itemContainerGenerator.ContainerFromItem(exchangePanel);
try
{
if (tileViewControl.tileViewItems != null)
{
if (tvi.TileViewItemState != TileViewItemState.Maximized)
{
tvi.TileViewItemState = TileViewItemState.Maximized;
}
}
}
catch (Exception e) { }
break;
}
}
}
}
else
{
foreach (PanelViewModel exchangePanel in tileViewControl.Items)
{
TileViewItem tvi = (TileViewItem)itemContainerGenerator.ContainerFromItem(exchangePanel);
tvi.TileViewItemState = TileViewItemState.Normal;
}
}
}
public static void SetSelectedExchangeID(DependencyObject depObj, String exchangeID)
{
depObj.SetValue(SelectedExchangeIDProperty, exchangeID);
}
public static String GetSelectedExchangeID(DependencyObject depObj)
{
return (String)depObj.GetValue(SelectedExchangeIDProperty);
}
And in ViewModel:
String _selectedExchangeID;
public String SelectedExchangeID
{
get { return this._selectedExchangeID; }
set
{
if (value == null)
{
this.ClearPanels();
this._selectedExchangeID = value;
}
else
{
this._selectedExchangeID = value;
PanelViewModel curPanelViewModel = this.GetPanelViewModel(this._selectedExchangeID);
if (curPanelViewModel != null)
{
curPanelViewModel.Activate(); // this will add to the observable collection for Treeview ItemsSource
}
}
this.OnPropertyChanged("SelectedExchangeID");
}
}
You can do that by doing the processing/heavy loading task asynchronously on a background thread and syncing with foreground thread using UI Dispatcher object only when everything is available and processed.
For details on BackgroundWorker refer to MSDN.
Please note BackgroundWorker is not the only way to do async task. you may opt for Tasks (introduced in .net 4.0) or BeginInvoke/EndInvoke.
And When you are done with Heavy task you may sync with foreground thread in the following way.
First initialize dispatcher on UI thread (lets say Views Constructor):
Dispatcher _UIDispatcher;
public MyView
{
...
_UIDispatcher = Dispatcher.CurrentDispatcher;
}
Then sync in after loading is complete:
public void SyncPostLoading(IEnumerable<Something> myData)
{
_UIDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, System.Threading.ThreadStart)delegate()
{
foreach(Something something in myData)
myObervableCollection.Add(something);
});
}
You have a couple of different options for how to do you work on a background thread. You can use the backgroundworker (slightly outdated) or the .NET 4.0 Tasks (part of the Task Parallel Library). You need to decide if you want to load all of the data into a new collection and invoke an update onto the GUI thread all at once or if you want to load the items in batches and invoke those batches onto the GUI in several rounds