files in C# - "file is used by another process" - c#

I have following code:
private void askforlocation()
{
if (File.Exists("location.txt"))
{
System.IO.StreamReader loc = new System.IO.StreamReader("location.txt");
string loca = loc.ReadToEnd();
if (loca != "")
{
int index = comboBox1.FindString(loca);
comboBox1.SelectedIndex = index;
}
else
{
label6.Text = "Please select the location!";
}
loc.Close();
}
else label6.Text = "Please select the location!";
}
It is supposed to read value "location" from the file and put it to the combo box, which works ok.
I run this script on Form1_Load.
Now, I have another script:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
string value = comboBox1.SelectedItem.ToString();
System.IO.File.WriteAllText("location.txt", value);
}
This one is supposed to record the choice so that user doesn't need to enter location every time.
What is happening is when I start a program, so the value is already set, then I try to change it (so that theoretically it should overwrite the previous one), but I get an exception, saying that file is already being used by another process.
I do close the file after I used it. I also tried FILE.DISPOSE.
What am I doing wrong?

I think what's happening here is that this code:
if (loca != "")
{
int index = comboBox1.FindString(loca);
comboBox1.SelectedIndex = index;
}
is causing the SelectedIndexChanged event to be raised on the combobox. When that event is raised, comboBox1_SelectedIndexChanged is called, and that method again tries to access location.txt.
To fix, I would first change the code in askforlocation to something like this:
if (File.Exists("location.txt"))
{
var loca = string.Emtpy;
using(var loc = new System.IO.StreamReader("location.txt"))
{
loca = loc.ReadToEnd();
}
....
}
since there's no need to keep the file open for longer than necessary (note that the using block will call the Dispose() method on the StreamReader when it exits, which in turn will call the Close() method). After that, I'd consider coming up with a way to keep the event from being fired when you set the selected index on the combobox (maybe use a flag or unwire/rewire the event handler).

It seems that you're changing the index of your combobox, thus writing to the same file before closing it. Call loca.Close() before writing to the file again.

comboBox1.SelectedIndex = index;
this will fire the event SelectedIndexChanged, so invoke the Close() method right behind ReadToEnd():
private void askforlocation()
{
if (File.Exists("location.txt"))
{
System.IO.StreamReader loc = new System.IO.StreamReader("location.txt");
string loca = loc.ReadToEnd();
loc.Close();//move that code to here
if (loca != "")
{
int index = comboBox1.FindString(loca);
comboBox1.SelectedIndex = index;
}
else
{
label6.Text = "Please select the location!";
}
//loc.Close();
}
else label6.Text = "Please select the location!";
}

Give this line loc.Close(); before setting the index of the combo box because the event is being raised earlier than you think.

You never need to call file.Close() or file.Dispose().
Please use a using statement ALWAYS (or mostly) when using a class that implements IDisposable. It will call the Dispose method for you.
using(System.IO.StreamReader loc = new System.IO.StreamReader("location.txt"))
{
string loca = loc.ReadToEnd();
}

Related

Storing value from try for use in second try C#

