2013-02-08 15 views
6

Ho implementato una tecnica per gestire più sottoreport in un rapporto rdlc, ma come ho cercato di renderlo generico e ripetibile, ho dovuto invece prendere il modello e modificarlo leggermente per ogni caso.Come può un delegato rispondere a più eventi con una classe generica ed estensibile?

Per esempio, se io definisco un'interfaccia astratta, come tale, ho appena tagliato e incollare da WinForm a WinForm in base alle esigenze:

abstract class ISolutionStrategy 
{ 
    public abstract void AlgorithmInterface(Int64 searchCriteria, SubreportProcessingEventArgs e); 
} 

In primo luogo, voglio essere in grado di portare questo in ogni forma includendo un oggetto has-a. Voglio anche incapsulare i comportamenti di gestione del dispacciamento da parte del delegato e rendere anche i metodi di gestione "generici".

Quindi, i requisiti di progettazione sono:

  • creare un oggetto che può essere incluso in una WinForm di gestire più di elaborazione sottoreport
  • un'istanza di e configurare l'oggetto nella winform
  • Costruire la tabella di invio o switch/case statement nel winform
  • Passa in tutti i metodi per gestire i requisiti specifici del visualizzatore di report di tale winform

L'obiettivo è di creare un oggetto che può essere testato autonomamente e reso robusto, e anche di non dover tagliare e incollare la ruota e fare un po 'di regolazione manuale per ogni nuova winform.

Mi sembra che qualcuno abbia trovato un design migliore rispetto a quello che ho attualmente.

Creare un oggetto che può essere inclusa in una WinForm per gestire l'elaborazione sottoreport multipla

Finora, ho un delegato in caso forme carico locale:

this.reportViewer1.LocalReport.SubreportProcessing += new SubreportProcessingEventHandler(LocalReport_SubreportProcessing); 

che è gestito da un interruttore dichiarazione nel metodo * LocalReport_SubreportProcessing *.

Il corpo del metodo contiene un'istruzione switch:

void LocalReport_SubreportProcessing(object sender, SubreportProcessingEventArgs e) 

     { 
      String commonSubreportKey = _commonSubreportKey; 

      switch (e.ReportPath) 
      { 
       case "rptSubAlternateParts": 
        runSubAlternatePart(e, commonSubreportKey, new GetAlternateParts()); 
        break; 
       case "rptSubGetAssemblies": 
        runSubFailurePart(e, commonSubreportKey, new GetAssemblies()); 
        break; 
       case "rptSubGetAssemblies": 
        runSubGetGetEndItemLRMFailureInfo(e, commonSubreportKey, new GetEndItemLRMFailureInfo()); 
        break; 
       case "rptSubGetAssemblies": 
        runSubGetSubAssemblies(e, commonSubreportKey, new GetSubAssemblies()); 
        break; 
       default: 
        break; 
      } 
parte:

A mio parere, l'interruttore è per lo più leggibile rispetto al alternativa ho considerato. Ho preso in considerazione l'utilizzo di un hash con il nome del report come chiave e la funzione chiama i dati come valore. Tuttavia, I non sapeva davvero come farlo e ho pensato che sarebbe stato più difficile da comprendere per lo .


Dopo di che, viene effettuata una chiamata a una funzione che riorganizza le informazioni passate dalla chiamata di funzione nella istruzione switch:

private static void runSubAlternatePart(SubreportProcessingEventArgs e1, String commonReportKey, GetAlternatePart myAP) 
      { 
       myAP.AlgorithmInterface(commonReportKey, e1); 
      } 

Questa riorganizzazione è sicuramente codice di balbuzie, ma è un intermedio apparentemente necessario al modello di strategia che sto tentando di implementare:

 abstract class IStrategy 
     { 
      public abstract void AlgorithmInterface(String searchParam, SubreportProcessingEventArgs e); 

     } 

Ecco una concreta attuazione della strategia per uno dei report:

class GetAlternatePart : IStrategy 
{ 
private BLL.AlternatePartBLL ds = new BLL.AlternatePartBLL(); 


public override void AlgorithmInterface(String searchParam, SubreportProcessingEventArgs e) 
       { 

        e.DataSources.Clear(); 
        DataTable myDataTable = ds.GetAlternativePart(searchParam); 
        DataSet myDataSet = new DataSet(); 
        myDataSet.Tables.Add(myDataTable); 
       e.DataSources.Add(new ReportDataSource("BLL_AlternatePartBLL", myDataSet.Tables[0])); 
      } 

     } 
    } 

In ogni caso, il mio desiderio è quello di non avere a filo mano la stessa logica più volte tra le relazioni, come ho molti rapporti con più sottoreport .

Vorrei un modo di libreria di utilizzare una classe per creare dinamicamente le parti centrali in cui si verifica la balbuzie, e vorrei passare in una funzione "anonima" che implementa effettivamente la connessione dettagliata del sottoreport alla sua corrispondente fonte di dati.

