WPF: Save current value of changing variable - c#

I want to save the current state of List<List<Button>> lloMatrix in an other variable List<List<Button>> lloMatrixCopy, create a Frame (a class i wrote) with it and add it to a list loFrames. lloMatrixCopy, as a property of the Frame, shall not change afterwards. I tried different ways, but my final List only lists equal lloMatrixCopy everytime, all identical to the latest Version of lloMatrix.
So my question is how to make a copy of the current state of lloMatrix without it getting overwritten afterwards as soon as lloMatrix changes.
List<List<Button>> lloMatrixCopy = new List<List<Button>>;
List<List<Button>> lloMatrix = new List<List<Button>>;
List<Frame> loFrames = new List<Frame>;
//...
//lloMatrix gets filled with objects
//...
private void Btn_Click(object sender, RoutedEventArgs e)
{
lloMatrixCopy = lloMatrix;
var oNewFrame = new Frame(lloMatrixCopy);
loFrames.Add(oNewFrame);
}
lloMatrix is getting changed afterwards, but loFrames shall only list it state at that Moment the button got pressed. I guess it's an easy question, but i tried many Things and it just doesn't work. Also sorry for not perfect english. I hope it's understandable.
EDIT: Thank you for fast Responses, but for some reasons
_lloMatrixCopy = _lloMatrixLeds.Select(original => original.ToList()).ToList();
also doesn't solve the Problem. Here the full Btn_Click()-Method
private void Btn_Click(object sender, RoutedEventArgs e)
{
lloMatrixCopy = lloMatrix.Select(original => original.ToList()).ToList();
var oNewFrame = new Frame(lloMatrixCopy);
loFrames.Add(oNewFrame);
//After adding the copy to the list i want to put lloMatrix back in Default
//mode - which means in my case Change the Background Color of every Button to a specific Default
//Color. but the foreach-loop doenst only Change the lloMatrix, but also the
//copy, so that every Matrix saved in loFrames is a Default Matrix
// Globals.LClickedButtons is a list of every Button in lloMatrix which Background-Color
// isn't equal to the Default-Color
foreach (var btn in Globals.LClickedButtons)
{
btn.Background = loLedColorBrushes[0];
}
}
Every Matrix in loFrames still is a default Matrix as soon as the foreach-loop is done.

this will make a deep copy
using System.Windows.Markup;
using System.Xml;
using System.IO;
T CloneXaml(T source){
string xaml = XamlWriter.Save(T);
using (StringReader stringReader = new StringReader(xaml))
using (xmlReader = XmlReader.Create(stringReader))
return (T)XamlReader.Load(xmlReader);
}
lloMatrixCopy = lloMatrix.Select( inner => inner.ToList().Select(CloneXaml)).ToList();
You need to understand that List<T> is a reference type.
There are two kinds of types in C#: reference types and value types.
Variables of reference types store references to their data (objects),
while variables of value types directly contain their data. With
reference types, two variables can reference the same object;
therefore, operations on one variable can affect the object referenced
by the other variable. With value types, each variable has its own
copy of the data, and it is not possible for operations on one
variable to affect the other (except in the case of ref and out
parameter variables, see ref and out parameter modifier).

This
lloMatrixCopy = lloMatrix;
Simply creates a second reference to each of the items in the list so if you modify an item in one of the lists the corresponding item in the other list gets modified too.
You need to duplicate each item in the list and put it into your second list. Something like this will work
lloMatrixCopy = lloMatrix.Select(original => original.ToList()).ToList();

You need to clone each of individual the Button elements. The easiest way to clone a WPF element is to use the XamlWriter.Save and XamlReader.Load methods as suggested here:
How can you clone a WPF object?
The following only creates a copy of the List<Button>:
_lloMatrixCopy = _lloMatrixLeds.Select(original => original.ToList()).ToList();
Both lists will still hold references to the very same Button elements.
So you need to do something like this to clone the actual Button elements:
foreach (var btn in Globals.LClickedButtons)
{
string xaml = System.Windows.Markup.XamlWriter.Save(btn);
using (System.IO.StringReader stringReader = new System.IO.StringReader(xaml))
{
using (System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(stringReader))
{
Button newButton = (Button)System.Windows.Markup.XamlReader.Load(xmlReader);
//...
}
}
}

Related

Adding a new value in property and adding to list of an object overwrites the property of the already added object C# [duplicate]

