Saturday, April 25, 2020

Saving Your WinForm Data

I wrote a blog post 5 months ago about Printing Your WinForm: https://geek-goddess-bonnie.blogspot.com/2019/11/printing-your-winform.html

Today's post is doing something "almost" similar for saving your WinForm data. I was helping a person on the MSDN forums with his particular need to save data entered by a user on a complex Form to one file, such that the Form could be re-populated by reading the file and processing the info. The Form contained multiple tabs with a picturebox and and a textbox on each tab, along with a button so the user could upload an image for the picturebox.

I suggested the use of DataSets, because the DataSet .ReadXml() and .WriteXml() are so easy to use. This particular person was not a developer, so I wanted to keep it easy. A more experienced developer can take what I lay out here in my blog and do more with it ... such as save the info contained in the DataTable to a database.

I would also recommend the use of Typed DataSets over plain old DataSets. I didn't get into that in my reply to the forum questioner, except to mention that Typed DataSets are better most of the time, IMHO. More on that later after this first set of code. In either case, the DataType for the PictureBoxImage column should be System.Byte[].

We'll need four additional methods on the Form to handle this functionality (with appropriate Save and Repopulate buttons to call the first two methods):

  1. SaveTabPageInfoToFile() to Save all your TabPage information to a DataSet XML file.
  2. FillTabPagesFromFile() to repopulate your TabPage information from that saved XML file.
  3. ImageToByteArray() to take an Image from a PictureBox and create a Byte[] array.
  4. ByteArrayToImage() to take a Byte[] array and create an Image from it.

Code Using Typed DataSets:

Number 1:

public void SaveTabPageInfoToFile()
{
    FormInfoDataSet dsFormInfo = new FormInfoDataSet();

    // Fill the DataTable with info from each TabPage
    FormInfoDataSet.TabPageRow rowTabPage;
    foreach (TabPage page in this.tabControl1.TabPages)
    {
        rowTabPage = dsFormInfo.TabPage.NewTabPageRow();
        dsFormInfo.TabPage.AddTabPageRow(rowTabPage);

        rowTabPage.TabPageName = page.Name;
        foreach (var ctrl in page.Controls)
        {
            if (ctrl is TextBox)
            {
                rowTabPage.TextBoxName = ((TextBox)ctrl).Name;
                rowTabPage.TextBoxText = ((TextBox)ctrl).Text;
            }
            else if (ctrl is PictureBox)
            {
                rowTabPage.PictureBoxName = ((PictureBox)ctrl).Name;
                rowTabPage.PictureBoxImage = ImageToByteArray(((PictureBox)ctrl).Image); // This is Number 3
            }
        }
    }

    // Now write the DataSet to an XML file
    dsFormInfo.WriteXml("FormInfo.xml");
}

Number 2:

public void FillTabPagesFromFile()
{
    FormInfoDataSet dsFormInfo = new FormInfoDataSet();

    // Fill the DataSet from the saved file
    dsFormInfo.ReadXml("FormInfo.xml");

    // And now, repopulate all your TabPages
    // Unfortunately, in using a DataTable.Select(), the resulting array of DataRow[] cannot be cast to
    // a TabPageRow[] array and we have to use the a plain old DataRow[], along with the non-typed syntax.
    DataRow[] rows;
    foreach (TabPage page in this.tabControl1.TabPages)
    {
        foreach (var ctrl in page.Controls)
        {
            if (ctrl is TextBox)
            {
                rows = dsFormInfo.TabPage.Select($"TextBoxName = '{((TextBox)ctrl).Name}'");
                if (rows.Length > 0)
                    ((TextBox)ctrl).Text = rows[0]["TextBoxText"].ToString();
            }
            else if (ctrl is PictureBox)
            {
                rows = dsFormInfo.TabPage.Select($"PictureBoxName = '{((PictureBox)ctrl).Name}'");
                if (rows.Length > 0)
                    ((PictureBox)ctrl).Image = ByteArrayToImage(rows[0]["PictureBoxImage"] as Byte[]); // This is Number 4
            }
        }
    }
}

