6

我已经设置了一种技术来处理 rdlc 报告中的多个子报告,但是由于我试图使其通用且可重复,因此我不得不采用该模型并针对每种情况对其进行微调。

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

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

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

所以,设计要求是:

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

目标是制作一个可以独立测试并变得健壮的对象,并且不必剪切和粘贴轮子并为每个新的 Winform 进行大量手动调整。

在我看来,有人找到了比我目前拥有的更好的设计。

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

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

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

这由 *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;
            }
在旁边:

在我看来,与我考虑的替代方案相比,这个开关主要是人类可读的。我考虑使用以报表名称作为键、函数调用数据作为值的散列。但是,我真的不知道该怎么做,我认为其他人会更难理解。


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

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

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

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

        }

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

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

        }
    }

无论如何,我的愿望是不必在报告之间重复使用相同的逻辑,因为我有许多带有多个子报告的报告。

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

对于带有子报表的单个报表,甚至是几个一次性报表,我所做的是可以的,但如何才能减少手动、更健壮和更可测试呢?

我的环境是 Visual Studio 2008,目标是 .NET 3.5;抽象类的声明方式和编译方式似乎有所不同。

4

1 回答 1

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

        }
    }
                                .
                                .
                                .

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

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

2) 定义报表将如何获取其所有子报表的数据。为此,我们只需要返回一个 DataTable 和一个标签,因为这就是报表数据源最终需要的全部内容。将 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));
    }
}

请注意,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 中重复,还是很常见以至于它应该进入基类?”

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

于 2013-02-14T00:27:21.170 回答