winforms 我想在c# winform中从网格视图中单击按钮时导出单行

j2datikz  于 2022-11-16  发布在  C#
关注(0)|答案(1)|浏览(134)

我能够将完整的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中导出的数据应该看起来像

下面的图片

lndjwyie

lndjwyie1#

将数据与其显示方式分开

在现代编程中,数据(=模型)与它与操作员(=视图)的通信方式是分开的。这样做的好处是,如果您决定以不同的方式显示模型,例如,如果您想以图形而不是表格的形式显示数据,则可以重用模型。
为了使模型与视图匹配,需要一个适配器类。这个适配器类通常被称为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,以及您要显示的每个Product属性的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<T>并不奇怪
将下列属性加入至表单:

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<T>没有实现IList<T>。设计者们发现直接访问this.DisplayedProducts[4]没有什么用处。毕竟:如果操作符可以重新排列行,您就不知道索引为[4]的行中有什么。
但是,您可能希望将产品作为序列进行访问。因此,实现了ICollection<T>
如果要访问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>();

因此,要在操作员按下Ok按钮后访问所选产品:

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),可以使用类似的工作来取代这个工作,公寓测试这个工作会容易得多。您可以以不同的形式重复使用这些程序中的每一个。因为您有适当的公寓测试,所以您不必担心这些程序会有未预期的行为。
结论:
不要让你的程序太大。每个程序都应该有一个明确的任务。这就是所谓的关注点分离的一部分。考虑阅读一些关于这方面的背景信息。

相关问题