Per un singolo report con sottoreport, o anche alcuni report una tantum, quello che sto facendo va bene, ma come può essere reso meno manuale, più robusto e più testabile?

Il mio ambiente è Visual Studio 2008 con una destinazione .NET 3.5; sembra esserci una differenza nel modo in cui le classi astratte vengono dichiarate e in che modo vengono compilate.

+0

Per ridurre al minimo il taglia e incolla, e per rimanere il più possibile ASCIUTATI, suggerirei un obiettivo di progettazione di avere meno codice in ogni Winform di quanto specificato. Ad esempio, hai bisogno di una tabella di spedizione in ogni WinForm, o potresti semplicemente nominare i tuoi metodi per abbinare e.ReportPath, e quindi richiamare tramite Reflection? Inoltre, il tuo codice di commutazione è illegale - hai 3 case "rptSubGetAssemblies", il che rende un po 'più difficile capire esattamente cosa stai cercando di fare. Potresti modificare per correggere il codice? –

+0

Un altro pensiero che ho è che la necessità di una leggibilità umana impallidisce rispetto a rendere il codice ASCIUTTO. Se hai bisogno di utilizzare generici e alberi di espressioni nidificati qui per semplificare l'implementazione all'interno di ogni winform, allora questa è la cosa giusta da fare. –

+0

Infine, quando si scrive "Passa in tutti i metodi per gestire i requisiti specifici del visualizzatore di report di questa finestra di esempio", quanto sono diversi questi metodi l'uno dall'altro? Derivano tutti della stessa classe base? Hanno firme di metodo simili tali da poter essere rappresentate da un delegato comune? Chiedo questo b/c non sono venduto per il bisogno di un modello di strategia qui, invece sospetto che questo potrebbe essere implementato più semplicemente con generici e riflessioni (se i tuoi metodi GetXxx sono molto simili tra loro), o alberi di espressione (in il caso più complesso che non sono). –

risposta

4

La soluzione che suggerirei è un refactoring molto semplice per una classe base e riduce il codice necessario per scrivere in ogni WinForm in due modi: 1) l'impostazione del Report utilizzato per quel modulo; e 2) la definizione di come ottenere i dati del report per quel modulo.

Supponendo che ogni WinForm eredita da una classe base denominata ReportForm, il codice per ogni WinForm sarà simile a questa:

public partial class Form1 : ReportForm 
{ 
    public Form1() 
    { 
     // Wire up the report used by the Visual Studio-designed report viewer to the base class 
     base.WinFormReport = reportViewer1.LocalReport; 

     InitializeComponent(); 
    } 

    // The search parameters will be different for every winform, and will presumably 
    // come from some winform UI elements on that form, e.g., parentPartTextBox.Text 
    protected override DataResult GetReportData(SubreportProcessingEventArgs e) 
    { 
     // Return the data result, which contains a data table and a label which will be 
     // passed to the report data source 
     // You could use DataSet in DataResult instead of DataTable if needed 
     switch (e.ReportPath) 
     { 
      case "rptSubAlternateParts": 
       return new DataResult(
        new BLL.AlternatePartBLL().GetAlternativePart(parentPartTextBox.Text) 
        , "BLL_AlternatePartBLL" 
       ); 

      case "rptSubGetAssemblies": 
       return new DataResult(
        new BLL.SubAssemblyBLL().GetSubAssemblies(someOtherTextBox.Text) 
        , "BLL_SubAssemblyBLL" 
       ); 

      default: 
       throw new NotImplementedException(string.Format("Subreport {0} is not implemented", e.ReportPath)); 

     } 
    } 
           . 
           . 
           . 

Il codice di cui sopra fa queste cose:

1) racconta la classe di base (ReportForm) quale rapporto è stato utilizzato nel modulo. Puoi rifattorare anche Report Report a ReportForm, ma il mio approccio ti consente comunque di creare e manipolare il tuo ReportViewer e i suoi Report in Visual Studio. Ma se si sta passando il Report a livello di programmazione e non nel designer, è possibile che si desideri inviare Report dalle classi WinForm derivate alla classe base.

2) Definisce come il report otterrà tutti i dati dei suoi sottoreport. Per questo, abbiamo solo bisogno di restituire un DataTable e un'etichetta, poiché questo è tutto ciò che alla fine sarà richiesto dall'origine dati del report. Il codice che associa DataTable ed etichetta all'origine dati RDLC appartiene alla classe base (ReportForm), in quanto quel codice di binding sarà comune a tutti i tuoi WinForms.

Ora, il codice ReportForm dovrebbe apparire come segue:

/// <summary> 
/// Don't cut & paste into any Windows Forms, inherit the behavior you want from a base class 
/// </summary> 
public abstract class ReportForm : System.Windows.Forms.Form 
{ 
    // I'm not sure exactly what this is used for, but I put it in base class in case there is some use for it here 
    protected string _commonSubreportKey = "12345"; 

