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).
Related
I have a very simple program that for some reason has me stumped. I put it down, came back at it again this morning and I'm still stumped. First off, I'm aware this is not an ideal solution. I have two forms: Main and Log. The Main form has a button that adds to List _debugLog when clicked. When btnDebug is clicked, it opens the Log form, passing _debugLog to it. Everything is fine, the timer is setup and runs, everything is normal. The event log.UpdateLog() is triggered every 2.5 seconds to update the Log form with the updated log. However, mainFormLog.Count and _log.Count are always the same and they BOTH increase when btnAdd is clicked on the main form. How does _log have the new _debugLog (mainFormLog) from the tick event?
namespace Tool
{
public partial class Main : Form
{
private List<string> _debugLog = new List<string>();
public Main()
{
InitializeComponent();
}
private void btnAdd_Click(object sender, EventArgs e)
{
_debugLog.Add("message!");
}
private void btnDebug_Click(object sender, EventArgs e)
{
Log log = new Log(_debugLog);
log.Show();
Timer dt = new Timer();
dt.Interval = 2500;
dt.Enabled = true;
dt.Tick += delegate {
log.UpdateLog(_debugLog);
};
}
}
public partial class Log : Form
{
private List<string> _log;
public Log(List<string> log)
{
InitializeComponent();
_log = log;
}
public void UpdateLog(List<string> mainFormLog)
{
if (mainFormLog.Count > _log.Count)
{
MessageBox.Show("Log has been updated!");
}
else
{
MessageBox.Show("Nothing new!" + mainFormLog.Count.ToString() + " / " + _log.Count.ToString());
}
}
}
}
Well, you're passing the reference to the list from Main to Log, so it's actually the same list.
If you want a separate list that gets initialized with the list from Main you can use:
public Log(List<string> log)
{
InitializeComponent();
_log = new List<string>(log);
}
Maybe this helps to understand the difference between variables and references:
For a value type, the value is the information itself. For a reference
type, the value is a reference which may be null or may be a way of
navigating to an object containing the information.
For example, think of a variable as like a piece of paper. It could
have the value "5" or "false" written on it, but it couldn't have my
house... it would have to have directions to my house. Those
directions are the equivalent of a reference. In particular, two
people could have different pieces of paper containing the same
directions to my house - and if one person followed those directions
and painted my house red, then the second person would see that change
too. If they both just had separate pictures of my house on the paper,
then one person colouring their paper wouldn't change the other
person's paper at all.
All your variables _debugLog, mainFormLog, and _log are pointing to the same list in memory. You've only created one list, and when you assign a new variable to that list, it's just a pointer to some location in memory, it doesn't automatically create a new copy of the list.
I am currently working on Windows Store App in c#.
Now,
I am having a list box 'Listbox1' which gets its items on a button click event from a text box 'tasks', and have selected Items delete property on other button click event.
private void add_Click(object sender, RoutedEventArgs e)
{
string t;
t = tasks.Text;
if (t != "")
{
Listbox1.Items.Add(t);
}
else
{
var a = new MessageDialog("Please Enter the Task First");
a.Commands.Add(new UICommand("Ok"));
a.ShowAsync();
}
tasks.Text = "";
}
private void del_Click(object sender, RoutedEventArgs e)
{
for (int p = 0; p < Listbox1.SelectedItems.Count; p++)
{
Listbox1.Items.Remove(Listbox1.SelectedItems[p].ToString());
p--;
}
}
Now I want to save this list into local application storage, after user complete the changes (on a button click event perhaps).
And also to send all Listbox Items to another page(s).
I am not much a coder, I design things.
Please guide me by sample or reference.
Thank you in advance :)
If you have already stored the data to local storage, you could just read it in the OnNavigatedTo override of the other page. Otherwise, use the navigation parameter: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/8cb42356-82bc-4d77-9bbc-ae186990cfd5/passing-parameters-during-navigation-in-windows-8
Edit: I am not sure whether you also need some information about local storage. This is easy: Windows.Storage.ApplicationData.Current.LocalSettings has a property called Values, which is a Dictionary you can write your settings to. Have a look at http://msdn.microsoft.com/en-us/library/windows/apps/hh700361.aspx
Edit: Try something like this code to store your list.
// Try to get the old stuff from local storage.
object oldData = null;
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
bool isFound = settings.Values.TryGetValue("List", out oldData);
// Save a list to local storage. (You cannot store the list directly, because it is not
// serialisable, so we use the detours via an array.)
List<string> newData = new List<string>(new string[] { "test", "blah", "blubb" });
settings.Values["List"] = newData.ToArray();
// Test whether the saved list contains the expected data.
Debug.Assert(!isFound || Enumerable.SequenceEqual((string[]) oldData, newData));
Note, this is only demo code for testing - it does not make real sense...
Edit: One advice: Do not persist the list in your click handlers as this will become extremely slow as the list grows. I would load and save the list in the Navigation handlers, i.e. add something like
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
if (this.ListBox1.ItemsSource == null) {
object list;
if (ApplicationData.Current.LocalSettings.Values.TryGetValue("List", out list)) {
this.ListBox1.ItemsSource = new List<string>((string[]) list);
} else {
this.ListBox1.ItemsSource = new List<string>();
}
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
if (this.ListBox1.ItemsSource != null) {
ApplicationData.Current.LocalSettings.Values["List"] = this.ListBox1.ItemsSource.ToArray();
}
base.OnNavigatedFrom(e);
}
Here is very nice simple example on SQLite DataBase Use in winRT app Development. Look at it and you will know how you can store your Data on the Local Machine. I learned Basic code from this example.
http://blogs.msdn.com/b/robertgreen/archive/2012/11/13/using-sqlite-in-windows-store-apps.aspx
Now, for ease of navigation let me suggest you a flow for this portion of your app.
take one ObservableCollection<> of string and store values of
that textBox into this ObservationCollection with onClick() and then
refer that ObservableCollection<String> to the ItemsList of the
listBox.
now at the time you need to send your Data to the next page, make one parameterised constructor of next page and pass that ObservableCollection<String> as it's parameter.
Now you can access those Data in your constructor and can use as however you want.
Hope this will help..
There is a MenuItem click event MainMenu_Define_Material which opens a window called Material. I get info from a textbox called txt_density and I save that info in density and return to main window via OK-button having click event Material_btn_OK_Click.
My question is, how I can avoid passing the last session values every time I open the window Material?
I want, once density is set, every time I open Material window I want to see the values of previous session.
private void MainMenu_Define_Material(object sender, RoutedEventArgs e)
{
newWin_material = new Material();
newWin_material.btn_OK.Click += new RoutedEventHandler(Material_btn_OK_Click);
if (density != -1)
{
newWin_material.txt_density.Text = density.ToString();
}
newWin_material.ShowDialog();
}
private void Material_btn_OK_Click(object sender, RoutedEventArgs e)
{
density = System.Convert.ToSingle(newWin_material.txt_density.Text);
newWin_material.Close();
}
Not very clear where that density comes from, but you can insert that field in uour data class what you can hold on data layer or may be like ObjectDataProvider for modelview.
There are a lor of different solutioms our there. The basic idea is:
Define common, shared data storage and keep there alll values you want to share between different windows.
Define a data class. I used here a static class with a static member Desnity.
public static class DataClass
{
public static double Desnsity;
}
After in any window you're able to access that field (read/write), like
DataClass.Density
Hope this is clear.
There is no concept of Session in WPF. You can try creating a static variable to do this or a app config value or pass a parameter via the constructor.
If you are going to new it ( e.g. new Material(); ) then it is going to start with zero information. That is what new does. What is wrong with passing the value in the ctor?
Try
MainWindow
{
private newWin_material = new Material(); // just new it ONCE
// this may need to be in MainWindow ctor.
newWin_material.btn_OK.Click += new RoutedEventHandler(Material_btn_OK_Click);
private void btn_friends_pressed(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/Friends.xaml", UriKind.Relative));
}
When I press the button I go to the Friends page, which loads many friends from isolated storage.Than I press "back" button and go to the Menu page, when I press again the button, I have "Operation not permitted on IsolatedStorageFileStream." message.
How I can not reload page and keep it in RAM.
Something like:
if (Friends.Page.IsRunning==true)
NavigationService.Navigate("/Friends.xaml");
else
NavigationService.Navigate(new Uri("/Friends.xaml", UriKind.Relative));
Whenever you navigate to a page, it is reloaded automatically. The pages themselves are not kept in memory once you've navigated away from them. If you want to store it memory, and not read it from Isolated Storage each time, then you can simply create a static class that contains a static List that stores your friends. Once you've loaded your friends, depending on their type, you can add it to the list. Whenever you need to access them, simply call it from the static List. For example, in your solution, create a new class:
using ... //your using directives
namespace MyApp //Your project Namespace
{
public static class FriendsStorage //rename `FriendsStorage` to whatever you want
{
public static List<Friends> ListOfFriends = new List<Friends>(); //Your list
}
}
To set it, you can load the information from IsolatedStorage and add it to the list:
foreach(Friend f in Friends)
FriendsStorage.ListOfFriends.Add(f);
Whenever you need to query the Friends list you can call it like this:
var friendList = FriendsStorage.ListOfFriends;
Even if you use the above method, you should try and fix the error you're getting. Can you post your Isolated Storage code?
If you want to get rid of the error message, you should use your stream in a using() block,
using (var stream = new IsolatedStorageFileStream(...))
{
// load your data here
}
Regarding saving page, it's generally not a good idea because your memory can exponentialy grow and your application will be very unresponsive.
Although you can always use your App.xaml.cs as a global instance of your application to cache some of your data sources:
List<Friend> _Friends;
List<Friend> _Friends
{
get
{
if(_Friends == null) _Friends = GetFriends();
return _Friends;
}
}
but if you did this be very careful not to store loads of data.
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.