I've used control arrays for this. Create a single instance of each type of control you're going to need--textbox, label, dropdown list, etc. Set the Index property of each control to 0, (which turns each control into the base control for a control array). Now set the other properties to what makes sense for each type of control in your application. These properties will be applied to each new control as it's created. Be sure that you set the Visible property to False.
Then, add controls at runtime with the Load statement. Position them appropriately, set any unique properties, load your data, etc., then when that's all done sweep through each control array by index and set the Visible property on each control to True. This is fast enough that it avoids the flicker, at least for a reasonable amount of controls. I've handled almost 200 controls this way without flicker.
Also, if/when you need to rebuild the form with new data, rather than destroying the controls with Unload, then recreating new ones, just make them all invisible, and re-use as many as you need. It's much, much faster to tweak the properties for each control this way as opposed to creating them from scratch each time.
True, you do need to manage an index for each control array's length to tell you if you can reuse or need to create a new one, but a couple of helper functions can go a long way toward simplifying the necessary tracking. Something like this:
Set newTextBox = GetNextTextBox
where the GetNextTextBox function handles tracking of the total textbox controls available, and which one is "next" so it can decide whether it can reuse an existing one or has to create a new one.
You may also have functions like, for example, ResetTextBoxes, that makes all of the textboxes invisible and resets the "next available" counter.