委托如何使用通用和可扩展的类响应多个事件?

ben*_*san 6 c# delegates rdlc strategy-pattern subreports

我已经在rdlc报告中编写了一种处理多个子报告的技术,但是由于我试图使其具有通用性和可重复性,因此我不得不采用模型并针对每种情况稍微调整一下.

例如,如果我定义一个抽象接口,就像这样,我只需根据需要将其从winform剪切并粘贴到winform:

abstract class ISolutionStrategy
{
    public abstract void AlgorithmInterface(Int64 searchCriteria, SubreportProcessingEventArgs e);
}
Run Code Online (Sandbox Code Playgroud)

首先,我希望能够通过包含has-a对象将其带入每种形式.我还想封装委托处理调度的行为,并使处理方法也"通用".

因此,设计要求是:

  • 创建一个可以包含在winform中的对象,以处理多个子报表处理
  • 在winform中实例化和配置对象
  • 在winform中构建调度表或switch/case语句
  • 传递所有方法来处理该winform报表查看器的特定要求

目标是制作一个可以独立测试并且坚固耐用的物体,也不必剪切和粘贴滚轮,并为每个新的winform做一堆手动调整.

在我看来,有人找到了比我现在更好的设计.

创建一个可以包含在winform中的对象,以处理多个子报表处理

到目前为止,我在本地表单加载事件中有一个委托:

this.reportViewer1.LocalReport.SubreportProcessing += new SubreportProcessingEventHandler(LocalReport_SubreportProcessing);
Run Code Online (Sandbox Code Playgroud)

它由*LocalReport_SubreportProcessing*方法中的switch语句处理.

该方法的主体包含一个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;
            }
Run Code Online (Sandbox Code Playgroud)
在旁边:

在我看来,与我考虑的替代方案相比,这种转换大多是人类可读的.我考虑使用带有报告名称的哈希作为键,并将函数调用数据作为值.但是,我真的不知道怎么做,我认为其他人更难理解.


之后,调用一个函数,该函数重新排列switch语句中从函数调用传递的信息:

    private static void runSubAlternatePart(SubreportProcessingEventArgs e1, String  commonReportKey, GetAlternatePart myAP)
            {
                myAP.AlgorithmInterface(commonReportKey, e1);
            }
Run Code Online (Sandbox Code Playgroud)

这种重新排列肯定是代码口吃,但是我试图实现的策略模式似乎是必要的中间件:

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

        }
Run Code Online (Sandbox Code Playgroud)

以下是其中一份报告的战略的具体实施:

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

        }
    }
Run Code Online (Sandbox Code Playgroud)

无论如何,我的愿望是不必在报告之间反复地连接相同的逻辑,因为我有许多报告有多个子报告.

我想要一个库质量的方法来使用类动态创建出现口吃的中间部分,并且我想传入一个"匿名"函数,它实际上实现了子报表与其相应数据源的详细连接.

对于包含子报告的单个报告,甚至是一些一次性报告,我正在做的事情还可以,但是如何减少手动,更强大和更可测试?

我的环境是Visual Studio 2008,目标是.NET 3.5; 在抽象类的声明方式和编译方式方面似乎存在差异.

J.T*_*lor 4

我建议的解决方案是对基类进行非常简单的重构,它将您需要在每个 WinForm 中编写的代码减少为两件事:1)用于该表单的报表的设置;2) 定义如何获取该表单的报告数据。

假设每个 WinForm 都继承自一个名为 ReportForm 的基类,则每个 WinForm 的代码将如下所示:

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

        }
    }
                                .
                                .
                                .
Run Code Online (Sandbox Code Playgroud)

上面的代码做了这些事情:

1) 告诉基类(ReportForm) Form 中使用了哪个Report。如果您愿意,您也可以将 Report 重构为 ReportForm,但我的方法仍然允许您在 Visual Studio 中创建和操作 ReportViewer 及其报表。但是,如果您以编程方式传递报表而不是在设计器中,则可能需要将报表从派生的 WinForm 类发送到基类中。

2) 定义报表如何获取其所有子报表的数据。为此,我们只需要返回一个数据表和一个标签,因为这就是报表数据源最终需要的全部内容。将 DataTable 和标签绑定到 RDLC 数据源的代码属于基类 (ReportForm),因为该绑定代码对于所有 WinForms 都是通用的。

现在,ReportForm 代码应如下所示:

/// <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));
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,ReportForm 基类继承自 Form,然后所有 WinForm 都将继承自 ReportForm——这是整个设计的关键。下面是这个 ReportForm 基类的工作原理:

1)当实例化WinForm时,设置了基本属性WinFormReport,因此基本对象知道正在使用哪个Report。

2)当WinForm加载时,Form Load事件在基类上被调用,因为它没有在派生类中定义。加载表单时,将连接报表的 Subreport_Processing 事件。

3) 当用户输入参数并单击某些内容以在报表查看器中创建报表时,最终子报表将由 RDLC 实例化,并且 Subreport_Processing 事件会多次触发,每个子报表触发一次。

4) 当事件触发时,基类事件处理程序调用 GetReportData(e),这将调用 WinForm 上定义的 GetReportData 方法。注意,该方法在基类中是抽象的,因此不能在基类上定义,而必须在派生类中定义。

5) WinForm 中的 GetReportData(e) 方法使用您最初指定的调度程序逻辑,将 DataTable(如果需要,也可以是 DataSet)和文本字符串返回到基本处理程序。

6) 基本处理程序获取 DataTable/DataSet 和文本字符串,将它们作为报告数据源提供给报告,并且还可以执行显示报告所需的任何其他操作。

经过深思熟虑,我决定建议将常见行为相当简单地重构为基类,因为我认为它可以满足您的要求,而且我没有看到哪里需要更复杂的东西。我认为您会发现这种方法非常可读,它绝对最小化了每个新 WinForm 中所需的内容,最重要的是,发现它具有极强的可扩展性;也就是说,当您继续开发系统时,您总是会问“这个新代码是否需要在每个 WinForm 中重复,或者是否很常见,因此应该进入基类?”

如果您对此方法有任何疑问或疑虑,请随时添加评论,祝您好运。我希望这正是您正在寻找的!