Adding a line to Textbox causes huge CPU load: how to optimize? - c#

For some logging ouput, I have a Windows.Forms.Textbox which is updated via
TB.AppendText("some new text");
The method is hungry at redrawing the text field with >2000 lines inside, however it feels faster than with TB.text += 'some new text.
What should I use instead of Textbox to get quick inserts with low CPU load and fast refresh? I thought about Listbox but not sure if it will fit (however, DebugView's 'listbox' seems to be able to handle billions of lines without heavy load).

A ListBox or ListView would be much better suited for what you are proposing.
And if size becomes an issue, you can use ListView's VirtualMode to only load the items as needed, based on the user's scrolling.

Related

Optimizing of winforms

I have a program that loads a tab that has roughly 332 text boxes it loads. When loading a new tab there is a 2-3 second delay before the tab can be displayed. I have tried to suspend and resume and even looked into threading but found out that a winform can only use one thread.
I was wondering if there was a way to create the text boxes in another thread and then add them into the main form or a way to better create 332 text boxes.
thanks for your help.
Depending on your layout, I'd strongly recommend replacing the textboxes with a DataGridView, or otherwise loading the textboxes on scroll with some kind of continous-control .
The main bottle neck is the creation of handles. In winforms, each control gets its own handle (even labels), and when used in large numbers such as this, can indeed give performance problems.
If a datagridview is not an option, can you give some more information on the layout (a screenshot perhaps?), so the community can have a go at alternatives. Creating the textboxes in another thread will create problems when you try to add them, but you could create them only when needed.
Simple: don't use 300 text boxes. You can only use one text box at a time. The rest of the data you can just draw, and when the user wants to edit it you create the edit control for it. This is exactly how a grid works, so I recommend using a grid or similar control.
I also had this problem, and it goes further to affect initialisation code for controls on tabs that has not get activated before.
My workaround is to block the program with a "loading..." splash and programatically click the tabs so that the controls get loaded/activated. Once this is done, the tabpage shoud not take as long to display again.
Although I agree with others in that you may want to look into reducing the number of controls in a single tab.

Datagrid with large number of rows

In my WPF application, I've got a screen with a tab control. Five of these tabs contain datagrids which need to display a large number of rows (at least 5000). The tables are bound to ObservableCollections of Part objects. Each row displays around 20 points of part data. My problem is that, after the user enters the information they require and generate the data, clicking on a tab causes the application to hang for 30-60 seconds. After this the datagrid finally loads, and with the right virtualization settings, they perform at an acceptable rate (not exactly fast, but not too slow). If I disable virtualization, the program uses up way too much memory, and the loading time isn't really affected.
The most offensive tables consist of about half a dozen template columns. Each template contains controls inside a stackpanel or a grid; basically each row is split into two, like a double-row. This layout is a requirement, and paging is probably not something that the customer is willing to accept.
This is the most important screen in my application and I'm pretty much at a loss about making this work. Is there anything I can do to speed up this process? Perhaps ObservableCollection is the wrong choice?
Can you please provide more insights...
Can you check how much time is spent in "generating" the 5 collections of 5000 rows each? (this is what I assume you are saying)
With virtulaization "on" what is the UI loading time "after" we assign the collection to the items source?
What happen if you bind "ItemsSource" to the respective datagrid only when the tabItem is actually Visible \ Selected?
Do you datagrids have default sort member path? Grouping? Filter Paths?
These are a few things I would target to start on the issue.

c# - fast ListBox data update

I want to display in about 20 lines some text data. Every line can be longer than the size of the form is. In this case the rest of the line should be cut away.
I don't want to have any h or v scroll bar.
Since the ListBox control can deal with my requirements I tried to use it. Everything is working fine but if I have a heavy disk load it can happen that the control begins to flicker and I don't think this has to be. I am updating the data once a second and I always have less then 20 lines of text data. Seems that I'm using the ListBox wrong.
I have the possibility to prepare the data in an other thread as a string or a string array but in every case have to update the whole ListBox. Which technique is the best for filling the ListBox with data? Can I have two buffers which I can toggle to be used with the ListBox?
Hope there is a better solution...
Try calling SuspendLayout() for the ListBox before adding data to it and then call ResumeLayout() on the ListBox. You may lose that flicker a bit.
You could either create a databinding and then just update the binding, that should work better.
A cheap way would be to change the visibility of the listbox and make it invisible at the beginning and visible at the end. Cheap, but should work. ;)

