Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I use a list to contain data parsed from an XML file, using strings as its members:
public class ServerList
{
public string ServerName { set; get; }
public string ServerReboot { set; get; }
public string ServerShutdown { set; get; }
public ServerList()
{
ServerName = "";
ServerReboot = "";
ServerShutdown = "";
}
}
From the main form I launch an editor form and pass the list into it. On this editor form the user is able to add or remove entry entries form the list, as well as make changes to parts of the list. If they click the OK button I want to be able to pull the list form the editor form back into the main form, but if they click Cancel I want these changes to get dropped. This is the way the editor form is pulled up:
private void mnuEdit_Click(object sender, EventArgs e)
{
frmEditor theEditor = new frmEditor();
theEditor.updatedServerList = theServerList;
DialogResult res = theEditor.ShowDialog();
if (res == DialogResult.OK)
{
theServerList = theEditor.updatedServerList.ToList();
SetupFilters(GroupOrBatch.Group);
// other processing to update the main form from the updated list
}
}
And on the Edit form this is how it is received:
public partial class frmEditor : Form
{
private List<ServerList> myServerList = new List<ServerList>();
public List<ServerList> updatedServerList
{
get { return myServerList; }
set { myServerList = value.ToList(); }
}
....
What I am finding is that while the list structure appears to be copied to the new variable, the actual data is still linked to the original list. Even if the user clicks Cancel, and the modified list is not copied back to the original, the original has already been changed.
This leaves me with one of two options - either I can find some way to do a full deep clone of the list to a new one (which can be dropped upon an Cancel), or I remove the Cancel button entirely and have all edits be live.
class is stored by reference inside the list. The .ToList() merely makes a shallow copy of the list with the same references pointing to those ServerList. Therefore by making any changes on the shadow copy, the original list is still affected.
You need to make a deep copy the list, and to pass them around for editing them :
ServerList::Clone
public class ServerList
{
// properties...
// ctor...
public ServerList Clone()
{
return new ServerList
{
ServerName = ServerName,
ServerReboot = ServerReboot,
ServerShutdown = ServerShutdown,
});
}
}
mnuEdit_Click
private void mnuEdit_Click(object sender, EventArgs e)
{
frmEditor theEditor = new frmEditor();
theEditor.updatedServerList = theServerList.Select(x => x.Clone()).ToList(); /*changed */
DialogResult res = theEditor.ShowDialog();
if (res == DialogResult.OK)
{
theServerList = theEditor.updatedServerList; /* changed */
SetupFilters(GroupOrBatch.Group);
// other processing to update the main form from the updated list
}
}
Note: The .ToList() on updatedServerList.get is not necessary.
As an alternative, since your data set is very small, convert your data to a struct:
public struct ServerList
{
public string ServerName { get; private set; }
public string ServerReboot { get; private set; }
public string ServerShutdown { get; private set; }
public ServerList(string name, string reboot, string shutDown)
{
this.ServerName = name;
this.ServerReboot = reboot;
this.ServerShutdown = shutDown;
}
}
A struct is a value type (as opposed to a reference type), and value semantics will apply to it. Consider the following:
var listA = new ServerList("Foo", "Daily", "Never");
var listB = listA;
A copy of listA and all of its values is stored in listB, not a reference. For the strings, copies of the references are made, but strings are immutable anyway, so there's no issue there.
CON: structs are supposed to be immutable. Once you initialize them, you can't change their data. Consider that before adopting this solution.
Related
I am working on an assignment, where I need to have the user input the name and time of a competitor in a race. Once they input the form, they can go to a "View" screen (which is my new form) which will allow them to view the times.
Each race has 6 categories, so I created 6 lists in the main form (with the input). These lists are created based on an object called Competitor (which requires the name and time)
Now I need to take that list, and sort it in the "View" screen.
I am having trouble with this (I am very inexperienced with C#)
I tried referencing the input form, but to no avail. I know I am doing something wrong, but I don't know what it is.
public List<RaceCompute.Competitor> egg_adult_list = new List<RaceCompute.Competitor>();
public List<RaceCompute.Competitor> egg_teen_list = new List<RaceCompute.Competitor>();
public List<RaceCompute.Competitor> egg_kids_list = new List<RaceCompute.Competitor>();
public List<RaceCompute.Competitor> sack_adult_list = new List<RaceCompute.Competitor>();
public List<RaceCompute.Competitor> sack_teen_list = new List<RaceCompute.Competitor>();
public List<RaceCompute.Competitor> sack_kids_list = new List<RaceCompute.Competitor>();
public input_form()
{
InitializeComponent();
}
public class RaceCompute
{
public class Competitor
{
public string Name { get; set; }
public double Time { get; set; }
public Competitor(string name, double time)
{
Name = name;
Time = time;
}
}
}
// VIEW FORM //
input_form test2 = new input_form();
EDIT: Thanks a lot to the quick reply. I set the lists to public static and I can now access those lists. Again, I am very inexperienced but thanks for the help
The code will not look pretty at the end however you can sort the List using Where or FirstOrDefault.
egg_kids_list.FirstOrDefault(i=> i.Name = NameToSearchFor);
Either do this 6 times for all your lists and then add all the result Competitor objects into another List and pass it in to your new form as a parameter.
DisplayList.Add(egg_kids_list_item_returned_by_filter);
Or a better option might be passing your first Form as a parameter to your
second form
private class display_class
{
public display_class(data_class data)
{
//Here you can already access your lists
public List<RaceCompute.Competitor> egg_adult_list = data.egg_adult_list;
}
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I have a winform with 5 text boxes and 2 data grids. I need a way of being able to press a button (or adding a menu at the top with a file button and select save from there) and save all of the values to a file that the user selects location/name for. Then I need a button (or again a menu option) to load the file that was previously saved and all the values from the "save" will be generated on screen so that it looks as if you just input all values.
How is this achieved in VS2017?
You describe your problem and your answer already. You can just straight forward implement like that: Save the data to a file, then when press the Button 2, load data from that file to form.
Other approach, you can have global variable, when press Button 1, you save data to that variable, then when Button 2 pressed, you load this variable's value to form.
public class UserData
{
public string Location { get; set; }
public string Name { get; set; }
}
Then in your form:
public partial class Form1 : Form
{
static List<UserData> savedData;
public Form1()
{
InitializeComponent();
savedData = new List<UserData>();
}
private void button1_Click(object sender, EventArgs e)
{
//This is for example only, you get data then save it like this
savedData.Add(new UserData
{
Location = "US",
Name = "Boston"
});
savedData.Add(new UserData
{
Location = "US",
Name = "Texas"
});
}
private void button2_Click(object sender, EventArgs e)
{
//This is for example only, you WRITE your own business here
foreach (var item in savedData)
{
label1.Text = item.Location;
label2.Text = item.Name;
}
}
}
I am converting some winform code to wpf, in the winform code i have the following lines
frmStartup parentfrm = (frmStartup)Application.OpenForms["frmstartup"];
if (parentfrm != null)
{
db = parentfrm.db;
}
I need to convert this into WPF, there is a Window called windowSplash that is designed to replace this, however changing frmstartup to windowSplash doesn't work.
You can do something like:
WindowStartup parentfrm = Application.Current.Windows.OfType<WindowStartup>().FirstOrDefault();
if (parentfrm != null)
{
db = parentfrm.db;
}
This would find the first window matching the type though. If that doesn't work for you (you may have several windows of the same type), The best way to do this would be making your windows implement some kind of interface. Off my head and just as an example:
public interface IDbWindow
{
string Key { get; }
DbContext Db { get; }
}
Then make your Window implement IDbWindow, something like (in the XAML code-behind):
public partial class MyWindow : Window, IDbWindow
{
public string Key { get; private set; }
public DbContext Db { get; private set; }
public MyWindow()
{
InitializeComponent();
Key = "ThisIsTheWindowImLookingFor"; // this key might be set somewhere else, or be passed in the constructor, or whatever
Db = new MyDbContext(); // for example
}
}
And then you can search the windows for the specific Key, instead of the window type:
IDbWindow parentfrm = Application.Current.Windows.OfType<IDbWindow>().FirstOrDefault(x => x.Key == "ThisIsTheWindowImLookingFor");
if (parentfrm != null)
{
db = parentfrm.Db;
}
I'd further add that you shouldn't really depend on Application.Current.Windows, and you should be managing your own collection (of IDbWindow in this case, but it could be called IDbHolder), adding and removing as necessary. This would remove your dependency on the objects containing Db being actual Windows (which doesn't make logical sense, they could be whatever).
You can iterate over the open Windows in the application using Application.Current.Windows and check for its name or type:
foreach (Window window in Application.Current.Windows)
{
if (window is TypeOfWindow)
{
// do what you want
break;
}
}
This question already has answers here:
How can I save application settings in a Windows Forms application?
(14 answers)
Closed 7 years ago.
Question
How can I save "settings" so that they can be used again after the application has been closed?
When I say settings I mean different properties of the controls on my form.
What is the easiest and most appropriate method? I've read you can save to the system registry or a xml file?
Background
I have a form that creates a row in a Table that changes depending on what control is set or change.
I would like to be able to save different configurations and display them in a combobox for repeated use.
Example
The end user fill in all textboxs and ticks checkboxes.
They then click add to favourites
They add a favourite name and save
The save is then permanently visible in the favourites combobox.
My form
There are many ways to do this. But in all these options, you need to store the users selection somewhere. You can store this in
A database table, associate the setting with a unique user ID, like LoginID
A Preferences XML file : Refer this
As a Setting in your project : Refer this
As a Registry Entry : Refer this
An INI File
You might want to take a look at Persisting Application Settings in the .NET Framework
One way you could save the data would be to write it to the registry, under the HKCU node. This way different users of your application will have their own settings even if the app is on the same machine. It also keeps the file system a little cleaner and doesn't require a database. But the downside is that the favorites only live on the machine, and don't roam with the user across devices.
A way to implement this would be to wrap your form settings in a class that knows how to save and load values from the registry. This, along with a registry helper class, could make it pretty easy to add "Favorites" functionality to your form.
For example, you could first create a Registry helper class that will read and write settings to the HKCU node (so the settings are specific to the logged in user):
public class RegHelper
{
private static readonly RegistryKey Root = Registry.CurrentUser
.CreateSubKey(#"Software\CompanyName\ApplicationName");
private readonly RegistryKey _thisKey = Root;
public RegHelper() { }
public RegHelper(string favoriteKey)
{
_thisKey = Root.CreateSubKey(favoriteKey);
}
public List<string> GetSubKeys()
{
return _thisKey.GetSubKeyNames().ToList();
}
public void SetProperty(string propertyName, string value)
{
_thisKey.SetValue(propertyName, value, RegistryValueKind.String);
}
public void SetProperty(string propertyName, bool value)
{
SetProperty(propertyName, value.ToString());
}
public string GetProperty(string propertyName)
{
return GetProperty(propertyName, string.Empty);
}
public string GetProperty(string propertyName, string defaultValue)
{
return _thisKey.GetValue(propertyName, defaultValue).ToString();
}
public bool GetPropertyAsBool(string propertyName)
{
return bool.Parse(GetProperty(propertyName, default(bool).ToString()));
}
}
Then, you could wrap the fields of your form into a class that not only has properties that match your form fields, but also has methods to save the values to the registry and some static methods to load all Favorites or a specific named Favorite. For example:
public class Favorite
{
public string Name { get; private set; }
public string Notes { get; set; }
public bool NotesFromPlanner { get; set; }
public string Project { get; set; }
public string DbLocation { get; set; }
public string AssesmentToolVersion { get; set; }
public string ProjectCodes { get; set; }
public bool StraightToNew { get; set; }
public Favorite(string name)
{
this.Name = name;
}
public void Save()
{
var reg = new RegHelper(this.Name);
reg.SetProperty("Name", Name);
reg.SetProperty("Notes", Notes);
reg.SetProperty("NotesFromPlanner", NotesFromPlanner);
reg.SetProperty("Project", Project);
reg.SetProperty("DbLocation", DbLocation);
reg.SetProperty("AssesmentToolVersion", AssesmentToolVersion);
reg.SetProperty("ProjectCodes", ProjectCodes);
reg.SetProperty("StraightToNew", StraightToNew);
}
public static Favorite GetFavorite(string favoriteName)
{
var reg = new RegHelper(favoriteName);
return new Favorite(favoriteName)
{
Notes = reg.GetProperty("Notes"),
NotesFromPlanner = reg.GetPropertyAsBool("NotesFromPlanner"),
Project = reg.GetProperty("Project"),
DbLocation = reg.GetProperty("DbLocation"),
AssesmentToolVersion = reg.GetProperty("AssesmentToolVersion"),
ProjectCodes = reg.GetProperty("ProjectCodes"),
StraightToNew = reg.GetPropertyAsBool("StraightToNew"),
};
}
public static List<Favorite> GetFavorites()
{
return new RegHelper().GetSubKeys().Select(GetFavorite).ToList();
}
public override string ToString()
{
return this.Name;
}
}
Then, you could use the Favorite class to populate your Favorites drop down:
private void Form1_Load(object sender, EventArgs e)
{
// Get all saved favorites and load them up in the combo box
foreach (var favorite in Favorite.GetFavorites())
{
cboFavorites.Items.Add(favorite);
}
}
Now, when a favorite is picked from the combo box, we want to populate our form with the details:
private void cboFavorites_SelectedIndexChanged(object sender, EventArgs e)
{
var favorite = (Favorite) cboFavorites.SelectedItem;
txtNotes.Text = favorite.Notes;
txtAssetToolVersion.Text = favorite.AssesmentToolVersion;
txtDbLocation.Text = favorite.DbLocation;
chkNotesFromPlanner.Checked = favorite.NotesFromPlanner;
txtProjectCodes.Text = favorite.ProjectCodes;
cboProjects.Text = favorite.Project;
chkStraightToNew.Checked = favorite.StraightToNew;
}
And when someone clicks "Save Favorite", we want to add (or update) the favorite details to the registry:
private void btnAddFavorite_Click(object sender, EventArgs e)
{
string favoriteName = cboFavorites.Text;
if (string.IsNullOrEmpty(favoriteName))
{
MessageBox.Show("Please type a name for the favorite in the Favorites box.");
return;
}
var favorite = new Favorite(favoriteName)
{
Notes = txtNotes.Text,
AssesmentToolVersion = txtAssetToolVersion.Text,
DbLocation = txtDbLocation.Text,
NotesFromPlanner = chkNotesFromPlanner.Checked,
ProjectCodes = txtProjectCodes.Text,
Project = cboProjects.Text,
StraightToNew = chkStraightToNew.Checked
};
favorite.Save();
// When saving a favorite, add it to the combo box
// (remove the old one first if it already existed)
var existingFav = cboFavorites.Items.Cast<Favorite>()
.FirstOrDefault(fav => fav.Name == favoriteName);
if (existingFav != null)
{
cboFavorites.Items.Remove(existingFav);
}
cboFavorites.Items.Add(favorite);
cboFavorites.Text = favoriteName;
}
This should be enough to get you started, if you want to go the registry route.
It depends on your application and what it's used for and its architecture.
There are multiple options:
You could save it in a database.
This option is nice when there are a lot of settings and especially nice in a multi-user platform. If this is a client server application, this may also be preferable for that reason. If you want to keep this simple and don't see user settings getting complex / having very many, this may not be the best option.
You could save it in a flat file. This option is similar to the first, but likely better in the case where your application is more stand-alone and/or you just don't have any other benefit of having those settings on a server.
You could store them in your Applications Settings. There is a good answer regarding how to do that here: https://msdn.microsoft.com/en-us/library/0zszyc6e%28v=vs.110%29.aspx
Another thing to consider is how you want to load those settings. For windows forms development, having classes which define your layout and binding to those classes can be useful. Therefore, you may want to store this data in XML which can be easily serialized directly into a class which defines what your form looks like. You would be able to store that XML anywhere really: Locally or on the server in a database.
i'm building a control that inherits from CompositeControl and creates/maintains a number of dynamically generated child controls. i'm trying to save a class to viewstate before the control tears down the control hierarchy and rebuilds it with a new set of dynamically generated controls. other than persisting this class (as xmlserialized) in viewstate on save, i have no viewstate customization and i am not overriding SaveViewState, SaveControlState, etc.
what's happening in the code below: i have a MySuperClass property on the control that handles my info from load to render. to persist the info through paging events i added a MySuperClassStore to hold onto it. currently, the only data being changed by users is within SubClassControl's SubClass.SubClassResponse (not shown). these changes are being correctly handed back to the SuperClass at the SuperClassControl level (using INotifyPropertyChanged).
the problem: seems to occur on postback (OnControlSave & OnControlPageChange) when you have an existing SuperClass already in viewstate. i'm picking up the existing SuperClass, updating it with new responses, and saving it back to viewstate. but if the SubClassResponse coming from user input is set to empty (by erasing text from or un-selecting the items in the child controls of the SubClassControl), the empty response class (not null; actually initialized as ValueType.Empty) does not update the corresponding entry in the MySuperClassStore before saving back to the viewstate. i've even tried to clear the value in SubClassCollection.UpdateResponse() before setting it, but no luck. i thought i might have a problem with my IEquatable<> but it seems fine. new response values and (non-empty) updated response values are both correctly updated and saved to the viewstate on each save/page change.
BUT (and here's where i'm losing it), when i pop some breakpoints in and step through the code (vs2010), it correctly overwrites the removed entry with the empty (but initialized!) response class and saves back to viewstate (in MySuperClassStore). perfect. every single time. until i quit the debugger, and then it stops working.
stripped down version of my classes:
public class SuperClass {
public Guid SuperClassId { get; set; }
public SubClassCollection ThisCollection { get; set; }
public static SuperClass Deserialize(string s) { /* deserialize xml */ }
}
public class SubClassCollection : KeyedCollection<Guid, SubClass> {
public void UpdateResponse(Guid id, SubClassResponse scr) {
this[id].Response = scr;
//this[id].Response = (!scr.IsEmpty) ? scr : new SubClassResponse();
//** seriously?!? that didn't fix it?
}
}
public class SubClass {
public Guid SubClassId { get; set; }
public int SomeIntProp { get; set; }
private SubClassResponse _response;
public SubClassResponse Response {
get { return (_response != null) ? this._response : new SubClassResponse(); }
set { if (!_response.Equals(value)) { _response = value; OnPropertyChanged("Response"); } }
}
}
public class SubClassResponse : IEquatable<SubClassResponse> {
public string Value { get; set; }
public ValueType ThisValueType { get; set; }
public bool IsEmpty {
switch (this.ThisValueType) {
case ValueType.Empty: return true; break;
case ValueType.StringValue: return String.IsNullOrEmpty(this.Value); break;
case ValueType.ListItemCollection: /* check for null, then .Count > 0 */ break;
default: return true; break;
}
}
public enum ValueType { Empty, StringValue, ListItemCollection, Etc }
}
and my top-level control has this going on:
public class SuperClassControl : CompositeControl, INamingContainer {
// OnInit: load MySuperClass from db
// OnPreRender: get MySuperClassStore from viewstate and output as debug info
protected SuperClass MySuperClass { get; set; }
protected SuperClass MySuperClassStore {
get {
return (ViewState["SuperClassStore"] == null) ? new SuperClass() : SuperClass.Deserialize((string)ViewState["SuperClassStore"]);
}
set { ViewState["SuperClassStore"] = value.ToSerialized(); }
}
protected override void CreateChildControls() {
// generate control hierarchy
SuperClass mysuperclass = this.MySuperClass;
foreach (SubClass mysubclass in mysuperclass.ThisCollection) {
this.Controls.Add(new SubClassControl(mysubclass));
}
// add top-level control linkbuttons, hook up events, etc...
}
protected void OnControlSave(object sender, EventArgs e) {
SuperClass mysuperclass = this.MySuperClass;
SuperClass mystorageclass = this.MySuperClassStore;
// loop through control hierarchy, make sure we've got any response changes
// copy the changes to mystorageclass for persistence
// ***** problem is occurring here?? *****
foreach (Guid g in mysuperclass.ThisCollection.HasChanges) {
mystorageclass.ThisCollection.UpdateResponse(g, mysuperclass.ThisCollection[g].Response);
}
// if the current page only displayed i=5 to 8, will only update i=5 to 8
this.MySuperClassStore = mystorageclass;
}
protected void OnControlPageChange(object sender, EventArgs e) {
OnControlSave(sender, e);
// set next or previous page index then reset control hierarchy
}
}
any thoughts? i've been messing with this for several days now, and i'm running out of ideas. thanks in advance!
will
so, i solved this problem by inverting the update process:
mystorageclass.ThisCollection.UpdateResponse(g, mysuperclass.ThisCollection[g].Response);
this.MySuperClassStore = mystorageclass;
to:
mysuperclass.ThisCollection.UpdateResponse(g, mystorageclass.ThisCollection[g].Response);
this.MySuperClassStore = mysuperclass;
where the loop gets all SubClass NOT existing on the current page.
i works, so "yay?" - but i'm still at a loss for why this actually works better than the original piece of code, so if anyone has some insight into this, i'll happily give you points for the answer!