my c# winforms program has one tabcontrol with a few tabs displaying listviews, labels, buttons etc. the tabpages are not shown on load. the code simplified looks like this:
public main()
{
InitializeComponent();
removeTabPages(); //removes all but one; deleting this line doesn't change anything
main_tabcontrol.SelectedIndex = 0; //doesn't change anything no matter where i put it
loadData();
doSomeCalculations();
addTabPages();
main_tabcontrol.SelectedIndex = 2; //same issue if i pick any other tab here
}
private void Tabs_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateDataInTab();
}
this loads some data, does some calculations and then switches to tabpage 2. i would expect to see the data processed by the loadData() and doSomeCalculations() functions displayed. instead it displays the default values (mostly nothing) until i switch to another tab and then back. that also verifies Tabs_SelectedIndexChanged() works as intended.
i'd like to understand why this happens and how i can make it work as planned.
running loadData() and doSomeCalculations() as async tasks, and awaiting them, does solve this, but it opens so many other problems that i'd like to avoid it (i don't need this async). since my issue is the exact opposite (i need the code to run synchronous), this shouldn't be the solution anyways.
It is because you have it in your constructor, the best place to put it is on the load:
private void main_Load(object sender, EventArgs e)
{
main_tabcontrol.SelectedIndex = 0;
loadData();
doSomeCalculations();
main_tabcontrol.SelectedIndex = 2;
}
Never do anything beside the actual initialization in constructor
Use Loaded event to implement initial work
Avoid calculations and other CPU heavy work in UI thread. Use the backgroung thread to do this. Use async/await
To use Load Event double click empty space on the form or use Properties Explorer and switch to events.
Related
I use the OnPaint (c#) event to draw something in my form. I want to get the value of a variable during the OnPaint process. But I cant get it during, only before or after the OnPaint process...
In fact, the variable is like a counter that I want to get to increase the value of a ProgressBar.
I tried adding a Thread, a Timer and "ValueChanged" events, I still can't get the value.
The code is quite long (it's for generating a HeatMap from some data).
I increase the value in some for loops during the event, and I call the OnPaint event by the "Invalidate()" function.
I hope to be clear without pasting my code (it's very long) !
Thanks.
With the code this is better : (Simplified)
public partial class HeatPainter : UserControl
{
public long _progress = 0; //My counter
public HeatPainter()
{
InitializeComponent();
}
public void DrawHeatMap(List<List<int>> Items, decimal Value, int MaxStacks, int Factor, string FileName)
{
if (_allowPaint) //If the control is ready to process
{
timer1.Start();
_progress = 0;
_allowPaint = false;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
for (int Pass = _factor; Pass >= 0; Pass--)
{
//Some draw stuff
//...
_progress++;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
Console.WriteLine(_progress);
}
}
It looks like your repainting takes a lot of time. You are unable to change anything on the form during the repainting (it won't be changed until the end).
So you should look at the task from other point of view. What if you will create the image(like How to create a jpg image dynamically in memory with .NET?) in the parallel thread(or another parallelization construct http://www.dotnetperls.com/backgroundworker)? After it is painted you will set it as background or the .Image of some PictureBox. The form will be responsive all the time.
You will have to synchronize(to update your progress bar) but that isn't such a difficult task (Thread not updating progress bar control - C#, C# Windows Forms Application - Updating GUI from another thread AND class?).
And for the future: Threads and BackgroundWorkers are slowly going away from the.NET world. They are still used in .NET < 4.0 but .NET 4.0 and higher provide better abstractions for asynchronous operations. I recommend you to read about Tasks and Async Await. They are more appropriate for many launch-the-work-get-the-result-on-completion scenoarios.
You should use only one asyncronous construct(for example BackgroundWorker) that will paint the image. You user control should provide an event (actually it is better to refactor this functionality out of the user interface) like
public event EventHandler ProgressChanged;
and raise this event in the code that creates the image when you modify the property of Progress. Just don't forget about synchronization and dispatching(see above).
I'm using winforms in c#.
I simplified my app so that it just has a ListBox and a Button.
Here is my button click event:
private void button1_Click(object sender, EventArgs e)
{
for (long i = 0; i < 66000; i++)
{
listBox1.Items.Add(i.ToString());
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
}
When I run my app and push the button I see the ListBox updating and after a while (it varies entry 3041 or so) the program will appear to hang as it adds the rest of the entries once its done, the ListBox will appropriately refresh.
Why the hang? Is it just too much to handle? I looked at my cpu usage and memory didn't seem to be using that much so I'm not sure what the problem is.
You misinterpret what's going on. Your listbox will freeze after 5 seconds. That's when Windows steps in when it notices that you are not taking care of the normal duties of a UI thread. Your window is replaced by the so-called "ghost window" to tell the user you've gone catatonic and that doing things like clicking the Close button isn't going to have any effect.
The easiest way to tell that you got the ghost window is from its title bar, it says "Not Responding".
A version of it that will fare somewhat better:
private void button1_Click(object sender, EventArgs e) {
listBox1.BeginUpdate();
for (long i = 0; i < 66000; i++) {
listBox1.Items.Add(i.ToString());
}
listBox1.EndUpdate();
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
You shouldn't otherwise expect windows to behave reasonable when you do unreasonable things. And you shouldn't expect users to behave reasonable either when they are faced with this UI disaster. But they'll let you know themselves.
Every time you add an item, the memory it takes gets bigger, and since the items are in a dynamic list, the program may have to reallocate space.
Add to that the fact that you're probably using just one thread in that app (you're not using a background worker thread to update the list, right?) and you see why your app hangs. Windows forms always take a while when they have to update their controls... It's just that for normal, everyday use, they take milisseconds to update. For arcane uses like a list containing more than a few hundreds of items in the GUI, they'll take seconds or minutes to refresh, and you'll perceive that as hanging. Place the item handling in another thread, and you'll see that the form will update it's GUI pretty fast, but will take a long time between updates.
I am developing an application in which two datagridviews are being populated from different data sources. I would like to have a single vertical scroll-bar that will make both gridviews work at the same time (scroll up and down together)
can anyone tell me how or direct me to a good tutorial.
If you have dgv1 and dgv2, you can create something like
dgv1.Scroll += new System.Windows.Forms.ScrollEventHandler(dgv1_Scroll);
Then, in dgv1_Scroll method, you can use FirstDisplayedScrollingRowIndex property:
dgv2.FirstDisplayedScrollingRowIndex = dgv1.FirstDisplayedScrollingRowIndex
Of course, if dgv's have different ammount of rows, you need to avoid IndexOutOfRange exception by checking each dgv rows count.
Use HorizontalScrollingOffset (or VerticalScrollingOffset).
this.dataGridViewDataSample.HorizontalScrollingOffset
I believe you can set up an event-like scenario, where whenever scrollbar A's "value" changes, change scrollbar B to the appropriate value as well.
(Note that value is a property of a scroll bar, I do not mean the value of the data inside the container.)
Please see the following msdn article regarding that property of a scroll bar for better reference:
http://msdn.microsoft.com/en-us/library/system.windows.forms.scrollbar.value.aspx
And the class itself --
http://msdn.microsoft.com/en-us/library/system.windows.forms.scrollbar.aspx
You could put the DataGridViews in Panels and use this:
public Form1()
{
InitializeComponent();
panel1.Scroll += new ScrollEventHandler(panel1_Scroll);
}
void panel1_Scroll(object sender, ScrollEventArgs e)
{
panel2.AutoScrollPosition = new Point(0,e.NewValue);
}
Unfortunately it does not seem that DataGridView has this property.
http://www.xs4all.nl/~wrb/Articles_2010/Article_DataGridViewScroll_01.htm
This link shows exactly what I needed and worked fine for me. The only problem I have now is that the datagrids does not have same rows in it. So even when one finishes (no more to scroll) the other must be able to continue.
Any suggestions?
take a look at this. i wanted to sync two listviews when i scroll any of them. u can achieve this using custom controls. code works like a charm.
_dataGridViewInput.Scroll += new ScrollEventHandler(_dataGridViewInput_Scroll);
_dataGridViewOutput.Scroll += new ScrollEventHandler(_dataGridViewOutput_Scroll);
void _dataGridViewInput_Scroll(object sender, ScrollEventArgs e)
{
this._dataGridViewOutput.FirstDisplayedScrollingRowIndex = this._dataGridViewInput.FirstDisplayedScrollingRowIndex;
}
void _dataGridViewOutput_Scroll(object sender, ScrollEventArgs e)
{
this._dataGridViewInput.FirstDisplayedScrollingRowIndex = this._dataGridViewOutput.FirstDisplayedScrollingRowIndex;
}
Okay, I was able to create a simple Windows Forms project that reproduces some strange behavior I found. In the designer, make a form with a ListBox (named lbx) anchored Top, Left, Right, and Bottom, and a button (button1). Now, the Form's code is here:
using System;
using System.Windows.Forms;
namespace ListBoxKaboom
{
public partial class Form1 : Form
{
private bool _initFinished = false;
public Form1()
{
InitializeComponent();
this._initFinished = true;
this.Height += 100;
this.Height -= 50;
this.Height += 50;
}
private void lbx_SelectedIndexChanged(object sender, EventArgs e)
{
this.button1.Enabled = (this.lbx.SelectedItem != null);
}
protected override void OnLayout(LayoutEventArgs e)
{
if (_initFinished)
{
int lines = (this.lbx.Height - 4) / this.lbx.ItemHeight;
this.SuspendLayout();
while (lines < this.lbx.Items.Count)
{
this.lbx.Items.RemoveAt(this.lbx.Items.Count - 1);
}
while (lines > this.lbx.Items.Count)
{
this.lbx.Items.Add("Item " + (this.lbx.Items.Count + 1).ToString());
}
this.ResumeLayout();
}
base.OnLayout(e);
}
}
}
PLEASE NOTE THE FOLLOWING INSTRUCTIONS:
Run this, click any of the items in the list box, and use the arrow keys to move down far enough to cause the list box to scroll. Kaboom.
Exception (sometimes NullReferenceException and sometimes IndexOutOfBoundsException). Any ideas why? Also, I would think that the items would be in order, but they're not. Is this just a goofy corner case that didn't get handled properly by Windows Forms, or am I doing something wrong?
Stack trace:
at System.Windows.Forms.ListBox.NativeUpdateSelection()
at System.Windows.Forms.ListBox.SelectedObjectCollection.EnsureUpToDate()
at System.Windows.Forms.ListBox.SelectedObjectCollection.get_InnerArray()
at System.Windows.Forms.ListBox.SelectedObjectCollection.get_Item(Int32 index)
at System.Windows.Forms.ListBox.get_SelectedItem()
I Copy/Pasted it to an empty Form and get a StackOverflow exception. Looking at it, with manipulation of the Items inside a Layout event I would say you deserve little better.
I realize this may be a simplification of something else, but there simply are limits to what you can do in a EDS.
My best guess: The ResumeLayout triggers a recursive layout operation. You could try to stem it with a sibling to _initFinished but I would suggest rethinking tour design here.
I copy/pasted wrong, my bad (used layout event).
Second try:
based on the two while-loops I would expect the Item strings to be in order, and no vertical scrollbar. It is clear that the listbox is confused, showing vertical scroll range and with the items out-of-order. So some 'error' is already present in the internals of the Listbox, waiting for a Scroll. I can also reproduce it with the mouse.
A workaround: You should be able to get the desired effect using the Resize event.
Attempt to an explanation: The (unmanaged part of the) Listbox gets confused by (multiple) Add/RemoveAt operations with suspended Layout. The last items are drawn at the wrong place, and the Listbox can't compute pixel-to-item.
You should not manipulate any GUI elements in the
constructor, e.g. this.Height += 100; in your example.
Strange things can happen. I have been bitten by this
several times in legacy code.
Wait until form load time - handle the base.Load event and do the
height manipulation there.
From "When does Form.Load event get raised?":
Q: "... I need to find out basically
what the difference is between
putting code in Load event's handler,
versus putting code in the Form's
constructor after the
InitializeComponents() line ..."
A: "The Load event is fired once the
control/form has been fully
initialized and has a window handle
created. Therefore once this event
has fired it is a fully usable user
interface control. Remember that
inside the constructor the actual
window handle for the control/form has
not yet been created, you are only
creating C# objects here and inside
the InitializeComponent call."
I am trying to load a preferences window for my application and I would like the apply button to initially be disabled, then when a preference is updated, the apply button gets enabled again. I have some controls data bound to a preferences object and what happens is that after the window loads, the combobox events get triggered. Is there any event that is guaranteed to happen dead last after everything is stable?
Here is what my code looks like (the apply button is always enabled after the window loads):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_preferencesData = new PreferencesDataContext();
LayoutRoot.DataContext = _preferencesData;
ButtonApply.IsEnabled = false;
}
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ButtonApply.IsEnabled = true;
}
Is it also interesting to note that this only happens with textboxes and comboboxes, not checkboxes or radiobuttons.
Best solution for simple need
Joseph's answer is the best solution by far for your simple need: Just use data binding and let the data model handle it.
Answer to question as posed
There are more complex scenarios when you really do need control after absolutely everything has finished loading and all events have fired. There is no single event that occurs "dead last", but it is easy to effectively roll your own using the Dispatcher queue.
This is how to do it:
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
var x = ComputeSomething(1, 2, 3);
DoSomething(x, "Test");
}));
Everything inside the { } will be executed when WPF finishes everything at a higher priority than ContextIdle, which includes all event handlers, loaded events, input events, rendering, etc.
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be found applied in addition to any element-specific initialization you may define [note: Initialized event not fired for leaves in a logical tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional object tree construction including more constructors and getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is shown. The other steps generally don't happen until a Window is shown, but they can happen earlier if triggered manually.
The Window.ContentRendered event fulfilled my requirements.
I just did kind of the same thing behaviorly in a systray WPF app.
However, I didn't do it using event handling. I simply bound the Enabled property of my button to a property in my ViewModel, and had the property updated whenever I needed the behavior.
You can use ManagedSpy to figure this out on your own.
http://msdn.microsoft.com/en-us/magazine/cc163617.aspx
Setting the DataContext will likely fire the SelectionChanged event, and you can't rely on when exactly it's fired. Some logic checking on what exactly is selected would be more reliable:
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (myComboBox.SelectedItem == null)
{
buttonApply.IsEnabled = false;
}
else
{
buttonApply.IsEnabled = true;
}
}
The reason it's happening afterwards with your code as-is is because the event gets queued on the thread for the UI, so it's up to Windows if it will execute the next line of code in Load, or to handle the other events on the queue.
Not to throw a whole lot of stuff at you that you may or may not be familiar with, but if this is a relatively new codebase, you may want to consider using the MVVM pattern and use Commands instead of the archaic (emphasis mine) eventing model.
Order of Events in Windows Forms
Control.HandleCreated
Control.BindingContextChanged
Form.Load
Control.VisibleChanged
Form.Activated
Form.Shown