User control constructor called twice

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Rimpinths

    User control constructor called twice

    I'm new at developing user controls in C#, and one thing I've noticed
    right off the bat is that the constructor gets called twice -- once at
    design time, once at run time.

    In short, I'm trying to develop a control derived from DataGridView. It
    will have a default set of columns (but I don't want to create them via
    the Properties window for various reasons) so I added a call to a
    method AddColumns() in the constructor. But the columns end up getting
    added twice -- once when I place the control in a form at design time,
    and then again when I run the application.

    I know that I can get around this by making an explicit call to
    AddColumns() at runtime rather than putting it in the constructor. But
    I like the fact that they show up at design time so that way I can see
    what it's going to look like.

    I tried adding an "initialize d" flag in my constructor to prevent it
    from callling AddColumns() twice, but that doesn't work because
    apparently its design time value is reset at run time.

    Any suggestions?

  • Rimpinths

    #2
    Re: User control constructor called twice

    Following up on my own message...

    I thought that I could at least work around this problem by calling
    Columns.Clear() in my AddColumns() method, but even though the column
    count is zero after I clear them, two sets of the columns still show
    up. So where is the other set being stored?

    I think I'm missing some fundamental understanding about user controls.
    Is the moral of the story to just not do anything in the constructor?

    Comment

    • Dave Sexton

      #3
      Re: User control constructor called twice

      Hi,

      The designer is creating an instance of your control at design-time. If it
      didn't, you wouldn't see anything :)

      When the designer creates your control, the constructor is executed and the
      columns are added. Since the columns can be "designed", the designer
      recognizes this and serializes code (I'm assuming) into your code-behind to
      initialize the columns. I suspect that you see the columns in the grid even
      at design-time, correct?

      Of course, when you run the application the constructor executes and adds
      the columns at runtime. This probably occurs in the InitializeCompo nent
      method of your Form, also in which the code was serialized to initialize the
      columns at design-time.

      So after your control is constructed in the InitializeCompo nent method,
      columns are added again by the designer-generated code.

      You can try to prevent the addition of the columns when the control is being
      hosted in a designer. In order to do this you need to check the
      Site.DesignMode property of the Control, however Site will always be null in
      the constructor. So you'll have to move the column-addition code into
      another method that can evaluate Site.DesignMode :

      protected override void OnControlAdded( ControlEventArg s e)
      {
      if (Site == null || !Site.DesignMod e)
      {
      // add columns
      }

      base.OnControlA dded(e);
      }

      Realize that the columns shouldn't appear when the control is added to a
      Form's designer.

      Let us know if that works for you :)

      --
      Dave Sexton

      "Rimpinths" <mike_renshaw@y ahoo.comwrote in message
      news:1164149476 .806033.64160@m 7g2000cwm.googl egroups.com...
      Following up on my own message...
      >
      I thought that I could at least work around this problem by calling
      Columns.Clear() in my AddColumns() method, but even though the column
      count is zero after I clear them, two sets of the columns still show
      up. So where is the other set being stored?
      >
      I think I'm missing some fundamental understanding about user controls.
      Is the moral of the story to just not do anything in the constructor?
      >

      Comment

      • Rimpinths

        #4
        Re: User control constructor called twice

        Thanks for your detailed response, Dave.

        Yeah, I guess it does make sense that the constructor gets called at
        design time, and then of course at run time. What I don't get is why
        the constructor gets called twice for the same instance of that
        control? Why doesn't the design time instance get thrown out and a new
        one created at run time? How can you even call a constructor twice for
        the same instance?

        Unfortunately, your suggestion doesn't work. Actually, it's worse -- it
        ends up adding two columns at design time and another two columns at
        run time. Go figure. And after adding it and removing it from my form
        several times, the form designer code has references to a
        dataGridViewTex tBoxColumn5 and dataGridViewTex tBoxColumn6, and so on.
        Weird stuff. I'm still trying to get my head around the original
        problem, not sure what's going on with your suggestion.

        Here's another interesting test that I ran. I tried this bit of code in
        the constructor...

        Columns.Add("co lumn1", "column1");
        Rows.Add(3);
        Rows[0].Cells[0].Value = "Hello,";
        Rows[1].Cells["Column1"].Value = "world!";

        try
        {
        Rows[2].Cells[1].Value = "I'm here.";
        }
        catch (ArgumentOutOfR angeException)
        {
        Rows[2].Cells[0].Value = "I can't find you";
        }

        Everything appears in the first column at design time and run time. And
        the ArgumentOutOfRa ngeException is raised even at run time, despite the
        fact that I can see that second column. How can you even access it?
        Where does it exist?

        I know that we developers are quick to attribute something to a bug
        when it doesn't work the way that we expect it to, but this seems like
        a genuine bug to me. I'm not sure if this is the general behavior of
        constructors of user controls, or more likely, a particular problem
        with the DataGridView control which I think went through a lot of
        changes in .NET 2.0. I'm trying the think of similar scenario with
        another control, but I can't. Oh wait, let me try deriving a ComboBox
        and call this in the constructor...

        Items.Add("Item #1");
        Items.Add("Item #2");

        Yep, same problem. Four items are listed, but Items.Count returns 2.
        What the...? This seems like such a basic problem if I ran into this
        after two days of playing around with derived controls. There must be
        some way around it, perhaps something that I may not be doing right
        after all, but I still think the proper behavior would be to throw out
        the design time instance and start over at run time.

        I'll probably just go back to calling a separate AddColumns method at
        runtime as I said in my original message. It's just not worth the
        effort to try figure out what's going on here, although I'm still
        curious, just for the sake of my C# knowledge.

        Thanks again for your help, much appreciated.

        Comment

        • Rimpinths

          #5
          Re: User control constructor called twice

          Thanks for your detailed response, Dave.

          Yeah, I guess it does make sense that the constructor gets called at
          design time, and then of course at run time.

          One other thing that I noticed is that Visual Studio adds
          this.dataGridVi ewDerived1.Colu mns.AddRange to the design code of the
          form that the control gets added to. Anything you do in the constructor
          of the user control gets added to the design code of the control's
          container. I'm not sure if I understand the logic behind that behavior.
          And one solution I did find is to remove the Columns.AddRang e in the
          design code after I add the control to a form, but what a hassle, why
          should I need to do that?

          Unfortunately, your suggestion doesn't work. Actually, it's worse -- it
          ends up adding two columns at design time and then another two columns
          at run time. Go figure. And after adding it and removing it from my
          form several times, the form designer code has references to a
          dataGridViewTex tBoxColumn5 and dataGridViewTex tBoxColumn6, and so on.
          Weird stuff. I'm still trying to get my head around the original
          problem, not sure what's going on with your suggestion.

          Here's another interesting test that I ran. I tried this bit of code in
          the constructor...

          Columns.Add("co lumn1", "column1");
          Rows.Add(3);
          Rows[0].Cells[0].Value = "Hello,";
          Rows[1].Cells["Column1"].Value = "world!";

          try
          {
          Rows[2].Cells[1].Value = "I'm here.";
          }

          catch (ArgumentOutOfR angeException)
          {
          Rows[2].Cells[0].Value = "I can't find you";
          }

          How can I even access that other column that I can see? Who does it
          even belong to? I'm trying the think of similar scenario with another
          control, but I can't. Oh wait, let me try deriving a ComboBox and call
          this in the constructor...

          Items.Add("Item #1");
          Items.Add("Item #2");

          Yep, same problem. Four items are listed, but Items.Count returns 2.
          What the...? This seems like such a basic problem if I ran into this
          after two days of playing around with derived controls. There must be
          some way around it, perhaps something that I may not be doing right
          after all, but I still think the proper behavior would be to throw out
          the design time instance and start over at run time.

          I'll probably just go back to calling a separate AddColumns method at
          runtime as I said in my original message. It's just not worth the
          effort to try figure out what's going on here, although I'm still
          curious, just for the sake of my C# knowledge.

          Thanks again for your help, much appreciated.

          Mike

          Comment

          • Dave Sexton

            #6
            Re: User control constructor called twice

            Hi,

            I'm so sorry - I accidentally gave you code overloading the wrong method :|

            The following code works just fine:

            public class CustomDataGridV iew : DataGridView
            {
            protected override void OnCreateControl ()
            {
            if (Site == null || !Site.DesignMod e)
            {
            Columns.Add("Te st 1", "Header 1");
            Columns.Add("Te st 2", "Header 2");
            }

            base.OnCreateCo ntrol();
            }
            }
            Yeah, I guess it does make sense that the constructor gets called at
            design time, and then of course at run time. What I don't get is why
            the constructor gets called twice for the same instance of that
            control? Why doesn't the design time instance get thrown out and a new
            one created at run time? How can you even call a constructor twice for
            the same instance?
            You can't call a constructor twice on the same instance. The runtime
            instance and design-time instance are not the same. When you start your
            application, even with a debugger attached, it will run in its own process.

            <snip - sorry - >

            --
            Dave Sexton


            Comment

            • Dave Sexton

              #7
              Re: User control constructor called twice

              Hi,
              One other thing that I noticed is that Visual Studio adds
              this.dataGridVi ewDerived1.Colu mns.AddRange to the design code of the
              form that the control gets added to. Anything you do in the constructor
              of the user control gets added to the design code of the control's
              container. I'm not sure if I understand the logic behind that behavior.
              Not just any code is generated.

              You've got to realize that the columns may be designed themselves. In other
              words, you can add and remove columns from within an editor dialog, which
              means that the designer has to serialize code to persist the changes that
              you make. Guess where that code is persisted?

              The point of a designer is to design the Form for use at runtime, so if you
              add a DataGridView with some predefined columns, the designer is going to
              see those columns and serialize code to generate them at runtime. It
              doesn't know that you've added them yourself in the constructor and not
              after the control was added to the Form.
              And one solution I did find is to remove the Columns.AddRang e in the
              design code after I add the control to a form, but what a hassle, why
              should I need to do that?
              The designer code is "live" code that will be modified by VS on-the-fly.
              Changes like that will probably not last for very long. Try closing the
              Form designer and reopening it or try closing VS and reopening it and you'll
              probably see what I mean.

              <snip - still sorry you wasted your time writing this - :) >

              --
              Dave Sexton


              Comment

              • Bruce Wood

                #8
                Re: User control constructor called twice


                Rimpinths wrote:
                One other thing that I noticed is that Visual Studio adds
                this.dataGridVi ewDerived1.Colu mns.AddRange to the design code of the
                form that the control gets added to. Anything you do in the constructor
                of the user control gets added to the design code of the control's
                container. I'm not sure if I understand the logic behind that behavior.
                And one solution I did find is to remove the Columns.AddRang e in the
                design code after I add the control to a form, but what a hassle, why
                should I need to do that?
                What is happening here is that the Columns property of the original
                data grid contains code that tells the Designer what the default
                columns look like. In fact, every property of a control has some code
                that tells the Designer what the default is for that property.

                When it comes time to serialize the control into code, the Designer
                serializes code for all properties that have values _different from
                their default values_. It doesn't know that the property's value was
                changed in the constructor rather than by the programmer in the
                Designer itself. All it knows is that property X has a value other than
                default, so it serializes that value.

                You need to read up on "design time" logic in Visual Studio and how to
                design your own controls. There are several MSDN articles out there,
                lots of documentation, and several newsgroups. Some things are very
                easy to do, while others are difficult. I still haven't gotten the hang
                of compound properties like Columns, but others have, so it can be done
                properly.
                Unfortunately, your suggestion doesn't work. Actually, it's worse -- it
                ends up adding two columns at design time and then another two columns
                at run time.
                No... it doesn't add "two columns at design time and then another two
                columns at run time." What it does is add two columns in the
                constructor, and then add them again in the serialized code for the
                control. Again, the Designer didn't know that those two columns were
                added in the constructor, because you don't have any attributes /
                methods to indicate that those two columns are defaults and shouldn't
                be serialized.

                In fact, this points out why having some "default columns" may be a
                problem: if you then add a third column at design time the Designer
                will see that the value of Columns is not the default of two columns
                and will then serialize code to add all three columns, resulting in
                five. You may have to write your own serialization code in order to get
                this to work, and IMHO that's pretty advanced design-time stuff.
                Probably not worth the hassle, although I'm told that it can be done.
                Go figure. And after adding it and removing it from my
                form several times, the form designer code has references to a
                dataGridViewTex tBoxColumn5 and dataGridViewTex tBoxColumn6, and so on.
                The names for the columns seem to be monotonically incrementing from
                the highest name used so far. The Designer doesn't appear to try
                reusing column names that are free. Not sure why, but there you go.
                Here's another interesting test that I ran. I tried this bit of code in
                the constructor...
                >
                Columns.Add("co lumn1", "column1");
                Rows.Add(3);
                Rows[0].Cells[0].Value = "Hello,";
                Rows[1].Cells["Column1"].Value = "world!";
                >
                try
                {
                Rows[2].Cells[1].Value = "I'm here.";
                }
                >
                catch (ArgumentOutOfR angeException)
                {
                Rows[2].Cells[0].Value = "I can't find you";
                }
                >
                How can I even access that other column that I can see? Who does it
                even belong to?
                Since you don't say what the result of running the code is, I'm not
                sure what the issue is here....
                I'm trying the think of similar scenario with another
                control, but I can't. Oh wait, let me try deriving a ComboBox and call
                this in the constructor...
                >
                Items.Add("Item #1");
                Items.Add("Item #2");
                >
                Yep, same problem. Four items are listed, but Items.Count returns 2.
                What do you mean, "Four items are listed"? Listed how? How do you know
                that there are supposedly four? Where are you checking Items.Count?

                Comment

                • Rimpinths

                  #9
                  Re: User control constructor called twice

                  The following code works just fine:

                  Yes, it does, thank you!
                  You can't call a constructor twice on the same instance. The runtime
                  instance and design-time instance are not the same. When you start your
                  application, even with a debugger attached, it will run in its own process.
                  Yeah, I realized that didn't make any sense about two minutes after I
                  posted that message and consequently deleted it. I considered that I
                  never run it as an exe, and then I saw that it did the same thing when
                  I did, which blows the design time and runtime instance out of
                  competition.

                  Mike

                  Comment

                  • Rimpinths

                    #10
                    Re: User control constructor called twice

                    The point of a designer is to design the Form for use at runtime, so if you
                    add a DataGridView with some predefined columns, the designer is going to
                    see those columns and serialize code to generate them at runtime. It
                    doesn't know that you've added them yourself in the constructor and not
                    after the control was added to the Form.
                    Okay, I think that nails the concept that I was missing: serialization.
                    This is what I need to learn more about.
                    <snip - still sorry you wasted your time writing this - :) >
                    No, not a waste of time at all. I'm so grateful that people like you
                    are willing to spend time answering questions from strangers.

                    I could've worked around this problem in no time, but I knew that I
                    should probably try to figure out what was going on, it could help my
                    C# knowledge, and it has.

                    Mike

                    Comment

                    • Rimpinths

                      #11
                      Re: User control constructor called twice

                      When it comes time to serialize the control into code, the Designer
                      serializes code for all properties that have values _different from
                      their default values_. It doesn't know that the property's value was
                      changed in the constructor rather than by the programmer in the
                      Designer itself. All it knows is that property X has a value other than
                      default, so it serializes that value.
                      Here we go again, serialization, this is what I need to learn more
                      about.
                      You need to read up on "design time" logic in Visual Studio and how to
                      design your own controls. There are several MSDN articles out there,
                      lots of documentation, and several newsgroups. Some things are very
                      easy to do, while others are difficult.
                      Yeah, I'm figuring that out. I was actually just wanted to create a few
                      controls that I could reuse within a project, to standardized fonts and
                      colors and such. If I wanted to sell it or use it throughout several
                      projects, I'd problem spend more time understanding the problem.

                      I think that this is probaby an aytpical case. The core problem is
                      adding objects to a collection (columns to a grid, items to a combobox)
                      in the constructor. In most cases, performing tasks in the constructor,
                      like setting properties and such, this wouldn't be a problem.
                      Since you don't say what the result of running the code is, I'm not
                      sure what the issue is here....
                      What, you can't do it in your head?

                      Sorry, the resulting code is that two columns are displayed with the
                      headers "column1", and everything gets written to the first column. If
                      I try to access the second columns with Rows[2].Cells[1], it throws
                      ArgumentOutOfRa ngeException. If I ask how many columns are there, it
                      returns Columns.Count = 1. You can't access that second column in any
                      way that I can figure out, so where does that object exist?
                      >
                      I'm trying the think of similar scenario with another
                      control, but I can't. Oh wait, let me try deriving a ComboBox and call
                      this in the constructor...

                      Items.Add("Item #1");
                      Items.Add("Item #2");

                      Yep, same problem. Four items are listed, but Items.Count returns 2.
                      >
                      What do you mean, "Four items are listed"? Listed how? How do you know
                      that there are supposedly four? Where are you checking Items.Count?
                      I'm checking Items.Count at runtime, after the constructor has been
                      called. The ComboBox displays 4 choices in the drop down menu, but
                      Items.Count says that there are only 2 items. It's a similar problem as
                      the one with the columns above.

                      Mike

                      Comment

                      • Dave Sexton

                        #12
                        Re: User control constructor called twice

                        Hi Mike,

                        <snip>
                        I could've worked around this problem in no time, but I knew that I
                        should probably try to figure out what was going on, it could help my
                        C# knowledge, and it has.
                        I admire your desire to learn instead of just copying :)

                        --
                        Dave Sexton


                        Comment

                        Working...