I have set up a dialog with several tabs. One of these contains twenty combo boxes, each with over 100 items, added like this :
foreach (var x in collection)
{
string text = FormatItem (x);
combo.Items.Add (text);
}
so there is nothing fancy at all with the items. They are plain strings and the combo boxes get filled when the dialog is created. This happens almost instantenously.
However, when the user clicks on the tab containing all these combo boxes for the very first time, the GUI freezes for several seconds (and I am running on a really beefy machine).
I loaded the symbols for System.Windows.Forms and tried to break into the debugger while the program is stuck. What I have discovered is a stack trace with the following calls:
System.Windows.Forms.Control.CreateHandle()
System.Windows.Forms.ComboBox.CreateHandle()
System.Windows.Forms.Control.CreateControl(...) x 3
System.Windows.Forms.Control.SetVisibleCore(true)
System.Windows.Forms.TabPage.Visible.set(true)
which results in plenty of native transitions, WndProc calls, etc. I suppose this happens for every single item in every combo box. Phew.
Obviously, I cannot optimize WinForms. But maybe I can take some actions in order to avoid all this hell getting lose on my poor GUI? Any ideas?
Nota bene:
I've no event handlers attached on the combo boxes which could be called when the controls get created for real.
If I try to access the Handle property of the combo boxes just after having created and populated the form, I pay the penalty at that moment, rather than when the tab becomes visible for the first time. But having to wait several seconds when creating the form is not acceptable either. I really want to get rid of the long set up time.
The idea of applying BeginUpdate and EndUpdate does not apply here: these should be used to prevent the control from repainting when its items list gets filled. But in my case, the problem happens well after the control has been set up.
What you're saying is not consistent with anything I ever observed... :s
But have you tried using .BeginUpdate / .EndUpdate ?
Another thing you coud try is not populate the boxes until needed. Delay it until the box gets focus for example... (If you trap the dropdown event some user might be annoyed that the up/down arrow keys won't work.)
Everything I tried failed up to now to speed up the first display of the tab containing all the combo boxes. Data binding didn't help either.
I finally decided to fix the issue by doing a trick, similar to what danbystrom proposed, i.e. only populate the Items collection when the focus first arrives on a combo. This still produces a noticeable delay, the time all items get created (within a BeginUpdate and EndUpdate pair of method calls), but it is tolerable (approx. 200ms versus several seconds in my original scenario).
Instead of iteration your collections, would't setting the ComboBox.DataSource be a viable, and much faster alternative?
comboBox1.DataSource = myCollection1;
comboBox2.DataSource = myCollection2;
comboBox3.DataSource = myCollection3;
// and so on...
Here is a more complete example:
public class Entity
{
public string Title { get; set; }
public override string ToString()
{
return Title;
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
List<Entity> list = new List<Entity>
{
new Entity {Title = "Item1"},
new Entity {Title = "Item2"},
new Entity {Title = "Item3"}
};
comboBox1.DataSource = list;
}
I've just been coming up against this same problem, where populating a combobox with around 4000k items was unacceptably slow.
I was filling the combo in the OnLoad event handler of a form, however, when I shifted this code to constructor, after InitializeComponent(), there was no delay at all.
I guess doing this operation in the OnLoad was causing a redraw of the combo to fire, hence the delay? Anyway, just thought I'd add this in case it's of use to anyone else in this situation.
Lots of controls on a form can be a problem. I once had a form that dynamically created between 50-100 textbox controls. It was slow to load.
We solved that problem by using a datagrid instead. It's a control that is optimized for lots of data. I don't know what your exact requirements are, but it might work.
Related
I have a Tcl/Tk program. In it I have a combobox, like so:
set cb [ttk::combobox .cb -state readonly -textvariable selection -postcommand [list choices .cb]]
The proc choices runs when the combobox posts, which is what I want. My question is, how do I detect when the combobox unposts?
I've tried both binding on <<ComboboxSelected>> and setting a variable trace on selection. The problem with each is that they only fire when the user actually changes the selection. I need some way to always detect when the combobox unposts.
Thanks!
edit
What I'm trying to accomplish: When the combobox posts it presents the user with a list of options. I don't expect the user to know what the options mean, therefor I am highlighting the options visually in a different area of my program. I have this highlighting triggering and working well with -postcommand. The issue is to know when to turn the highlighting back off.
<<ComboboxSelected>> doesn't fire if the user doesn't change the selected value.
<Leave> and <FocusOut> fire too soon (e.g. as soon as the box posts).
The combobox's popdown is actually its own nest of windows, and if your combobox is called .cb then the popdown has the imaginative name .cb.popdown (note that this implementation and is not guaranteed). If you add a binding to that widget's <Unmap> event you'll get to see the unposting; <Unmap> events are exactly the notifications sent when a window ceases to be displayed in the virtual desktop layer sense (as opposed to just ceasing to be visible, say because there's another window on top; there's events for that too, but they're not cross-platform).
The tricky bits:
The popdown is usually created when needed, i.e., the first time it appears. You need the window to exist (but not necessarily be visible) before you bind to it. You can get the handle of the popdown widget with ttk::combobox::PopdownWindow, which will make the widget if it doesn't already exist. (It's part of the implementation, but it is more likely to be stable than the name.)
set popdown [ttk::combobox::PopdownWindow .cb]
bind $popdown <Unmap> {yourCallback %W}
It is possible to dig around within the internal arrangement of the popdown, but I don't recommend it; it's much more likely to change without warning.
Binding to the toplevel has the usual issues with events also being delivered for subwindows. Your callback should check that the event it has been given is actually for the toplevel:
proc yourCallback {w} {
if {$w ne [winfo toplevel $w]} { return }
# The rest of your code here...
}
The window name of the listbox used by ttk::combobox is:
set popdown [ttk::combobox::PopdownWindow .combobox].f.l
I believe this is what you need.
bind <Leave> $popdown mycommand
I am currently a little bit troubled by the following problem. I have a user interface which basically shows a graphic (a canvas made of Lines, Circles, ... these are all WPF objects). Depending on the selection a user makes in the menu, some items get deleted and some get added. So the basic image looks the same, but a few modifications are made.
The user has the possibility to select - say - 10 different "pages" by clicking a Next/Previous Button.
I am using MVVM Light and my ViewModel contains all the items of the graphic (all Lines, ...).
Now I would like to print that graphic to multiple pages. The first page should contain the graphic with changes from page 1, the second page contains the graphic with changes from page 2 and so on. The actual number of pages is dynamic. I track this with a property CurrentPage and a property PagesTotal.
Whenever I push the "Next" button, this causes a command to be executed which will change the variable CurrentPage and also makes sure that the correct items are displayed.
Now I would like to print this but this is where I'm stuck. I dont' mind leaving the MVVM zone and doing some dirty work in code-behind but I would refuse to draw everything again like in the old GDI days.
Any ideas are really welcome.
Create a UserControl containing your display logic (you graphic, for instance). Grab you ViewModel list and project then in UserControls, setting each ViewModel as each UserControl's DataContext.
Force each one to render calling Measure with infinite value and then Arrange with the resulting DesiredHeight and Width. Then follow the procedures to print WPF visuals (link link link).
Essentially, this should be quite simple if and only if your views work independently; i.e. your ViewModel doesn't contain UiElements that are placed into your View.
Simple solution is to basically print your visual root. If need be encapsulate your Views in a user control first.
PrintDialog printDlg = new PrintDialog();
UserControl1 uc = new UserControl1();
printDlg.PrintVisual(uc, "User Control Printing.");
Reference
Alright, I have to admin that I now switched back to doing the printing through code only. I would have really liked doing it "WPF-style" but handling the multiple pages issue was just too much trouble.
Anyway, there is still one issue regarding the printout left but this will be another question.
I have a DataGridView bound to a List[of Parts]. The last item in the list is always a 'dummy part', which is there to create an extra row in the grid so the user can start typing into the first cell to add a part to the list.
As soon as the user types more than x characters into that particular cell, a new DataGridView with search results appears under the cell where he is typing (a bit look Google's suggestions), and he can select one of the matches to add the part to the list.
I do this by handling the EditControlShowing event, which assigns the 'editing control' to a TextBox variable which has a handler for TextChanged.
The bug:
I want the focus to change to the second DataGridView (search results) if the user hits the down arrow key.
The EditControlShowing handler works fine first time, moving the focus to the second DataGridView and leaves the first DataGridView in a 'seemingly' functional state, i.e. all other event handlers work. But the second time the EditControlShowing handler fires, the DataGridView throws the NullReferenceException on InitializeEditingControlValue.
Because it is the DataGridView's own call which throw the error, the debugger brings me to "Application.Run" in my code, which isn't helpful.
However, I determined that the call which causes the DataGridView to go into that state, and the point at which exception is thrown, is when I steal focus from the first DataGridView by calling Focus() on the second DataGridView (or any other control for that matter).
If however I let the user change focus to the DataGridView (e.g. by clicking with the mouse), then the DataGridView stays fine.
Does anyone know why stealing focus away from a DataGridView during a handler for EditControlShowing should result in it behaving differently afterwards?
I've tried CancelEdit, Refresh, Enable/Disable etc...
Any suggestions on helping me find out what is going on within the DataGridView? I can't spot anything obvious by inspecting it at breakpoints before and after. I don't even know how to find out what object is null as its the DataGridView's own code...
Many thanks.
OK, I resolved this using BeginInvoke in the TextChanged event handler (code is in Boo but should make sense):
//The event handler
private def CellAsTextBoxTextChanged(sender as object, e as EventArgs):
...
self.BeginInvoke(ShowPartSelectionArea, currentCell, _CellAsTextBox.Text)
//The method which amongst other things, sets the focus to another control
private def ShowPartSelectionArea(currentCell as DataGridViewCell, searchString as string):
...
AnotherDataGridView.Focus()
...
I had previously tried using BeginInvoke directly at the point of calling Focus() on the other control, like so:
private def CellAsTextBoxTextChanged(sender as object, e as EventArgs):
...
ShowPartSelectionArea(currentCell, _CellAsTextBox.Text)
private def ShowPartSelectionArea(currentCell as DataGridViewCell, searchString as string):
...
self.BeginInvoke(AnotherDataGridView.Focus())
...
But the latter didn't work, perhaps because the first way actually allows the DataGridView's cell to finish everything it does whereas the second way doesn't.
So for anyone facing the same problem, I'd advise playing about with where in the call stack you use BeginInvoke.
I have a very strange issue that I'm finding it extremely hard to debug, thus I once more turn to the SO community. :-)
First a bit on my setup (note! I'm new to silverlight, just learning by doing, so my entire premise might be wrong!):
I have some root nodes (forms) under which are some leaves (questions).
Now, I simply want to show / hide the questions depending on which form is currently selected - fairly easy.
I do the following:
A number of controls are added to a LayoutControl (I'm using the DeveloperExpress components)
When you select another form, the created controls are saved in a list on the previously selected form
When you select a form, the list of controls is iterated through, and each element is added to my layoutcontrol again
When I save the elements in the list on a Form I make sure to first .Remove() them from their parent to make sure there are no issues with that.
And, this works.
If I have 1 or controls.
If I have MORE than that, everything is added as usual, no exception is thrown - but Silverlight apparently does an infinite loop somehow? No matter if I run it in IE or Chrome, it just crashes the browser! (Or, I guess, the browser plugin).
I've tried pausing the debugger during this to see where the problem is, but it just stops in "external code".
I don't think a code sample will do much good, but here's the two methods that save and load the elements: (Note: AddControl is a method I've defined - it wraps the element in another control to provide a bit of functionality. Likewise .MyChildren removes the control from the wrapper using .Remove() and returns it)
private void LoadElementsFromCurrentForm()
{
foreach (var child in _currentForm.Elements)
{
layoutControl1.AddControl(child);
}
}
private void SetElementsOnCurrentForm()
{
_currentForm.Elements.Clear();
foreach (var child in layoutControl1.MyChildren)
{
_currentForm.Elements.Add(child);
}
}
How do I even go about debugging this!?
I don't know if it matters, but all these controls have a unique name as well.
Regards
Søren
To take full advantage of Silverlight you should try to use XAML to do the markup of design.
If you want to make a list of questions use e Listbox or ItemsControl.
Then you can style each item in the list to "look right".
If you bind the listbox to ObservableCollection you just add or remove in the list and the gui will be updated according to whats in your list.
Take a look at MVVM which works really good with Silverlight.
I try to programmatically update the selected items in a listbox, with extended selection mode. Here is how I do it:
foreach (var selectedItem in ItemsForSelection)
{
_myList.SelectedItems.Add(selectedItem);
}
My problem is, that when the number of the selected items is big, the update is very slow.
The root of the problem is that listbox doesn't derrive from MultiSelector, which can be tweaked to perform a fast bulk update, by using the methods BeginUpdateSelectedItems and EndUpdateSelectedItems.
Is there a way to get a similar result in a listbox?
Is there a BeginUpdate and EndUpdate method available in the ListBox...
_myList.BeginUpdate();
foreach (var selectedItem in ItemsForSelection)
{
_myList.SelectedItems.Add(selectedItem);
}
_myList.EndUpdate();
That is assuming that _myList is a ListBox...The pair for Begin/End Update methods freezes the WM_PAINT message and unfreezes respectively thereby making it flicker free and fast.
After adding a chunk of items, try pumping the dispatcher by pushing a dispatcher frame into the dispatcher.
http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherframe.aspx
There is a SetSelectedItems(IEnumerable) available on ListBox you could use. This function wraps selection changes in a SelectionChange.Begin/End (unfortunately only available internally of course) which ought to result in only a single selection change event going out.
Note that SetSelectedItems is protected, so you'll have to use your own ListBox derivative to call it. Odd choice, that.