using System; using System.Collections.Generic; // allows for lists using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; // allows for reading and writing of files // Purpose: Program that saves and loads data by reading the Controls (textboxes, checkboxes, and comboboxes) // // Author: Trevor Hickey // // Date: January 29, 2021 // // Description: This program uses a simple series of arrays to read the various controls and save their data (and respective Controls' names) // and load data in the reverse fashion. All the programer has to do is make a control and connect it to the respective event handler. // See **Instructions** at the bottom. namespace Mortgage_Manager_3._0 // file name { public partial class mainForm : Form { int numberOfControls = 0; // will hold the number of Controls, which (when doubled) will be the size of the userData array string[] userData; // array that will hold data the user will input in Controls (textboxes, checkboxes, and comboboxes), prefaced by respective Control's name int userDataCounter = 0; // will represent the number of unique pieces of data that user has entered, doubled string[] fromFile; // new array to hold incoming data from source file when loading bool dirtyData; // new (bool) variable "dirtyData" telling program if data has been edited, but not yet saved int lineCounter; // variable that will count the number of lines in the source file, when loading string saveFileName = ""; // variable that will hold the filename that User selected to save data to bool userKnows = false; // bool variable that will tell program the User knows what kind of file to upload // all of the above need to be class variables public mainForm() { InitializeComponent(); // on opening, do the following... // the following counts the number of Controls (who's data you intend to save) in the form. // It doesn't count controls like buttons nor lines etc. foreach (Control c in this.Controls) // for each type of control in the form... { if (c.GetType() == typeof(TextBox) || c.GetType() == typeof(CheckBox) || c.GetType() == typeof(ComboBox)) // if textbox, checkbox or combobox... { numberOfControls++; // add one to the count (numberOfControls) } } // setting array size to accomodate all spots needed to save control names and data userData = new string[numberOfControls * 2]; // setting the userData array size to numberOfControls (x2), this is so that there // is enough space to hold each control's name and text/checked data } private void saveAsButton_Click(object sender, EventArgs e) // event handler for the save button { // open the file if (!userKnows) // if User has not yet loaded nor saved a file, display this message b/c they may not know you need a .txt file { MessageBox.Show("Note: You will be asked to choose a file, please select a text (.txt) file"); // messagebox that tells User what kind of file to select } // declare new (SaveFileDialog) variable "sfd" and prompt user to select a file to save to SaveFileDialog sfd = new SaveFileDialog(); DialogResult result = sfd.ShowDialog(); if (result != DialogResult.OK) // if user hits anything other than OK (aka "Save") { return; // give up } StreamWriter output = new StreamWriter(sfd.FileName); // declare a (StreamWriter) variable "output" and set its value to file selected by user saveFileName = sfd.FileName; // making saveFileName equal to filename/location that User selected to save data to // this will be used by the Save button // NTS: needs to be a text file userKnows = true; // telling program that the User knows what kind of file to upload and therefore doesn't need // to be told to use a .txt file // save userData array to text file for (int i = 0; i < userDataCounter; i++) // do the following for as many times User entered data (x2) // each time User changed a saveable control (ie. textbox), userDataCounter went // up by 2. // Limiting the loop to this amount ensures it only goes through it as many // times as needed to get all the info ie. just in case User hasn't edited ALL // possible saveable controls { output.WriteLine(userData[i]); // write consecutive array spots' data to User selected txt file // data will be saved in "name" \n "data" format } dirtyData = false; // telling program that all edits have been saved this.Text = saveFileName; // making program window's title @ top, display file name w/o asterix' ahead of it output.Close(); // close txt file } private void saveButton_Click(object sender, EventArgs e) { if (saveFileName.ToString() == "") // if User hasn't saved/loaded the file before... { MessageBox.Show("You have not selected a file to save your data to.\n\nPlease click the \"Save As\" button."); return; } StreamWriter output = new StreamWriter(saveFileName); // if User has saved or loaded file before, save new data to // last file either loaded from or saved to // save userData array to text file for (int i = 0; i < userDataCounter; i++) // do the following for as many times User entered data (x2) // each time User changed a saveable control (ie. textbox), userDataCounter went // up by 2. // Limiting the loop to this amount ensures it only goes through it as many // times as needed to get all the info ie. just in case User hasn't edited ALL // possible saveable controls { output.WriteLine(userData[i]); // write consecutive array spots' data to User selected txt file // data will be saved in "name" \n "data" format } dirtyData = false; // telling program that all edits have been saved this.Text = saveFileName; // making program window's title @ top, display file name w/o asterix' ahead of it output.Close(); // close txt file } private void dirtyDataTrue(object sender, EventArgs e) // method which tells program that data has been edited, but not yet saved { dirtyData = true; // telling program that data has been edited but not yet saved this.Text = ($"***{saveFileName}"); // making header display asterix' ahead of file name so User knows data is unsaved } private void comboBox_TextChanged(object sender, EventArgs e) // event handler for when a combobox is changed { ComboBox comB = (ComboBox)sender; // create (ComboBox) variable "comB" & making its value to that of sender for (int i = 0; i < userDataCounter; i++) // run this loop as many times as User entered unique data x 2 { if (userData[i] == (string)comB.Name) // if sender combobox has been edited before, then its name will be in the array somewhere // this will read through the array and if it is in there... { userData[i + 1] = comB.Text; // it will save sender combobox's text property to next array spot dirtyDataTrue(sender, e); // calling method that tells program that data is not saved return; // give up } } // if User hasn't edited said combobox before... userData[userDataCounter] = comB.Name; // insert sending combobox's name into next blank array spot userData[userDataCounter + 1] = comB.Text; // insert sending combobox's text property into next blank array spot userDataCounter += 2; // increase userDataCounter by 2, to reflect the two new entries dirtyDataTrue(sender, e); // calling method that tells program that data is not saved and makes // form's title (at top) display file name, prefaced by 3 *'s, so User knows // data is not saved } private void textbox_TextChanged(object sender, EventArgs e) // event handler for when a textbox is changed { TextBox tb = (TextBox)sender; // create (TextBox) variable "tb" & making its value to that of sender for (int i = 0; i < userDataCounter; i++) // run this loop as many times as User entered unique data x 2 { if (userData[i] == (string)tb.Name) // if sender textbox has been edited before, then its name will be in the array somewhere // this will read through the array and if it is in there... { userData[i + 1] = tb.Text; // it will save sender textbox's text property to next array spot dirtyDataTrue(sender, e); // calling method that tells program that data is not saved return; // give up } } // if User hasn't edited said textbox before... userData[userDataCounter] = tb.Name; // insert sending textbox's name into next blank array spot userData[userDataCounter + 1] = tb.Text; // insert sending textbox's text prop. into next blank array spot userDataCounter += 2; // increase userDataCounter by 2, to reflect the two new entries dirtyDataTrue(sender, e); // calling method that tells program that data is not saved } private void checkbox_CheckChanged(object sender, EventArgs e) // event handler for when a checkbox is changed { CheckBox cb = (CheckBox)sender; // create (CheckBox) variable "cb" & making its value to that of sender for (int i = 0; i < userDataCounter; i++) // run this loop as many times as User entered unique data x 2 { if (userData[i] == (string)cb.Name) // if sender checkbox has been edited before, then the present array spots will // have its name in there. This will read through them and if it is in there... { if (cb.Checked == false) // and if sending checkbox has been edited and is presently unchecked... { userData[i + 1] = "false"; // make next array spot read "false" } else // if sending checkbox is presently checked... { userData[i + 1] = "true"; // make next array spot read "true" } dirtyDataTrue(sender, e); // calling method that tells program that data is not saved return; // give up } } // if sender checkbox is not in the array, it means it is a unique edit and... userData[userDataCounter] = cb.Name; // insert sending checkbox's name into next blank array spot if (cb.Checked == false) // if sending checkbox is presently unchecked... { userData[userDataCounter + 1] = "false"; // make next array spot read "false" } else // if sending checkbox is presently checked... { userData[userDataCounter + 1] = "true"; // make next array spot read "true" } userDataCounter += 2; // increase counter by 2, to reflect the two new entries dirtyDataTrue(sender, e); // calling method that tells program that data is not saved } private void reset(object sender, EventArgs e) // method that resets the program's defaults, as if re-openning (for the most part) { for (int i = 0; i < (numberOfControls * 2); i++) // clear spots in userData array { userData[i] = ""; } for (int i = 0; i < lineCounter; i++) // run this loop lineCounter times (b/c the fromFile array has lineCounter spots) { fromFile[i] = ""; // clear fromFile array spots } userDataCounter = 0; // reset userDataCounter variable foreach (var c in this.Controls) // make (var) variable "c" and make a relationship between it and all the Controls in this form // and for each of the Controls, do the following... { // clear existing Controls { if (c is TextBox) // if a Control is a TextBox, ... { ((TextBox)c).Text = String.Empty; // clear its contents } else if (c is CheckBox) // if a Control is a CheckBox, ... { ((CheckBox)c).Checked = false; // Unclick them } else if (c is ComboBox) // if a Control is a ComboBox, ... { ((ComboBox)c).Text = ""; // clear its contents } } } dirtyData = false; // telling program that all edited data has been saved this.Text = saveFileName; // making header display file name without asterix' so User knows data is saved // setting all controls back to the top, by clicking the up button as many times as allowed for (int i = 0; i < 1;) { if (upButton.Enabled == true) // if you can click the up button { upButton_Click(sender, e); // click it } else // if can't click up button { i++; // increase the counter by one to short out the loop } } } private void isDataClean(object sender, EventArgs e) // this method is triggered if User tries to load new data (or exit), while unsaved data exists in the form { DialogResult result = MessageBox.Show("Not all of your edits have been saved.\nIf you proceed, some of your edits will be lost.\nDo you want to proceed?", // message box message/text "Error! Not all edits saved.", // message box title MessageBoxButtons.YesNo, // displays yes & no buttons MessageBoxIcon.Warning); // displays hand icon switch (result) // the following is similar to if else ladder and produces different results, depending on what button (in the messagebox) the User presses { case DialogResult.Yes: // if User pressed YES button... dirtyData = false; // telling program that all data is clean (aka, has been saved) - although it hasn't - so that the calling method can proceed this.Text = saveFileName; // making header display file name without asterix' so User knows data is saved break; // give up (similar to return), returning control to calling method case DialogResult.No: // if User pressed NO button... break; // give up, returning control to calling method default: // default is the "else" of the "switch" statement break; // give up (similar to return) } } private void loadButton_Click(object sender, EventArgs e) // load button event handler { int controlsEdited = 0; // new variable that will keep count of how many successful match-ups b/w Control name and // data there are if (dirtyData == true) // if data is "dirty" ie. edited, but not yet saved... { isDataClean(sender, e); // call method that asks user if they are sure that they want to proceed } if (dirtyData == true) // if data is still "dirty"... { return; // quit } // open the file if (!userKnows) // if User has not yet loaded nor saved a file, display this message b/c they may not know you need a .txt file { MessageBox.Show("Note: You will be asked to choose a file, please select a text (.txt) file"); // messagebox that tells User what kind of file to select } // declare (OpenFileDialog) variable "ofd" and prompt User to select a file to read OpenFileDialog ofd = new OpenFileDialog(); DialogResult result = ofd.ShowDialog(); if (result != DialogResult.OK) // if user hits anything other than OK (aka "Open") { return; // give up } StreamReader input = new StreamReader(ofd.FileName); // declare a (StreamReader) variable "input" and set its value to file selected by user saveFileName = ofd.FileName; // making saveFileName equal to filename/location that User selected to save data to // the Save button will use this variable userKnows = true; // telling program that the User knows what kind of file to upload and therefore doesn't need // to be told to use a .txt file lineCounter = File.ReadLines(ofd.FileName).Count(); // read how many lines are in the source file and make lineCounter equal to this number fromFile = new string[lineCounter]; // making fromFile array the same size as there are lines in source file if (lineCounter > (numberOfControls * 2)) // and if lineCounter is more than the number of controls x 2 (1 for data, 1 for title of Control) ... { MessageBox.Show($"Data Error.\nThe source file cannot exceed {numberOfControls}.\nYour file has {lineCounter} lines.", // display message "Data Error", // message box title MessageBoxButtons.OK, // button type MessageBoxIcon.Error); // icon return; // give up } reset(sender, e); // calling method to empty arrays' data, reset userDataCounter and empty Controls // read through source txt file and copy contents to fromFile array for (int i = 0; i < lineCounter; i++) // do the following as many times as lineCounter... { string valueString = input.ReadLine().Trim(); // new (string) variable "valueString" and sets value to the (trimed) present line being read from data file fromFile[i] = valueString; // inserting present line being read into respective spot in array fromFile } foreach (var c in this.Controls) // make (var) variable "c" and make a relationship between it and all the Controls in this form // and for each of the Controls in this form, do the following... // start reading fromFile array and writing to respective Controls { for (int i = 0; i < lineCounter; i += 2) // run this loop as many times as lineCounter (b/c that's the size of the // fromFile array) and add 2 to the counter each time the loop runs { if (c is TextBox) // if Control in question is a TextBox, ... { if (fromFile[i] == ((TextBox)c).Name) // and if said textbox's name is same as contents of array spot in question... { ((TextBox)c).Text = fromFile[i + 1]; // make text property of said textbox = next array spot controlsEdited++; // add one count to the controlsEdited variable } } if (c is CheckBox) // if Control in question is a CheckBox, ... { if (fromFile[i] == ((CheckBox)c).Name) // and if said CheckBox's name is same as contents of said array spot in question... { if (bool.TryParse(fromFile[i + 1], out bool checkedOrNot)) // and if next array spot in question can be parsed into a bool value... { ((CheckBox)c).Checked = checkedOrNot; // make Checked property of said CheckBox = checkedOrNot variable (true or false) } controlsEdited++; // add one count to the controlsEdited variable } } if (c is ComboBox) // if Control in question is a ComboBox, ... { if (fromFile[i] == ((ComboBox)c).Name) // and if said ComboBox's name is same as contents of array spot in question... { ((ComboBox)c).Text = fromFile[i + 1]; // make text property of said ComboBox = next array spot controlsEdited++; // add one count to the controlsEdited variable } } input.Close(); // close file this.Text = saveFileName; // making the form header display source file name without astrix' prefacing it dirtyData = false; // telling program all data has been saved } } if (controlsEdited < (lineCounter / 2)) // each time a control (ie. textbox etc) is edited, the variable "controlsEdited" increases by one // and if lineCounter (number of lines in source file) รท 2 is greater than controlsEdited // then there are some pieces of data in the file that are not in the format of Control name \n Control data // meaning some data in source file was not being read and reflected in Controls' text/checked fields // basically, the file being loaded is either inelligible ie. pdf or someone was in there messing around with it { MessageBox.Show($"Some of the data in the source file is not compatible\nwith the fields in this form.\n\nNot all data in the .txt source file\n{saveFileName} were loaded."); return; } } private void resetButton_Click(object sender, EventArgs e) { if (dirtyData == true) // if data is "dirty" ie. edited, but not yet saved... { isDataClean(sender, e); // call method that asks user if they are sure that they want to proceed } if (dirtyData == true) // if data is still "dirty"... { return; // do nothing } reset(sender, e); // calling method to empty array data, reset userDataCounter and empty Controls } private void quitButton_Click(object sender, EventArgs e) { if (dirtyData == true) // if data is "dirty" ie. edited, but not yet saved... { isDataClean(sender, e); // call method that asks user if they are sure that they want to proceed } if (dirtyData == true) // if data is still "dirty"... { return; // do nothing } Application.Exit(); // close program } private void upButton_Click(object sender, EventArgs e) // this is the event handler called when the "up" button is pushed { foreach (Control c in this.Controls) // for each type of control in the form... { if (c.GetType() == typeof(Button)) // if Control in question is a button ... { // do nothing } else // otherwise... { c.Location = new Point(c.Location.X, c.Location.Y + (50)); // take Control's present location and move it down the screen by 50 units if (c.Location.Y > 40) // if Control's height is more ("more" means further down on screen) than 40... // (in this case 40 is the minimum, as any smaller number (ie. 30) would allow the // Control to overlap the buttons @ top) { c.Visible = true; // make Control in question, visible } // the immediately above only works for making things visible that come back down into view, as they move down past // a height of 40. // Things that dissapear off the bottom are automatically hidden by leaving the border of the program, but their // "Visible" property remains as "true", and do not need to be made visible when they move up again // if ANY control violates the boundaries if (c.Location.Y > 40 + 650) // If ANY control is at a height of more (ie. lower on screen) than 40 + 650... { upButton.Enabled = false; // stop allowing the up button to be pressed } // every time the "up" button is pushed, it should give enough space to make the "down" button useable again downButton.Enabled = true; // enable down button } } } private void downButton_Click(object sender, EventArgs e) // this is the event handler called when down button is pushed { foreach (Control c in this.Controls) // for each type of control in the form... { if (c.GetType() == typeof(Button)) // if Control in question is a button ... { // do nothing } else // otherwise... { c.Location = new Point(c.Location.X, c.Location.Y - (50)); // take Control's present location and move it up the screen by 50 units if (c.Location.Y < 40) // if Control's height is less ("less" means further up on screen) than 40... // (in this case 40 is the minimum, as any smaller number (ie. 30) would allow the // Control to overlap the buttons) { c.Visible = false; // make invisible } // the immediately above only works for making things invisible that move up out of view, as they move up past // a height of 40. // Things that dissapear off the bottom are automatically hidden by leaving the border of the program, but their // "Visible" property remains as "true", and do not need to be made visible when they move up again // if ANY control violates the boundaries... if (c.Location.Y < 40 - 650) // If ANY control is at a height of less than... // see explination of this in upButton_Click method { downButton.Enabled = false; // stop allowing the down button to be pressed } // every time the "down" button is pushed, it should give enough space to make the "up" button useable again upButton.Enabled = true; // enable the "up" button } } } //******************INSTRUCTIONS*********************** // // 2021-05-13 above rev'd and looks pretty efficient/okay // The code above handles saving and loading of data so that the programer just needs to connect new TextBoxes, ComboBoxes, and CheckBoxes to the respective event handlers. // TextBoxes (TextChanged event) --> textbox_TextChanged // CheckBoxes (CheckedChanged event) --> checkbox_CheckChanged // ComboBoxes (TextChanged event) --> comboBox_TextChanged // as long as User doesn't play with source files, everything should be fine // // It was my goal to make a template program that would make reading and writing automatic, so the programer didn't have to painstakingly add each // new field to every method that needed it ie. saving and loading etc. // This program pretty well does this. If you copy and paste the above into another program // I am hoping that you should be able to easily build another program with these same capabilities. // // Note: it won't save multi-line text boxes nor radio buttons // // Working with COMBO BOXES // Make sure when you make a combo box that you include a blank spot in the "collection" of "items". // by a "blank spot" I mean, simply hit enter and make a new line, but do not make a space on that line. // Also, make combo box's "DropDownStyle" as a "DropDownList" // // If you want the program to be able to scroll more, just change the boundaries in the event handlers for the UP and DOWN buttons. // HINT: Built your program, THEN play with the scrolling // // Note: there is very little safeguard for uploading the wrong file. I suppose someone could put in something checking that the extension is .txt, but // I have not yet done this. // There is however, a safeguard against loading files that are too big and/or with incompatible data which I think should catch basically any non-compatible/corrupt files. //****************** // all above rev'd and looks good 2021-05-13 TFH } }