List<BillOfLading> bolList = new List<BillOfLading>();
protected void Button1_Click(object sender, EventArgs e)
{
BillOfLading newBol = new BillOfLading("AXSY1414114");
bolList.Add(newBol);
newBol.BillOfLadingNumber = "CRXY99991231";
bolList.Add(newBol);
}
I was expecting that bolList would container two different objects or values, but it appears that this simple code doesn't work. Any ideas?
Resulting Immediates:
bolList
Count = 2
[0]: {kTracker.BillOfLading}
[1]: {kTracker.BillOfLading}
bolList[0]
{kTracker.BillOfLading}
_billOfLadingNumber: "CRXY99991231"
BillOfLadingNumber: "CRXY99991231"
bolList[1]
{kTracker.BillOfLading}
_billOfLadingNumber: "CRXY99991231"
BillOfLadingNumber: "CRXY99991231"
You've only created one object, and added it twice. The fact that you modified that object between the first and second add is irrelevant; the list contains a reference to the object you added, so later changes to it will apply.
You need to replace newBol.BillOfLadingNumber = ".."; with newBol = new BillOfLading("..");
Flynn1179's answer is correct, but to answer your comment - you don't need a different variable for each object. You can do:
protected void Button1_Click(object sender, EventArgs e)
{
BillOfLading newBol = new BillOfLading("AXSY1414114");
bolList.Add(newBol);
newBol = new BillOfLading("CRXY99991231");
bolList.Add(newBol);
}
The important thing to understand is that you're not adding the variable to the list, nor are you adding the object to the list... you're adding the current value of the variable to the list. That current value is a reference to an instance of BillOfLading. In the above code, the list ends up with references to two different objects.

Trying to pass a list to a form by value not reference

Working on a project where I am storing my data using a GenericList, based off a class I created which contains a handful of string members. This works great, but things get troublesome when I try to pass this list to a second form. I initially setup a get/set block in the second form to accept the list and then return it, but the destination is always null. As a workaround I changed the local list on the second form to public and am able to access it that way, but then instead of getting a copy of the list, I get ByRef passing so any changes made are reflected in the master list. Since I want to be able to not accept these changes, I really want this to be a pass by value. These two problems have to be linked...
Here is the setup. In form1 I have this definition at the class level, making this somewhat of a global variable (yeah its bad, but it works for me):
private List<ServerList> theServerList = new List<ServerList>();
Later on I create the new form and (try to) pass my data into it using:
frmEditor theEditor = new frmEditor();
theEditor.updatedServerList = theServerList;
DialogResult res = theEditor.ShowDialog();
On the second from, I have this to receive the data:
private List<ServerList> myServerList = new List<ServerList>();
public List<ServerList> updatedServerList
{
get { return myServerList; }
set { myServerList = updatedServerList; }
}
This results a list on form2 (the myServerList) always being empty. Since this was not working, I commented out all these lines and changed the myServerList definition on form2 to public. Now after instantiating form2 I am able to get the data over by doing this:
theEditor.myServerList = theServerList;
This works in that the data nicely shows up form2, but this kind of an assignment just copies the pointer of the data block in memory from one variable to the other (basically a ByRef passing), so any changes I make on form2 change "both" lists since they are the same. I would like to have a local copy in form2 so I can make changes and then accept them or not depending of if the user clicks Cancel (drop all changes), or OK (copy the local data from form2 back to form1).
Your original does not work because your setter should be:
public List<ServerList> updatedServerList
{
get { return myServerList; }
set { myServerList = value; }
}
If you want to copy the list, you can use ToList on the incoming value:
public List<ServerList> updatedServerList
{
get { return myServerList; }
set { myServerList = value.ToList(); }
}
This is not to do with 'by value' vs 'by reference' since List<T> is a reference type, so the value of a variable of type List<ServerList> is a reference which is copied into the setter method. This reference will point to the same object in both the caller and receiver classes. If you want to copy the contents of the list you need to do it manually using ToList or similar.

Entity framework object context saves new entities that were not added

