How can a delegate respond to multiple events with a generic and extensible class?

淺唱寂寞╮ 提交于 2019-12-04 05:29:59

The solution I would suggest is a very simple refactoring to a base class, and it reduces the code you would need to write in each WinForm to two things: 1) the setting of the Report used for that form; and 2) the definition of how to get the report data for that form.

Assuming that each WinForm inherits from a base class called ReportForm, the code for each WinForm will look like this:

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));

        }
    }
                                .
                                .
                                .

The above code does these things:

1) Tells the base class (ReportForm) which Report has been used in the Form. You could refactor Report down to ReportForm as well if you like, but my approach allows you to still create and manipulate your ReportViewer and its Reports in Visual Studio. But if you are passing the Report programmatically and not in the designer, you might want to send Report from the derived WinForm classes into the base class.

2) Defines how the report will get all of its subreports' data. For that, we just need to return a DataTable and a label, as that is all that will eventually be required by the report data source. The code which binds the DataTable and label to the RDLC data source belongs in the base class (ReportForm), as that binding code will be common for all your WinForms.

Now, the ReportForm code should look as follows:

/// <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));
    }
}

Notice that the ReportForm base class inherits from Form, and then all WinForms will inherit from ReportForm--that is the key to the whole design. Here's how this ReportForm base class works:

1) When the WinForm is instantiated, the base property WinFormReport is set, so the base object knows which Report is in use.

2) When the WinForm loads, the Form Load event is called on the base class since it is not defined in the derived class. On form load, the report's Subreport_Processing event is wired up.

3) When the user enters parameters and clicks something to create the report in the report viewer, eventually the subreports are instantiated by RDLC and the Subreport_Processing event fires multiple times, once for each subreport.

4) When the event fires, the base class event handler calls GetReportData(e), which will invoke the GetReportData method defined on the WinForm. Note that this method is abstract in the base class, so it cannot be defined on the base class, but must be defined in the derived class.

5) The GetReportData(e) method in the WinForm uses the dispatcher logic you initially indicated, to return a DataTable (could also be a DataSet if you need) and a text string to the base handler.

6) The base handler takes the DataTable/DataSet and the text string, feeds them to the report as the report data source, and there could also do whatever else is needed to display the report.

After much thought, I decided on recommending a fairly straightforward refactoring of common behavior into a base class, because I thought it would work given your requirements, and I didn't see where anything more complicated would be needed. I think you will find this approach very readable, have it absolutely minimize what is needed in each new WinForm, and above all, find it extremely extensible; that is, as you continue to develop the system, you will always ask "is this new code something that will need repeating in each WinForm, or is it common such that it should go into the base class?"

Feel free to add a comment if you have any questions or concerns about this approach, and I wish you the best of luck with it. I hope it is just what you are looking for!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!