【问题标题】:I want to export single row on button click from the gridview in c# winform我想在 c# winform 中从 gridview 中单击按钮时导出单行
【发布时间】:2023-01-14 01:37:07
【问题描述】:

我能够将完整的 gridview 导出为 pdf,但我无法理解如何定位特定行并在单击按钮时使用 itextsharp 将其导出为 pdf

下面是我导出为 pdf 的代码,我可以在其中导出完整的 gridview


private void gvSamplereports_CellContentClick(object sender, DataGridViewCellEventArgs e)
    {
        if (e.ColumnIndex == gvSamplereports.Columns["btnPDFsingle"].Index)
        {
            DateTime PrintTime = DateTime.Now;
            if (gvSamplereports.Rows.Count > 0)
            {
                SaveFileDialog sfd = new SaveFileDialog();
                sfd.Filter = "PDF (*.pdf)|*.pdf";
                sfd.FileName = "SampleDataReports_" + PrintTime.ToShortDateString() + ".pdf";
                bool fileError = false;
                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    if (File.Exists(sfd.FileName))
                    {
                        try
                        {
                            File.Delete(sfd.FileName);
                        }
                        catch (IOException ex)
                        {
                            fileError = true;
                            MessageBox.Show("It wasn't possible to write the data to the disk." +      ex.Message);
                        }
                    }
                    if (!fileError)
                    {
                        try
                        {
                            PdfPTable pdfTable = new PdfPTable(gvSamplereports.Columns.Count);
                            pdfTable.DefaultCell.Padding = 3;
                            pdfTable.WidthPercentage = 100;
                            pdfTable.HorizontalAlignment = Element.ALIGN_CENTER;
                            //Below line is to add the header column name on each page of pdf
                            pdfTable.HeaderRows = 1;

                            foreach (DataGridViewColumn column in gvSamplereports.Columns)
                            {
                                Font fon = FontFactory.GetFont("ARIAL", 6);
                                fon.SetStyle(1);
                                PdfPCell cell = new PdfPCell(new Phrase(column.HeaderText, fon));
                                cell.HorizontalAlignment = Element.ALIGN_CENTER;
                                pdfTable.AddCell(cell);
                            }

                            foreach (DataGridViewRow row in gvSamplereports.Rows)
                            {
                                foreach (DataGridViewCell cell in row.Cells)
                                {
                                    Font fon = FontFactory.GetFont("ARIAL", 6);
                                    PdfPCell cell2 = new PdfPCell(new Phrase(cell.Value?.ToString(),       fon));
                                    cell2.HorizontalAlignment = Element.ALIGN_CENTER;
                                    pdfTable.AddCell(cell2);
                                    //pdfTable.AddCell(cell.Value.ToString());
                                }
                            }

                            using (FileStream stream = new FileStream(sfd.FileName, FileMode.Create))
                            {
                                Document pdfDoc = new Document(PageSize.A4, 30f, 30f, 100f, 50f);
                                PdfWriter writer = PdfWriter.GetInstance(pdfDoc, stream);
                                //PDFFooter is class created for adding header and footer in the pdf
                                writer.PageEvent = new PDFFooter();
                                pdfDoc.Open();

                                pdfDoc.Add(pdfTable);
                                pdfDoc.Close();
                                stream.Close();
                            }

                            MessageBox.Show("Data Exported Successfully !!!", "Info");
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Error :" + ex.Message);
                        }
                    }
                }
            }
            else
            {
                MessageBox.Show("No Record To Export !!!", "Info");
            }
        }
    }

我添加了图像以供参考,单击按钮后,我想使用 c# winform 中的 Itextsharp 以 pdf 格式导出带有标题列名称的单行,以 pdf 格式导出的数据应如下图所示

【问题讨论】:

  • 不要打电话给DataGridViewa GridViewDataGrid,反之亦然!!这是错误和令人困惑的,因为它们是不同的控件。总是用他们的名字来称呼事物正确的名称! - 目前你正在遍历所有行。为什么?除了包含太多行之外,结果还好吗? - 要限制到带有单击按钮的行,只需不要循环,而是使用 e.RowIndex 访问单击的行。
  • 谢谢你的回复,你能用任何例子或任何文章帮助我吗我如何使用 e.RowIndex 而不是循环,itextsharp 对我来说是一个新概念所以我无法想象如何使用 e.RowIndex @TaW
  • 这与 iTextSharp 无关。这完全是关于你的循环:将 foreach (DataGridViewRow row in gvSamplereports.Rows) 替换为 DataGridViewRow row = gvSamplereports.Rows[e.RowIndex]