Good day fellow helpers, i have following problem:
(running MS Visual Community Edition 2015)
private void button4_Click(object sender, EventArgs e) // Senden
{
serialPort2.WriteLine("SR,00,002\r\n");
textBox1.Text = "gesendet";
textBox3.Text = "";
try
{
System.IO.StreamReader file = new System.IO.StreamReader("C:\\blub.txt");
String line = file.ReadToEnd();
string Hallo = line; \\in the beginning there is "0" in the file
file.Close();
decimal counter = Convert.ToDecimal(Hallo); \\just for testing
counter++;
string b = serialPort2.ReadLine();
string[] b1 = Regex.Split(b, "SR,00,002,"); \\cuts off unwanted input from device
decimal b2 = decimal.Parse(b1[1]); \\number like -3000
System.IO.StreamWriter test = new System.IO.StreamWriter("C:\\blub.txt");
test.WriteLine(counter);
test.Close();
textBox7.Text = "Das ist counter:" + counter;
}
catch (TimeoutException)
{
textBox3.Text = "Timeout";
throw;
}
}
Now, the Serialport is a device that returns a lengthmeasurment. As it is a bit weird, or just the way its build it start with a negitve number (between -5000 and -3370). Now as i want to get measurement on the screen that is realistic i want to set the value to 0 and calculate the difference.
Means: I start the programm - press send - get a value (say -3000) - press send again (after pushing the seonsor in) and get the value that its been pushed in > 0 by adding the difference to 0.
I only learned to store values externally when i had a C course a year back like i did within my programm. Is there a way to store the value from the first measurement in the programm so i can use it on the next send/try?
The counter was just for testing and I would exchange it for the "decimal b2"
I hope there is an easy fix for that, not really a pro with C# yet but i'm eager to learn. I thank the willing helpers in advance, MfG, Chris
OK, I will simplify this in order to show concept so it will not have all the code you are actually using.
So, what you want is to click on button, get some values and store them for next click.
Value is stored in variable. If you have variable in function that is handler for click event, as soon as function completes execution, value will be destroyed.
So, what you need is to create variable in outer scope (class level). Your function is already in class of the form so let's get to code:
class Form1
{
string BetweenClickStorage;
private void button4_Click(object sender, EventArgs e)
{
//Load data here
BetweenClickStorage = LoadedData;
}
}
After this, when you click again on the button, value will still be in BetweenClickStorage. It will be also available to all other buttons click handlers and other code in that form.
If I'm understanding your question correctly, the answer is simply to declare a variable outside the try/catch:
//declare variable //
var measurement;
// TRY #1 //
try
{
//assign value to the variable here
}
catch
{
}
// TRY #2 //
try
{
// reference variable here
}
catch
{
}

C# WinForm + Barcode Scanner input, TextChanged Error

