Non wtf winform application



  • Hi all. I don't really post much here, so I know I'm prolly gonna bungle this but here goes. I have a program I'm writing in c# that I want to display different "screens" when buttons in a left hand navigation type area are clicked. (Think something similar to frameset navigation on web pages, or more like the UI to SpyBot) What's a good non-wtf way to do this? The only thing close to what I want would be to abuse the tab control, and I don't really want to use it because I don't want tabs along the top, I want buttons close to the left hand side. (BTW, this is being built using VS 2008, and is not a WPF application). I've done coding for years, but mostly console, services, or simple winforms applications, so I'm a little lost on the right way to do this. Thanks in advance for your suggestions.



  • Oh, Jeez. If I remember correctly, isn't there a control like the one Outlook uses to accomplish this? I don't know if Microsoft ever released that control, but here is what appears to be a reasonable copy: http://www.codeproject.com/KB/menus/OutlookBar.aspx



  • I'm assuming you are using .Net.  If so, Microsoft's standard.Net controls are pretty limited.  Even when MS releases the UI elements from Office 2007 for C++, I don't know whether a control like this will be included.  

    You got a couple options.

    • Code one yourself
    • Purchase or find a third-party control/library
    • Find another way

    Having done custom controls, I find it is a royal pain and not worth the hassle when you can buy a full blown third party suite for around $1000 US.  Smaller packages focused just on editors can be purchased for around $250.  I've used DevExpress and ComponentOne's third party controls have found both to be of excellent quality.

     



  • More info:

    Yes, I'm using .Net, specifically C# 2.0

     Basically, I'm not that concerned about the navigation control. What I'm trying to get at is what scheme / method do you use to manage shifting many controls onto / off the form?

    I don't want to end up doing something like the crazy fox pro wtf that was shown a few years back, where controls were programmatically hidden / shown. I want to do it the "right' way. A tab control is close to what I want because I can drop controls for each section on individual tabs, and just programatically change the active tab. But like I said earlier, I don't really want to go that route because I don't want tabs on the top or side of the screen.

    And I have looked at Dxperience and the like. I've actually got the set of free controls that Dxperience puts out there, and I may use a few of them. I like the shinyness of them :)~

     What I'm thinking about doing is making seperate user controls for each screen, and programatically adding / remvoing them from a split container / panel container control. Is this a good way to go about what I'm trying to do?

     Thanks again for the help.



  • What's wrong with using a tab control and hiding the tabs? Then you just programmatically move back and forth between each tabsheet based on the button the user clicked, and you can still visually design each tabsheet in the VS IDE.



  • Umm, becuase there isn't an "offical" way to hide the tabs on the c# tab control. The best you can do is force the region of the panel to cover up the tabs (several google searches turned this up) and that's exactly the kind of hackish code I want to avoid. TRWTF is that you can't say tabcontrol1.tab1.visible = false;

     



  • @talaxor said:

    and that's exactly the kind of hackish code I want to avoid.
     

    If you write it correctly as a reusable (and documented) control class, it would not be 'hackish'. Copying code off a google search and pasting it into your project for every instance you need this? Yes. That would be 'hackish'.



  • its been awhile since I done some intensive gui work, but I'm sure there should be a way to have like a "owner drawn" tab control that should do what you like.  It should also be pretty easy to mock one up yourself into a usercontrol. You should be able to make a generic collection of panels and swap between the "active" one in some way (tab button, tool button, keystroke, image click, etc). You can make some user controls with the same panel size and push them onto that panel. You could even use inherited user controls to try to maintain some consistency. I'm sure you know all this, but you asked...



  • @talaxor said:

    Hi all. I don't really post much here, so I know I'm prolly gonna bungle this but here goes. I have a program I'm writing in c# that I want to display different "screens" when buttons in a left hand navigation type area are clicked. (Think something similar to frameset navigation on web pages, or more like the UI to SpyBot) What's a good non-wtf way to do this? The only thing close to what I want would be to abuse the tab control, and I don't really want to use it because I don't want tabs along the top, I want buttons close to the left hand side. (BTW, this is being built using VS 2008, and is not a WPF application). I've done coding for years, but mostly console, services, or simple winforms applications, so I'm a little lost on the right way to do this. Thanks in advance for your suggestions.

    The dead-obvious solution to your problem is to have usercontrols dynamically added to and removed from some container control.

    IIRC this is as simple as emptying the container control's control collection and manually instantiating the requested user control and adding it to the collection instead. Something like:

    container.SuspendLayout();
    container.Controls.Clear();
    container.Controls.Add(new MyUserControl());
    container.ResumeLayout();

    There's absolutely no need for any WTFs resulting from hacking away at the Tabsheet control.



  • First, let me state what I think you're saying, my answer is based on these assumptions:

    • C# Winform
    • "Frame Like" control on the left hand side(LHS) that allows different clicks to "load" different screens
    • New screens will be loaded on the right hand side(RHS) of the screen.

    So we might have something like "Text, Spreadsheet, Database" as possible options on the left hand side, then when Text is clicked a notepad or something similar opens on the right, then spreadhseet launches an excel like form on the right.

    If that's true, what about looking at MDI forms.  You would have your main form with the navigation pane.  Then each "screen" that needed to be loaded could be its own form.  Then when you click on an item in the navigation pane, you would close the current form that's on the RHS and load the one selected.

    This avoids the whole hiding tabs etc.  It also allows you to leave open forms instead of closing them, and allowing the user to return to a previous form if need be.

     

    Whatever you do, DON'T create a screen with 50 group boxes and simply change the zorder.  I've seen that done, and it works fine, but maintaining it sucks.  As you spend 5+ minutes trying to find cmbMaterialType only for it to be 3 group boxes deep. 

    If you didn't understand that last line, count yourself lucky :)



  • Honestly, I don't see why you couldn't just use panels and then toggle the visibility based on which button they press, for example:

    Left side:  button1,button2,button3,button4,button5 -- you can change the properties on these to make them big, like spybot S&D, add graphics, etc.

    Right side: panel1, panel2, panel3, panel4, panel5 -- these panels are all the same size and same docking and stacked on top of each other, and then you can just toggle the "Visible" property on them based on which button is clicked.

    This method can be a little painful to develop only because you have to keep "sending to back" on the panels in your development interface, but it will accomplish your desired functionality, and it's not really a WTF way of doing it.  If you want some sample code, I could throw together a small project for you to illustrate the idea.

    As a secondary option, you could purchase a control from somebody that does this type of thing.  Krypton Navigator comes to mind.  Check it out on http://www.componentfactory.com/.  It's pretty cheap, and it works very well.  I've used it in the past.

    Cheers!



  • @swaj said:

    Honestly, I don't see why you couldn't just use panels and then toggle the visibility based on which button they press, for example:

    Left side:  button1,button2,button3,button4,button5 -- you can change the properties on these to make them big, like spybot S&D, add graphics, etc.

    Right side: panel1, panel2, panel3, panel4, panel5 -- these panels are all the same size and same docking and stacked on top of each other, and then you can just toggle the "Visible" property on them based on which button is clicked.

    This method can be a little painful to develop only because you have to keep "sending to back" on the panels in your development interface, but it will accomplish your desired functionality, and it's not really a WTF way of doing it.  If you want some sample code, I could throw together a small project for you to illustrate the idea.

    As a secondary option, you could purchase a control from somebody that does this type of thing.  Krypton Navigator comes to mind.  Check it out on http://www.componentfactory.com/.  It's pretty cheap, and it works very well.  I've used it in the past.

    Cheers!

    OP was asking for a non-wtf winform application. The fact that your solution has maintainability and scalability issues should clue you in on the fact that it is, in fact, a WTF.

    As I've tried to explain before; more or less the only 'correct' way to do this is to encapsulate all your 'tabsheets' into separate user controls and then dynamically load the relevant user control onto the form. Personally I think the MDI approach is a good solution as well, but it wasn't what the OP was looking for. (Perhaps though, it is what he should have been looking for...)



  • @Ragnax said:

    OP was asking for a non-wtf winform application. The fact that your solution has maintainability and scalability issues should clue you in on the fact that it is, in fact, a WTF.

    As I've tried to explain before; more or less the only 'correct' way to do this is to encapsulate all your 'tabsheets' into separate user controls and then dynamically load the relevant user control onto the form. Personally I think the MDI approach is a good solution as well, but it wasn't what the OP was looking for. (Perhaps though, it is what he should have been looking for...)

     

    Dynamically loading the right side doesn't seem to be the best possible approach in all cases, though. In some cases, you'd rather want the initial loading to take longer, so you can switch quickly once the form is loaded. Your proposal includes removing the controls when the user switches to another view - but that means reloading when the user switches back. Possible problems include performance if loading is non-trivial, and losing the state of the removed control, which could be very anoying.

     



  • @ammoQ said:

    Possible problems include performance if loading is non-trivial, and losing the state of the removed control, which could be very anoying.

    That could be handled by .Net's native serialization.  Even on complex forms, serialization is not normally a big performance hit.



  • @taylonr said:

    That could be handled by .Net's native serialization.  Even on complex forms, serialization is not normally a big performance hit.

     

    But what if the complex form contains stuff that cannot be serialized, e.g. a database connection? To me, it smells a bit like "premature optimization meets the square wheel".



  • @ammoQ said:

    But what if the complex form contains stuff that cannot be serialized, e.g. a database connection?

     Why would an individual form have a database connection?  Why wouldn't that be more of an application property, regardless of if there is 1 form or 330 there'd still be just the one connection.



  • @ammoQ said:

    Dynamically loading the right side doesn't seem to be the best possible approach in all cases, though. In some cases, you'd rather want the initial loading to take longer, so you can switch quickly once the form is loaded. Your proposal includes removing the controls when the user switches to another view - but that means reloading when the user switches back. Possible problems include performance if loading is non-trivial, and losing the state of the removed control, which could be very anoying.
     

    In regards to the unloading of the child controls from the parent, this can cause a very noticeable flash when switching.  Having the child control on parent and setting the visibility/z-order drastically reduces the apparent flash.  

    Loading all the child controls at once can be an issue.  The right solution (although more difficult to program) is to have the primary child control loaded on the main UI thread and then load the secondaries on a background thread.  That way you get fast initial load and fast switching but you have to deal with synchronization and the rare case where someone wants to load up a secondary before the background thread has processed it.

    @taylonr said:

    @ammoQ said:
    But what if the complex form contains stuff that cannot be serialized, e.g. a database connection?

     Why would an individual form have a database connection?  Why wouldn't that be more of an application property, regardless of if there is 1 form or 330 there'd still be just the one connection.

    I think what ammoQ is refering to is loading dropdowns and lists that need to pulled from a database.   And yes, native .Net serialization is fast.  Where you get into problems is layout controls like the TableLayoutPanel and other 3rd party controls that need to perform a lot of calculations for docking and control layout.

     

     



  • @lpope187 said:

    I think what ammoQ is refering to is loading dropdowns and lists that need to pulled from a database.

    If that's the case, I completely misread what was written, I was thinking trying to serialize the actual connection.

     



  •  @taylonr said:

    @lpope187 said:

    I think what ammoQ is refering to is loading dropdowns and lists that need to pulled from a database.

    If that's the case, I completely misread what was written, I was thinking trying to serialize the actual connection. 

    I really meant the actual connection. Why should that be a property of the form and not property of the application?

    Well, there might be reasons for that. For example, it could be that the connection is not going to the program's primary database (which most or all forms access and which is open as long as the program runs), but the database of another system, and this form is the only one that peeks into that database. Or: the program's main function goes without databases, but this single form is the database wizard used to import and export from/to a database.

    The point of my statement is: by going the serialization approach, you limit the possible data types that can be used as properties of the form and its children. I wouldn't do that without a compelling reason.

     



  •  there's a lot of posts and i can't be bothered reading them all so i'm not sure if this has been suggested yet but here goes.

    To do the buttons down the side thing i'd suggest QIOS, it's a sweet set of controls, plugs into visual studio etc.  I've used it many times and i'm sure it will do what you want display wise.

    In terms of switching between screens i'd recommend making each screen as a separate control and then add it to the tab page.  It just keeps things neater, if you need to communicate with the parent form you can cast the .ParentForm to your custom one but a better solution is to write custom events for that kind of communication and then you can reuse the controls again inother places without casting problems.

     

    Hope this helps 



  • @lpope187 said:

    Loading all the child controls at once can be an issue.  The right solution (although more difficult to program) is to have the primary child control loaded on the main UI thread and then load the secondaries on a background thread.  That way you get fast initial load and fast switching but you have to deal with synchronization and the rare case where someone wants to load up a secondary before the background thread has processed it.

    I don't think so. There's this little problem with WinForms: controls are 'owned' by the thread that created them, which means no other thread can safely update them. Therefore you must load your controls in the main (STA)thread of your WinForms application. (There are some ways around this limitation, but they involve manually toying with windows message loops etc. and are simply not practical.)



  • @Ragnax said:

    I don't think so. There's this little problem with WinForms: controls are 'owned' by the thread that created them, which means no other thread can safely update them. Therefore you must load your controls in the main (STA)thread of your WinForms application. (There are some ways around this limitation, but they involve manually toying with windows message loops etc. and are simply not practical.)
     

    You're probably thinking about OS level threads, but in terms of managed threads it is possible.  The code below illustrates my point (I just hacked it together in a few minutes based on what I do normally in my data access components).  Looking at Spy++, both the form and the created textbox are owned by the same OS thread.  I certainly wouldn't do this in a real app.  What I would do is implement an Initialize method on the child control, instantiate the control on the main UI thread, and execute the Initialize method on a background thread. 

        public partial class TestForm : Form
        {
            HybridDictionary userStateToLifetime = new HybridDictionary();
            private delegate void LoadChildControlWorkerDelegate(AsyncOperation asyncOperation);
            private SendOrPostCallback OnLoadChildControlWorkerCompleted;
            private event EventHandler<LoadChildControlCompletedEventArgs> LoadChildControlCompleted;
            Guid taskId = new Guid();
            TextBox childTextBox;

            public TestForm()
            {
                InitializeComponent();

                OnLoadChildControlWorkerCompleted = new SendOrPostCallback(LoadChildControlWorkerCompleted);

                this.LoadChildControlCompleted += new EventHandler<LoadChildControlCompletedEventArgs>(TestForm_LoadChildControlCompleted);
            }

            void TestForm_LoadChildControlCompleted(object sender, TestForm.LoadChildControlCompletedEventArgs e)
            {
                childTextBox = e.Result;

                this.clientPanel.Controls.Add(childTextBox);
            }

            public TextBox CreateChildControl()
            {
                TextBox childControl = new TextBox();
                childControl.Dock = DockStyle.Top;

                System.Threading.Thread.Sleep(5000);

                System.Diagnostics.Debug.WriteLine(string.Format("Child control created on thread {0}, IsBackground: {1}", System.Threading.Thread.CurrentThread.ManagedThreadId, System.Threading.Thread.CurrentThread.IsBackground));

                return childControl;
            }

            public void LoadChildControlAsync(object taskId)
            {
                // Create an AsyncOperation for taskId.
                AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId);

                // Multiple threads will access the task dictionary,
                // so it must be locked to serialize access.
                lock (userStateToLifetime.SyncRoot)
                {
                    if (userStateToLifetime.Contains(taskId))
                    {
                        throw new ArgumentException(
                            "Task ID parameter must be unique",
                            "taskId");
                    }

                    userStateToLifetime[taskId] = asyncOp;
                }

                // Start the asynchronous operation.
                LoadChildControlWorkerDelegate workerDelegate = new LoadChildControlWorkerDelegate(LoadChildControlWorker);
                workerDelegate.BeginInvoke(asyncOp, null, null);
            }

            private void LoadChildControlWorker(AsyncOperation asyncOp)
            {
                Exception e = null;
                TextBox childControl = null;

                // Check that the task is still active.
                // The operation may have been canceled before
                // the thread was scheduled.
                if (!TaskCanceled(asyncOp.UserSuppliedState))
                {
                    childControl = CreateChildControl();
                }

                this.LoadChildControlCompletionMethod(
                    childControl,
                    e,
                    TaskCanceled(asyncOp.UserSuppliedState),
                    asyncOp);
            }

            private void LoadChildControlCompletionMethod(TextBox childControl, Exception exception, bool canceled, AsyncOperation asyncOp)
            {
                // If the task was not previously canceled,
                // remove the task from the lifetime collection.
                if (!canceled)
                {
                    lock (userStateToLifetime.SyncRoot)
                    {
                        userStateToLifetime.Remove(asyncOp.UserSuppliedState);
                    }
                }

                // Package the results of the operation in a
                // LoadAdditiveTypesCompletedEventArgs.
                LoadChildControlCompletedEventArgs e =
                    new LoadChildControlCompletedEventArgs(childControl, exception, canceled, asyncOp.UserSuppliedState);

                // End the task. The asyncOp object is responsible
                // for marshaling the call.
                asyncOp.PostOperationCompleted(OnLoadChildControlWorkerCompleted, e);

                // Note that after the call to OperationCompleted,
                // asyncOp is no longer usable, and any attempt to use it
                // will cause an exception to be thrown.
            }

            public void LoadChildControlWorkerCompleted(object operationState)
            {
                LoadChildControlCompletedEventArgs e = operationState as LoadChildControlCompletedEventArgs;

                OnLoadChildControlCompleted(e);
            }

            private void OnLoadChildControlCompleted(LoadChildControlCompletedEventArgs e)
            {
                if (LoadChildControlCompleted != null)
                {
                    LoadChildControlCompleted(this, e);
                }
            }

            private bool TaskCanceled(object taskId)
            {
                return (userStateToLifetime[taskId] == null);
            }

            private void button2_Click(object sender, EventArgs e)
            {
                this.clientPanel.Controls.Clear();

                System.Diagnostics.Debug.WriteLine(string.Format("Button click executing on thread {0}, IsBackground: {1}", System.Threading.Thread.CurrentThread.ManagedThreadId, System.Threading.Thread.CurrentThread.IsBackground));


                LoadChildControlAsync(taskId);
            }

            private class LoadChildControlCompletedEventArgs : AsyncCompletedEventArgs
            {
                private TextBox result;

                public LoadChildControlCompletedEventArgs(TextBox childControl,
                    Exception e, bool canceled, object state)
                    : base(e, canceled, state)
                {
                    this.result = childControl;
                }

                public TextBox Result
                {
                    get
                    {
                        return result;
                    }
                }
            }

            private void button3_Click(object sender, EventArgs e)
            {
                if (childTextBox != null)
                {
                    System.Windows.Forms.MessageBox.Show(childTextBox.Text);

                }
                else
                {
                    System.Windows.Forms.MessageBox.Show("TextBox not created yet");
                }
            }

            private void button1_Click(object sender, EventArgs e)
            {
                this.Close();
            }

            private void button4_Click(object sender, EventArgs e)
            {
                if (childTextBox != null)
                {
                    childTextBox.Text = this.textBox1.Text;
                }
                else
                {
                    System.Windows.Forms.MessageBox.Show("TextBox not created yet");
                }
            }
        }



  • @lpope187 said:

    @Ragnax said:

    I don't think so. There's this little problem with WinForms: controls are 'owned' by the thread that created them, which means no other thread can safely update them. Therefore you must load your controls in the main (STA)thread of your WinForms application. (There are some ways around this limitation, but they involve manually toying with windows message loops etc. and are simply not practical.)

    You're probably thinking about OS level threads, but in terms of managed threads it is possible.  The code below illustrates my point (I just hacked it together in a few minutes based on what I do normally in my data access components).  Looking at Spy++, both the form and the created textbox are owned by the same OS thread.  I certainly wouldn't do this in a real app.  What I would do is implement an Initialize method on the child control, instantiate the control on the main UI thread, and execute the Initialize method on a background thread. 

    I'm not thinking of OS level threads. I'm thinking of managed threads. You should take a look at MSDN under System.Windows.Forms.Control.InvokeRequired for more information, but I'll quote the relevant passages for you here:

    "Controls in Windows Forms are bound to a specific thread and are not thread safe. Therefore, if you are calling a control's method from a different thread, you must use one of the control's invoke methods to marshal the call to the proper thread."
    "In addition to the InvokeRequired property, there are four methods on a control that are thread safe to call: Invoke, BeginInvoke, EndInvoke and CreateGraphics if the handle for the control has already been created. Calling CreateGraphics before the control's handle has been created on a background thread can cause illegal cross thread calls. For all other method calls, you should use one of these invoke methods when calling from a different thread."

    Continuing on, your example code does not match your explanation: It is not instancing the controls in the UI thread, it's instacing them within the worker thread. CreateChildControl() is called from LoadChildControlWorker() which, inside LoadChildControlAsync(), is pointed to by workerDelegate and is eventually invoked on a new thread with a BeginInvoke call.

    That's funny because, again from MSDN:

    "In the case where the control's handle has not yet been created, you should not simply call properties, methods, or events on the control. This might cause the control's handle to be created on the background thread, isolating the control on a thread without a message pump and making the application unstable."

    And certainly a control's handle has not yet been created before you entered the background thread when you're actually creating it on said background thread and are setting properties on it before attaching it to a child control on a form (which is when the handle is created, iirc). It's a miracle this whole thing isn't exploding in your face, because you've been dodging bullets with regards to violating thread safety.

    Also, your worker thread implementation looks like a reinvented square wheel. Take a look at the System.ComponentModel.BackgroundWorker class for a standardized solution.



  • I have a project that does exactly what you want.

    It's true, when updating anything on a Control, you have to Invoke() to the UI thread like so:

    protected AutoResetEvent m_Update = new AutoResetEvent(true);

    //Called from a background thread
    public virtual void ExpandRootItems()
    {
     m_Update.WaitOne();
     try
     {
      if (m_TreeView.InvokeRequired)
      {
       MethodInvoker d = new MethodInvoker(ExpandRootItemsThreadSafe);
       m_TreeView.Invoke(d);
      }
      else
       ExpandRootItemsThreadSafe();
     }
     finally
     {
      m_Update.Set();
     }
    }

    private void ExpandRootItemsThreadSafe()
    {
     m_TreeView.BeginUpdate();
     foreach (TreeNode node in m_TreeView.Nodes)
     {
      node.Expand();
     }
     m_TreeView.EndUpdate();
    }

     


    As for the dynamic Control updating, implement everything as one main control Like a UserControl and then Add and remove at your will. You should call Dispose() on it after removing it from the parent control. Also, Event Handlers should be removed in the Dispose calls, otherwise thing accumulate in the form.

    To Add one:
     public delegate void AddControlDelegate(Control i_Control);

    [...]

     

     System.Windows.Forms.Control control = mgr.GetControl();
     if(control!= null)
     {
      if(m_Panel.InvokeRequired)
      {
       AddControlDelegate addDelegate = new AddControlDelegate(m_Panel.Controls.Add);
       m_Panel.Invoke(addDelegate, new object[]{control});
      }
      else
       m_Panel.Controls.Add(control);
     }

     

    To remove one:


     System.Windows.Forms.Control control = mgr.GetControl();

     m_Panel.Controls.Remove(control);
     control.Dispose();


    also, if you are going to launch a thread, the constructor probably isn't the best place. A better place is in the Load event for a form or control. The Load event is fired after the control is created.



  • @talaxor said:

    Hi all. I don't really post much here, so I know I'm prolly gonna bungle this but here goes. I have a program I'm writing in c# that I want to display different "screens" when buttons in a left hand navigation type area are clicked. (Think something similar to frameset navigation on web pages, or more like the UI to SpyBot) What's a good non-wtf way to do this? The only thing close to what I want would be to abuse the tab control, and I don't really want to use it because I don't want tabs along the top, I want buttons close to the left hand side. (BTW, this is being built using VS 2008, and is not a WPF application). I've done coding for years, but mostly console, services, or simple winforms applications, so I'm a little lost on the right way to do this. Thanks in advance for your suggestions.

    I can't guarantee this, but one approach I would try would be to create a groupbox for each 'screen' and put the needed controls in it. Set all of the Left positions of the groupboxes to -20000 except for the one selected by the buttons. That should hide all but the one you want and let you have any button design you want.


  • @Puzzled said:

    I can't guarantee this, but one approach I would try would be to create a groupbox for each 'screen' and put the needed controls in it. Set all of the Left positions of the groupboxes to -20000 except for the one selected by the buttons. That should hide all but the one you want and let you have any button design you want.

    That would leave any control still present in the tab order, just not reachable with the mouse. This kind of solution is really the most bottom of the barrel, imho.



  • @Ragnax said:

    That would leave any control still present in the tab order, just not reachable with the mouse. This kind of solution is really the most bottom of the barrel, imho.
    It was the method used in VB3. Are you sure it won't work? 



  • @Puzzled said:

    It was the method used in VB3. Are you sure it won't work?

    That depends on your definition of 'work'.

    'Work' as in: "It does the job as its supposed to do, without side effects."

    Or 'work' as in: "Does it work? Ship it!".



  • Well, first off, I'd like to thank everyone for the wonderful amount of input I've recieved so far. This is really what community is all about.

    For right now, I'm using custom controls that I add / remove from the program. You do see the controls as they load, but for version 1.0, I can live with that.

    Someone made a comment about creating the controls on a seperate thread and then add them in... That would be nice but VS 2008 complains bitterly when you try to mess with the UI on a thread it wasn't created on. I'm sure there are ways to do it, but it got way more complicated. 

    As for an MDI form... I haven't used them much, but I remember they were a pain in C++. I'm self taught so maybe I just haven't been exposed to them the right way.  

    Also, another reason  I like using the controls approach is that is makes it much easier to re-use the same screens in more than one app. In my situation, I have the normal "Client" software, a special "Debug" version with extra info for me, and a "Verifier" program that's somewhere between the two.

    The control's state is reset when it is reloaded, so losing state is not an issue.

     I can't really say what the program is right now, because it's part of a business I am co-founding, but I will say this: I think it's gonna be pretty cool. When we get to a beta stage, I'll post here with some info and screenshots if you guys are interested. 



  • @talaxor said:

    That would be nice but VS 2008 complains bitterly when you try to mess with the UI on a thread it wasn't created on.
     

    That isn't VS, that is a basic programming concept...

    @talaxor said:

    I'm sure there are ways to do it, but it got way more complicated. 

    Not really, just need to use invoke()... But I guess a few more lines of code would kill your application?



  • @MasterPlanSoftware said:

    Not really, just need to use invoke()... But I guess a few more lines of code would kill your application?

    No, I tried using Invoke, but it caused problems with other parts of the code....

    Basically the control gets poked and prodded by other parts of the code and creating it on a seperate thread was more trouble than it was worth. 



  • @talaxor said:

    Basically the control gets poked and prodded by other parts of the code and creating it on a seperate thread was more trouble than it was worth. 
     

    Right, but my point is that anytime a control needs to be accessed from a thread other than the one it was created on it needs to be handled through invoke(). You cannot just access any control on any thread at anytime. 



  • Yes, flicker is a big problem. There are some things you can do to minimize flicker.

    - Try creating sections invisible and then showing them,

    - try reducing the use of docking and use Anchoring instead.

    - Keep controls to a minimum as there is a big load on the UI as the number of .NET controls increases. I've had to eliminate nested panels and replace images and text with stuff drawn directly on the UI in Paint events.

     Using a background thread doesn't help unless you have work to do and want sections to load data asynchronously. There is a huge amout of overhead in calling Invoke() over calling a method directly so your code could be slower if you used a background thread strictly to build UI.



  • @Kelly said:

    Yes, flicker is a big problem. There are some things you can do to minimize flicker.

    - Try creating sections invisible and then showing them,

    - try reducing the use of docking and use Anchoring instead.

    - Keep controls to a minimum as there is a big load on the UI as the number of .NET controls increases. I've had to eliminate nested panels and replace images and text with stuff drawn directly on the UI in Paint events.

    Using a background thread doesn't help unless you have work to do and want sections to load data asynchronously. There is a huge amout of overhead in calling Invoke() over calling a method directly so your code could be slower if you used a background thread strictly to build UI.

    Iirc, all the layout math is still done even if you create sections invisible. Also docking or anchoring doesn't, or atleast; shouldn't, really matter all that much as docking is just anchoring once at the extreme points. Eliminating a lot of controls and manually mucking around with the paint events might not be the most desirable approach either. It will absolutely kill maintainability.

    The standard solution is to enable double buffering and make good use of calls to SuspendLayout() and ResumeLayout() to not perform needless layout resizing and repainting while adding/removing or moving/resizing sets of controls.

    Indeed, I wouldn't recommend building UI in a background thread either. Not only because the overhead associated with Invoke might end up making it more expensive, but also because it is plain pointless. Why build a section of UI asynchronously, if you have to wait until said building is complete to do anything with it anyway? Generally speaking, it's not like you still need to do something with the old section of UI either: you accessed a new section to do something there instead.


Log in to reply