标签: c# winforms pdf itext


【解决方案1】:

将数据与显示方式分开

在现代编程中,有一种趋势是将数据(=模型)与它与操作员(=视图)通信的方式分开。这样做的好处是,如果您决定以不同方式显示模型,例如,如果您想将数据显示为图形而不是表格,则可以重用该模型。

为了将模型与视图相匹配,需要一个适配器类。这个适配器类通常称为 Viewmodel。这三个类一起缩写为 MVVM。考虑阅读有关此的一些背景信息。

在使用 Winforms 和 DataGridView 时,人们倾向于直接摆弄行和单元格,而不是将数据与其显示方式分开。这通常会导致很多问题。除此之外,你不能在没有表单的情况下对数据进行单元测试,你不能在其他表单中重用数据,也不能在不更改 DataGridView 的情况下更改数据。

Winforms 使用属性 DataGridView.DataSource 支持 MVVM。

如何轻松高效地访问您的 DataGridView 的数据?

唉,您忘了告诉我们您的 DataGridView 中有什么,而且我无法从您的代码中提取显示的内容。因此,对于示例,假设您显示了 Products 集合的几个属性:

class Product
{
    public int Id {get; set;}
    public string ProductCode {get; set;}
    public string Description {get; set;}
    public ProductType ProductType {get; set;}  // some enumeration: food / non-food / etc
    public decimal Price {get; set;}
    public int LocationId {get; set;}   // foreign key to Location table  
    ...
}

您可能不想显示所有属性。

所以当然你有一个程序来获取你想要最初展示的产品:

IEnumerable<Product> FetchProductsToShow() {...}

实施超出了问题的范围。

使用 Visual Studio 设计器,您已为要显示的每个产品属性添加一个 DataGridView 和一个 DataGridViewColumn。您必须定义哪个 DataGridViewColumn 将显示哪个属性的值。这可以使用设计器来完成。我通常使用 nameof 在构造函数中执行此操作。