I have been working with Entity Framework (VS2010 Framework 4.0) in my proyect. I had some trouble with using a different object context per form. What I did then, was to create a object context in the Main Menu Form (stays opened) and everytime I create and show one form, I pass that object context to this new form. Example:
public partial class frm_Menu : Base
{
public Sistema_financiero_Entities db = new Sistema_financiero_Entities();
private void cancelarCuotaToolStripMenuItem_Click(object sender, EventArgs e)
{
frm_Cancelacion_Cuota Form1 = new frm_Cancelacion_Cuota();
Form1.db = db;
Form1.Show();
}
}
Ok, that solution worked fine until now because I needed to use and pass objects throw the differents forms sometimes, and if the objects contexts were different, I got an error.
Now, I have detected a huge issue using it this way. I have a form, where I can pay for the different installments of a loan. I´ll attach an image so then you can see what I´m talking about.
There, you select the different installments you want to pay for. Then, you introduce the value you will finally pay in "Total cobrado". Here is the important thing: When the checkbox image is checked (the blue one - already checked in the image), I create a "payment" entity per installment. Every "payment" object is stored in a list. If I uncheck it, I can change the value and the same thing is done. Obviously, I´m clearing the list before doing a list.Clear();. Then, one the checkbox checked, I can press "Aceptar" (accept). There I add to the database every "payment"(PAGO) in the list. After that, I save all changes.
foreach (Pago p in Lista_nuevos_pagos)
{
db.AddToPago(p);
}
try
{
db.SaveChanges();
this.Close();
}
My problem, is that it´s not only adding those "payments" in the list but the other "payments" entities that were in the list before clearing it. I reach the conclusion that when I clear the list, the objects remains in the object context. I thought that if the entity is not in the database, I have to Add it to the entity in the object context as I did with pago (db.AddToPago(p);).
I wanted to ask you guys how can I solve this issues. I solved it now doing this:
private void cancelarCuotaToolStripMenuItem_Click(object sender, EventArgs e)
{
Sistema_financiero_Entities db = new Sistema_financiero_Entities();
frm_Cancelacion_Cuota Form1 = new frm_Cancelacion_Cuota();
Form1.db = db;
Form1.Show();
}
Instead of creating just one global db for all forms, I create one in the Main Menu for every form. Then, in that form closed event, I dispose that object context.
Then, when i check the checkbox image, before creating the "payments", I delete every "Pago" entity from the object context:
foreach (Pago p in Lista_nuevos_pagos)
{
db.DeleteObject(p);
}
Lista_nuevos_pagos.Clear();
Doing this works correctly, but I´m still having trouble with some other created entities (Installments) that are not deleted when I clear a list. I think I´m doing it wrongly, thats why I need some direction to use EF correctly. I really need to get this done really soon, I don´t have too much time to read EF tutorials.
Just in case, this is how I create every "Pago" (payment)
Pago p = new Pago();
p.desc_aumento_intereses = nudwb1.Value;
p.desc_aumento_punitorios = nudwb2.Value;
p.desc_aumento_gastos = nudwb3.Value;
p.desc_aumento_comision = nudwb4.Value;
p.cotizacion = ntxt_Cotizacion.Value;
p.fecha_hora = fecha_hora;
Cuota c = new Cuota();
string name = tbx.Name.Substring(tbx.Name.IndexOf("-") + 1);
int nro_cuota = Convert.ToInt32(name);
c = Lista_cuotas_cobrar.Where(x => x.num_cuota == nro_cuota).First();
p.Cuota.Add(c);
Thank you for reading, I know this is a lot of info. Hope some guide soon..
I guess that you have references to those object in your Lista_nuevos_pagos list. This is why they will be duplicated.

Two comboBoxes with same members, when one selected, other must be unable to select same member

