I have inherited a form and have been tasked with speeding it up. Basically, it is a form which makes a WCF call to a service for some data, and then displays the data in a DataGridView.
I originally thought the bottleneck was the WCF call, so I moved that to a BackgroundWorker, to stop the UI from freezing up.
That helped a little bit, but it turns out that the bottleneck is actually this line:
DumpInfoGrid.DataSource = dumpGridBinding;
where the data retrieved from the WCF is assigned to the DataSource property.
The code basically boils down to this:
private BackgroundWorker dataGatherer;
dataGatherer.DoWork += dataGatherer_DoWork;
dataGatherer.RunWorkerCompleted += dataGatherer_RunWorkerCompleted;
private void dataGatherer_DoWork(object sender, DoWorkEventArgs e)
{
dumpGridBinding = WCF.GetDataFromService();
}
private void dataGatherer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DumpInfoGrid.DataSource = dumpGridBinding;
}
Is there anything I can do to speed this up? Or any part I can move to the BackgroundWorker ? The assignment freezes the UI for far too long.
One possible solution for this would be to use FastDataGridView (or FastDataListView) which is found in ObjectListView, which greatly improves the speed of the DataGridView control by not attempting to process all the records at one time.
Another solution would be to page the DataGridView you currently have, spreading out the load time across the pages as you use them as seen here
You could use virtual mode -
MSDN: http://msdn.microsoft.com/en-us/library/15a31akc.aspx
This mode shows just the rows the user is looking at through the viewable region - you only load the rows actually being viewed - which is fast.
You handle the CellValueNeeded event and provide data from your list when the user scrolls through the list. Tex example shows you how to do that, but it's easy to write a linq statement to skip, take whatever is requested from your datasource (returned from the WCF service).
Related
I have a problem with populating my comboBoxes in a WinForm application that I'm writing. The data that I use to populate these comboBoxes is pulled from database. The problem is that there is a lot of data that needs to be binded to the comboBoxes, so this process takes a very long time during which the entire application is locked (the entire process of binding data takes over 9 seconds, while the process of pulling the data from the database takes only 400 milliseconds). I'm trying to speed things up by splitting the processes of creating the controls (main thread) and populating the comboBoxes (background worker), but naturally I get the cross thread error.
This is the part of code that I use:
private void Populate()
{
comboBox1.BindingContext = new System.Windows.Forms.BindingContext();
comboBox1.DataSource = MyClass.dtMyDataTable;
comboBox1.DisplayMember = "TitleColumn";
.//I repeat the same code for each comboBox
.//I use the BiningContext because some of the comboBoxes have to display the
.//same data.
}
I created a class that contains all DataTables that I need in this form - there are multiple forms that use the same data from the database, so I created a class and created an object that fills all of these DataTables on the parent Form.Load(), and then I pass them to the children forms when I create them. This way I load the data when the application loads (it doesen't even take that long), so it should be ready to use when I call it from child forms. I've tried to call the Populate() method from backgroundWorker.DoWork() method, and there I get the cross thread error.
My question is - is there a way to make this work, and if not, what could I use as alternative solution.
Thank you
I'm not a full bottle on invoking but try this:
PopulateData()
{
if (combobox1.InvokeRequired)
{
combobox1.Invoke(new EventHandler(delegate(object o, EventArgs a)
{
PopulateData();
}
));
}
else
{
// Do your updates here...
}
}
I believe this will find the thread responsible for combobox1, which is going to be the same thread for the other combo's, and then run.
I'm sure someone else will chime in with a better way to invoke perhaps at the form level ?
I've found a good alternative, and it sped things up from 9 seconds to 1.5 seconds. The solution is to place the comboBox.DisplayMember BEFORE the 'comboBox.DataSource' line because when you change the DisplayMember (or ValueMember) the data source repopulates itself. So if the comboBox.DisplayMember is after the 'comboBox.DataSource' line, the data source populates itself 2 times (I think that ClearBeforeFill is enabled by default when binding data sources, that is why there are no duplicates in the binded data).
Thanks anyway.
Is there a specific time in the page's lifecycle that the Map.SetView() function should be called? In our app we use this on various map objects and it seems to work randomly, sometimes perfectly and sometimes with no effect but also no exception.
example code:
RouteMap.SetView(LocationRectangle.CreateBoundingRectangle(DirectionCoordinates));
Where RouteMap is the mapping component and DirectionCoordinates contains the start/end coordinates for the map.
I can see that the bounding box is being created properly, but the map's positioning is not always being affected even loading the same data. If I add a break point it does seem to work, so I was assuming it had something to do with the map loading, but adding the SetView() functionality to the Loaded event has the same issue. We currently process the map information in the page Loaded event.
Update
I've been testing more and added events to what I could, I know for a fact that the MapLoaded event is being called before SetView. After SetView is called, it is working sometimes and not others. Neither ViewChanging or ViewChanged events are called.
This is obviously not the best solution, but there must be something that is not quite finished loading when the Loaded event is called that is preventing this from finishing.
I added a 100ms sleep to the Map_Loaded event and it has solved the problem I was having.
System.Threading.Thread.Sleep(500);
update
100ms isn't working for some people, you may want to play around with the numbers, 200, 500 etc. It's still a very short delay on the map's load time. I have contacted Microsoft about this and they have told me that they are looking into the issue and we will hopefully have some sort of response from them shortly.
update and edit
Use the following code instead to prevent UI hanging:
await Task.Delay(250);
I tackled this issue using ResolveCompleted event and boolean flag.
private void Map_ResolveCompleted(object sender, MapResolveCompletedEventArgs e)
{
if (this.zoomReq)
{
this.zoomReq = false;
if (this.locationList != null && this.locationList.Count > 0)
{
var viewRect = LocationRectangle.CreateBoundingRectangle(this.locationList);
this.Map.SetView(viewRect);
}
}
}
There is noticeable pause before map zooms but at least this seems to work all the time. The flag is needed because ResolveCompleted is fired every time the map moves.
I was both constructing a map layer (Microsoft.Phone.Maps.Controls.MapLayer) and setting the view (public void SetView(LocationRectangle boundingRectangle);) in an async method:
public async Task CreateMap()
{
map.Add(mapLayer);
map.SetView(locationRectangle);
}
I was doing some loading, that's why I used async.
This would only set the view once, the first time I navigated to the page.
The solution was to dispatch the set view call:
public async Task CreateMap()
{
map.Add(mapLayer);
Dispatcher.BeginInvoke(() =>
{
map.SetView(locationRectangle);
});
}
Hope that helps.
The Loaded event is the proper place for SetView(). You could try creating your rectangle in you OnNavigatedTo method. When I'm working with locations I always start my watcher in OnNavigatedTo and work with any map layers in _Loaded.
I worked myself some time at this problem. It didn't help to put most of the stuff to load into the constructor of the page. I tried to the trick with System.Threading.Thread.Sleep(500) but it took far beyond 500ms to take effect and this wasn't acceptable for me. For some people it helped to trigger an ZoomLevelChanged event and set the view in it. For myself I used a DispatcherTimer in which I used SetView() and fired an `ViewChanging´ event to stop the timer. If you use an animation the difference is pretty small.
I had this problem for MapAnimationKind.Linear but for MapAnimationKind.None it works without any problem
map.SetView(LocationRectangle.CreateBoundingRectangle(...), MapAnimationKind.None);
I had a very similar problem. Basically the setview of map would work the first time a page loaded (i.e. after all the data had finished loading) but if I left the page and came back and did not need to reload all the data, it did not work. While debugging, it seemed like I was setting the information for the map before it was finished loading.
So what I did to resolve the challenge was:
In the XAML - I added an event handler for the Loaded event of the map.
Example: Loaded="myMap_Loaded"
In the myMap_Loaded event, I simply called an async method to wait for the data to load then map
it.
Example:
private void myMap_Loaded(object sender, RoutedEventArgs e)
{
WaitAndLoadMap();
}
Coded the WaitAndLoadMap method to wait for the data to finish loading before loading the
map.
private async void WaitAndLoadMap()
{
//Check if the data is loaded and if it is not - loop.
while (!App.NearbyLocationsViewModel.IsLocationDataLoaded)
await Task.Delay(250);
//Load the map content and set the mapview.
}
It seems to be working. Hope this helps others.
I have a weird issue with the DataGridView control in virtual mode and threading. I use a Cache class to stored 2 pages of data. When I scroll down and I have to load a new page in cache, there is a little delay before data is display (it's ok). But, instead of having
the next row displayed(199th,200th,201th), the control skip many rows (CellValueNeeded). By example, I cached 1 to 200 rows, when I scrolling at 199, I'm loading a page to 200 à 299. However, in the datagrid it's display the row 320.. instead of 300. (I kept the mouse button pressed 2-3 secondes, before releasing it). I notice I have the problem only when I load the cache in a particular thread. Does anyone have a idea how I can fix this?
private void datagridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
e.Value = m_rfiCache.RetrieveElement(e.RowIndex, e.ColumnIndex);
}
....
public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
e : " + lowerPageBoundary);
DataTable dt = new DataTable();
if (m_useThreading) //THIS DOESN'T WORK WELL
{
Thread MulThread = new Thread(delegate()
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
});
MulThread.Start();
MulThread.Join();
}
else //OK.
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
}
return dt;
}
P.S. : This is only a snippet, I used an external API to extract data from ODB. This API used thread to load data.
Does anyone have a idea or I can fix this?
You can load the data in a background thread, but you can't add it to your main DataTable in the background thread itself.
One potential approach would be to load the data as you're doing, but into a separate DataTable. You could then, on the UI thread, call DataSet.Merge to merge this data in with your bound data correctly (which will be reasonable fast).
Finally, I found the solution. When the tread is processing and the scroll down button is pressed, my event for scrolling aren't sent to the datagrid unless my thread is finished. So I received a bunch of calls for drawing cells and the index was wrong. To avoid this, I removed the vs scrollbar from the datagridview and added a VSscrollbar control. When I'm loading data in my thread, I set to false the enable property on my scrollbar. When the thread is completed, I enable the scrollbar. Is not the BEST solution, but at least, there is now row skipped.
Note : Virtual Mode works very well if you don't load your data in a separate thread.
i have a winform application in which i have a lot of controls that needs continuos monitoring. For example there is a button and it should be enabled only when two other buttons are disabled, and they disable at separate instances. So what i am doing now is using a thread to monitor the two other buttons in a continuos while loop such as
while(true)
{
if(btn.enabled==false and btn1.enabled==false)
{
bt3.enabled==true
}
}
though it does what i need it seems wrong to me. Also its very expensive considering the number of threads i have to spawn to manage my controls, there are certain controls that needs to check five or six different things to before it can do an action and threading seems the only way possible to me.
Please tell me if there is any other way to do this
Not only is that inefficient, it is incorrect; you should never access a control's properties except from the UI thread, due to thread affinity. Setting properties (the enabled assignment) is especially bad, but reading them (the enabled check) is bad enough.
Rather than continuous monitoring, those forms should update themselves, based on event notifications. For example, by hooking EnabledChanged on the two buttons.
// (in the form initialization code)
btn.EnabledChanged += UpdateButtons;
btn1.EnabledChanged += UpdateButtons;
//...
private void UpdateButtons(object sender, EventArgs args)
{
bt3.Enabled = !btn.Enabled && !btn1.Enabled;
}
you could also (instead) do this at the code that causes the Enabled property to change.
I am creating an array of ListViewItems on a background thread and then i populate my listview with it on the ui thread. The problem is that if the array is too big the ui blocks while the listview is updated.
Is there a way to populate the listview with a small impact on the ui?
If you have a lot of data going into it you might want to use it in virtual mode, by setting the VirtualMode property of the ListView control to true. That means that the ListView will not be populated in the traditional sense, but you will hook up event handlers where you deliver the information to the list view in small chunks as the items are being displayed.
Very simple example:
private List<string> _listViewData = new List<string>();
private void toolStripButton1_Click(object sender, EventArgs e)
{
_listViewData = GetData(); // fetch the data that will show in the list view
listView1.VirtualListSize = _listViewData.Count; // set the list size
}
// event handler for the RetrieveVirtualItem event
private void listView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (e.ItemIndex >= 0 && e.ItemIndex < _listViewData.Count)
{
e.Item = new ListViewItem(_listViewData[e.ItemIndex]);
}
}
You should also look into using the CacheVirtualItems event.
You can also use BeginUpdate and EndUPdate methods
listView1.BeginUpdate();
//Add Values
listView1.EndUpdate();
My comments got quite long, so I decided to make this a separate post.
First, +1 for Fredrik Mörk, using VirtualMode is the way to go. However, you do lose some functionality, e.g. column autosize, and sorting is easier handled on your own.
If that is a problem, populating from a worker thread may sound sounds tempting. However, the population will still take place in the thread that owns the list control (i.e. virtually always the main thread) - .NET makes that visible by enforcing you to use (Begin)Invoke. Additionally, the context switches will notably increase the total time you need to fill all items if you fill one by one, so you want to fill in chunks (say 50 items at a time, or even better fill as many as you can in 20 milliseconds). Add to that the additional synchronization required when contents changes, you have a quite complex solution for a not-so-stellar result.