|
Hi,
if ComboBox1 is part of panelB, then there must be a panelB.Controls.Add(ComboBox1) somewhere.
if you want to remove panelB and reuse ComboBox1 elsewhere, say on panelC, then you must
execute a panelC.Controls.Add(ComboBox1).
Events get dispatched by the active form using its chain of Controls as it is set through
the Controls property. A Control that is not part of the Controls chain will not receive
events.
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
I apologise, I forgot to mention that ComboBox1 does not receive events even though it is part of another panel's controls collection.
So basically, when I destroy PanelB - ComboBox1 has no parent for a while - and then when I create another panel (PanelC), I assign ComboBox1.Parent = PancelC.
Events won't fire.
|
|
|
|
|
Hi,
I am not very familiar with the Parent property (I use it mainly when linking a dialog
to a parent form), so I am not sure ComboBox1.Parent = PanelC; is equivalent
to PanelC.Controls.Add(ComboBox1);
You could give it a try, if it does not help maybe you should publish relevant code.
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
Here's the partial class code for a proof of concept:
public partial class Form1 : Form
{
Panel panela = new Panel();
ComboBox comboBox1 = new ComboBox();
public Form1()
{
InitializeComponent();
this.panela.Name = "panel";
this.panela.Size = new Size((int)(this.ClientSize.Width * 0.765), (int)(this.ClientSize.Height * 0.75));
this.panela.BackColor = Color.SteelBlue;
this.panela.Hide();
this.panela.Parent = this;
this.comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
this.comboBox1.Items.AddRange(new string[] { "item1", "item2", "item3" });
this.comboBox1.SelectedIndexChanged += new EventHandler(comboBox1_SelectedIndexChanged);
}
void button1_Click(object sender, EventArgs e)
{
this.panela.Location = new Point((this.ClientSize.Width / 2) - (this.panela.ClientSize.Width / 2), (this.ClientSize.Height / 2) - (this.panela.ClientSize.Height / 2));
int width = (int)(this.panela.ClientSize.Width * 0.965);
int height = (int)(this.panela.ClientSize.Height * 0.95);
Panel panelb = new Panel();
panelb.Size = new Size(width, height);
panelb.Location = new Point((this.panela.ClientSize.Width / 2) - (width / 2), (this.panela.ClientSize.Height / 2) - (height / 2));
panelb.BackColor = Color.White;
Rectangle parentRect = panelb.ClientRectangle;
int maxWidth = parentRect.Width - 4;
this.comboBox1.Bounds = new Rectangle(parentRect.Left + 2, parentRect.Top + 2, maxWidth, this.comboBox1.Height);
this.comboBox1.SelectedIndex = -1;
this.comboBox1.Parent = panelb;
Button ok = new Button();
ok.Bounds = new Rectangle(parentRect.Width - ok.Width, parentRect.Bottom - ok.Height, 72, 23);
ok.Text = "OK";
ok.FlatStyle = FlatStyle.System;
ok.Click += new EventHandler(ok_Click);
ok.Parent = panelb;
panelb.Parent = this.panela;
this.panela.Show();
this.panela.BringToFront();
}
void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
MessageBox.Show("index changed");
}
void ok_Click(object sender, EventArgs e)
{
this.comboBox1.SelectedIndex = -1;
this.panela.Hide();
this.comboBox1.Parent.Controls.Remove(this.comboBox1);
int count = this.panela.Controls.Count;
int i = 0;
while (i < count)
{
this.panela.Controls[0].Dispose();
i++;
}
}
}
If instead of disposing the objects I use "this.panela.Controls.Clear()" the events will fire without any problem.
So it has to do with the Dispose() of the parent panel
|
|
|
|
|
Hi,
I have some problems with the last part of your code; the while loop continuously
calls Dispose() on the same Control, the first item in panela.Controls; I don't think
this is what you intended. The code resembles how one removes objects from a list,
but calling Dispose() does not remove things from a list, does it?
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
By doing a Dispose() on the first control of the list removes it from the list automatically.
So, panela.Controls[0].Dispose() will remove the first control and dispose it. Next time it runs that code (panela.Controls[0].Dispose()) it will be a different control.
|
|
|
|
|
Hi,
indeed some strange things are happening here.
I did run your code (with some logging added) and it fails to trigger the combo events
from the second button click;
but when the OK button is clicked, when resetting the combo's index back to -1, it does
generate an event, even before panela is hidden, hence before anything really has changed.
So it seems like the combobox events, for unknown reasons, get temporarily disabled.
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
Ok, so I'm not hallucinating, something really strange is going on here.
I'm inclined to think it's a bug, but I don't really know.
Obviously there are ways around this, but I shouldn't have to do them. I'm not doing anything special!
|
|
|
|
|
tuga-x wrote: Ok, so I'm not hallucinating, something really strange is going on here.
I'm inclined to think it's a bug, but I don't really know.
Obviously there are ways around this, but I shouldn't have to do them. I'm not doing anything special!
It's not a bug, you're abusing the framework and relying on an implementation detail that may change, and HAS changed between .NET 2.0 and .NET 3.0.
The detail you're relying on is that calling Dispose() on a control causes it to be removed from its parent's control collection.
What you DON'T know is that that call also destroys the control's handle, which is why your events are no longer being received. The event model relies on the handle being created to raise events (or in most cases, even receive them from the operating system - how do you think the OS knows which control to send the event to?). Dispose() on a Control ALSO kills the handle's message loop - you know, that pesky little thing that is actually dispatching the Windows messages that allow your events to be raised.
If you want to remove the control from the parent collection, do that. Don't invent some convoluted system of destruction just to remove a control from the parent collection.
To add a control to panel B:
comboBox1.Parent = panelb;
To remove the control from panel B:
comboBox1.Parent = null;
You should not be calling dispose anywhere in your code. Anywhere. Dispose() is one of those functions that should almost never need to be explicitly used.
"If you think of yourselves as helpless and ineffectual, it is certain that you will create a despotic government to be your master. The wise despot, therefore, maintains among his subjects a popular sense that they are helpless and ineffectual."
- Frank Herbert
|
|
|
|
|
Hi Patrick,
I do agree with most of what you said.
I did not like the Dispose() calls either, but then the little mystery was the
combobox stopped, then resumed, getting events fired, and it was never disposed.
So while I did not like the code, I could not explain its behavior.
But what triggered me to react now was your statement: "Dispose() is one of those functions
that should almost never need to be explicitly used".
Well I have gotten the advice, and have been repeating it over and over, to "call
Dispose on instances you no longer need whenever the class offers Dispose()"; the rationale
is you don't know whether any unmanaged resources are being used (and how big the
managed ones could be), so it is best practice to call Dispose() when you're done with
an object. This is quite the opposite of what you write though. Care to elaborate?
[Added] I typically don't call dispose on controls, except for forms/dialogs, since
my controls tend to always be part of some Form, and I trust .NET to dispose properly
of the Controls that are used on a Form that gets Closed or Disposed.[/added]
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
Luc Pattyn wrote:
[Added] I typically don't call dispose on controls, except for forms/dialogs, since
my controls tend to always be part of some Form, and I trust .NET to dispose properly
of the Controls that are used on a Form that gets Closed or Disposed.[/added]
I should have been more specific.. you shouldn't call Dispose on controls and GUIs. The framework is built to take care of that, and this example is proof enough of that. Dispose should only be called if the object is *really* never going to be needed again, which in this example is not true, and especially on desktop applications, is really an unnecessary code complication that will only lead to additional code to debug. (On CF applications, calling Dispose can be necessary because of the severely limited resources. Even so, I have never actually found it helpful or necessary to.)
Anywhere documentation tells you to call Dispose (Framework objects or 3rd party objects), you should call Dispose. For example I'm using a 3rd party serial port control that uses significant unmanaged resources and the application will lock up if they're not released.
"If you think of yourselves as helpless and ineffectual, it is certain that you will create a despotic government to be your master. The wise despot, therefore, maintains among his subjects a popular sense that they are helpless and ineffectual."
- Frank Herbert
|
|
|
|
|
Patrick Sears wrote: you shouldn't call Dispose on controls and GUIs
OK, that works for me.
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
Hello Patrick,
Thanks for your reply.
There's no need for you to get irritated because I don't know something. No one is born with all knowledge - we are always learning.
Today I learnt I should not rely on "calling Dispose() on a control" to remove it from its parent control collection, thanks to you.
However, I feel compelled to say that this code was originally done for .NET Compact Framework and at the time the code was using a lot of memory. The solution then was to call Dispose() on all unused objects in order to free resources. And let me tell you it worked. Performance was even enhanced because of it. This code runs fine in .NET CF, obviously there are differences between the two, but we're not going into that.
Patrick Sears wrote: If you want to remove the control from the parent collection, do that. Don't invent some convoluted system of destruction just to remove a control from the parent collection.
So again, don't assume you know why I did that code - obviously I know how to remove a control from its parent control collection. The reason for it has nothing to do with removing it from the collection.
But I'm not here to bash anyone. I thank you for sharing your knowledge, but I don't appreciate how you wrote it.
Thanks.
|
|
|
|
|
There is an ongoing "discussion" among my colleagues about exactly how a ValueType is stored internally. We are looking for some other views.
If a ValueType is derived from System.Object, does it have a vtable or is it stored in raw form until boxed. In other words, is an System.Int32 stored on the stack as a 4 byte integer or does it have a vtable therby making it larger than 4 bytes?
|
|
|
|
|
|
The first sentence of the article reads "A Value Type instance on stack without any Method Table ..." Does that mean an System.Int32 is stored on the stack in just 4 bytes (sounds like it to me)?
If so, how does System.ValueType's derivation from System.Object (in which it overides the System.Object methods) avoid a "Method Table?"
Assuming the compiler "changes" a System.Int32 to its 4 byte raw form for storage on the stack, what is the purpose of deriving System.ValueType from System.Object?
Thanks for the article...
|
|
|
|
|
Yes, an integer will only take 4 bytes.
If the "Method table" is needed it will be generated by the CLR on demand. Google boxing and C# for details.
This design allows the lower memryconsumption and faster performance of native types, while still allowing object oriented programming with the native types. The "price" you pay is a performance hit when boxing/unboxing.
|
|
|
|
|
I understand how and why boxing is used...
The question remains: If a System.Int32 occupies only 4 bytes on the stack, what is the purpose of deriving System.ValueType from System.Object?
Thanks for your reply...
|
|
|
|
|
If System.ValueType did not derive from System.Object, how would you make a collection like this one:
System.Collections.Generic.List<System.Object>
which can contain objects of any time - including value types.
If System.ValueType did not derive from System.Object, you would basically need an interface to provide the standard methods (ToString, Hash value, etc)... but to be accessed from an interface the value type would still need to be boxed and now you also need to cast anything derived from System.Object to the interface constantly. So basically nothing was simplified but complexity was added.
Just out of curiosity, what do you think the benefit of NOT deriving from System.Object would be?
|
|
|
|
|
Your explanation implies that System.ValueType has more information than just the raw memory needed to store the data (an 4 byte integer in my previous examples).
I'll try to restate my original question, again using the ValueType System.Int32 as an example:
Does an instance of System.Int32 occupy only 4 bytes in memory when it is allocated on the stack?
If so, what is the purpose of deriving System.ValueType from System.Object if it does not carry any information from System.Object?
|
|
|
|
|
Yes an instance of System.Int32 occupies exactly 4 bytes in memory when it is allocated on the stack.
Yes an instance of System.Int32 is also a System.Object and if the CLR needs its object
behavior, the compiler will have provided the necessary code to make it act as an object,
but that does not change the data foot print.
Example:
int a=12;
string s=a.ToString();
the ToString() method applies to "object" a, the compiler will allocate a 4B integer on stack
and generate code that calls System.Int32.ToString().
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
Bravo! That is the best explanation yet.
This is exactly what MOST of my colleagues believe. The devils advocate of the group maintains that:
int a=12; // is not a ValueType until
string s = a.ToString() // is called
Thanks everybody for your input.
|
|
|
|
|
Well, most of your colleagues are right.
It suffices to compile some code and look at the generated MSIL (e.g. with ILDASM)
to observe an int is simply an int, and some class methods get called when appropriate.
Luc Pattyn [Forum Guidelines] [My Articles]
this months tips:
- use PRE tags to preserve formatting when showing multi-line code snippets
- before you ask a question here, search CodeProject, then Google
|
|
|
|
|
I am sorry my answer:
"Yes, an integer will only take 4 bytes."
could leave you in doubt how much memory an integer takes, though I have no idea what so ever how it could be written clearer. And no, my explanation does obviously not imply that more data is stored than the 4 bytes.
I do not know what you mean with "more information available" - yes, obviously it has. Every single object ever created has more information available than the bytes it takes up in memory (as it can easily access the information for the class itself). Or maybe you are referring to the information built into the CLR allowing it to do the boxing/unboxing - sure there is information available for that as well, but obviously per class, not per object, so it does not affect the amount of bytes each object use.
With regards to:
int a=12; // is not a ValueType until
string s = a.ToString() // is called
it probably goes on details in the CLR. "a" is not boxed before the a.ToString call, but at any time "a is ValueType" would return true, and at any time it can be used as a ValueTime. Hence I would consider it a ValueType no matter how it happens to be represented in memory, but I can see the other side of the argument as well - it all comes down to seeing it from the level of C# or from the level of the CLR implementation.
|
|
|
|
|
Also, this is valid:
string s = 12.ToString();
"We make a living by what we get, we make a life by what we give." --Winston Churchill
|
|
|
|
|