Does the Windows Forms DataGridView implement a true virtual mode?

I have a SQL table containing currently 1 million rows that will grow over time.
There is a specific user requirement to present a sortable grid that displays all rows without paging. The user expects to be able to very quickly jump from row to row and top to bottom by using the scrollbar.
I am familiar with "virtual mode" grids that only present a visible subset of the overall data. They can provide excellent UI performance and minimal memory requirements, (I've even implemented an application using this technique many years ago).
The Windows Forms DataGridView provides a virtual mode that looks like it should be the answer. However unlike other virtual modes I've encountered, it still allocates memory for every row (confirmed in ProcessExplorer). Obviously this causes overall memory usage to needlessly greatly increase and, while allocating these rows, there is a noticeable delay. Scrolling performance also suffers on 1 million + rows.
A real virtual mode would have no need to allocate any memory for rows not being displayed. You just give it the total row count (eg 1,000,000) and all the grid does is scale the scrollbar accordingly. When it is first displayed the grid simply asks for data the first n (say 30) visible rows only, instantaneous display.
When the user scrolls the grid, a simple row offset and the number of visible rows are provided and can be used to retrieve data from the data store.
Here's an example of the DataGridView code I'm currently using:
public void AddVirtualRows(int rowCount)
{
dataGridList.ColumnCount = 4;
dataGridList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
dataGridList.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
dataGridList.VirtualMode = true;
dataGridList.RowCount = rowCount;
dataGridList.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridList_CellValueNeeded);
}
void dataGridList_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
e.Value = e.RowIndex;
}
Am I missing anything here, or is the "virtual" mode of the DataGridView not really virtual at all?
[Update]
It looks like the good old ListView implements exactly the sort of virtual mode I'm looking for. But unfortunately the ListView does not have the cell formatting capabilities of the DataGridView, so I can't use it.
For others that might be able to, I tested it with a four column ListView (in Detail mode), VirtualMode= True and VirtualListSize =100,000,000 rows.
The list is displayed immediately with the first 30 rows visible. I can then scroll rapidly to the bottom of the list with no delay. The memory usage is constant 10 MB at all times.
We just had a similar requirement to be able to display arbitrary, unindexed 1M+ row tables in our application with "very good" performance, using the stock DataGridView. At first I thought it wasn't possible, but with enough head scratching, we came up with something that works very well after spending days pouring over Reflector and .NET Profiler. This was difficult to do, but the results were well worth it.
The way we tackled this problem was by creating a class that implements ITypedList and IBindingList (you can call it LargeTableView, for example) to manage the asynchronous retrieval and caching of information from the database. We also created a single PropertyDescriptor-inheriting class (e.g. LargeTableColumnDescriptor) to retrieve data from each column.
When the DataGridView.DataSource property is set to a class implementing IBindingList, it goes into a pseudo-virtual mode that differs from regular VirtualMode, where as when each row is painted (such as when the user scrolls), the DataGridView accesses the indexer [] on the IBindingList and the respective GetValue methods on each column's PropertyDescriptor to retrieve the values as needed. The CellValueNeeded event is not raised. In our case, we access the database when the indexer is accessed, and then cache the value, so that subsequent re-paints don't hit the database.
I performed similar tests re: memory usage. The DataGridView does allocate an array that is the size of the list (i.e. 1M rows), however each item in the array initially references a single DataGridViewRow, so memory usage is acceptible. I am not sure if the behavior is the same when VirtualMode is true. We were able to eliminate scroll lag by immediately returning String.Empty in the GetValue method if the row is not cached, and then performing the database query asynchronously. When the async request is finished, you can raise the IBindingList.ListChanged event to signal to the DataGridView that it should repaint the cells, except this time reading from the cache which is readily available. That way, the UI is never blocked waiting for database calls.
One thing we noticed is that performance is significantly better if you set the DataSource or number of virtual rows before adding the DataGridView to the form - it cut initialization time in half. Also, make sure that you have both Row and Column autosizing set to None or else you will have additional performance problems.
Side note: the way we accomplished "loading" such a large table in our .NET application was by creating a temporary table on the SQL server that listed the primary keys in the desired sort order along with an IDENTITY (row number), and then persisting the connection for subsequent row requests. This naturally takes time to initialize (approx. 3-5s on a reasonably fast SQL server), but without knowledge of available indexes, we have no better alternative. Then, in our ITypedList implementation, we request rows in pages of 100 rows, where the 50th row is the row that is being painted, so that we limit the number of queries performed each time the indexer is accessed, and that we give the appearance of having all of the data available in our application.
Further reading:
http://msdn.microsoft.com/en-us/library/ms404298.aspx
http://msdn.microsoft.com/en-us/library/system.componentmodel.ibindinglist.aspx
The answer is NO
see first comment here
if anyone knows a better way please tell us
I would say yes... as long as you stick to the events triggered by the Virtual Mode behavior (e.g. CellValueNeeded) and that you take good care of clearing your hand built-buffer. I already displayed large amount of data, more than 1M without a fuss.
I'm a little bit curious about the implementation of Kevin McCormick using a DataSource based on ITypedList or any IList related interface implementations. I guess it's just yet another abstraction layer who makes the use of an internal and transparent buffer in addition to let the user or the developer powers the DataGridView with this, but still internally dealing with the native VirtualMode to display the information you have loaded in the buffer.
Besides of the fashion way to workaround the virtual mode, to me, the the only tough problem remaining with the DataGridView is the RowCount limitation: it's still a Int32.Max. It is probably due to the legacy of the Winforms Drawing System... just like the Images or even every Size Members, Width, Height of Winform Controls... why not stick to a UInt32 Type?
I assume that nobody has seen a Control or a picture with negative dimensions, but still the type is not properly appropriate to the context of use.
See my answer there below, could help you if you are still stuck with that trouble, event if, I guess it's already resolved for ages now.
https://stackoverflow.com/a/16373108/1906567

