Answered by:
revert single xml record to original state

Question
-
Hey gurus....
My little project has become quite a leviathan... so here's the scenario:
I have a user interface that displays records from an XML database and allows manipulation of the record. I am using
doc = XDocument.Load(path) to load the XML document. As the record is changed, the changes are written to the doc... Here's the problem: the interface allows more than one record to be displayed at a given time.
If the user closes one of the records, he is queried as to whether he wants to save any changes. If so, the doc is written (and eventually saved as well, when the program closes)
HOWEVER... if he wishes to abandon any changes, I need to reload the doc... in so doing I also lose any changes to other open records....
I would like to selectively reload a single record from the source document and leave the rest of the doc (virtual copy) in its current state...
thanks
Pete
- Edited by peteepoo Saturday, August 10, 2013 7:25 PM
Saturday, August 10, 2013 7:24 PM
Answers
-
Hi peteepoo;
I have looked at the code and made some changes, basically I modified it to make the save changes as you go, but this is not the way I would do it. I would take a class like your recipeinfo and add a some more properties to it like a flag to state that the original data has changed and I also would have properties for original values and new values. this way before updating the document You could decide to update with new value on revert to old values. Then when you load the XML you create a recipeinfo object for each recipe when you modify the recipe you update your object. Then when you are ready to update the document you could iterate through the list and update the document and save.
In your code you used the TextChange to save your object to the document. The problem with this is that that event is called every time you press a keystroke so if you named the fruit banana it will have executed 6 times. In the posted code below I changed it to LostFocus this way the event is called only once when you move to another control.I placed comments in the code please read.
Public Class Form1 Public recipepath As String = " C:\Working Directory\recipe.xml" Public doc As XDocument = XDocument.Load(recipepath) Dim myrecipeinfo1 As New recipeinfo Dim myrecipeinfo2 As New recipeinfo Dim change1 As Boolean = False Dim change2 As Boolean = False Dim recipechoice1 As String Dim recipechoice2 As String Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load loadrecipebox() recipebox1.SelectedIndex = 0 recipechoice1 = recipebox1.SelectedItem ' Initialize to what the ComboBox is set to ADDED recipebox2.SelectedIndex = 1 recipechoice2 = recipebox1.SelectedItem ' Initialize to what the ComboBox is set to ADDED End Sub Private Sub loadrecipebox() 'loads the 2 comboboxes with the list of choosable recipes Dim result1 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe) For Each L As XElement In result1.ToList If recipebox1.Items.Contains(L.Value.ToString) = False Then ' Seeming that both combo box have the same items and they both start out empty ' you can load them both at the same time. recipebox1.Items.Add(L.Value.ToString) recipebox2.Items.Add(L.Value.ToString) End If Next End Sub Private Sub recipebox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox1.SelectedIndexChanged 'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display recipechoice1 = recipebox1.SelectedItem ' Saving as you make the changes, then this is not needed 'If change1 = True Then ' querysave() ' 'doc = XDocument.Load(recipepath) 'Else ' 'doc = XDocument.Load(recipepath) 'End If myrecipeinfo1.recipe = recipechoice1 fruit1.Text = myrecipeinfo1.fruitname amount1.Text = myrecipeinfo1.fruitamount End Sub Private Sub recipebox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox2.SelectedIndexChanged 'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display recipechoice2 = recipebox2.SelectedItem ' Saving as you make the changes, then this is not needed 'If change2 = True Then ' querysave() 'End If myrecipeinfo2.recipe = recipebox2.SelectedItem fruit2.Text = myrecipeinfo2.fruitname amount2.Text = myrecipeinfo2.fruitamount End Sub Private Sub fruit1_LostFocus(sender As Object, e As EventArgs) Handles fruit1.LostFocus 'as the value is changed, it is written to doc (this is necesary for real time calcs) If fruit1.Text <> myrecipeinfo1.fruitname Then change1 = True Dim newamt = (From el In doc.Descendants("recipe") Where el.Elements("name").Value = recipechoice1 Select el.Element("ingredients").Element("fruit")).SingleOrDefault() If newamt IsNot Nothing Then newamt.Element("name").Value = fruit1.Text End If querysave() ' Added this line of code End If End Sub Private Sub fruit2_LostFocus(sender As Object, e As EventArgs) Handles fruit2.LostFocus If fruit2.Text <> myrecipeinfo2.fruitname Then change2 = True Dim newamt = (From el In doc.Descendants("recipe") Where el.Elements("name").Value = recipechoice2 Select el.Element("ingredients").Element("fruit")).SingleOrDefault() If newamt IsNot Nothing Then 'rnd added to make the ingredient unique to find later newamt.Element("name").Value = fruit2.Text MsgBox(newamt.Element("name").Value.ToString) End If querysave() ' Added this line of code End If End Sub Private Sub querysave() 'checks if you want to save your changes... if yes: save the doc to the xml file then reload it... ' if no: reload the doc as it is originally (revert) If MsgBox("Save Changes to the Recipe?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then doc.Save(recipepath) doc = XDocument.Load(recipepath) Else doc = XDocument.Load(recipepath) End If change1 = False change2 = False End Sub End Class
Fernando (MCSD)
If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.- Marked as answer by Starian chenMicrosoft contingent staff, Moderator Sunday, August 25, 2013 6:29 AM
Friday, August 16, 2013 4:38 PM
All replies
-
Hi peteepoo
Can you please post all relevant code for the above question.
Thanks
Fernando (MCSD)
If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.Sunday, August 11, 2013 1:05 AM -
Fernando...
ridiculously long program I have going on... I'll try to snippet out the relevant codes and post... I'll post when complete...
Sunday, August 11, 2013 6:00 PM -
Fernando,
thanks so much for the response. I've attached the vb code (main page and class recipeinfo) as well as the xml code ... couple of caveats:
- this is a simplified version of my project but encapsulates the crux of my issue
- two sets of recipe characteristics are shown at a time
- The doc is updated as it is changed so other displays are easily updated in real time (I need to do it that way as far as I know)
- My problem is in the SUB querysave:
if I try to change the recipe name combobox and one of the fruit names has been changed (fruit1 or fruit2), you will be queried if you would like to save the changes.
if you answer yes, ALL changes (possibly from the other recipe which you don't want saved) are saved to the doc and then reloaded... conversely, if you want to discard the changes on recipe1 but keep working on recipe 2, its going to reload BOTH recipes with the original reverted XML data.
It seems that the solution lies in either selectively saving the single recipe node to the XML file or selectiveloy loading the single recipe node from the XML file.... I don't know how to do either...
any help would be appreciated
Pete Hahn
Imports System.Xml Imports System.IO Public Class Form1 Public recipepath As String = " C:\Users\pete\Desktop\shadow\shadow\recipe.xml" Public doc As XDocument = XDocument.Load(recipepath) Dim myrecipeinfo1 As New recipeinfo Dim myrecipeinfo2 As New recipeinfo Dim change1 As Boolean = False Dim change2 As Boolean = False Dim recipechoice1 As String Dim recipechoice2 As String Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load loadrecipebox() recipebox1.SelectedIndex = 0 recipebox2.SelectedIndex = 1 End Sub Private Sub loadrecipebox() 'loads the 2 comboboxes with the list of choosable recipes Dim result1 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe) For Each L As XElement In result1.ToList If recipebox1.Items.Contains(L.Value.ToString) = False Then recipebox1.Items.Add(L.Value.ToString) End If Next Dim result2 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe) For Each L As XElement In result2.ToList If recipebox2.Items.Contains(L.Value.ToString) = False Then recipebox2.Items.Add(L.Value.ToString) End If Next End Sub Private Sub recipebox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox1.SelectedIndexChanged 'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display recipechoice1 = recipebox1.SelectedItem If change1 = True Then querysave() 'doc = XDocument.Load(recipepath) Else 'doc = XDocument.Load(recipepath) End If myrecipeinfo1.recipe = recipechoice1 fruit1.Text = myrecipeinfo1.fruitname amount1.Text = myrecipeinfo1.fruitamount End Sub Private Sub recipebox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox2.SelectedIndexChanged 'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display recipechoice2 = recipebox2.SelectedItem If change2 = True Then querysave() End If myrecipeinfo2.recipe = recipebox2.SelectedItem fruit2.Text = myrecipeinfo2.fruitname amount2.Text = myrecipeinfo2.fruitamount End Sub Private Sub fruit1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles fruit1.TextChanged 'as the value is changed, it is written to doc (this is necesary for real time calcs) If fruit1.Text <> myrecipeinfo1.fruitname Then change1 = True Dim newamt = (From el In doc.Descendants("recipe") Where el.Elements("name").Value = recipechoice1 Select el.Element("ingredients").Element("fruit")).SingleOrDefault() If newamt IsNot Nothing Then newamt.Element("name").Value = fruit1.Text End If End If End Sub Private Sub fruit2_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles fruit2.TextChanged 'as the value is changed, it is written to doc (this is necesary for real time calcs) If fruit2.Text <> myrecipeinfo2.fruitname Then change2 = True Dim newamt = (From el In doc.Descendants("recipe") Where el.Elements("name").Value = recipechoice2 Select el.Element("ingredients").Element("fruit")).SingleOrDefault() If newamt IsNot Nothing Then 'rnd added to make the ingredient unique to find later newamt.Element("name").Value = fruit2.Text MsgBox(newamt.Element("name").Value.ToString) End If End If End Sub Private Sub querysave() 'checks if you want to save your changes... if yes: save the doc to the xml file then reload it... ' if no: reload the doc as it is originally (revert) If MsgBox("Save Changes to the Recipe?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then doc.Save(recipepath) doc = XDocument.Load(recipepath) Else doc = XDocument.Load(recipepath) End If change1 = False change2 = False End Sub End Class
Public Class recipeinfo Public Property recipe() As String Public ReadOnly Property fruitname() As String Get Dim query = (From r In Form1.doc.Descendants("recipe") Where r.Elements("name").Value = Me.recipe From re In r.Descendants("fruit") Select re.Elements("name").Value).SingleOrDefault() fruitname = If(query IsNot Nothing, query.ToString, String.Empty) End Get End Property Public ReadOnly Property fruitamount() As String Get Dim query = (From r In Form1.doc.Descendants("recipe") Where r.Elements("name").Value = Me.recipe From re In r.Descendants("fruit") Select re.Elements("amount").Value).SingleOrDefault() fruitamount = If(query IsNot Nothing, query.ToString, String.Empty) End Get End Property End Class
<?xml version="1.0" encoding="iso-8859-1"?> <recipes> <recipe> <name>gmas apple pie</name> <type>pie</type> <date>08/15/2013</date> <ingredients> <fruit> <name>apple</name> <amount>5</amount> </fruit> </ingredients> </recipe> <recipe> <name>lemonade</name> <type>drink</type> <date>08/15/2013</date> <ingredients> <fruit> <name>lemon</name> <amount>4</amount> </fruit> <sweetener> <name>aspartame</name> <amount>3 tsp</amount> </sweetener> </ingredients> </recipe> <recipe> <name>my marmalade</name> <type>jam</type> <date>08/15/2013</date> <ingredients> <fruit> <name>orange</name> <amount>3</amount> </fruit> <sweetener> <name>cane sugar</name> <amount>2 cups</amount> </sweetener> </ingredients> </recipe> </recipes>
- Edited by peteepoo Thursday, August 15, 2013 10:14 PM
Thursday, August 15, 2013 10:08 PM -
Hi peteepoo;
I have looked at the code and made some changes, basically I modified it to make the save changes as you go, but this is not the way I would do it. I would take a class like your recipeinfo and add a some more properties to it like a flag to state that the original data has changed and I also would have properties for original values and new values. this way before updating the document You could decide to update with new value on revert to old values. Then when you load the XML you create a recipeinfo object for each recipe when you modify the recipe you update your object. Then when you are ready to update the document you could iterate through the list and update the document and save.
In your code you used the TextChange to save your object to the document. The problem with this is that that event is called every time you press a keystroke so if you named the fruit banana it will have executed 6 times. In the posted code below I changed it to LostFocus this way the event is called only once when you move to another control.I placed comments in the code please read.
Public Class Form1 Public recipepath As String = " C:\Working Directory\recipe.xml" Public doc As XDocument = XDocument.Load(recipepath) Dim myrecipeinfo1 As New recipeinfo Dim myrecipeinfo2 As New recipeinfo Dim change1 As Boolean = False Dim change2 As Boolean = False Dim recipechoice1 As String Dim recipechoice2 As String Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load loadrecipebox() recipebox1.SelectedIndex = 0 recipechoice1 = recipebox1.SelectedItem ' Initialize to what the ComboBox is set to ADDED recipebox2.SelectedIndex = 1 recipechoice2 = recipebox1.SelectedItem ' Initialize to what the ComboBox is set to ADDED End Sub Private Sub loadrecipebox() 'loads the 2 comboboxes with the list of choosable recipes Dim result1 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe) For Each L As XElement In result1.ToList If recipebox1.Items.Contains(L.Value.ToString) = False Then ' Seeming that both combo box have the same items and they both start out empty ' you can load them both at the same time. recipebox1.Items.Add(L.Value.ToString) recipebox2.Items.Add(L.Value.ToString) End If Next End Sub Private Sub recipebox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox1.SelectedIndexChanged 'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display recipechoice1 = recipebox1.SelectedItem ' Saving as you make the changes, then this is not needed 'If change1 = True Then ' querysave() ' 'doc = XDocument.Load(recipepath) 'Else ' 'doc = XDocument.Load(recipepath) 'End If myrecipeinfo1.recipe = recipechoice1 fruit1.Text = myrecipeinfo1.fruitname amount1.Text = myrecipeinfo1.fruitamount End Sub Private Sub recipebox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox2.SelectedIndexChanged 'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display recipechoice2 = recipebox2.SelectedItem ' Saving as you make the changes, then this is not needed 'If change2 = True Then ' querysave() 'End If myrecipeinfo2.recipe = recipebox2.SelectedItem fruit2.Text = myrecipeinfo2.fruitname amount2.Text = myrecipeinfo2.fruitamount End Sub Private Sub fruit1_LostFocus(sender As Object, e As EventArgs) Handles fruit1.LostFocus 'as the value is changed, it is written to doc (this is necesary for real time calcs) If fruit1.Text <> myrecipeinfo1.fruitname Then change1 = True Dim newamt = (From el In doc.Descendants("recipe") Where el.Elements("name").Value = recipechoice1 Select el.Element("ingredients").Element("fruit")).SingleOrDefault() If newamt IsNot Nothing Then newamt.Element("name").Value = fruit1.Text End If querysave() ' Added this line of code End If End Sub Private Sub fruit2_LostFocus(sender As Object, e As EventArgs) Handles fruit2.LostFocus If fruit2.Text <> myrecipeinfo2.fruitname Then change2 = True Dim newamt = (From el In doc.Descendants("recipe") Where el.Elements("name").Value = recipechoice2 Select el.Element("ingredients").Element("fruit")).SingleOrDefault() If newamt IsNot Nothing Then 'rnd added to make the ingredient unique to find later newamt.Element("name").Value = fruit2.Text MsgBox(newamt.Element("name").Value.ToString) End If querysave() ' Added this line of code End If End Sub Private Sub querysave() 'checks if you want to save your changes... if yes: save the doc to the xml file then reload it... ' if no: reload the doc as it is originally (revert) If MsgBox("Save Changes to the Recipe?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then doc.Save(recipepath) doc = XDocument.Load(recipepath) Else doc = XDocument.Load(recipepath) End If change1 = False change2 = False End Sub End Class
Fernando (MCSD)
If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.- Marked as answer by Starian chenMicrosoft contingent staff, Moderator Sunday, August 25, 2013 6:29 AM
Friday, August 16, 2013 4:38 PM