Number 3:

private Byte[] ImageToByteArray(Image image)
{
    var ms = new System.IO.MemoryStream();
    image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
    return ms.ToArray();
}

Number 4:

private Image ByteArrayToImage(Byte[] bytes)
{
    var ms = new System.IO.MemoryStream(bytes);
    return Image.FromStream(ms);
}

Another few words about Typed DataSets

In this particular example, a Typed DataSet was useful in Number 1, but not so much in Number 2. The moral of this story is that, in cases like this, unless you have an existing Typed DataSet already defined in your application, possibly for other uses, then you may not want to go through the steps to create one for something as simple as this. In which case, I suggest using a global DataSet variable and defining the DataSet in your Form's constructor to be utilized in the two methods, like this:

Code that does not use a Typed DataSet:


// define two global variables:
private DataSet dsFormInfo;
private DataTable dtTabPage;

// In the Form's constructor:
public Form1()
{
    InitializeComponent();

    // add the code to define your DataSet/DataTable
    this.dsFormInfo = new DataSet();
    this.dtTabPage = new DataTable();
    this.dtTabPage.Columns.Add("TabPageName", typeof(string));
    this.dtTabPage.Columns.Add("TextBoxName", typeof(string));
    this.dtTabPage.Columns.Add("TextBoxText", typeof(string));
    this.dtTabPage.Columns.Add("PictureBoxName", typeof(string));
    this.dtTabPage.Columns.Add("PictureBoxImage", typeof(System.Byte[]));
    this.dsFormInfo.Tables.Add(this.dtTabPage);
}

Now change the bolded code below in your two methods (Number 1 and 2) accordingly:

public void SaveTabPageInfoToFile()
{
    // Start with an empty DataSet/DataTable
    this.dsFormInfo.Clear();

    // Fill the DataTable with info from each TabPage
    // using the non-Typed syntax
    DataRow rowTabPage
    foreach (TabPage page in this.tabControl1.TabPages)
    {
        rowTabPage = this.dtTabPage.NewRow();
        this.dtTabPage.Rows.Add(rowTabPage);

        rowTabPage["TabPageName"] = page.Name;
        foreach (var ctrl in page.Controls)
        {
            if (ctrl is TextBox)
            {
                rowTabPage["TextBoxName"] = ((TextBox)ctrl).Name;
                rowTabPage["TextBoxText"] = ((TextBox)ctrl).Text;
            }
            else if (ctrl is PictureBox)
            {
                rowTabPage["PictureBoxName"] = ((PictureBox)ctrl).Name;
                rowTabPage["PictureBoxImage"] = ImageToByteArray(((PictureBox)ctrl).Image); // This is Number 3
            }
        }
    }

    // Now write the DataSet to an XML file
    this.dsFormInfo.WriteXml("FormInfo.xml");
}


public void FillTabPagesFromFile()
{
    // Start with an empty DataSet/DataTable
    this.dsFormInfo.Clear();

    // Fill the DataSet from the saved file
    this.dsFormInfo.ReadXml("FormInfo.xml");

    // And now, repopulate all your TabPages
    DataRow[] rows;
    foreach (TabPage page in this.tabControl1.TabPages)
    {
        foreach (var ctrl in page.Controls)
        {
            if (ctrl is TextBox)
            {
                rows = this.dtTabPage.Select($"TextBoxName = '{((TextBox)ctrl).Name}'");
                if (rows.Length > 0)
                    ((TextBox)ctrl).Text = rows[0]["TextBoxText"].ToString();
            }
            else if (ctrl is PictureBox)
            {
                rows = this.dtTabPage.Select($"PictureBoxName = '{((PictureBox)ctrl).Name}'");
                if (rows.Length > 0)
((PictureBox)ctrl).Image = ByteArrayToImage(rows[0]["PictureBoxImage"] as Byte[]);
            }
        }
    }
}

Happy Coding!  =0)