I have a program where the user can add products to the system, and then search them by the product name.
Everything is working fine, except at the moment is able to enter two products with the same name. I need the program to not allow this.
I have a method assigned to the 'Add' button, which saves the product name, customer name and firmware location to a text file. Here is that method:
private void button_Click(object sender, RoutedEventArgs e)
{
bool found = false;
string searchTerm = productNameTextBox.Text.ToUpper();
if ((productNameTextBox.Text == "") || (customerNameTextBox.Text == "") || (firmwareLocationTextBox.Text == ""))
{
MessageBox.Show("Please fill in all the text boxes");
}
else if (Contains(searchTerm) == true)
{
MessageBox.Show("Product already added");
}
else
{
string inputCustomerName = customerNameTextBox.Text.ToUpper();
string inputProductName = productNameTextBox.Text.ToUpper();
string inputFirmwareLocation = firmwareLocationTextBox.Text;
try
{
Product newProduct = new Product(inputProductName, inputCustomerName, inputFirmwareLocation);
newProduct.Save("Products.txt");
File.AppendAllText("ProductNames.txt", inputProductName + Environment.NewLine);
MessageBox.Show("Product added");
emptyTheTextBoxes();
}
catch
{
MessageBox.Show("Product could not be added");
}
}
}
I have also made a method which will search a text file to see if the users product name has already been stored, and then return a Boolean. This is the method:
public bool Contains (string searchTerm)
{
string line;
bool found = false;
System.IO.StreamReader file = new System.IO.StreamReader("ProductNames.txt");
while ((line = file.ReadLine()) != null)
{
if (line.Contains(searchTerm))
{
found = true;
break;
}
}
if (found == true)
{
return true;
}
else
{
return false;
}
file.Close();
}
When I try to save the input, a message box appears saying "Product could not be added". However, if I comment out the else if statement which calls the method, it works fine.
I thought it may be because I open the file when the method is called, and maybe it wasn't closing properly. So I added the 'file.Close()' and it hasn't made a difference.
I feel like I've just made a silly mistake somewhere, but its been bugging me for hours! Definitely appreciate a fresh pair of eyes!
Thanks
Lucy
In general I would suggest that you separate out your persistence of objects from your object/data management.
You are trying to read and write to the filesystem for the same file in different parts of the program and it seems like you are having an issue with the file not being released, probably because you didn't close it correctly.
You are trying to treat the file system as if it is a database, and this is probably not the best approach. There are of course use cases where this might be needed.
Instead I would suggest the following approach.
During start up read the file. Load the products into a collection
which you keep in memory.
Allow your program to read, update, create, delete products in the
collection.
During shutdown (can also be triggered manually if you want), save
your products onto disk.
This will allow you to avoid such issues. And also be quicker.
Optionally you could also then use something like HashSet<T> for your collection. This does not allow duplicate entries (remember to override equals and hashcode in your Product object). Then when trying to add to the collection if it returns false then it was not added, which would indicate a duplicate. So this might make it easier and quicker to check for you.
I had the file.close() in the wrong place. Here is where I moved it to:
public bool Contains (string searchTerm)
{
string line;
bool found = false;
System.IO.StreamReader file = new System.IO.StreamReader("ProductNames.txt");
while ((line = file.ReadLine()) != null)
{
if (line.Contains(searchTerm))
{
found = true;
break;
}
}
file.Close();
if (found == true)
{
return true;
}
else
{
return false;
}
}
Related
I am new to working with System.IO.
I have an application that grabs a Json file from the web and only grabs partial data to display on the Windows Application Form's controls.
The form allows the user to save the data as a new file and load a file if ONLY it contains the "indicator" I added in when the file was saved that tells the program it was saved by my program.
Everything works.
Whenever a file that doesn't contain that indicator is loaded to the program, it doesn't show nothing which is what I want it to do, but I also want a Messagebox.Show() to pop up and let the user know why the values are empty and why nothing happened.
if(openFile.ShowDialog() == DialogResult.OK)
{
string dataLine = string.Empty;
using (StreamReader read = new StreamReader(File.OpenRead(openFile.FileName)))
{
dataLine = read.ReadToEnd();
string[] beginningOfData = dataLine.Split(',');
string temporary = string.Empty;
foreach (string line in beginningOfData)
{
//Indicator
if(line.Contains("Indicator")
{
temporary = line.substring(9);
//Goes through the rest of the file
//Converts data to control value and adds it
}
else
{
//Where I tried to display the message box
}
}
}
}
This what I've tried, but it wasn't working as I wanted it too
else
{
MessageBox.Show("Can't load data.");
}
It would still show the MessageBox even if it read that the indicator was there and displayed the data inside the corresponding controls. Also whenever I tried to close the MessageBox it would just show it again.
So I decided to this instead:
else if(!line.contains("Indicator"))
{
MessageBox.Show("Can't load data.");
break;
}
and in this way too:
else
{
if(!line.contains("Indicator"))
{
MessageBox.Show("Can't load data.");
break;
}
}
I also tried making it more specific by doing
if(line.contains("Indicator") == false)
{
//Code here
}
But it would still display it even if the file was created by the program.
The break; did stop the message box from re-appearing again, but I only want the MessageBox to show when it's the incorrect text file (not containing the indicator) and allow me to close the MessageBox to try again.
You can wrap the foreach into an if statement which will use some LINQ code to determine if all lines contain "indicator":
if (beginningOfData.All(line => line.ToLower().Contains("indicator")))
{
string temporary = string.Empty;
foreach (string line in beginningOfData)
{
temporary = line.Substring(9);
//Goes through the rest of the file
//Converts data to control value and adds it
}
}
else
{
System.Windows.Forms.MessageBox.Show("Can't load data.");
}
Contains is case-sensitive. Try this for your evaluation:
line.IndexOf("Indicator", StringComparison.InvariantCultureIgnoreCase) >= 0
I did this instead and it worked for my application
if(openFile.ShowDialog() == DialogResult.OK)
{
string dataLine = string.Empty;
using (StreamReader read = new StreamReader(File.OpenRead(openFile.FileName)))
{
//Changed happened here
dataLine = read.ReadToEnd();
string[] beginningOfData = dataLine.Split(',');
string temporary = string.Empty;
if(beginningOfData.Contains("Indicator"))
{
temporary = dataLine.Substring(9);
foreach(string realData in beginningOfData)
{
//Goes through file
}
}
else
{
MessageBox.Show("Can't load data");
}
}
}
Really new to C#. I need to search a text file for a keyword. If after the whole file is searched, the keyword is found pop a message box. If after the whole file is searched, the keyword is NOT found pop a message box.
So far I have this below. The problem is that it reads the file line by line. If in the first line the keyword is not found it shows the alert "Not found". Then goes to the next line and shows "Not found" again. And so on. I need the script to search the whole file, and only then show "Not found" only once. Thank you!
private void SearchButton_Click(object sender, EventArgs e)
{
System.IO.StreamReader file = new System.IO.StreamReader("c:\\test.txt");
String line;
String[] array;
while ((line = file.ReadLine()) != null)
{
if (line.Contains("keyword"))
{
MessageBox.Show("Keyword found!");
}
else
{
MessageBox.Show("Keyword not found!");
}
}
}
Try using File class instead of readers (which you have to Dispose in order to prevent resource leakage):
bool found = File
.ReadLines("c:\\test.txt") // Try avoid "All" when reading: ReadAllText, ReadAllLines
.Any(line => line.Contains("keyword"));
if (found)
MessageBox.Show("Keyword found!");
else
MessageBox.Show("Keyword not found!");
Your code amended (if you insist on StreamReader):
private void SearchButton_Click(object sender, EventArgs e) {
// Wra IDisposable (StreamReader) into using in order to prevent resource leakage
using (file = new StreamReader("c:\\test.txt")) {
string line;
while ((line = file.ReadLine()) != null)
if (line.Contains("keyword")) {
MessageBox.Show("Keyword found!");
return; // Keyword found, reported and so we have nothing to do
}
}
// File read with no positive result
MessageBox.Show("Keyword not found!");
}
File.ReadAllText is better suited for that, you can read in all the text at once in one string:
string file = File.ReadAllText("path");
if (file.Contains(keyword)) {
//..
}
else {
//..
}
or in one line:
if (File.ReadAllText("path").Contains("path")) {
}
else {
}
Like stated in the comments, you can run out of memory for very large files but for normal day to day use this wont happen.
In my application the user can enter a filename. Before processing I'd like to check if the input String is a valid filename on Windows Vista.
Whats the easiest way to do that?
By valid I'm reffering to legal and non-existing
Check whether filename.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 and !File.Exists(Path.Combine(someFolder, filename))
Check against GetInvalidFileNameChars():
var isValid = !string.IsNullOrEmpty(fileName) &&
fileName.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 &&
!File.Exists(Path.Combine(sourceFolder, fileName));
If the file is going to be created, You should use a file dialog to specify the directory path. There's a short list of illegal characters for file names.
The only truly reliable way to tell if a file name is acceptable is to try it. Permissions
is a morass.
I use this:
public static bool IsValidFileName(string name) {
if(string.IsNullOrWhiteSpace(name)) return false;
if(name.Length > 1 && name[1] == ':') {
if(name.Length < 4 || name.ToLower()[0] < 'a' || name.ToLower()[0] > 'z' || name[2] != '\\') return false;
name = name.Substring(3);
}
if(name.StartsWith("\\\\")) name = name.Substring(1);
if(name.EndsWith("\\") || !name.Trim().Equals(name) || name.Contains("\\\\") ||
name.IndexOfAny(Path.GetInvalidFileNameChars().Where(x=>x!='\\').ToArray()) >= 0) return false;
return true;
}
Should take care of everything but reserved names, permissions, and length restrictions. This accepts both relative and absolute filenames.
This is just an idea. One should populate the exception list:
public static bool IsValidFilename(string filename)
{
try
{
File.OpenRead(filename).Close();
}
catch (ArgumentException) { return false; }
catch (Exception) { }
return true;
}
For first part(Valid Filename), I use all ways and a temporary file creation to check if a file can be named as expected or throws an exception.
In some cases creating a file will not raise an exception until trying to delete it(eg: CON).
I also usa removePath arg to dictate it that file is just the name of file without its path.
using System.IO;
using System.Text;
private static readonly byte[] TestFileBytes = Encoding.ASCII.GetBytes(#"X");
public bool IsFileNameValid(string file, bool removePath = false)
{
try
{
if (string.IsNullOrEmpty(file))
return false;
string fileNamePart = removePath ? Path.GetFileName(file) : file;
if (fileNamePart.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
return false;
string fileName = Path.Combine(Path.GetTempPath(), fileNamePart);
using FileStream fileStream = File.Create(fileName);
{
fileStream.Write(TestFileBytes, 0, TestFileBytes.Length);
}
File.Delete(fileName);
return true;
}
catch
{
return false;
}
}
If there is any denial of access to temp folder use a custom folder for creating test file.
This method will result false for . or .. or ... r any sequence of dot-only names in Windows, and also you can't create them manually, but those are not actually invalid names! those are uncreatable names for file or something like that ;).
And for next part(Not exists) just use: !File.Exists(yourFileNameWithPath).
If you create a DirectoryInfo for the file, it will throw an exception if there are any problems with the file/directory name, either invalid chars or length.
DirectoryInfo di = new DirectoryInfo(myFileName);
Console.WriteLine("This filename/path could be valid. The folder might not exist yet though.")
if(di.Exists)
Console.WriteLine("This file already exist.")
Not great that it is using exceptions for flow control, but you can be confident that it is right. OTOH, if you already planned to give the calling code an exception, mission accomplished.
I am working on selecting a text file with a folder pathway via a Windows form in C# and gathering information on each pathway. At the minute, I can import the file and display only the second pathway in the text file, but no information on the folder. Here is the code I have:
private void btnFilePath_Click(object sender, EventArgs e)
{
//creating a stream and setting its value to null
Stream myStream = null;
//allowing the user select the file by searching for it
OpenFileDialog open = new OpenFileDialog();
open.InitialDirectory = "c:\\";
open.Filter = "txt files (*.txt)|*.txt";
open.FilterIndex = 2;
open.RestoreDirectory = true;
//if statement to print the contents of the file to the text box
if (open.ShowDialog() == DialogResult.OK)
{
try
{
if ((myStream = open.OpenFile()) != null)
{
using (myStream)
{
txtFilePath.Text = string.Format("{0}", open.FileName);
if (txtFilePath.Text != "")
{
lstFileContents.Text = System.IO.File.ReadAllText(txtFilePath.Text);
//counting the lines in the text file
using (var input = File.OpenText(txtFilePath.Text))
{
while (input.ReadLine() != null)
{
//getting the info
lstFileContents.Items.Add("" + pathway);
pathway = input.ReadLine();
getSize();
getFiles();
getFolders();
getInfo();
result++;
}
MessageBox.Show("The number of lines is: " + result, "");
lstFileContents.Items.Add(result);
}
}
else
{
//display a message box if there is no address
MessageBox.Show("Enter a valid address.", "Not a valid address.");
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read the file from disk. Original error: " + ex.Message);
}
}
}
I was thinking that copying each line to a variable using a foreach or putting each line into an array and looping through it to gather the information.
Can anyone advise me which would be most suitable so I can go to MSDN and learn for myself, because, I'd prefer to learn it instead of being given the code.
Thanks!
I am not sure what your question is since you seemed to have answered it. If you want us to review it you question would be better suited to Code Review: https://codereview.stackexchange.com/
If you want to use MSDN look here: http://msdn.microsoft.com/en-us/library/System.IO.File_methods(v=vs.110).aspx
Spoiler alert, here is how I would do it:
string[] lines = null;
try
{
lines = File.ReadAllLines(path);
}
catch(Exception ex)
{
// inform user or log depending on your usage scenario
}
if(lines != null)
{
// do something with lines
}
to just gather all lines into array i would use
var lines = File.ReadAllLines(path);
If you want to have more reference rather than the answer itself, take these links one by one all of them explaining things in different manner.
C# File.ReadLines
How to: Read From a Text File (C# Programming Guide)
How to: Read a Text File One Line at a Time (Visual C#)
Hope it will help you to learn more about File IO operations in C#.
I need to know if is possible to save the state of a CheckBox in C#? I mean if I check the CheckBox and close the program, once I restart the program the CheckBox will still stay checked. Is it possible to?
This is rather a general question. You need to serialise the state yourself somehow, but how, and where to depends on a lot of things.
Possibly take a look at a Settings file for a simple start.
For this, you will need to record the state of the CheckBox yourself. For example, you could store the value in an XML document that would contain your application's UI states. An example, in a very simplistic form, you could do the following:
// ... as the application is closing ...
// Store the state of the check box
System.IO.File.WriteAllText(#"C:\AppFile.txt", this.CheckBox1.IsChecked.ToString());
// ...
// ... as the application is being initialized ...
// Read the state of the check box
string value = System.IO.File.ReadAllText(#"C:\AppFile.txt");
this.CheckBox1.IsChecked = bool.Parse(value);
As you can see, this simply stores the value in a file and reads it back in during initialization. This is not a great way of doing it, but it demonstrates a possible process to follow.
The easiest way of doing this would be to use a config XML file. You can add this very easily through visual studio, there is no need to use registry and it can be used if the app is portable as the settings are saved with the program. A tutorial of how to set this up is here:
http://www.sorrowman.org/c-sharp-programmer/save-user-settings.html
If you are using Web application cookie enabled and storing the information in cookie then it is possible.
You can checkout http://www.daniweb.com/web-development/aspnet/threads/30505
http://asp.net-tutorials.com/state/cookies/
In C# you can use the Settings file. Information how to use it can be found here : http://msdn.microsoft.com/en-us/library/aa730869%28v=vs.80%29.aspx
If you wanted to save this to the Registry you could do something like this
RegistryKey Regkey = "HKEY_CURRENT_USER\\Software\\MyApplication";
RegKey.SetValue("Checkbox", Checkbox.Checked);
but personally I would save it to the .Config file
Here is an example of how to do it using the Config File if you so desire
private static string getConfigFilePath()
{
return Assembly.GetExecutingAssembly().Location + ".config";
}
private static XmlDocument loadConfigDocument()
{
XmlDocument docx = null;
try
{
docx = new XmlDocument();
docx.Load(getConfigFilePath());
return docx;
}
catch (System.IO.FileNotFoundException e)
{
throw new Exception("No configuration file found.", e);
}
}
private void rem_CheckedChanged(object sender, EventArgs e)
{
if (rem.Checked == true)
{
rem.CheckState = CheckState.Checked;
System.Xml.XmlDocument docx = new System.Xml.XmlDocument();
docx = loadConfigDocument();
System.Xml.XmlNode node;
node = docx.SelectSingleNode("//appsettings");
try
{
string key = "rem.checked";
string value = "true";
XmlElement elem = (XmlElement)node.SelectSingleNode(string.Format("//add[#key='{0}']", key));
if (elem != null)
{
elem.SetAttribute("value", value);
}
else
{
elem = docx.CreateElement("add");
elem.SetAttribute("key", key);
elem.SetAttribute("value", value);
node.AppendChild(elem);
}
docx.Save(getConfigFilePath());
}
catch (Exception e2)
{
MessageBox.Show(e2.Message);
}
}
}
I would use Settings like this:
Assuming a boolean setting called boxChecked has been created.
//if user checks box
Properties.Settings.Default.boxChecked = true;
Properties.Settings.Default.Save();
//...
//when the program loads
if(Properties.Settings.Default.boxChecked)
{
checkBox1.Checked = true;
}
else
{
checkBox1.Checked = false;
}