I'm have a combobox listing some single byte cmds that can be sent to some custom hardware I've developed. With the C# code below, users can currently select commands from the cbCANcmd by name only. I also found ways to display the values only, but prefer to display both name and number.
How can I display both hex value & cmd in the cb dropdown? e.g. 0d - CommsSoftReset
And still able to type in un-enumerated values, like 05, for unlisted commands?
Can I hide more dangerous items easily(i.e. 09-WipeAllFlash), but still numerically enter them as per #2 above?
Note: The enum is from a straight C language .h file, and the header is changing more times daily than the c# app. For this reason, I'm hoping to avoid adding [Description()] for each value, or dramatically change the formatting, since it will have to be copied and redone many times as we continue development)
P.S. I normally write only in simple C, for the 8bit micro receiving these commands..As this is my first test app in c#, please be gentle :)
enum COMMS_MESSAGE_ID_t : byte
{
CommsRAMRead = 0x00,
CommsRAMWrite = 0x01,
CommsCommitRAMbufferToFlash = 0x02,
CommsWipeAllFlash = 0x0c,
CommsSoftReset = 0x0d,
CommsGetVersion = 0xff
}
private void SendTab_Enter(object sender, EventArgs e)
{
//need to populate the pulldowns with the available commands
cbCANcmd.DataSource = Enum.GetValues(typeof(COMMS_MESSAGE_ID_t));
}
private void SendDownlinkCmd_Click(object sender, EventArgs e)
{
// send the command selected in the send tab's combobox
byte CANcmd = (byte)(COMMS_MESSAGE_ID_t)cbCANcmd.SelectedValue;//first byte
}
If this is a WinForms app, here is a possible solution for #1. If this works, we can move on from there.
public partial class Form1 : Form
{
private void Form1_Load(object sender, EventArgs e)
{
foreach (var val in Enum.GetNames(typeof(COMMS_MESSAGE_ID_t)))
{
cbCANcmd.Items.Add(new CommsMessage(val));
}
}
}
public class CommsMessage
{
public string Name { get; set; }
public COMMS_MESSAGE_ID_t Message { get; set; }
public CommsMessage(string msgName)
{
Name = msgName;
Message = (COMMS_MESSAGE_ID_t)Enum.Parse(typeof (COMMS_MESSAGE_ID_t), msgName);
}
public override string ToString()
{
return string.Format("{0:x} - {1}", Message, Name);
}
}
Then, any time you get the value of the ComboBox.SelectedItem, you can do something like:
COMMS_MESSAGE_ID_t msg = (cbCANcmd.SelectedItem as CommsMessage).Message;
I've left out lots of exception handling that you should probably do, but I hope this is helpful.
Related
my project
I was wondering how to save the User input in a ListView and prevent it from disappearing when I go to another Form
if (string.IsNullOrEmpty(txtName.Text) || string.IsNullOrEmpty(txtReview.Text))
return;
ListViewItem item = new ListViewItem(txtName.Text);
item.SubItems.Add(txtReview.Text);
listView1.Items.Add(item);
txtName.Clear();
txtReview.Clear();
As far I got your concern! You have a form in which you are adding a reviews. You are closing it soon after adding review. But you want all previous reviews when you visit that form again.
you cannot use database (it certainly would have been easiest way to do though), but you are allowed to use file system (you said text files, i'm assuming serialization too)
But reading and writing files on every now and then is costly process, I would recommend you keep data in memory cache (insert new reviews, update and delete them if there may such option). While closing an application, you store last updated copy into file and while starting software you read that file to get last updated copy of data.
(this way of storing data on closing software can cause data loss when software crash or stopped abnormally. but as it is class project, i would not worry much about that. however you can always use low priority thread to store data periodically)
For this approach, I would recommend to implement MVVM architecture
At least you should create a class which store all the data statically
(why static? it is an interesting question and i m leaving it on you to find out the answer)
Example code For Model:
public class Model
{
public static Dictionary<string, Review> ReviewData;
//this method should be called at application startup.
public static void SetModel()
{
//Desrialize lastly saved file, I'm just initializing it with new
ReviewData = new Dictionary<string, Review>();
}
public static void AddReview(string movie, string reviewerName, string review)
{
if (!ReviewData.ContainsKey(movie + "-" + reviewerName))
{
ReviewData.Add(movie + "-" + reviewerName, new Review(reviewerName, reviewerName));
}
}
}
public class Review
{
public string reviewerName;
public string review;
public Review(string reviewerName, string review)
{
this.reviewerName = reviewerName;
this.review = review;
}
}
Example Code for Add review form:
private void btnPost_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtName.Text) || string.IsNullOrEmpty(txtReview.Text))
return;
//First we should set Model data
Model.AddReview("moive1", txtName.Text, txtReview.Text);
LoadListView();
}
private void AddReviewForm_Load(object sender, EventArgs e)
{
LoadListView();
}
private void LoadListView()
{
listView1.Clear();
foreach (string reviewKey in Model.ReviewData.Keys)
{
Review review = Model.ReviewData[reviewKey];
ListViewItem item = new ListViewItem(review.reviewerName);
item.SubItems.Add(review.review);
listView1.Items.Add(item);
}
}
And last thing, while closing entire application, store lastly updated copy of Model.ReviewData (Serialize it).
A little background: I'm currently writing a sample project using Winforms/C# that emulates Conway's Game of Life. Part of this sample involves UI Automation using the White Automation Framework. The relevant layout of the form includes a custom grid control for setting up the world and a list box control that displays/stores past generations of the world.
I have a World object that stores a list of Cell objects and calculates the next generation of a World from its current state:
public class World
{
public IReadOnlyCollection<Cell> Cells { get; private set; }
public World(IList<Cell> seed)
{
Cells = new ReadOnlyCollection<Cell>(seed);
}
public World GetNextGeneration()
{
/* ... */
}
}
In my UI, when I calculate the next world generation, the past generations list is updated. The past generation list stores World objects as its items, and I have subscribed to the Format event of the list box to format the item display. _worldProvider.PreviousGenerations is a collection of World objects.
private void UpdatePastGenerationsList()
{
GenerationList.SuspendLayout();
GenerationList.Items.Add(_worldProvider.PreviousGenerations.Last());
GenerationList.SelectedItem = _worldProvider.PreviousGenerations.Last();
GenerationList.ResumeLayout();
}
From this snippet you can see that the items of the ListBox are World objects. What I want to do in my test code is get the actual World object (or some representation of it) from the selected ListBox item, and then compare it to the grid's representation of the world. The grid has a full automation implementation so I can easily get a representation of the grid using existing automation calls in White.
The only idea I had was to make a derived ListBox control that sends an ItemStatus property changed automation event when the selected index changes from an automation click event, and then listening for that ItemStatus event in the test code. The World is first converted to a string (WorldSerialize.SerializeWorldToString) where each live cell is converted to formatted coordinates {x},{y};.
public class PastGenerationListBox : ListBox
{
public const string ITEMSTATUS_SELECTEDITEMCHANGED = "SelectedItemChanged";
protected override void OnSelectedIndexChanged(EventArgs e)
{
FireSelectedItemChanged(SelectedItem as World);
base.OnSelectedIndexChanged(e);
}
private void FireSelectedItemChanged(World world)
{
if (!AutomationInteropProvider.ClientsAreListening)
return;
var provider = AutomationInteropProvider.HostProviderFromHandle(Handle);
var args = new AutomationPropertyChangedEventArgs(
AutomationElementIdentifiers.ItemStatusProperty,
ITEMSTATUS_SELECTEDITEMCHANGED,
WorldSerialize.SerializeWorldToString(world));
AutomationInteropProvider.RaiseAutomationPropertyChangedEvent(provider, args);
}
}
The problem I have with this is that the event handler code in the test class is never being called. I think the problem is with the AutomationInteropProvider.HostProviderFromHandle call returning a different provider object from the one in the test code, but I am not sure.
My questions are:
Is there a better approach I can take, such as something provided by the MS Automation API?
If not - is there a way I can get the default C# IRawElementProviderSimple implementation for the ListBox control (to raise the Property Changed event)? I would rather not re-implement it just for this little bit of functionality.
Here is the code from the test side, which adds the listener for ItemStatusProperty change event. I am using SpecFlow for BDD which defines ScenarioContext.Current as a dictionary. WorldGridSteps.Window is a TestStack.White.Window object.
private static void HookListItemStatusEvent()
{
var list = WorldGridSteps.Window.Get<ListBox>(GENERATION_LIST_NAME);
Automation.AddAutomationPropertyChangedEventHandler(list.AutomationElement,
TreeScope.Element,
OnGenerationSelected,
AutomationElementIdentifiers.ItemStatusProperty);
}
private static void UnhookListItemStatusEvent()
{
var list = WorldGridSteps.Window.Get<ListBox>(GENERATION_LIST_NAME);
Automation.RemoveAutomationPropertyChangedEventHandler(list.AutomationElement, OnGenerationSelected);
}
private static void OnGenerationSelected(object sender, AutomationPropertyChangedEventArgs e)
{
if (e.EventId.Id != AutomationElementIdentifiers.ItemStatusProperty.Id)
return;
World world = null;
switch (e.OldValue as string)
{
case PastGenerationListBox.ITEMSTATUS_SELECTEDITEMCHANGED:
world = WorldSerialize.DeserializeWorldFromString(e.NewValue as string);
break;
}
if (world != null)
{
if (ScenarioContext.Current.ContainsKey(SELECTED_WORLD_KEY))
ScenarioContext.Current[SELECTED_WORLD_KEY] = world;
else
ScenarioContext.Current.Add(SELECTED_WORLD_KEY, world);
}
}
I was able to work around this problem by using non-persisted memory mapped files to allow additional communication between the window GUI and the test process.
This ended up being much easier than trying to completely re-write IRawElementProviderSimple implementations for both my "custom" ListBox and the items contained within.
My custom ListBox ended up looking like this:
public class PastGenerationListBox : ListBox
{
public const string SELECTEDWORLD_MEMORY_NAME = "SelectedWorld";
public const string SELECTEDWORLD_MUTEX_NAME = "SelectedWorldMutex";
private const int SHARED_MEMORY_CAPACITY = 8192;
private MemoryMappedFile _sharedMemory;
private Mutex _sharedMemoryMutex;
public new World SelectedItem
{
get { return base.SelectedItem as World; }
set { base.SelectedItem = value; }
}
public PastGenerationListBox()
{
_sharedMemory = MemoryMappedFile.CreateNew(SELECTEDWORLD_MEMORY_NAME, SHARED_MEMORY_CAPACITY);
_sharedMemoryMutex = new Mutex(false, SELECTEDWORLD_MUTEX_NAME);
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
WriteSharedMemory(SelectedItem);
base.OnSelectedIndexChanged(e);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_sharedMemoryMutex.WaitOne();
if (_sharedMemory != null)
_sharedMemory.Dispose();
_sharedMemory = null;
_sharedMemoryMutex.ReleaseMutex();
if (_sharedMemoryMutex != null)
_sharedMemoryMutex.Dispose();
_sharedMemoryMutex = null;
}
base.Dispose(disposing);
}
private void WriteSharedMemory(World world)
{
if (!AutomationInteropProvider.ClientsAreListening) return;
var data = WorldSerialize.SerializeWorldToString(world);
var bytes = Encoding.ASCII.GetBytes(data);
if (bytes.Length > 8188)
throw new Exception("Error: the world is too big for the past generation list!");
_sharedMemoryMutex.WaitOne();
using (var str = _sharedMemory.CreateViewStream(0, SHARED_MEMORY_CAPACITY))
{
str.Write(BitConverter.GetBytes(bytes.Length), 0, 4);
str.Write(bytes, 0, bytes.Length);
}
_sharedMemoryMutex.ReleaseMutex();
}
}
My test code looks like this:
private static World GetWorldFromMappedMemory()
{
string str;
using (var mut = Mutex.OpenExisting(PastGenerationListBox.SELECTEDWORLD_MUTEX_NAME))
{
mut.WaitOne();
using (var sharedMem = MemoryMappedFile.OpenExisting(PastGenerationListBox.SELECTEDWORLD_MEMORY_NAME))
{
using (var stream = sharedMem.CreateViewStream())
{
byte[] rawLen = new byte[4];
stream.Read(rawLen, 0, 4);
var len = BitConverter.ToInt32(rawLen, 0);
byte[] rawData = new byte[len];
stream.Read(rawData, 0, rawData.Length);
str = Encoding.ASCII.GetString(rawData);
}
}
mut.ReleaseMutex();
}
return WorldSerialize.DeserializeWorldFromString(str);
}
Let's say I have a thread that waits for a user to click a button before advancing:
System.Threading.AutoResetEvent dialoguePause = new System.Threading.AutoResetEvent(false);
public void AskQuestion()
{
/* buttons containing choices created here */
dialoguePause.WaitOne();
/*Code that handles choice here */
}
public void Choice_Clicked(object sender, EventArgs e)
{
dialoguePause.Set();
}
How can I pass data from the thread Choice_Clicked is on to AskQuestion without relying on class variables? The best I can do is this:
System.Threading.AutoResetEvent dialoguePause = new System.Threading.AutoResetEvent(false);
string mostRecentChoice;
public void AskQuestion()
{
/* buttons containing choices created here */
dialoguePause.WaitOne();
MessageBox.Show("You chose " + mostRecentChoice + ".");
}
public void Choice_Clicked(object sender, EventArgs e)
{
mostRecentChoice = (sender as Button).Content.ToString(); //Ugly!
dialoguePause.Set();
}
There are many ways to achieve this:
Use a property in a Singleton or Monostate. There is always one instance of such property, so regardless which thread writes, and which reads, the will share it, as long as they are in one Application Domain.
Use messaging
If you are in same class, use field or property (look out for cross-threads!)
...
What I am trying to say, it depends on the application. I have no clue if this is WinForm, WPF or Web ...
I just want to know how to use the updated rate throughout the whole program. Here's my code so far for reference...
//Form 1
private void update_Click(object sender, EventArgs e)
{
if (fromcountry.Text == tocountry.Text)
{
MessageBox.Show(" Please Choose Two Different Currencies To Use This Function", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
else
{
btnconvert.Enabled = true;
Exchange_Rate frm = new Exchange_Rate();
frm.Show(this);
}
}
//Form 1 one of the comboboxes for selecting 2nd country
private void tocountry_SelectedIndexChanged(object sender, EventArgs e)
{
btnupdate.Enabled = true;
btnconvert.Enabled = true;
txtvalue.Enabled = true;
exchange();
}
private void exchange()
{
if (fromcountry.Text == tocountry.Text)
{
lblexchange.Text = "1";
}
else if (fromcountry.Text == "SGD - Singapore Dollar" && tocountry.Text == "USD - US Dollar")
{
lblexchange.Text = "1.26";
}
else if (fromcountry.Text == "SGD - Singapore Dollar" && tocountry.Text == "MYR - Malaysian Ringgit")
{
lblexchange.Text = "2.35";
}
else if (fromcountry.Text == "SGD - Singapore Dollar" && tocountry.Text == "EUR - Euro")
{
lblexchange.Text = "0.60";
}
//Form 2
private void btnok_Click(object sender, EventArgs e)
{
try
{
double exchange;
exchange = Double.Parse(txtcurrent.Text);
var frm = (currencyconverter)this.Owner;
frm.PassValue(txtcurrent.Text);
this.Close();
}
catch
{
MessageBox.Show("Please Enter Numbers", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
txtcurrent.Text = "";
}
}
I know by using if-else method it's too vague to get rates at the start of the program and I'm just a student learning simple programming. But still I need to know how use the updated rate when I press the same conversion again. If there's not enough info, I can help you get more coding
You can use a shared currency object to hold information about rate of the currency
public class Currency
{
private Currency(string name)
{
Name = name;
}
public string Name {get; private set;}
public decimal Rate {get; private set;}
private void SetRate(decimal rate)
{
Rate = rate;
OnRateChanged(this);
}
public static event EventHandler RateCanged;
private static OnRateChanged(Currency currency)
{
var handler = RateChanged;
if(handler != null)
{
handler(currency, EventArgs.Empty);
}
}
private Dictionary<string, Currency> currencies = new Dictionary<string, Currency>();
public static Currency GetCurrency(string name)
{
Currency currency;
if(!currencies.TryGetValue(name, out currency))
{
currency = new Currency(name);
currencies[name] = currency;
}
}
}
So you had a simple shared rate's storage, you can use it everywere
class Form1
{
public Form1()
{
...
Currency.RateChanged += RateChanged;
}
private void RateChanged(object source, EventArgs e)
{
labelRate.Text = Currency.GetCurrency("USD").Rate;
}
}
class Form2
{
public Form2()
{
...
rateTextBox.Text = Currency.GetCurrency("USD").Rate.ToString();
}
void updateButtin_Click()
{
Currency.GetCurrency("USD").SetRate(decimal.Parse(rateTextBox.Rate));
}
}
There are a number of different ways to achieve this and it's going to be impossible to answer in full without making a design decision for you. The approaches which spring to mind are either using a configuration file, database or some external source.
As you've pointed out you need to have some way of storing these values outside your application so if an conversion rate changes you can update it in your software without rewriting your code.
You need to make a decision on how to do this.
Database
A database is probably the most flexible, however it will require you to maintain it. There are countless mechanisms to access a database from ADO.NET, through Linq2SQL or NHibernate.
External Source
I'm sure there are various online sources you could get currency data from, either a webservice or RSS feed you could access - it could be worth reading up on these?
Configuration
Personally this is the approach I'd suggest. As you're clearly not very experienced I'd suggest the easier solution of config, work on your database skills - in the future it will be a no brainer for you.
I would use the AppSettings section of the config file similar to here.
You would add an App.Config file to your application, this would store the conversion rates so you can update them without needing to rewrite your tool. You can create a new file by right clicking on the project and adding New Item, then Configuration File.
You'll also need to add a reference onto System.Configuration as it's not referenced by default.
There is a section in the config file called AppSettings, this is a simple section for key/value type properties. We're going to create a set of app settings, one for each conversion rate. For example:
You can then use your countries to generate this key. For Example:
string settingKey = string.Concat(fromcountry.Text, "_", tocountry.Text);
You can access this configuration value using the ConfigurationManager:
decimal rate = decimal.Parse(ConfigurationManager.AppSettings[settingKey]);
Once you've got the rate you'll be able to perform your multiplication to calculate the correct values.
Please bear in mind there's no error handling in here - what happens if there country is not known or the config doesn't contain the exchange rate!
If you are not using actual currency data and just a static data, then here are the steps to improve:
Have one currency as base currency. Usually it's USD with value 1
Store all the rates for all the currencies in a collection [Key,Value] in USD.
Here the Key is your Currency Code eg, SGD and value is its rate in USD.
Now you can pass the selected dropdown value as Key to retrieve the value eg, Currencies[toCountry.Code]
Now to get the rate. You can divide like this to get value of FromCountry in terms of ToCountry
var FromCountryRate = Currencies[FromCountry.Value]/Currencies[ToCountry.Value];
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.