Populating a FlowLayoutPanel with a large number of controls and painting thumbnails on demand

I'm trying to make an ImageListBox kind of control that will display a large numbers of thumbnails, like the one that Picasa uses.
This is my design:
I have a FlowLayoutPanel that is populated with a lot of UserControl objects, for example 4,000.
Each UserControl is assigned a delegate for the Paint event.
When the Paint event is called, it checks a memory cache for the thumbnail and if the image is not in cache, it retrieves it from the disk.
I have two problems that I'm trying to solve:
It seems that WinForms will trigger a Paint event even if the UserControl is not in view. Only 10 or so controls are in fact in view, the rest are not (the FlowLayoutPanel.AutoScroll is set to true). As a result, it tries to retrieve thumbnails for all the images and that takes a long time.
Adding the UserControl objects to the FlowLayoutPanel takes a somewhat long time, about 2-3 seconds. I can live with it but I'm wondering if there is a better way to do it than:
UserControl[] boxes = new UserControl[N];
// populate array
panel.SuspendLayout();
panel.Controls.AddRange(boxes);
panel.ResumeLayout();
To improve the speed of populating the FlowLayoutPanel with your user controls, disable layout updating while you add the controls.
Immediately before your loop, call SuspendLayout() and then at the end call ResumeLayout(). Make sure to use a try-finally to guarantee the ResumeLayout() runs even if an exception occurs.
I wouldn't add that many user controls. Rather, I'd have a series of data structures that stores information about what thumbnail to use, positioning, etc, etc, and then handle the rendering of each thumbnail required.
Of course, you would only render what you need, by checking the paint event args in your control and rendering the thumbnails that are in view and that require rendering.
Aha! I found something.
When the UserControl is not in view and it receives a Paint event, then e.ClipRectangle.IsEmpty is true!

Categories

Resources