    // This will be the one line of code needed in each WinForm--providing the base class a reference 
    // to the report, so it has access to the SubreportProcessing event 
    protected Report WinFormReport { get; set; } 

    // Making this abstract requires each derived WinForm to implement GetReportData--foolproof! 
    protected abstract DataResult GetReportData(SubreportProcessingEventArgs e); 

    // Wire up the subreport_processing handler when any WinForm loads 
    // You could override this in derived WinForms classes if you need different behavior for some WinForms, 
    // but I would bet this default behavior will serve well in most or all cases 
    protected virtual void Form1_Load(object sender, EventArgs e) 
    { 
     Report.SubreportProcessing += new SubreportProcessingEventHandler(LocalReport_SubreportProcessing); 
    } 

    // When the Subreport processing event fires, handle it here 
    // You could also override this method in a derived class if need be 
    protected virtual void LocalReport_SubreportProcessing(object sender, SubreportProcessingEventArgs e) 
    { 
     // Get the data needed for the subreport 
     DataResult dataResult = this.GetReportData(e); 

     e.DataSources.Clear(); 
     e.DataSources.Add(new ReportDataSource(dataResult.Label, dataResult.Table)); 
    } 
} 

Si noti che la classe di base ReportForm eredita dalla forma, e quindi tutte le WinForms erediterà da ReportForm - che è la chiave di tutta la progettazione.Ecco come funziona questa classe base ReportForm:

1) Quando il WinForm viene istanziato, viene impostata la proprietà di base WinFormReport, quindi l'oggetto di base sa quale Report è in uso.

2) Quando si carica WinForm, l'evento Form Load viene chiamato sulla classe base poiché non è definito nella classe derivata. Al caricamento del modulo, l'evento Subreport_Processing del report è cablato.

3) Quando l'utente immette parametri e fa clic su qualcosa per creare il report nel visualizzatore di report, alla fine i sottoreport vengono istanziati da RDLC e l'evento Subreport_Processing si attiva più volte, una volta per ogni sottoreport.

4) Quando l'evento si attiva, il gestore eventi della classe base chiama GetReportData (e), che invocherà il metodo GetReportData definito su WinForm. Si noti che questo metodo è astratto nella classe base, quindi non può essere definito sulla classe base, ma deve essere definito nella classe derivata.

5) Il metodo GetReportData (e) nel WinForm utilizza la logica del dispatcher inizialmente indicata, per restituire un DataTable (potrebbe anche essere un DataSet se necessario) e una stringa di testo per il gestore di base.

6) Il gestore di base acquisisce DataTable/DataSet e la stringa di testo, li invia al report come origine dati del report e può anche fare qualsiasi altra cosa necessaria per visualizzare il report.

Dopo aver riflettuto molto, ho deciso di raccomandare un refactoring abbastanza semplice del comportamento comune in una classe base, perché pensavo che avrebbe funzionato in base alle vostre esigenze, e non ho visto dove sarebbe stato necessario qualcosa di più complicato. Penso che troverai questo approccio molto leggibile, che minimizzi assolutamente ciò che è necessario in ogni nuovo WinForm e, soprattutto, lo trovi estremamente estensibile; cioè, mentre continui a sviluppare il sistema, ti chiederai sempre "è questo nuovo codice qualcosa che dovrà essere ripetuto in ogni WinForm, o è comune in modo tale che dovrebbe entrare nella classe base?"

Non esitate a aggiungere un commento se avete domande o dubbi su questo approccio, e vi auguro buona fortuna. Spero che sia proprio quello che stai cercando!

+0

Mi piace questa risposta. Ho intenzione di fare un po 'di test, ma sembra che io possa utilizzare questo modello per risolvere questo problema in futuro con la segnalazione di WinForms. Non ho mai pensato di astrarre il comportamento di una classe base; per qualche ragione, il modello che avevo di una classe base è che è stato progettato per primo e modificato raramente. Refactoring UP fino a una classe base ha aperto il mio pensiero a questo proposito. La risposta fornisce una soluzione specifica a questo problema e insegna un importante concetto di refactoring. – bentaisan

+0

Sto cambiando alcuni dettagli, ma questo concettualmente e praticamente copre la portata della mia domanda, così come i requisiti immateriali che ho messo fuori. – bentaisan

+0

Visual Studio (2008) mi sta dando due problemi: (1) DataResult nella definizione della classe astratta non è riconosciuto nel metodo LocalReport_SubreportProcessing (2) Se eredito da ReportWinForm (il mio nome per ReportForm), invece di Form, non posso modificare il mio modulo a causa di "Visual Studio non può aprire un designer per il file perché la classe al suo interno non eredita da una classe che può essere progettata visivamente". Anche se ReportForm è derivato da Form, sembra che manchino alcuni metadati o qualcosa del genere. – bentaisan

Problemi correlati