I have a collection of an object called Bookmarks which is made up of a collection of Bookmarks. This collection of bookmarks are bound to a treeview control.
I can get the bookmarks back out that I need, but I need a copy of the bookmarks so I can work with it and not change the original.
Any thoughts.
Thanks.
Create a new constructor for your bookmark class that takes an existing bookmark as the parameter.
Within this new constructor, copy all the property values from the existing bookmark onto the new one.
This technique is known as a "Copy Constructor".
There's an article on MSDN that goes into more detail - see How to Write a Copy Constructor.
Most collection classes in .Net provide a constructor overload that allow you to pass in another collection like
dim copyOfBookMars as New List(of BookMark)(myOriginalBookMarkList)
Been awhile with VB, but c# offers a clone() method.
You don't generally make a copy of an Object, an Object makes a copy of itself (clone). Since an object contains state inforrmation, a bitwise copy can't be counted on as being appropriate; so the defining class needs to take care of it.
You may want to implement multiple simultaneous pointers (bookmarks) in your case.
Related
I am implementing Undo/Redo for my .NET 5 application. I have an ObservableCollection of Activity objects (the collection is named Activities). Each Activity object contains two ObservableCollections "ActionItems" and "Notes". The Activities collection is displayed using a ListBox, and the ActionItems and Notes collections are displayed using two respective ItemsControls/ScrollViewers. The Activities ListBox is IsSynchronizedWithCurrentItem, which keeps the ItemsControls in synch.
After each change to an object anywhere in my application, I store a clone on the Undo/Redo stack. When it's time to unto an action on the higher level objects, e.g. an Activity, I replace the Activity object in the ObservableCollection directly:
Activities[0] = activityCloneFromUndoStack;
After this replacement, the UI updates and everything is just fine. However, when I subsequently add a new ActionItem or Note to their respective ObservableCollections on this particular Activity instance, their ItemsControls on the UI do not update. The items are successfully added to the model, but the ItemsControl UI is not reflecting the change.
I have tried refreshing the Items collection:
ItemsControlActivitiesActionItems.Items.Refresh();
But that has no effect. Any other ideas or advice would be appreciated.
UPDATE:
I can see that the Activity object that the new Note/ActionItem is being added to successfully is the Activity object PRIOR to the Undo operation. So even though the parent Activites ListBox control contains the latest Activity object and its synched properties are displaying correctly in the UI, the Notes and ActionItems ItemsControls are still pointing to the original Activity object before the Undo operation. So now the question is
"How do I force the 'child' ItemsControls to recognize the newly replaced object in the 'parent' (IsSynchronizedWithCurrentItem) ListBox control?"
So I was finally able to trace the problem back to the way I was cloning the objects. I was using the .MemberwiseClone method to make a shallow copy, and then turn that into a deep copy by manually copying all of the reference types. But somehow the WPF engine seemed to be holding onto a reference to the last object before the undo operation.
So I change my method of cloning to use a constructor that accepts the object to be copied as an argument. This is one of the other approaches suggested in the documentation:
There are numerous ways to implement a deep copy operation if the
shallow copy operation performed by the MemberwiseClone method does
not meet your needs. These include the following:
Call a class constructor of the object to be copied to create a second
object with property values taken from the first object. This assumes
that the values of an object are entirely defined by its class
constructor.
Call the MemberwiseClone method to create a shallow copy of an object,
and then assign new objects whose values are the same as the original
object to any properties or fields whose values are reference types.
The DeepCopy method in the example illustrates this approach.
Serialize the object to be deep copied, and then restore the
serialized data to a different object variable.
Use reflection with recursion to perform the deep copy operation.
I'm trying to wrap my head around array lists in vb.net. I am self teaching via the internet but can't seem to figure it all out. Some points i'm having trouble connecting the dots to:
How do i make the array list universal so it's not stuck in a subroutine and I can allow any sub to access the list.
Allowing the list to be added to or removed from from another control on the form.
Saving this array list so the program will populate the list box with it on startup.
Here is an image of the basic concept for the visual:
https://imgur.com/lBbopD8
Up to question 3, using a List(Of T) was the way to go. It may still be but certainly not completely and maybe not at all. Before the advent of the List(Of T), Microsoft recognised that storing Strings in a collection was the most common requirement so, to provide type-safety in that case, they provided the StringCollection class. You say that you want to persist your list of values between sessions so that probably means using My.Settings and it is actually possible to create a setting of type StringCollection.
I would suggest that you open the Settings page of the project properties and add a setting of type StringCollection. Once added, that list will be automatically loaded at startup and saved at shutdown, with no code required from you. You can access it anywhere in the app via My.Settings and you can call Add and Remove or index it or loop over it in exactly the same way as you would an ArrayList or List(Of String).
There is one small gotcha with a StringCollection in settings though. It will actually be Nothing by default. The trick to avoiding that is to edit its Value on the Settings page to add an item, commit that, then edit it again to remove the item. You'll see that, instead of the Value field being empty, it will then contain a snippet of XML. It's that that creates the StringCollection object in the settings file.
As I said, if you want to persist this list between sessions then I strongly recommend using settings this way. Just note that, in order to edit settings, they have to be User-scoped rather than Application-scoped. What that means is that each separate Windows user will have their own copy of the setting and thus their own value. If you only log into Windows with one account then it's of no consequence. If multiple Windows users use the app then it may be considered beneficial in most cases but may be a problem if you want universal settings that can be edited. If it's a problem, you will need to handle persistence yourself but be aware that a standard Windows user (as opposed to an admin) won't have access to write data everywhere, which is exactly why User-scoped settings work the way they do.
Also, while you must use a StringCollection for persistence in settings, you may or may not want to use the same collection in the rest of your code. You might access the collection directly all the time or you may choose to copy the collection to a List(Of String) at startup and then copy the data back at shutdown. Unless you want to avoid committing items until shutdown, I wouldn't bother with the extra collection.
So an important thing to know is that you can directly populate and edit the listbox without having an additional ArrayList. You would use the example code below as follows:
'addTb is the text box you had in the image; this will run on button press event
ListBox1.Items.Add(addTB.Text)
If you are looking to dump ArrayList data into the list box use something like this:
'creates new arraylist and adds items to it
Dim listStuff As ArrayList = New ArrayList
listStuff.Add("Hi")
listStuff.Add(2)
'makes listStuff the datasource for your list box
ListBox1.DataSource = listStuff
Finally, if you are wanting to loop through ArrayList items use something like this:
'remember to do count - 1 or you will receive error since index will be out of range
For i = 0 To listStuff.Count - 1
If listStuff.Item(i) = "" Then
'do stuff here
End If
Next
Hopefully that helps. Let me know if I need to be more clear since this is my first stack overflow answer :)
You are using a ListBox control to visualize a collection of presumably String values from a TextBox control. The ListBox exposes the visualized collection via the Items property.
How do i make the array list universal so it's not stuck in a subroutine and I can allow any sub to access the list.
Because the ListBox control resides on the Form, you can access the Items property through any access level in your Form's code.
Allowing the list to be added to or removed from from another control on the form.
From the Items property, you can use the Add method to add a single value, the AddRange method to add multiple values via an array or another ListBox collection, the Insert method to insert a value at a given index, the Remove method a specific item, and the RemoveAt method to remove an item at a given index.
So in your case, since you're presumably adding the value from the Text property of the TextBox to the ListBox, it is as simple as:
ListBox1.Items.Add(TextBox1.Text)
Saving this array list so the program will populate the list box with
it on startup.
You have a few options, but generally the idea is to is write each value in the Items property to its respective line at a given file when the application closes and then load each value back by reading each line from the same file. Another option is to use My.Settings, though I think with your level of expertise, it would probably be better to stick with the read/write to a file option so you don't have to worry about some pitfalls associated with this option. Here would be a quick example of reading/writing the items to a file:
'Write the items to the file
Dim items(ListBox1.Items.Count - 1) As String
ListBox1.Items.CopyTo(items, 0)
IO.File.WriteAllLines("file.txt", items)
'Read the items to the file
ListBox1.Items.AddRange(IO.File.ReadAllLines("file.txt"))
Having to work with a legacy silverlight application I ran into a strange piece of code. The viewmodel has a List dependency property as binding source for the grid. This DP has a default value, an other List that is used globally in the app. This is used to easily share entity data between different parts of the application.
DependencyProperty MyEntitiesProperty = DependencyProperty.Register("MyEntities", typeof(List<Entity>), typeof(...), new PropertyMetadata(Global.Entities));
Now, when the list is changed (on user actions), the global list is repopulated from database but MyEntities is never set explicitly. This does not work: the grid (the binding target) never changes. So its a wrong solution.
I speculate that the idea behind all this could have been been the following: if you have a DP with a given value and you never set a local value for it then the effective value of the DP will be the default value. If the 'underlying' default value is changed, the changes are reflected in the effective value.
If it worked, it was a nice way of sharing data between independent viewmodels without fiddling with property change events and such.
What is wrong here? Is it a big misunderstanding of how DPs work or the idea was ok and some implementation details were missed?
Please comment if something is not clear.
Well, taking also your comment into account, it is a big misunderstanding of how DPs work. Let me explain:
Setting a globally known list as the default value of MyEntities might not be a pattern I recommend, but is technically not faulty and can be done to share a list. MyEntities now holds a reference to this very list.
If you now replace the global list with a new list instance, the old instance does not cease to exist. Your property MyEntities still holds a reference to the old list. The value of a DP is only updated automatically if it is bound via Binding to either an ordinary property that is wired with the INotifyPropertyChanged mechanism or another DP.
Setting a default value happens neither via a Binding to an ordinary property nor via a Binding to another DP, it is just a plain old object reference.
I can think of several ways to correct the situation:
First solution
If the global list implements INotifyCollectionChanged (e.g. ObservableCollection, DependencyObjectCollection) you can - instead of creating a new list instance - just delete the old items from the list and add the new items. The views that have a reference to the list will perform an update as soon as they receive the associated CollectionChanged event.
Second solution
Make sure the Global.Entities list is available and always up-to-date as a public property (wired with INotifyPropertyChanged) on the DataContext of the root view. Now when you want a nested view somewhere deep down inside the UI tree to be connected to this Global.Entities list you can bind it to the root view's DataContext' public list property.
<MyRootView>
... nested views spread across multiple files ...
<MyNestedEntitiesListDisplay
MyEntities="{Binding
Path=DataConext.GlobalEntities,
RelativeSource={RelativeSource AncestorType=MyRootView}}"/>
I have a problem. I have two observable collections with the same objects as content.
I grep one object from my datagrid with observablecollection.
datagrid.selecteditem as object
and want to delete it from the second observable collection. The line statement looks like
obscollection.remove(datagrid.selecteditem as object);
The objects are completly the same, but when I count obscollection the object isn't removed...
How can I solve this? Please no questions on why I need 2 collections with the same content ;)
When you say "the objects are completely the same", do you mean they are equivalent, or they are the same instance? My guess is that they are equivalent, but they are not actually the same object instance. The .remove() method of the ObservableCollection will be looking for reference equality, and therefore if it is not the same object instance, it won't find the object you're looking for (and therefore won't remove it).
I'd recommend looking at information about object equivalency, this article speaks to it, as does this answer (and there are many more out there if you do a search).
If the two collections have equivalent objects, but do not reference the same instance, then there are many simple solutions. One way would be to implement the IComparable interface, or use something like LINQ to find the equivalent object in the second collection and manually remove it.
Hope this helps.
I'm working on a WPF project and have implemented a very simple way to undo one level of change which works nicely throughout the project except for one case where changes to an object's property reflects in the MemberwiseClone.
What I am doing is to do a MemberwiseClone in my object before adding or editing properties in that object, and then if the user wants to undo, I copy each property from the MemberwiseClone object back into my current object.
Because I am using WPF binding, using the MemberwiseClone is attractive to me because up until now, any change made in a property was not reflected in the MemberwiseClone. This time I have a property in my object that is an ObservableCollection of another object, and what is happening is that if I add an item to the ObservableCollection, it also gets added to the object created by MemberwiseClone and I can never truly undo.
Is there any way around this? Any thoughts you might have on this are welcomed.
Thanks.
According to Object.MemberwiseClone Remarks the object references in your ObservableCollection will be copied but not the referenced object itself. Therefore your undo collection references the same possible changed objects.
You need a deep copy, not a shallow one. Take a look at How do you do a deep copy an object in .Net (C# specifically)?