je crois que le concepteur VS qu'il fait en obtenant une instance du concepteur du contrôle (voir le Designer
attribute), et, si le concepteur est un ComponentDesigner
, obtenir la propriété AssociatedComponents
.
EDIT:
Bon, je suppose que c'est un peu vague. Un avertissement, cependant: ce qui suit est un peu compliqué, et pourrait ne pas valoir l'effort.
Une note sur la nomenclature:
Ci-dessous, je me référerai à la fois le concepteur de Visual Studio, qui est le nom utilisé pour faire référence à la fonctionnalité de Visual Studio par lequel la mise en page et le contenu des formulaires et des contrôles sont modifiés visuellement - et aux classes de concepteur - qui seront expliquées ci-dessous. Pour éviter toute confusion à laquelle je fais référence à un moment donné, je ferai toujours référence à la fonctionnalité de concepteur dans Visual Studio en tant que "concepteur", et je ferai toujours référence à une classe de concepteur comme un "IDesigner", qui est le interface chacun doit mettre en œuvre. Lorsque le concepteur Visual Studio charge un composant (généralement un contrôle, mais également Timer
et autres), il recherche un attribut personnalisé sur la classe de type DesignerAttribute
. (Ceux qui ne sont pas familiers avec les attributs peuvent vouloir read up on them avant de continuer.)
Cet attribut, s'il est présent, fournit le nom d'une classe, un IDesigner, que le concepteur peut utiliser pour s'interfacer avec le composant. En effet, cette classe contrôle certains aspects du concepteur et du comportement au moment du design du composant. Il y a en effet beaucoup de choses que vous pouvez faire avec un IDesigner, mais en ce moment nous ne sommes intéressés que par une chose.
La plupart des contrôles qui utilisent un IDesigner personnalisé en utilisent un qui dérive de ControlDesigner
, lui-même dépendant de ComponentDesigner
. La classe ComponentDesigner
possède une propriété virtuelle publique appelée AssociatedComponents
, qui est censée être remplacée dans les classes dérivées pour renvoyer une collection de références à tous les composants «enfants» de celle-ci.Pour être plus précis, le contrôle ToolStrip
(et par héritage, le contrôle MenuStrip
) a un DesignerAttribute
qui fait référence à une classe appelée ToolStripDesigner
. Il semble un peu comme:
/*
* note that in C#, I can refer to the "DesignerAttribute" class within the [ brackets ]
* by simply "Designer". The compiler adds the "Attribute" to the end for us (assuming
* there's no attribute class named simply "Designer").
*/
[Designer("System.Windows.Forms.Design.ToolStripDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), ...(other attributes)]
public class ToolStrip : ScrollableControl, IArrangedElement, ...(other interfaces){
...
}
La classe ToolStripDesigner
n'est pas publique. C'est interne à System.Design.dll. Mais comme il est spécifié ici par son nom complet, le concepteur VS peut utiliser Activator.CreateInstance
pour en créer une instance de toute façon.
Cette classe ToolStripDesigner
, car elle hérite [indirectement] de ComponentDesigner
a une propriété AssociatedComponents
. Lorsque vous l'appelez, vous obtenez un nouveau ArrayList
qui contient des références à tous les éléments qui ont été ajoutés au ToolStrip
.
Alors qu'est-ce que votre code devrait ressembler à faire la même chose? Plutôt alambiquée, mais je pense avoir un exemple de travail:
/*
* Some controls will require that we set their "Site" property before
* we associate a IDesigner with them. This "site" is used by the
* IDesigner to get services from the designer. Because we're not
* implementing a real designer, we'll create a dummy site that
* provides bare minimum services and which relies on the framework
* for as much of its functionality as possible.
*/
class DummySite : ISite, IDisposable{
DesignSurface designSurface;
IComponent component;
string name;
public IComponent Component {get{return component;}}
public IContainer Container {get{return designSurface.ComponentContainer;}}
public bool DesignMode{get{return false;}}
public string Name {get{return name;}set{name = value;}}
public DummySite(IComponent component){
this.component = component;
designSurface = new DesignSurface();
}
~DummySite(){Dispose(false);}
protected virtual void Dispose(bool isDisposing){
if(isDisposing)
designSurface.Dispose();
}
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
public object GetService(Type serviceType){return designSurface.GetService(serviceType);}
}
static void GetComponents(IComponent component, int level, Action<IComponent, int> action){
action(component, level);
bool visible, enabled;
Control control = component as Control;
if(control != null){
/*
* Attaching the IDesigner sets the Visible and Enabled properties to true.
* This is useful when you're designing your form in Visual Studio, but at
* runtime, we'd rather the controls maintain their state, so we'll save the
* values of these properties and restore them after we detach the IDesigner.
*/
visible = control.Visible;
enabled = control.Enabled;
foreach(Control child in control.Controls)
GetComponents(child, level + 1, action);
}else visible = enabled = false;
/*
* The TypeDescriptor class has a handy static method that gets
* the DesignerAttribute of the type of the component we pass it
* and creates an instance of the IDesigner class for us. This
* saves us a lot of trouble.
*/
ComponentDesigner des = TypeDescriptor.CreateDesigner(component, typeof(IDesigner)) as ComponentDesigner;
if(des != null)
try{
DummySite site;
if(component.Site == null)
component.Site = site = new DummySite(component);
else site = null;
try{
des.Initialize(component);
foreach(IComponent child in des.AssociatedComponents)
GetComponents(child, level + 1, action);
}finally{
if(site != null){
component.Site = null;
site.Dispose();
}
}
}finally{des.Dispose();}
if(control != null){
control.Visible = visible;
control.Enabled = enabled;
}
}
/* We'll use this in the ListComponents call */
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
const int WM_SETREDRAW = 11;
void ListComponents(){
/*
* Invisible controls and disabled controls will be temporarily shown and enabled
* during the GetComponents call (see the comment within that call), so to keep
* them from showing up and then disappearing again (or appearing to temporarily
* change enabled state), we'll disable redrawing of our window and re-enable it
* afterwards.
*/
SendMessage(Handle, WM_SETREDRAW, 0, 0);
GetComponents(this, 0,
/* You'll want to do something more useful here */
(component, level)=>System.Diagnostics.Debug.WriteLine(new string('\t', level) + component));
SendMessage(Handle, WM_SETREDRAW, 1, 0);
}
Pouvez-vous expliquer un peu plus comment faire cela? Je n'ai jamais utilisé ces attributs auparavant ... – Miles
Bon, j'ai essayé de l'expliquer complètement et de le démontrer (voir le post édité). Désolé, cela a pris tellement de temps. La vraie vie a un peu dérangé. Je déteste ça. –