UPDATE: SOLUTION AT END
I have a Winform, label1 will display some info returned from a SQL Search using the input (MemberID) received from barcode scanner via txtBoxCatchScanner.
Scenario is people swiping their MemberID Cards under the scanner as they pass through reception and the Winform automatically doing a Search on that MemberID and returning their info including for example "Expired Membership" etc on the receptionist's PC which has the winForm in a corner of her desktop.
I have the Below Code working fine on first swipe (eg. first person)
The number MemberID, for example 00888 comes up in the text box, ADO.NET pulls the data from SQL and displays it fine.
one thing to note maybe, the cursor is at the end of the memberID: 00888|
All good so far, THEN:
when swipe 2 (eg. next person) happens
their number (say, 00999) gets put onto the end of the first in the txtBox eg: 0088800999 so naturally when TextChanged Fires it searches for 0088800999 instead of 00999 ....
I've tried:
txtBoxCatchScanner.Clear();
and
txtBoxCatchScanner.Text = "";
and
reloading the form
at the end of my code to "refresh" the text box
but i guess they trigger the TextChanged Event
How can i refocus or ... clear the old number and cursor back to start of txtBox after the previous swipe has done its stuff...
I'm a beginner so I'm sure the code below is pretty crap....
But if anyone has time, please let me know how fix it to do what i want.
UPDATE:
Ok after much experimenting I''ve managed to get this 1/2 working now hopefully someone more experience can help me to completion! :P
if (txtBoxCatchScanner.Text.Length == 5)
{
label1.Text = txtBoxCatchScanner.Text; // just a label for testing .. shows the memmber ID
txtBoxCatchScanner.Select(0, 5);
}
SO scan 1, say 00888 , then that gets highlighted, scan 2 , say 00997 ... sweet! overwrites (not appends to) 00888 and does it's thing ... scan 2 0011289 ... DOH!!
Problem: not all barcodes are 5 digits!! they are random lengths!! Memeber ID range from 2 digit (eg. 25) to 10 digits, and would grow in the future...
Edit: Something I've discovered that is that the barcodes are read as indvidual key presses. I think this is why answer 1 below does not work and while the big probmlems:
for example with 00675 the input (?output) from the scanner is:
Down: Do
Up: Do
Down: Do
Up: Do
Down: D6
Up: D6
Down: D7
Up: D7
Down: D5
Up: D5
down: Retunn
Up: Return
other info: barcode scanner is: an Opticon OPL6845 USB
Thanks
private void txtBoxCatchScanner_TextChanged(object sender, EventArgs e)
{
Member member = new Member();
member.FirstName = "";
member.LastName = "";
//Get BarCode
//VALIDATE: Is a Number
double numTest = 0;
if (Double.TryParse(txtBoxCatchScanner.Text, out numTest))
{
//IS A NUMBER
member.MemberID = Convert.ToInt32(txtBoxCatchScanner.Text);
//SEARCH
//Search Member by MemberID (barcode)
List<Member> searchMembers = Search.SearchForMember(member);
if (searchMembers.Count == 0)
{
lblAlert.Text = "No Member Found";
}
else
{
foreach (Member mem in searchMembers)
{
lblMemberStatus.Text = mem.MemberStatus;
lblMemberName.Text = mem.FirstName + " " + mem.LastName;
lblMemberID.Text = mem.MemberID.ToString();
lblMessages.Text = mem.Notes;
if (mem.MemberStatus == "OVERDUE") // OR .. OR .. OR ...
{
lblAlert.Visible = true;
lblAlert.Text = "!! OVERDUE !!";
//PLAY SIREN aLERT SOUND
//C:\\WORKTEMP\\siren.wav
SoundPlayer simpleSound =
new SoundPlayer(#"C:\\WORKTEMP\\siren.wav");
simpleSound.Play();
}
else
{
lblAlert.Visible = true;
lblAlert.Text = mem.MemberStatus;
}
}
}
}
else
{
//IS NOT A NUMBER
lblAlert.Text = "INVALID - NOT A NUMBER";
////
//lblMemberName.Text = "";
//lblMemberID.Text = "";
//lblMemberID.Text = "";
}
SOLUTION:
The System won't let me answer my own question for another 3 hours, as I'm a newbie only 1 post, so will put here:
First thanks everyone for your help and Patience.
I Have finally figured a solition, not fully tested yet as its 2am and bed time.
following along from my updates where I had success but hit the variable length of MemberID problem. I've now overcome that with the Code below:
namespace SCAN_TESTING
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void txtBoxCatchScanner_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyValue == (char)Keys.Return)
{
e.Handled = true;
int barcodeLength = txtBoxCatchScanner.TextLength;
txtBoxCatchScanner.Select(0, barcodeLength);
//TEST
label3.Text = barcodeLength.ToString();
//TEST
label2.Text = txtBoxCatchScanner.Text;
}
}
I'll add this to my previous "real" code and test in the morning
But at this stage is doing exactly what I want! =]
Update: Tested it .. works exactly what needed:
private void txtBoxCatchScanner_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyValue == (char)Keys.Return)
{
e.Handled = true;
int barcodeLength = txtBoxCatchScanner.TextLength;
txtBoxCatchScanner.Select(0, barcodeLength);
//
//INSERT ORGINAL CODE HERE. No Changes were needed.
//
}//end of if e.KeyValue ...
}//end txtBoxCatchScanner_KeyUp
Hope that helps anyone in the future!! :)
Thanks again for the 2 very good solutions, I can see how they work, and learnt alot.
Just didn't work in my case - more likely due to myself or my error/not understanding, or scanner type.
I´m not exactly sure what the actual problem is.
txtBoxCatchScanner.Clear();
txtBoxCatchScanner.Text = "";
both trigger the "Changed" Event.
But they also clear the box. So that should be what you want to do.
You could check at the beginning if the box is actually empty, and return in case it is. Like:
if(txtBoxCatchScanner.Text == "" |txtBoxCatchScanner.Text == string.Empty)
return;
So nothing else happens, if the box is empty.
If I misunderstood your problem, please specify and I will try to help.
Regards
EDIT:
Your function should work if it looked something like this:
private void txtBoxCatchScanner_TextChanged(object sender, EventArgs e)
{
Member member = new Member();
member.FirstName = "";
member.LastName = "";
if(txtBoxCatchScanner.Text == "" | txtBoxCatchScanner.Text == string.Empty)
return; // Leave function if the box is empty
//Get BarCode
//VALIDATE: Is a Number
int numTest = 0;
if (int.TryParse(txtBoxCatchScanner.Text, out numTest))
{
//IS A NUMBER
//member.MemberID = Convert.ToInt32(txtBoxCatchScanner.Text);
member.MemberID = numTest; // you already converted to a number...
//SEARCH
//Search Member by MemberID (barcode)
List<Member> searchMembers = Search.SearchForMember(member);
if (searchMembers.Count == 0)
{
lblAlert.Text = "No Member Found";
}
else
{
foreach (Member mem in searchMembers)
{
lblMemberStatus.Text = mem.MemberStatus;
lblMemberName.Text = mem.FirstName + " " + mem.LastName;
lblMemberID.Text = mem.MemberID.ToString();
lblMessages.Text = mem.Notes;
if (mem.MemberStatus == "OVERDUE") // OR .. OR .. OR ...
{
lblAlert.Visible = true;
lblAlert.Text = "!! OVERDUE !!";
//PLAY SIREN aLERT SOUND
//C:\\WORKTEMP\\siren.wav
SoundPlayer simpleSound =
new SoundPlayer(#"C:\\WORKTEMP\\siren.wav");
simpleSound.Play();
}
else
{
lblAlert.Visible = true;
lblAlert.Text = mem.MemberStatus;
}
}
}
}
else
{
//IS NOT A NUMBER
lblAlert.Text = "INVALID - NOT A NUMBER";
////
//lblMemberName.Text = "";
//lblMemberID.Text = "";
//lblMemberID.Text = "";
}
txtBoxCatchScanner.Clear();
}
The barcode scanner you use seems to function as a HID - a keyboard emulation. Every simple barcode scanner I know (and I'm working with them on a daily basis) has the option of specifying a suffix for the scanned barcode. Change the suffix to CRLF and add a default button to your form. Scanning a barcode that ends with CRLF will then automatically "push the button".
Move the code that performs the checks from TextChanged event in to the event handler for the buttons Click event and remove the TextChanged event handler. Then, when the button is clicked, also clear the text box and set the focus back to the text box.
You should be good to go, now.
You can easily check whether the barcode scanner already has the correct suffix configured: Open up Notepad and scan some barcodes. If they all appear on separate lines, then everything's fine. Otherwise you'll need to scan some configuration barcodes from the scanner's manual.
To sum it all up, this should be the code for the button's Click event:
private void btnCheckMember_Click(object sender, EventArgs e)
{
Member member = new Member();
member.FirstName = "";
member.LastName = "";
string memberText = txtBoxCatchScanner.Text.Trim();
txtBoxCatchScanner.Text = String.Empty;
int numTest = 0;
if (String.IsNullOrEmpty(memberText) ||!Int32.TryParse(memberText, out numTest))
{
//IS NOT A NUMBER
lblAlert.Text = "INVALID - NOT A NUMBER";
return;
}
member.MemberID = numTest;
List<Member> searchMembers = Search.SearchForMember(member);
if (searchMembers.Count == 0)
{
lblAlert.Text = "No Member Found";
}
else
{
foreach (Member mem in searchMembers)
{
lblMemberStatus.Text = mem.MemberStatus;
lblMemberName.Text = mem.FirstName + " " + mem.LastName;
lblMemberID.Text = mem.MemberID.ToString();
lblMessages.Text = mem.Notes;
if (mem.MemberStatus == "OVERDUE") // OR .. OR .. OR ...
{
lblAlert.Visible = true;
lblAlert.Text = "!! OVERDUE !!";
SoundPlayer simpleSound = new SoundPlayer(#"C:\\WORKTEMP\\siren.wav");
simpleSound.Play();
}
else
{
lblAlert.Visible = true;
lblAlert.Text = mem.MemberStatus;
}
}
}
This solution avoids the following problems:
The event being triggered upon every character added/removed from the content of the text box (which is also the case when scanning a barcode: They are added one by one as if they were entered on a keyboard)
Resulting from 1. the problem that a member check is performed upon every entered character
Resulting from 2. the problem that member XYZ will never be found if there is a member XY in the database, as the check stops after finding XY
Resulting from 3. the problem that member XY will also not be found, but only member Z, because in 3. the text box is cleared and Z is the only character being entered.
The best way to clear the textBox on the next textChange event.
Insert this line
txtBoxCatchScanner.SelectAll();
at the end of TextChange function.. This will select the text, so that i can be replaced easily on the next event.

Accessing a dynamic RadioButtonList array object

I've created an array of RadioButtonList class, but apparently can't seem to access it or use the answer retrieved from it. I always get the exception: Object reference not set to an instance of an object
static int jimmy = 0;
protected void Button5_Click(object sender, EventArgs e)
{
int sizeOfPain = GlobalVariables.sympLCWR1Pain.Count;
RadioButtonList[] RBLPain = new RadioButtonList[sizeOfPain];
Label1.Visible = false;
RadioButtonList1.Visible = false;
Label[] Labella = new Label[sizeOfPain];
if (jimmy < sizeOfPain)
{
Labella[jimmy] = new Label();
RBLPain[jimmy] = new RadioButtonList();
Labella[jimmy].Text = GlobalVariables.sympLCWR1Pain[jimmy];
RBLPain[jimmy].Items.Add("Yes");
RBLPain[jimmy].Items.Add("No");
Panel1.Controls.Add(Labella[jimmy]);
Panel1.Controls.Add(RBLPain[jimmy]);
if (RBLPain[jimmy].SelectedIndex == 0)
{
GlobalVariables.sympLCWR1Yes.Add(GlobalVariables.sympLCWR1Pain[jimmy]);
}
}
else
{
Label2.Text = "YOUS DONE!";
Label3.Text = GlobalVariables.sympLCWR1Yes[0];
Button5.Visible = false;
}
jimmy++;
}
i get the exception at the if condition. Any help would be appreciated thanks :)
What that error means is that you are trying to access something that hasn't yet been instantiated. In your updated code, I see you have the following within your click event handler:
RadioButtonList[] RBLPain = new RadioButtonList[sizeOfPain];
Label[] Labella = new Label[sizeOfPain];
This means that every time the click event is handled, you are redeclaring the RBLPain and Labella arrays. Also, when execution leaves leaves the handler, the variables fall out of scope, so you will not be able to use them in other functions, or use the changes made within the handler from one call to the next. I don't know what the rest of your code is doing, but despite the seemingly unnecessary arrays, execution should survive your click event.
In your original post you were trying to access the SelectedItem.Text property of the RBLPain[jimmy]. In this revision you are checking the SelectedIndex instead. When SelectedIndex is -1, SelectedItem will be null, perhaps this led to your original problem. Regardless of what is changed on your form, because you are creating a new RadioButtonList during every click event, you are not working with the values from your form - SelectedIndex will always be -1 from what I can see.
I dont understand why you checking the condition .If you are creating rbl on buttonclick first item should always get selected. Anyway use RBLPain[jimmy].SelectedIndex=0;before if condition.

Context.Items clears during page refresh/transfer

I am working on a class project and I've run into a problem I can't figure out. I have a feeling it's actually pretty easy, but I've been working on stuff so long I can't think straight anymore.
I have a login page that allows a user to login and pass 2 data items to the next page using Context.Items and Server.Transfer. Here is the code snippet:
Context.Items["preferred"] = true;
Context.Items["pageNum"] = 1;
Server.Transfer("ProductsShelf.aspx");
On the "ProductsShelf" page I can access those two items and use the data like so:
pageNumber = (int)Context.Items["pageNum"];
I am then using a switch-statement with pageNumber to display certain information:
switch (pageNumber)
{
case 1:
imgProd.ImageUrl = "assets/laptop.bmp";
lbl_Name.Text = "Laptop";
lbl_desc.Text = "This is a cheap laptop!";
lbl_price.Text = "199.99";
break;
}
Obviously there's other entries I'm omitting. What I want to do is click a next or previous button and use the event to change the Context.Items["pageNum"] data so the Page_Load() event uses different data in the switch-statement. Hope that makes sense. Here is one of the button click events:
protected void btn_Prev_Click(object sender, EventArgs e)
{
if (pageNumber == 1 || pageNumber == 2)
{
Context.Items["pageNum"] = 1;
}
else if (pageNumber == 3)
{
Context.Items["pageNum"] = 2;
}
Context.Items["preferred"] = preferredCustomer;
Server.Transfer("ProductsShelf.aspx");
}
The problem is that before the button click event fires, the form posts and clears the Context.Items and pageNumber values. This means that the button event if-statements never fire and it results in:
pageNumber = (int)Context.Items["pageNum"];
Being null, throwing an exception and making me very sad. So my question is, how can I go about retaining the values? Should I switch to Response.Redirect and have something like ?page=1 in the URL? Or will that clear too when the form posts? Hopefully I'm not doing this completely wrong.
If TL;DR, here's a quick summary:
Context.Items has 2 values passed with Server.Transfer
These values determine what's shown on the next page
The form clears Context.Items and variables before button click event fires
The values are null, the if-statement doesn't run, and the app throws an exception
Question: how should I go about retaining those values?
Thanks a lot. :)
HttpContext items can be used within one request only - it will be recreated for next request so your values are bound to lose. You should use view-state to preserve data across post-backs. In page load, you should check if data exists in context and then copy it to view-state. Then in button click events, you can read the data from view-state, put into the context items and do server.transfer.
Here's simple sample code:
private int PageNumber
{
get
{
var value = ViewState["pageNum"];
return null == value? 1: (int)value;
}
set
{
ViewState["pageNum"] = value;
}
}
private bool IsPreferredCustomer
{
get
{
var value = ViewState["preferred"];
return null == value? false: (bool)value;
}
set
{
ViewState["preferred"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
var preferred = Context.Items["preferred"];
if (null != preferred)
{
IsPreferredCustomer = (bool)preferred;
}
var pageNum = Context.Items["pageNum"];
if (null != pageNum )
{
PageNumber = (int)Context.Items["pageNum "];
}
}
Use the same PageNumber property in event code.

Google Suggestish text box (autocomplete)

What would be the best way to develop a text box that remembers the last x number of entries that were put into it. This is a standalone app written with C#.
This is actually fairly easy, especially in terms of showing the "AutoComplete" part of it. In terms of remembering the last x number of entries, you are just going to have to decide on a particular event (or events) that you consider as an entry being completed and write that entry off to a list... an AutoCompleteStringCollection to be precise.
The TextBox class has the 3 following properties that you will need:
AutoCompleteCustomSource
AutoCompleteMode
AutoCompleteSource
Set AutoCompleteMode to SuggestAppend and AutoCompleteSource to CustomSource.
Then at runtime, every time a new entry is made, use the Add() method of AutoCompleteStringCollection to add that entry to the list (and pop off any old ones if you want). You can actually do this operation directly on the AutoCompleteCustomSource property of the TextBox as long as you've already initialized it.
Now, every time you type in the TextBox it will suggest previous entries :)
See this article for a more complete example: http://www.c-sharpcorner.com/UploadFile/mahesh/AutoCompletion02012006113508AM/AutoCompletion.aspx
AutoComplete also has some built in features like FileSystem and URLs (though it only does stuff that was typed into IE...)
#Ethan
I forgot about the fact that you would want to save that so it wasn't a per session only thing :P But yes, you are completely correct.
This is easily done, especially since it's just basic strings, just write out the contents of AutoCompleteCustomSource from the TextBox to a text file, on separate lines.
I had a few minutes, so I wrote up a complete code example...I would've before as I always try to show code, but didn't have time. Anyway, here's the whole thing (minus the designer code).
namespace AutoComplete
{
public partial class Main : Form
{
//so you don't have to address "txtMain.AutoCompleteCustomSource" every time
AutoCompleteStringCollection acsc;
public Main()
{
InitializeComponent();
//Set to use a Custom source
txtMain.AutoCompleteSource = AutoCompleteSource.CustomSource;
//Set to show drop down *and* append current suggestion to end
txtMain.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
//Init string collection.
acsc = new AutoCompleteStringCollection();
//Set txtMain's AutoComplete Source to acsc
txtMain.AutoCompleteCustomSource = acsc;
}
private void txtMain_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
//Only keep 10 AutoComplete strings
if (acsc.Count < 10)
{
//Add to collection
acsc.Add(txtMain.Text);
}
else
{
//remove oldest
acsc.RemoveAt(0);
//Add to collection
acsc.Add(txtMain.Text);
}
}
}
private void Main_FormClosed(object sender, FormClosedEventArgs e)
{
//open stream to AutoComplete save file
StreamWriter sw = new StreamWriter("AutoComplete.acs");
//Write AutoCompleteStringCollection to stream
foreach (string s in acsc)
sw.WriteLine(s);
//Flush to file
sw.Flush();
//Clean up
sw.Close();
sw.Dispose();
}
private void Main_Load(object sender, EventArgs e)
{
//open stream to AutoComplete save file
StreamReader sr = new StreamReader("AutoComplete.acs");
//initial read
string line = sr.ReadLine();
//loop until end
while (line != null)
{
//add to AutoCompleteStringCollection
acsc.Add(line);
//read again
line = sr.ReadLine();
}
//Clean up
sr.Close();
sr.Dispose();
}
}
}
This code will work exactly as is, you just need to create the GUI with a TextBox named txtMain and hook up the KeyDown, Closed and Load events to the TextBox and Main form.
Also note that, for this example and to make it simple, I just chose to detect the Enter key being pressed as my trigger to save the string to the collection. There is probably more/different events that would be better, depending on your needs.
Also, the model used for populating the collection is not very "smart." It simply deletes the oldest string when the collection gets to the limit of 10. This is likely not ideal, but works for the example. You would probably want some sort of rating system (especially if you really want it to be Google-ish)
A final note, the suggestions will actually show up in the order they are in the collection. If for some reason you want them to show up differently, just sort the list however you like.
Hope that helps!
I store the completion list in the registry.
The code I use is below. It's reusable, in three steps:
replace the namespace and classname in this code with whatever you use.
Call the FillFormFromRegistry() on the Form's Load event, and call SaveFormToRegistry on the Closing event.
compile this into your project.
You need to decorate the assembly with two attributes: [assembly: AssemblyProduct("...")] and [assembly: AssemblyCompany("...")] . (These attributes are normally set automatically in projects created within Visual Studio, so I don't count this as a step.)
Managing state this way is totally automatic and transparent to the user.
You can use the same pattern to store any sort of state for your WPF or WinForms app. Like state of textboxes, checkboxes, dropdowns. Also you can store/restore the size of the window - really handy - the next time the user runs the app, it opens in the same place, and with the same size, as when they closed it. You can store the number of times an app has been run. Lots of possibilities.
namespace Ionic.ExampleCode
{
public partial class NameOfYourForm
{
private void SaveFormToRegistry()
{
if (AppCuKey != null)
{
// the completion list
var converted = _completions.ToList().ConvertAll(x => x.XmlEscapeIexcl());
string completionString = String.Join("¡", converted.ToArray());
AppCuKey.SetValue(_rvn_Completions, completionString);
}
}
private void FillFormFromRegistry()
{
if (!stateLoaded)
{
if (AppCuKey != null)
{
// get the MRU list of .... whatever
_completions = new System.Windows.Forms.AutoCompleteStringCollection();
string c = (string)AppCuKey.GetValue(_rvn_Completions, "");
if (!String.IsNullOrEmpty(c))
{
string[] items = c.Split('¡');
if (items != null && items.Length > 0)
{
//_completions.AddRange(items);
foreach (string item in items)
_completions.Add(item.XmlUnescapeIexcl());
}
}
// Can also store/retrieve items in the registry for
// - textbox contents
// - checkbox state
// - splitter state
// - and so on
//
stateLoaded = true;
}
}
}
private Microsoft.Win32.RegistryKey AppCuKey
{
get
{
if (_appCuKey == null)
{
_appCuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(AppRegistryPath, true);
if (_appCuKey == null)
_appCuKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(AppRegistryPath);
}
return _appCuKey;
}
set { _appCuKey = null; }
}
private string _appRegistryPath;
private string AppRegistryPath
{
get
{
if (_appRegistryPath == null)
{
// Use a registry path that depends on the assembly attributes,
// that are presumed to be elsewhere. Example:
//
// [assembly: AssemblyCompany("Dino Chiesa")]
// [assembly: AssemblyProduct("XPathVisualizer")]
var a = System.Reflection.Assembly.GetExecutingAssembly();
object[] attr = a.GetCustomAttributes(typeof(System.Reflection.AssemblyProductAttribute), true);
var p = attr[0] as System.Reflection.AssemblyProductAttribute;
attr = a.GetCustomAttributes(typeof(System.Reflection.AssemblyCompanyAttribute), true);
var c = attr[0] as System.Reflection.AssemblyCompanyAttribute;
_appRegistryPath = String.Format("Software\\{0}\\{1}",
p.Product, c.Company);
}
return _appRegistryPath;
}
}
private Microsoft.Win32.RegistryKey _appCuKey;
private string _rvn_Completions = "Completions";
private readonly int _MaxMruListSize = 14;
private System.Windows.Forms.AutoCompleteStringCollection _completions;
private bool stateLoaded;
}
public static class Extensions
{
public static string XmlEscapeIexcl(this String s)
{
while (s.Contains("¡"))
{
s = s.Replace("¡", "¡");
}
return s;
}
public static string XmlUnescapeIexcl(this String s)
{
while (s.Contains("¡"))
{
s = s.Replace("¡", "¡");
}
return s;
}
public static List<String> ToList(this System.Windows.Forms.AutoCompleteStringCollection coll)
{
var list = new List<String>();
foreach (string item in coll)
{
list.Add(item);
}
return list;
}
}
}
Some people shy away from using the Registry for storing state, but I find it's really easy and convenient. If you like, You can very easily build an installer that removes all the registry keys on uninstall.

Categories

Resources