I have a datagrid that is receiving an update of 100,000+ rows which is bound to a CollectionViewSource.View whose source is an ObservableCollection. If I try to update in the current setup the UI freezes completely when I'm adding the new rows to the ObservableCollection. To work around this I set the CollectionViewSource.Source = null before adding the rows to the ObservableCollection which allows it to work fine. The only problem is that once I do this for the first load the next load will still have the UI freezing problem.
Here's the code.
public CollectionViewSource ViewSource { get; set; }
private ObservableCollection<ScreenerRow> internalRows = new ObservableCollection<ScreenerRow>();
private async Task Search()
{
internalRows.Clear();
ViewSource.Source = null;
var data = await Task.Run(() =>
{
return DoStuff();
});
if (data == null) return;
foreach (ScreenerRow sRow in data)
{
//freezes in here on second run
internalRows.Add(sRow);
}
ViewSource.Source = internalRows;
}
Just wondering if anyone else has had this problem or see's an issue with the way I am doing this.
Thanks for the help.
Edit: Changing my ObservableCollection to a List allows this to work fine.
//private ObservableCollection<ScreenerRow> internalRows = new ObservableCollection<ScreenerRow>();
private List<ScreenerRow> internalRows = new List<ScreenerRow>();
I take it from your edit that this may not be the case, but if there are cases where you actually need the collection to be observable, then you can also use the CollectionViewSource.DeferRefresh() method to prevent the UI from being updated after each individual change:
private async Task Search()
{
internalRows.Clear();
using(ViewSource.DeferRefresh())
{
var data = await Task.Run(() => GetSearchResults());
if (data == null) return;
foreach (ScreenerRow sRow in data)
{
internalRows.Add(sRow);
}
}
}
And also make sure that you have the virtualization mode set correctly on the control.
Related
I am not quite sure if I am asking the right question. I assume other people have had this issue.
I built my own Blazor Grid component. I am using an bound to a property.
I have a function to load my grid. I changed my bound property to a full getter,setter. In the setter, I call my function to load the grid. This works fast and easy in pretty much all instances. But, I have one grid that when binding it will take a few extra seconds to complete.
The problem: I can't seem to figure out how to get my waiting spinner component to show when loading my grid.
Example Blazor Markup:
#if (dataGrid == null)
{
<hr />
<BitcoSpinner></BitcoSpinner>
}
else
{
<BitcoGrid TheGrid="dataGrid"></BitcoGrid>
}
Here is my property and GridLoading:
private string selectedGroup1 = "";
public string selectedGroup
{
get => selectedGroup1;
set
{
selectedGroup1 = value;
LoadGrid();
}
}
private void LoadGrid()
{
dataGrid = null;
PT_Grid_Admin ptGrid = new PT_Grid_Admin(permitTraxLibrary, gridParams);
dataGrid = ptGrid.ADMIN_FeeList(feeList.Fee_Key, selectedGroup);
}
You should define LoadGrid method asynchronously. Therefore, at the beginning of the program, when the data grid value is set, your spinner will be displayed until the data grid value is not received. Then, after receiving the data grid value, the else part of the condition will be executed and its value will be displayed to the user.
It may not take much time to receive information from the DB in local mode, so the following code can be used to simulate the delay:
System.Threading.Thread.Sleep(5000);
In general, I think that if your code changes like this, you can see the spinner.
private string selectedGroup1 = "";
public string selectedGroup
{
get => selectedGroup1;
set
{
selectedGroup1 = value;
LoadGrid();
}
}
private async Task LoadGrid()
{
dataGrid = null;
System.Threading.Thread.Sleep(5000);
.
.
}
Of course, it is better to load the datagrid in OnInitializedAsync method. For more info you can refer to this link.
Seems like a very basic MVVM question, but when it comes to Catel, I have issues doing it.
I have a property - registered as it should - which is a List, named Lines.
I bind it to a ListBox.
I also have a Button with a command adding a entry to Lines.
Lines is mapped to a model, and when I check the values of the model, I see it gets updated correctly when adding a value to Lines.
So everything seems to work, except that my view isn't updating when Lines is modified.
I tried to solve this by adding a RaisePropertyChanged("Lines") in Lines' setter, and in the command that adds a new value to Lines.
It gives something like this for the property:
[ViewModelToModel("MyModel", "Lines")]
public List<string> Lines
{
get { return GetValue<List<string>>(LinesProperty); }
set
{
SetValue(LinesProperty, value);
RaisePropertyChanged("Lines");
}
}
public static readonly PropertyData LinesProperty =
RegisterProperty("Lines", typeof(List<string>), null, (s, e) => {});
and this for the command (yes, I have AddLine = new Command(OnAddLineExecute); in the viewmodel's constructor):
public Command AddLine { get; private set; }
private async void OnAddLineExecute()
{
// this doesn't seem relevant, but since we're talking async and stuff, that may as well be the issue
if (!lineCountChecked && Lines.Count >= 4)
{
if (await messageService.Show(MainDialogs.LineCountCheck, "Lines count", MessageButton.OKCancel, MessageImage.Warning) != MessageResult.OK)
return;
else
lineCountChecked = true;
}
//
Lines.Add("New Line");
RaisePropertyChanged("Lines");
}
It's more than likely a very stupid mistake, but I can't get it. What did I miss? Thanks
You have 2 options to make this work:
1) RaisePropertyChanged(() => Lines) => will update the whole collection
2) Use ObservableCollection instead of List so the UI can actually respond to updates
I recommend 2.
There are a lot of questions on here about updating controls(mainly the ObservableCollection) using a background worker thread. I am trying to implement this solution. However, my situation has just a little bit more depth to it because it calls to a function in the add method that returns the object to be added.
This is what my solution looks like right now (note that my ObservableCollection is bound to a TreeView control):
//Pass from Background Thread
MainTreeViewModel.AddLocation(locationName, locationValue);
//UI Thread (MainTreeViewModel)
public void AddLocation(MultiItemString displayName, int locationValue)
{
var node = Data.GetAllChildren(x => x.Children).Distinct().ToList().First(x => x.identify == 'P'); //Location Parent
App.Current.Dispatcher.BeginInvoke((Action)delegate()
{
node.Children.Add(CreateLocationNode(displayName, locationValue));
});
}
CreateLocationNode:
//Also location in MainTreeViewModel
private HierarchicalVM CreateLocationNode(MultiItemString displayName, int locationValue)
{
MultiItemString locationNumber = new MultiItemString(new[] {locationValue.ToString() + " "}, false);
var newLocationNode = new HierarchicalVM()
{
DisplayName = displayName, //Examples of props that turn out as NULL
LocationNumber = locationNumber,
TreeView_LocValue = locationValue,
Children = new ObservableCollection<HierarchicalVM>(), //This is what is being added to
Commands =
};
return newLocationNode;
}
When doing this I find that an object gets added, but all of the properties attached to it receive null values. Oppositely, when I am doing everything in the UI thread and just using node.Children.Add(CreateLocationNode(displayName, locationValue));, everything attaches how it should. Why am I getting a different result here, and how can I fix it?
Please let me know if you need more code.
I have a DataGrid that is bound to a DataGridCollectionView which is using an Observable Collection. The collection contains about 650+ items with around 20 columns. Every 60 seconds a new collection of data is received. That data is compared against the existing collection and then items Added, Removed, and Updated as needed. For the update I am doing the following:
private async void LoadData()
{
await RefreshData(TimeSpan.FromSeconds(100), cts.Token);
}
private async Task RefreshData(TimeSpan interval, CancellationToken token)
{
// Get the writable properties for the ContingencyViewModel
var writableProperties = typeof(ContingencyViewModel).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanWrite);
while (!token.IsCancellationRequested)
{
var list = viewModel.Items;
var cvs = GetItems(); // Service call that gets the updated collection
var existingIds = list.Select(s => s.UniqueId).Distinct().ToList();
var sourceIds = cvs.Select(s => s.UniqueId).Distinct().ToList();
var toAdd = sourceIds.Except(existingIds).ToList();
var toRemove = existingIds.Except(sourceIds).ToList();
var toUpdate = existingIds.Intersect(sourceIds).ToList();
var itemsToAdd = cvs.Where(x => toAdd.Contains(x.UniqueId)).ToList();
var itemsToRemove = list.Where(x => toRemove.Contains(x.UniqueId)).ToList();
var itemsToUpdate = list.Where(x => toUpdate.Contains(x.UniqueId)).ToList();
// Add new items
foreach (ItemViewModel item in itemsToAdd)
{
list.Add(item);
}
// Remove dropped items
foreach (ItemViewModel item in itemsToRemove)
{
list.Remove(item);
}
// Update existing items
foreach (ItemViewModel item in itemsToUpdate)
{
// Get a reference to the Updated Item
var source = cvs.First(x => x.UniqueId == item.UniqueId);
// This works but locks the UI for a little bit
this.UpdateItem<ItemViewModel>(source, item, writableProperties);
// This also works but all the results in my grid disappear when I scroll or resize screen. To get them back I have to actually Expand and Collapse groups.
/*
Action d = delegate()
{
this.UpdateItem<ItemViewModel>(source, item, writableProperties);
};
Dispatcher.CurrentDispatcher.InvokeAsync(d, DispatcherPriority.Normal, token);
*/
}
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
if (interval > TimeSpan.Zero)
await Task.Delay(interval, token);
}
}
private void UpdateItem<T>(T source, T target, IEnumerable<PropertyInfo> properties)
{
foreach (var p in properties)
{
var value = p.GetValue(source);
p.SetValue(target, value);
}
}
Doing a straight update lags the UI as I expected, however trying to do it from another thread seems to cause the data to disappear whenever you scroll or resize the window. By disappear I mean the rows are there but they are empty. The only way to get it back is to collapse and expand a group. I've even tried putting refreshes in for the datasource (which seems like a bad idea to me because it would get called after every single field update).
Why does the data disappear on Async updates? Is there a better or more appropriate way to do these kinds of updates on items bound to a datagrid?
As you know WPF follows STA architecture . So as a thumb rule all the updations should be done on the UI thread . You may use Dispatcher for the above scenario :
You can read more about dispatcher here and here
Ideally you can try something like :
ThreadStart start = delegate()
{
// make your calls to the db
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<object>(UpdateCollection),
new object[] { myData });
};
new Thread(start).Start();
private void UpdateCollection(object data)
{
//iterate your collection and add the data as needed
}
I'm using the new Windows Phone 8 LongListSelector control, which has its ItemsControl assigned to a List<Group<object>> as so:
List<Group<PlacePoint>> searchResults;
async void doSearch()
{
this.searchResults = await SearchHelper.Instance.getSearchResults(txtSearchTerm.Text);
longList.ItemsSource = this.searchResults;
}
Unfortunately, the second time that I search, re-setting the .ItemsSource property has no effect and the control simply displays the old List.
How can I change the binding?
It would seem that re-assigning longList.ItemsSource does not have any effect, whether this is a bug or by design I can't say.
However, an easy workaround is simply to use an ObservableCollection> instead and then work with this collection rather than re-assigning the ItemsSource.
Sample code:
ObservableCollection<Group<PlacePoint>> searchResults = new ObservableCollection<Group<PlacePoint>>();
public SearchPage()
{
InitializeComponent();
longList.ItemsSource = this.searchResults;
}
async void doSearch()
{
List<Group<PlacePoint>> tempResults = await SearchHelper.Instance.getSearchResults(txtSearchTerm.Text);
// Clear existing collection and re-add new results
this.searchResults.Clear();
foreach (Group<PlacePoint> grp in tempResults )
{
this.searchResults.Add(grp);
}
}
Sometimes it helps to set the ItemsSource to null and then to your result right after.
You need to define your doSearch() method using async for await to function properly.
Try declaring you method like this:
private async Task doSearch() {
}