2009-04-24 16 views
7

Quindi abbiamo un progetto # WinForms C con un modulo che contiene un bazillion UserControl s. Ogni UserControl espone naturalmente tutti i metodi, proprietà, ecc. UserControl oltre ai propri membri specifici.UserControl come interfaccia, ma visibile in Progettazione

Ho pensato che un modo per ridurre la complessità di gestione di questi UserControl s è di accedervi tramite un'interfaccia. Così, invece di drag-and-drop per mettere il UserControl sulla forma, qualcosa di simile nel costruttore:

public class MyGiantForm 
{ 
    ICustomerName cName; 

    public MyForm() 
    { 
     InitializeComponent(); 

     var uc = new SomeCustomerNameUserControl(); 
     this.Controls.Add(uc); 
     cName = uc; 
    } 
} 

SomeCustomerNameUserControl implementa ICustomerName, naturalmente, e ICustomerName contiene le proprietà specifiche che mi interessa (ad esempio, FirstName e LastName). In questo modo posso riferimento alla UserControl attraverso l'elemento cName e, invece di essere travolti da tutti i membri UserControl, ottengo solo quelli in ICustomerName.

Tutto bene, ma il problema è che se lo faccio in questo modo, non riesco a vedere SomeCustomerNameUserControl nella finestra di progettazione. Qualcuno sa che posso farlo ma vedere ancora lo UserControl sulla superficie di progettazione del modulo?