So I have two comboBoxes (comboBoxFromAccount and comboBoxToAccount). Each has the same datasource, which is AccountsList (a list of BankAccount objects that was passed from the parent form).
I would like to make it so that if an item is selected in one of the comboBoxes, it would no longer be selectable in the other. The way I'm trying to do this is by copying the list of BankAccounts from the comboBoxFromAccount to the comboBoxTo account, and removing the selected index of comboBoxFromAccount from the comboBoxToAccount.
I think I'm close, but what seems to happen is I have a blank comboBoxToAccount.
Here is my code:
private BankAccountCollection accountsListTransferTo = new BankAccountCollection();
// public property for passing collection data to the dialog
public BankAccountCollection AccountsList
{
get { return accountsListTransferTo; }
set { accountsListTransferTo = value; }
}
// Initial loading
private void TransferFundsDialog_Load(object sender, EventArgs e)
{
textBoxAmount.Text = String.Empty;
textBoxAmount.Select();
comboBoxFromAccount.DataSource = AccountsList;
accountsListTransferTo.AddRange(AccountsList); // Copy content
accountsListTransferTo.Remove(comboBoxFromAccount.SelectedItem as BankAccount); // Remove item
comboBoxToAccount.DataSource = accountsListTransferTo; // Data binding
}
private void comboBoxFromAccount_SelectedIndexChanged(object sender, EventArgs e)
{
accountsListTransferTo.Clear(); // Clear list, if you don't to it, AddRange will just add more items.
accountsListTransferTo.AddRange(AccountsList); // Copy ALL accounts
accountsListTransferTo.Remove(comboBoxFromAccount.SelectedItem as BankAccount); // Remove selected, so user cannot transfer to same account
// Refresh data binding
comboBoxToAccount.DataSource = null;
comboBoxToAccount.DataSource = accountsListTransferTo;
// Select very first item in "TO" combobox
comboBoxToAccount.SelectedIndex = 0;
}
Help would be appreciated.
Try removing the line
comboBoxToAccount.DataSource = null;
I have a vague recollection about comboboxes having problems with this.
Another possible problem that I can see is that you are using accountsListTransferTo both as your master collection and the one where you are removing the selected account from. Every time comboBoxFromAccount_SelectedIndexChanged is called one more account will disappear from the collection (and therefore from the available options in comboBoxToAccount).
I think I have seen comboboxes behave in a way where the SelectedIndexChanged (or a similar) event is triggered as new items are being added. If that is the case here it will explain the empty comboBoxToAccount, because comboBoxFromAccount_SelectedIndexChanged will run once for each bank account being added, essentially removing them from the master list and then rebinding the reduced list. You can easily verify this with a break point in your event handler.

c# How to reach a panel that is beeing created while program is running?

I have this for loop:
int iPanelNumber = 1;
foreach (string[] Persons in alItems)
{
Panel pPanelContainer = new Panel();
pPanelContainer.Width = contentPanel.Width;
pPanelContainer.Height = 50;
pPanelContainer.BackColor = Color.FromArgb(
Convert.ToInt32(aWhiteContentBackgroundColors[0]),
Convert.ToInt32(aWhiteContentBackgroundColors[1]),
Convert.ToInt32(aWhiteContentBackgroundColors[2]));
pPanelContainer.Name = "PanelContainer" + iPanelNumber.ToString();
pPanelContainer.Visible = false;
pPanelContainer.Location = new Point(0, 0);
}
So as you can see, i have given the panels i create the name "PanelContainer1", "PanelContainer2" etc...
But how can i reach these panels?
I certainly could not reach them by writing:
PanelContainer1.visible = true;
Anyone got an idea?
Thanks in advance
Easiest way is probably to add a List<Panel> field to your class and store references to all panels in that list, e.g:
class MyClass
{
private List<Panel> _panels = new List<Panel>();
void MethodWhichCreatesThePanels()
{
//..
foreach (string[] Persons in alItems)
{
Panel pPanelContainer = new Panel();
_panels.Add(pPanelContainer);
...
}
}
Then you can access each panel later using an index:
Panel aPanel = _panels[i];
Martin's answer is pretty much what you are looking for, but it appears you are in confusion over what the .Name property of the panel control does.
What it doesn't do is set the name of the variable.
What it does do is the following (from MSDN: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.name.aspx)
The Name property can be used at run time to evaluate the object by name rather than type and programmatic name. Because the Name property returns a String type, it can be evaluated in case-style logic statements (Select statement in Visual Basic, switch statement in Visual C# and Visual C++).
You can't reference the Panel by its name because there's no field in the form (or local variable) with that name. Those fields are defined in the partial class file for the Form that the Form Designer generates; they aren't (and can't be) created at runtime.
This doesn't mean you can't access it by its name; you just can't access it by using its name as a variable name in your code.
The most obvious way to do this is to add the Panel to its containing control's Controls collection after creating it. Since you've set Visible to false, this won't have any visible effect on your form. So in your code, you'd add something like
contentPanel.Add(p);
You can then reference it by name:
contentPanel.Controls["PanelContainer1"].Visible = true;
If for some reason you don't want to add it to the Controls collection yet (there are plenty of reasons you might not), the next approach is to create an instance of a collection class of some kind and add the Panel to that collection. Since you want to be able to reference it by name, the most obvious choice would be a dictionary, e.g.:
Dictionary<string, Panel> panels = new Dictionary<string, Panel>;
...
panels.Add(p.Name, p);
And again, you can then reference it by name:
panels["PanelContainer1"].Visible = true;
...though in this case, the Panel wouldn't actually become visible, because it's not in the Controls collection of a visible container control.
Just as an aside: if it's within your power to do so, you should put an end to using type prefixes on your variable names. There are still shops that use this convention, but it's been generally abandoned by the community of C# programmers.

Categories

Resources