public MyForm : Form
{
    InitializeComponents();

    // Define which column shows which Property:
    coilumnProductId.DataPropertyName = nameof(Product.Id);
    columnProductCode.DataPropertyName = nameof(Product.ProductCode);
    columnProductPrice.DataPropertyName =  nameof(Product.Price);
    ...

使用nameof 的好处是,如果您稍后决定更改属性的名称,它会自动在此处更改。编译器检测到键入错误。

现在要显示所有产品,您所要做的就是将 ProductsToDisplay 分配给 `dataGridView1.DataSource:

this.dataGridView1.DataSource = this.FetchProductsToShow().ToList();

并显示您的数据。

但是,如果操作员编辑表,则数据不会更新。如果需要,您必须将必须显示的产品放入实现IBindingList 的对象中。幸运的是已经有这样一个类,毫不奇怪地命名为BindingList&lt;T&gt;

将以下属性添加到您的表单中:

public BindingList<Product> DisplayedProducts
{
    get => (BindingList<Product>)this.dataGridView1.DataSource;
    set => this.dataGridView1.DataSource = value;
}

现在,操作员所做的所有更改都会在 BindingList 中自动更新:对单元格的更改,以及添加和删除的行。

private void ShowInitialProducts()
{
    this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());
}

要访问已编辑的表格,例如在操作员按下 OK 按钮后:

public void OnButtonOk_Clicked(object sender, ...)
{
    BindingList<Product> editedProducts = this.DisplayedProducts;
    // find out which products are changed, and process them:
    this.ProcessEditedProducts(editedProducts);
}

回到你的问题

但我不明白如何定位特定行

BindingList&lt;T&gt; 没有实现 IList&lt;T&gt;。设计人员发现直接访问this.DisplayedProducts[4] 没有用。毕竟:如果操作员可以重新排列行,您不知道索引为[4] 的行中有什么。

但是,您可能希望按顺序访问产品。因此实现了ICollection&lt;T&gt;

如果要访问 current 行或 selected rows,请考虑将以下属性添加到表单中:

public Product CurrentProduct => this.dataGridView1.CurrentRow?.DataBoundItem as Product;

这将返回当前产品,如果未选择任何内容,则返回 null

public IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
    .Cast<DataGridViewRow>()
    .Select(row => row.DataBoundItem)
    .Cast<Product>();

因此,要在操作员按下“确定”按钮后访问所选产品:

public void OnButtonOk_Clicked(object sender, ...)
{
    IEnumerable<Product> selectedProducts = this.SelectedProducts;
    // process the selected products:
    this.ProcessProducts(selectedProducts);
}

还有改进的余地

如果我查看您的代码,在我看来,如果操作员单击名称为btnPDFsingle 的列中的单元格(为什么不使用解释列显示内容的名称?),那么您会做几件事:

  • 您要求操作员提供文件名,
  • 如果文件存在,你删除它(如果不能删除,解决问题)
  • 然后创建一个PdfPTable 并用 DataGridView 的内容填充它
  • 最后将 PdfPTable 写入文件。

你决定在一个过程中完成所有这些。这使得很难对其进行单元测试。您不能重用此代码的任何部分,如果您更改其中的一部分,则很难检测到您的代码的哪些部分也必须更改。

private void gvSamplereports_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
    if (e.ColumnIndex == gvSamplereports.Columns["btnPDFsingle"].Index)
    {
        this.SaveProducts();
    }
    else
        ... // report problem to operator
}

private void SaveProducts()
{
    string fileName = this.AskOperatorForFileName();
    if (String.IsNullOrEmpty(fileName))
    {
        ... // report operator that no filename is given
        return;
    }

    // fetch the products that must be in the Pdf
    IEnumerable<Product> productsToSave = this.FetchProductsToSave();
    this.SaveProducts(fileName, productsToSave);
}

ICollection<Product> FetchProductsToSave()
{
    // Do you want to save all Products or Only the selected ones?
    return this.DisplayedProducts;
}

注意:如果您决定保存不同的东西,只保存选定的产品,或者可能只保存前 10 个产品,或者只保存非食品产品,您所要做的就是更改此方法。其他方法不知道,也不必知道保存了哪些产品。

顺便说一下,您是否注意到,直到现在我都没有提到产品保存为 PDF 格式?如果稍后您决定将它们另存为 XML、CSV 或纯文本,则这些过程都无需更改。

private void SaveProducts(string fileName, IEnumerable<Product> productsToSave)
{
    PdfPTable tableToSave = this.CreatePdfPTable(productsToSave);
    this.SavePdfPTable (fileName, tableToSave);
}

private PdfPTable CreatePdfPTable(IEnumerable<Product> products)
{
    ...
    foreach (Product product in products)
    {
        ...
    }
}

您是否看到,为了创建 PdfPTable,我不必再访问 DataGridViewRows 或 Cells?如果您决定更改 PdfPTable 的布局,则只会更改此过程。外部的任何方法都不知道表的内部格式。易于单元测试,易于重用,易于更改。

private void SavePdfPTable (string fileName, PdfPTable pdfPTable)
{
    // if the file already exists, if will be overwritten (FileMode.Create)
    // so no need to delete it first
    using (FileStream stream = new FileStream(sfd.FileName, FileMode.Create))
    {
        ... etc, write the PdfTable in the stream.
    }
}

你看到了吗,因为所有的小程序,每个程序只有一个特定的任务。对这个任务进行单元测试要容易得多,如果你想要一个小的改变(例如保存为 XML),用类似的任务替换这个任务。您可以以不同的形式重用这些过程中的每一个。因为您有适当的单元测试,所以您不必担心这些过程会出现意外行为。

结论:

不要让你的程序太大。每个程序都应该有一个特定的明确任务。这是所谓的关注点分离的一部分。考虑阅读有关此的一些背景信息。

【讨论】:

  • 非常感谢,解释得很好,我一定会尝试添加改进点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多