MODIFICA: Un modo per eseguire questa operazione, che non è eccessivamente complicato, consiste nel posizionare i controlli su un modulo di base. Per impostazione predefinita (in C#) i membri del controllo sono privati. Quindi creo una proprietà per ogni controllo esponendolo attraverso l'interfaccia.

Tuttavia, sarei interessato in qualche altro modo per fare questo, anche se è più complesso. Sembra che ci sia un modo per farlo con IDesignerHost, ma non riesco a trovare alcun esempio applicabile.

+0

Potrebbe per favore darci un esempio del risultato atteso? Come sarà il tuo codice una volta trovata una soluzione? – DonkeyMaster

+0

Il risultato sarà che UserControl personalizzato è visibile nel modulo e nel codice è presente un membro di tipo ICustomerName, ma nel codice non è * un * un membro del tipo SomeCustomerNameUserControl. Se questo non è abbastanza chiaro, fammi sapere dove elaborare. –

+0

Ok, dopo aver letto la tua spiegazione e il resto delle risposte, capisco la tua domanda. Ehi, è come Jeopardy! – DonkeyMaster

risposta

3

Dopo aver aggiunto l'UserControl utilizzando il progettista, è possibile impostare GenerateMember su false nella finestra Proprietà per sopprimere la generazione di un membro.

È quindi possibile utilizzare qualche altra tecnica nel costruttore per assegnare il vostro riferimento cName, ad es .:

foreach(Control control in this.Controls) 
{ 
    cName = control as ICustomerName; 
    if (cName != null) break; 
} 

cName sarebbe allora l'unico riferimento alla UserControl.

+0

Questa è la prima * buona * possibilità che ho visto. GenerateMember non mi è nemmeno venuto in mente. A mio parere, un modo migliore per farlo rispetto al modo in cui descrivi è quello di utilizzare: cName = this.Controls ["controlName"] come ICustomerName; Ha la chiara debolezza di rottura se il controllo viene rinominato, tuttavia, quindi non è ancora una soluzione perfetta. La ragione per cui non mi piace la soluzione di casting che hai citato è che potrei avere più di uno stesso UserControl cliente sullo stesso modulo. In effetti, nel progetto a cui sto lavorando, conosco almeno un caso definito del genere. –

+0

Sì, questo è stato pensato per essere solo un esempio di una tecnica per ottenere il riferimento desiderato dalla raccolta Controls. Se hai più di un'istanza di un controllo, hai ragione che dovrai utilizzare alcune altre caratteristiche del controllo, ad es. il nome, per distinguerli. La soluzione "migliore" dipenderà dalla tua app. – Joe

+0

mi viene in mente, guardando questo molto più tardi, che potrei usare il tuo metodo senza dover inserire il nome del controllo tra virgolette e senza confusione tra più controlli che implementano la stessa interfaccia. Un modo per farlo sarebbe quello di creare la mia interfaccia ('IStuff', dire) e poi avere i" controlli di interfaccia "sul modulo ogni implementare un'interfaccia (senza membri aggiuntivi) che eredita da' IStuff'. Quindi potrei usare il codice sopra con queste specifiche interfacce per accedere ai vari controlli. –

6

Se SomeCustomerNameUserControl è definito in questo modo:

class SomeCustomerNameUserControl : UserControl, ICustomerName 
{ 
} 

È ancora possibile eliminare questo controllo nella finestra di progettazione (che crea someCustomerNameUserControl1) e fare questo ogni volta che è necessario:

ICustomerName cName = someCustomerNameUserControl1; 

Forse' Mi manca qualcosa, ma penso che sia così semplice.

+1

A meno che non ci sia un dettaglio che non abbiamo penso che sia così semplice ... Questo è quello che stavo per dire. –

+0

Questo non risponde alla domanda. Voglio che ICustomerName sia l'opzione * only * per accedere alla variabile UserControl. L'idea è che uno sviluppatore non debba "solo ricordare" di lanciarlo. Ho modificato la domanda per indicare un modo per farlo, ma sto cercando altri modi che non richiedono un modulo di base. –

0

si potrebbe anche fare come diceva Bob, ma assegnare tutte le variabili membro nel costruttore, allora si hanno in un unico luogo.

+0

Ciò lascia comunque al programmatore l'opzione di utilizzare distrattamente il riferimento UserControl al posto del riferimento all'interfaccia. Voglio che il riferimento all'interfaccia sia l'unico membro che fa riferimento a UserControl, se possibile. –

6

C'è un modo per ottenere ciò che si vuole - nascondere i membri che non si vuole vedere - ma rendono applica automaticamente, senza la necessità di una cooperazione degli altri in termini di loro utilizzando un'interfaccia personalizzata. Puoi farlo reintroducendo tutti i membri che non vuoi vedere e taggandoli con gli attributi.

Questo è ciò che fa Windows Form quando, ad esempio, una proprietà di classe base non significa nulla per un determinato discendente. Ad esempio, Control ha una proprietà Text, ma una proprietà Text non ha senso su, ad esempio, un TabControl. Quindi TabControl sovrascrive la proprietà Text e aggiunge attributi al suo override dicendo "A proposito, non mostrare la mia proprietà Text nella Griglia Proprietà o in Intellisense." La proprietà esiste ancora, ma dal momento che non la vedi mai, non ti ostacola.

Se si aggiunge un [EditorBrowsable (EditorBrowsableState.Never)] attributo a un membro (proprietà o un metodo), quindi Intellisense non sarà più dimostrare che membro nelle sue liste completamento del codice. Se sto capendo correttamente la tua domanda, questa è la grande cosa che stai cercando di ottenere: rendere difficile per il codice dell'applicazione utilizzare il membro per sbaglio.

Per le proprietà, probabilmente anche da aggiungere [sfogliabile (false)] per nascondere la proprietà dalla griglia delle proprietà, e [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] per evitare che il progettista di scrivere il valore della proprietà al file .designer.cs.

Ciò renderà molto difficile l'utilizzo accidentale del metodo/proprietà. Non sono ancora una garanzia, però. Se hai bisogno di una garanzia, inserisci anche un attributo [Obsoleto] e costruisci con "Considera gli avvertimenti come errori" - quindi ti prendi cura di te.

Se il membro di base è virtuale, è probabile che si desideri sovrascriverlo e fare in modo che l'override faccia semplicemente call base. Non lanciare un'eccezione, poiché il membro sottoposto a override verrà probabilmente chiamato dalla classe base durante il normale corso degli eventi. D'altra parte, se il membro base non è virtuale, allora si vuole usare "nuovo" invece di "sovrascrivere", e si può decidere se l'implementazione deve chiamare base, o semplicemente lanciare un'eccezione - nessuno dovrebbe usare il tuo membro reintrodotto comunque, quindi non dovrebbe importare.

public class Widget : UserControl 
{ 
    // The Text property is virtual in the base Control class. 
    // Override and call base. 
    [EditorBrowsable(EditorBrowsableState.Never)] 
    [Browsable(false)] 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
    [Obsolete("The Text property does not apply to the Widget class.")] 
    public override string Text 
    { 
     get { return base.Text; } 
     set { base.Text = value; } 
    } 

    // The CanFocus property is non-virtual in the base Control class. 
    // Reintroduce with new, and throw if anyone dares to call it. 
    [EditorBrowsable(EditorBrowsableState.Never)] 
    [Browsable(false)] 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
    [Obsolete("The CanFocus property does not apply to the Widget class.")] 
    public new bool CanFocus 
    { 
     get { throw new NotSupportedException(); } 
    } 

    // The Hide method is non-virtual in the base Control class. 
    // Note that Browsable and DesignerSerializationVisibility are 
    // not needed for methods, only properties. 
    [EditorBrowsable(EditorBrowsableState.Never)] 
    [Obsolete("The Hide method does not apply to the Widget class.")] 
    public new void Hide() 
    { 
     throw new NotSupportedException(); 
    } 
} 

Sì, questo è un bel po 'di lavoro, ma basta farlo una volta ... per ogni membro, per classe ... umm, sì. Ma se quei membri della classe base non si applicano davvero alla tua classe, e averli lì causerà confusione, allora potrebbe valere la pena di andare allo sforzo.

+1

In realtà, sembra che dovrei farlo una sola volta per qualsiasi UserControl e quindi inserire questa nuova classe di occultamento dei membri nell'albero di ereditarietà tra UserControl e i miei vari controlli derivati. Se così fosse, non sarebbe una quantità insopportabile di lavoro. D'altra parte, qualsiasi quantità di lavoro è troppo se non si ha davvero bisogno di farlo. –

+0

Questa è una buona informazione. – hmcclungiii

0

Sembra quasi che si desideri implementare un modello di mediazione. Invece di dover gestire direttamente ciascuno dei miliardi di UserControl, interagiresti con loro attraverso il mediatore. Ogni mediatore definirebbe l'interfaccia sottile che si desidera vedere da ciascun controllo. Ciò ridurrebbe la complessità complessiva rendendo il tuo progetto più esplicito e conciso. Ad esempio, non avresti bisogno delle 20 proprietà e 50 metodi disponibili su uno dei tuoi controlli. Invece dovresti trattare con il mediatore per quel controllo che definisce le 2 proprietà e 5 metodi che ti interessano veramente. Tutto sarebbe ancora presente nel designer, ma altre parti della tua app non interagirebbero con quei controlli: interagirebbero con i mediatori.

Uno dei grandi vantaggi di questo approccio è che semplifica notevolmente la manutenzione. Se si decide che MyCrappyUserControl deve essere riscritto perché l'implementazione è errata, è sufficiente aggiornare la classe mediatore per quel controllo. Tutte le altre classi che interagiscono con il controllo lo fanno attraverso il mediatore e rimarrebbero invariate.

Alla fine si tratta di disciplina: tu e il tuo team dovete essere abbastanza disciplinati da usare i mediatori/interfacce/qualsiasi cosa invece di colpire direttamente i controlli. Installa una revisione del codice spalla da un programmatore capo se la tua squadra si trova nella fascia bassa della scala della disciplina.

+1

Sto cercando una soluzione che non richieda molta disciplina, perché so cosa succede quando una soluzione richiede disciplina dal programmatore medio. Abbiamo troppe cose da pensare e ricordare già; una soluzione "devi solo ricordare" non è una soluzione. –

+0

Questo diventerebbe parte del tuo design standard per il codice prodotto dal tuo negozio. Se stai trasformando i programmatori nel tuo codice senza prima averli informati sul tuo design, questo problema di UserControl che espone troppe proprietà sarà l'ultimo dei tuoi dubbi. –

+0

Amico, non sono il tizio che comanda. Non posso imporre standard di codifica sugli altri. Sto cercando di trovare un modo sottile ma efficace per migliorare il design senza costringere le persone a fare le cose in un certo modo. Per guidare con l'esempio piuttosto che con l'editto, forse. –

1

È possibile scrivere un metodo di estensione che consente di restituire eventuali controlli nel modulo che implementa un'interfaccia.

public static class FormExtensions 
{ 
    public static IDictionary<string, T> GetControlsOf<T>(this Form form) 
      where T: class 
    { 
     var result = new Dictionary<string, T>(); 
     foreach (var control in form.Controls) 
     { 
      if ((control as T) != null) 
       result.Add((control as T).Tag, control as T); 
     } 
     return result; 
    } 
} 

Poi, nel modulo si potrebbe chiamare dovunque si desidera da:

this.GetControlsOf<ICustomerName>()["NameOfControlHere"]; 

Nel caso in cui restituisce più di un controllo utente che si avrebbe bisogno di gestire che in qualche modo, magari con l'aggiunta proprietà tag all'interfaccia per tenere traccia di ogni univocamente controllo utente o qualcosa, in questo modo

public partial class UserControl1 : UserControl, ICustomerName 
{ 
    public string Tag { get { return this.Name; } } 
} 

È quindi possibile trascinare e rilasciare i controlli utente nel form dal progettista. Tag restituirà sempre il nome del tuo controllo, che ti permetterà di accedere direttamente al controllo attraverso l'interfaccia di IDictionary. Gli sviluppatori potrebbero inserire qualsiasi identificatore univoco che desiderano nel nome del controllo e portarlo all'interfaccia.

Inoltre, è opportuno notare che questo approccio consentirà ANCHE di utilizzarlo su TUTTI i moduli nella soluzione.

L'unica altra cosa che dovresti fare è impostare il tuo GenerateMember su false.

+0

Molto interessante. È simile alla soluzione di Joe, ma un po 'più elegante. Ha la stessa debolezza del suo (più controlli che hanno la stessa interfaccia), ma penso che il passaggio nel nome UserControl lo allevierebbe, anche se è ancora un problema se il nome del controllo è cambiato. –

+0

@Kyralessa in realtà non ci sono problemi con più controlli, puoi invece usare un dizionario e indicizzarlo in base al nome del controllo.Quindi puoi rimuovere il tag extra tutti insieme. Modificherò il mio campione per illustrare. – Joseph

+0

Se si desidera che Tag restituisca la proprietà Name, perché non usare semplicemente Name? –

4

'Voglio che ICustomerName sia l'solo l'opzione per accedere alla variabile UserControl. L'idea è che uno sviluppatore non debba "solo ricordare" di lanciarlo.

Il problema che si verifica è che esistono due usi completamente divergenti per il modulo e i controlli che esso ospita. Non c'è alcun trucco incorporato in Visual Studio o winforms che risolva questo per te. Potrebbe essere possibile, ma esiste un modo molto più pulito e orientato agli oggetti per separare i due metodi di interazione con i controlli.

Se si desidera nascondere il fatto che questi oggetti ereditano da UserControl e si desidera trattarli come IDoSomeThingYouShouldDealWith, è necessario separare la logica che si occupa dei problemi di presentazione (progettazione + logica dell'interfaccia utente) dalla logica aziendale.

La classe del modulo, dovrebbe trattare correttamente i controlli come UserControls, docking, ancoraggio ecc. Ecc., Niente di speciale qui. Dovresti mettere tutta la logica che deve occuparsi di ICustomerName.FirstName = etc in una classe completamente separata. Questa classe non si cura o non conosce i font e il layout, ma sa solo che c'è un'altra istanza che può presentare un nome cliente; o un DateTime come un controllo di data di nascita che sceglie correttamente, ecc.

Questo è un esempio veramente zoppo, ma devo andare adesso. Si dovrebbe essere in grado di ottenere il idea covered here in more detail:

public interface ICustomerName 
    { 
     void ShowName(string theName); 
    } 

    public partial class Form1 : Form, ICustomerName 
    { 
     public Form1() 
     { 
      InitializeComponent(); 
     } 

     #region ICustomerName Members 

     public void ShowName(string theName) 
     { 
      //Gets all controls that show customer names and sets the Text propert 
      //totheName 
     } 

     #endregion 
    } 

    //developers program logic into this class 
    public class Form1Controller 
    { 
     public Form1Controller(ICustomerName theForm) //only sees ICustomerName methods 
     { 
      //Look, i can't even see the Form object from here 
      theForm.ShowName("Amazing Name"); 
     } 
    } 
+0

Noel, so tutto su Model-View-Controller, Model-View-Presenter e il resto. Ma non sono libero di cambiare l'app in modo così radicale in qualunque momento. Sto cercando qualcosa che funzioni con l'app così com'è. Ci sono già diverse possibilità elencate qui che possono fare ciò che voglio, a vari livelli di completezza e facilità d'uso. Se quello che sto cercando qui fosse * facile *, avrei già capito come farlo, e non mi sarei preso la briga di postare una domanda su StackOverflow. –

+0

Ciao Kyralessa, grazie per il feedback. Non sei sicuro del motivo per cui stai cercando una soluzione più complessa, che sono sicuro che acconsentire, sono hack o soluzioni alternative in misura maggiore o minore. Se sei in grado (e accetto che potresti non essere libero), proverei a trovare un modo per rifattorizzare un pattern dell'interfaccia utente, anche se non puoi farlo subito. Ad esempio, è possibile creare il controller nel costruttore della vista, che non è così interessante da ciò che suggerisce Joe. Puoi anche spostarti tra funzionalità e logica nel controller, bit per bit, in modo da evitare il big bang. –

+0

È un progetto di lavoro, non un progetto personale, e le persone non sono sempre libere nei progetti di lavoro per rifattorizzare i contenuti del nostro cuore. E non sono specificamente alla ricerca di una soluzione complessa. Sto cercando una buona soluzione, ed è OK se è complessa purché sia ​​buona. Sto anche cercando una soluzione a un problema molto specifico. La mia domanda non è "Come posso risolvere questa app?" La mia domanda è "Come posso accedere a UserControls solo tramite un'interfaccia, ma sono ancora presenti in Designer?" –

-3

Si supponga che MyUserControl viene definito in questo modo:

class MyUserControl : UserControl, IMyInterface 
{ 
    // ... 
} 

Poi nel modulo, si dovrebbe avere qualcosa di simile:

public class MyForm : Form 
{ 
    IMyInterface cName; 

    public MyForm() 
    { 
     InitializeComponent(); 

     cName = new MyUserControl(); 
     Controls.Add((UserControl)cName); 
    } 
} 

In questo modo, cName è l'unico modo per accedere a questa istanza del nostro controllo degli utenti.

+1

Tuttavia, questo non consente di visualizzare MyUserControl nella finestra di progettazione. –

+1

Se si inserisce questo codice nel file di progettazione, sarà possibile visualizzare il controllo. Il problema è che le tue modifiche andranno perse se rigenerate. Questo può essere risolto utilizzando una macro. – CSharper

+0

Questa è esattamente la soluzione che ho proposto nella mia domanda iniziale. In quanto tale, non è affatto d'aiuto. –

Problemi correlati