I am making a UndoRedo Functionlity for my application. Now on function DoAction I have the list of Action taken in Stack<UndoRedoAction> Now I want to get the Last Action Taken Which automatically will be First in the List To take out the First in the List I have used actionList.Peek(); Now the Situation arise is that the next time I wanted to take the second one from the list. I am not sure how to do that
private void DoAction(Stack<UndoRedoAction> actionList, Stack<UndoRedoAction> storeList, Action<UndoRedoAction> action)
{
if (actionList.Count > 0)
{
UndoRedoAction urAction = actionList.Peek();
action(urAction);
storeList.Push(urAction);
}
}
You need to use Stack<T>.Pop instead of Peek. Pop removes the last added item in the stack and returns it while Peek returns the last added item without removing it from the stack.
Use Pop for sure (as #Matias) said.
Also check out the Command Pattern as it is perfect for getting abstraction between the code that actually does the work and provides a great basis for undo.
Related
As a reaction from Shane Neuville I'm changing my question. But yes. It may be more complicated then I first thought it was.
I have a ReactiveList<ServiceEntryModel> Services; that will be populated when the view have appeared. With every entry in the list I have used the function called Services.ItemsAdded.Subscribe() where the RxCell will be populated with data that at the end of the whole list should be ordered in a specific way. As of now I have have two separate ReactiveCommands.
1.ReactiveCommand<Unit, List<ServiceEntryModel>> RefreshServices;
2.ReactiveCommand<Unit, List<ServiceEntryModel>> OrderByCommand;
The second command should be called when the whole list is populated from the Services.ItemsAdded.Subscribe method. How can I check/know when every entry is done?
I have tried something with counters and ObservablePropertyHelpers, but without any luck so far.
Here is my ViewModel part that should be relevant
Services.ItemsAdded.Subscribe(async entryItem =>
{
ServiceEntityStatusDto serviceEntityStatus = await GetServiceEntryStatus(entryItem);
if (serviceEntityStatus != null)
{
entryItem.AvailabilityStatus = serviceEntityStatus.AvailabilityStatus;
entryItem.PerformanceStatus = serviceEntityStatus.PerformanceStatus;
}
counterServices++;
//await OrderServices.Execute(); //TODO infinite loop ?
});
The part is commented is a infinite loop, because the Services.ItemsAdded will be continue to be called.
The user can pull to refresh and the first command will be called again, so right when that is done. The seconds should order the whole list again.
Is this enough?
RefreshServices
.SelectMany(_ => OrderServices.Execute())
.Subscribe();
Or the InvokeCommand syntax
RefreshServices
.InvokeCommand(OrderServices);
The distinction being that with InvokeCommand OrderServices will not execute if it's already executing which might not fit your use case. For example maybe RefreshServices can execute again while OrderServices is still running.
I have some mysterious ArgumentException I have been beating the whole day - still have no idea why does it happen.
I have the next simple method in my MainPage:
public void FavsRefresh()
{
favsCanvas.Children.Clear();
for (short i = 0; i < (App.Current as App).favUnits.Count; i++)
{
FavsItems tmpUnit;
(App.Current as App).favUnits.TryGetValue((App.Current as App).ids[i], out tmpUnit);
Canvas.SetTop(tmpUnit.subCanvas, i * 120);
favsCanvas.Children.Add(tmpUnit.subCanvas);
}
}
Here tmpUnit is an instance of my class FavsCanvas. Its code doesn't matter - it merges some elements into Canvas, which is called here subCanvas and a series of them must be added into parent Canvas, called favsCanvas.
The sense in all this, that we have several items initially and the user may delete existing and add new. Every time an item is deleted or added I call this procedure (including initially program loading).
The joke is that it works during loading and when I call it from another pages, but when I call it from class method throws an exception, besides it adds the first element properly and refuses to do that with others.
Every item has unique name, I even tried not to use names at all or use random ones - not a chance. I have no idea why this exception appears?!
I call this method using following:
MainPage.MPInstance.FavsRefresh();
This way works good from another pages, but from class - fails. I even left only one line (simple reload those items in Canvas):
private void FavMenuItem_Click(object sender, RoutedEventArgs e)
{
//Delete favorite
if (((MenuItem)sender).Header.ToString() == AppRes.FavsMenuDeleteFav)
{
MainPage.MPInstance.FavsRefresh();
}
}
The fun is that this code worked when I wrote it first a couple weeks ago, but now somehow stopped.
Another thing I tried is to make this particular call from a method in App.xaml.cs, which in its turn is called from the class, but it didn't help either.
In fact I have studied most of parameters - everything is the same in both cases: when it works and when not, except the place from where the method is called. But I don't see any proper alternative.
--- added 05 Aug.
I am not sure if it is important, but it always point the next line after the line where exception is thrown:
(the forum does not allow me to post images, so here.
I tried to move this method to class both to class itself and to App.xaml.cs - the same problem.
It works properly when is called during loading (my MainPage is Pivot and this page which contains this favCanvas is one of the pivots, but not the first) and when I call it from another page while overriding its OnNavigatingFrom. And when it is called while the the MainPage and this pivot is active. May be something with that?
Well guys, I still can't catch the reason itself, but at last have found a dirty way to walk around.
The page, for some reason, does not like to be modified when it is active (at least this way). So for now I am forced to simply redirect to another page, where I just call my method and then go back.
If you have some ideas, they are still of demand as my current was is like a crutch and I dislike it
I'm using ObjectListView with checkboxes, I would like to run a function on selected items to delete them. So i tried this Method but it not working:
private List<Matricule> matrs;
private void button1_Click(object sender, EventArgs e)
{
//List<Song> s = this.olvSongs.CheckedObjects.Count;
//MessageBox.Show(this.olvSongs.CheckedItems.Count + " " + this.olvSongs.CheckedObjects.Count);
string s = "";
foreach (var item in olvMatrs.SelectedItems)
{
matrs.Remove((Matricule)item);
}
this.olvSongs.SetObjects(matrs);
}
how can i do this task.
You talk about check boxes. The line
foreach (var item in olvMatrs.SelectedItems)
iterates through the ITEMS that are SELECTED, not CHECKED! Is that really what you want?
To get the CHECKED OBJECTS use
objectListView1.CheckedObjects
If you really want to get the SELECTED OBJECTS, don't use Selected*Items*. Use
objectListView1.SelectedObjects;
instead. Thats what the OLV is all about. You want to work with the objects, not with ListViewItems.
If you decided WHAT you want to remove, don't remove the objects from your List, but directly from your ObjectListView using
objectListView1.RemoveObjects(myObjects);
You should probably (re-)read this. Especially the section "Mental gear shift - This is important. You need to understand this.".
well, i see that you don't show the code where it adds anything to matrs, so we are certainly short of useful source code. Also, we don't know what a Matricule is, but i can take a pretty good guess with what you already shared.
i believe 1 of 3 things must be happening if matrs is not getting any items removed.
1: are you sure your function is tied to the click event of the button? you can set a break point in the function to make sure it is even executing. or you can add a line to show a messagebox MessageBox.Show("Yes", this.Text); inside that button1_Click() method.
2: if the function is being executed (so it is not option #1), then my 2nd consideration is that perhaps the (Matricule)item is not in the matrs List to be able to be deleted. that Remove function returns a boolean value indicating whether the remove actually deleted something or not.
3: are you sure it is not getting deleted and that what is really happening is that it really is being deleted but your new updated List is not being shown to you?
I believe you think it is #2, but might want to eliminated the possibility of the other 2 easier options (#1 and #3) first. if you do deduce it to be #2, so options #1 and #3 are not happening, then here's the thing with deleting objects by referencing those objects: it easily leads to problems like what you are having. it is so easy to have code that actually attempts to delete a new object with the same properties as another object that is in a List. the clean way that i solve this is try to remove items by their index # rather than a reference to the object itself. but you are not even grabbing the object to be deleted from the List itself. you are grabbing that object from olvMatrs, which is another object list. my best guess from the information you shared is that this is why it's not working, that if you look deeper, that you are trying to Remove an object that is not in the list, so nothing is being Removed. it's an easy mistake to make. i only know because i've done it too before i learned to be super careful about this.
(note: I have labelled this C# but I am using Monotouch, so it might behave differently, I'm not too sure)
Here is my scenario:
I have a List which persists throughout my app, this holds onto an entire list of objects. I then filter this data (via user choices in the app) and display the appropriate items.
The list gets updated by a call to the webservice, the way I do this is as follows:
HandleWebServiceComplete(object sender, ItemRetreivedEventArgs e)
{
// snip - error handling above this
if (e.Result != null)
{
foreach (var item in e.Result)
{
if (!mainList.Contains(item))
mainList.Add(item);
}
RefreshDisplayList(mainList);
}
}
So obviously this checks every single item returned by the web service, to check that it doesn't already exist in the list (there are situations where it could) - so my question is, would it be better to use List.AddRange() then check for duplicates in the list afterwards or just carry on the way I'm going already?
I definitely would not AddRange and then remove duplicates, as each removal will trigger a rebuild of the backing array. The way you are doing it is fine. I presume you are not actually having performance issues currently.
If you're not interested into the order of the items you can just use an HashSet<> instead of List<>. The UnionWith() method accept a "range" and will do just what you want.
I'm getting kind of confused right now, having one of those days I guess.
I need to implement an Undo and Redo functionality for a form. For simplicities sake, let's say that I only save the control which was modified and the value it had when it left Focus.
How do I save this information in a way that lets me go back or forth in the 'timeline'.
I thought about using a Stack, but while I was testing my little demo, I had a mild aneurysm and here I am.
Code needed, not really but would help. I'm more interested in the algorithm I'd need to implement. Any suggestions?
Yes, you would use a stack. There are a couple ways to do it; read these references:
http://en.wikipedia.org/wiki/Command_pattern
http://en.wikipedia.org/wiki/Memento_pattern
Each has its pros/cons.
A stack is perfect if you push a "change" onto it, and when undo pop a "change" from it. You then push that popped change into another stack representing redo. At some point in the future, hopefully on save, you clear both stacks.
It's not actually as simple as that, as you need to record the type of change, understand the old and new values etc. So when you pop from the undo stack, the thing you pop must describe what the prior value was and what control it was set to.
Inverse for the redo stack, it needs to understand what the new value was and where it went. But yes, the idea of two stacks is a good start for a homebrew undo-redo.
A good example of a business object based undo is CSLA.NET, which has UndoableBase:
http://www.lhotka.net/cslanet/
http://www.koders.com/csharp/fidCF6AB2CF035B830FF6E40AA22C8AE7B135BE1FC0.aspx?s=serializationinfo
However this records a snapshot of an object's state, so it would be more advanced that your form-based concept. However, CSLA.NET offers full data binding support so a data bound object inheriting from UndoableBase would naturally support undo (not redo) in the UI.
I would use an IUndoableAction interface. The implementations could store whatever data they needed to be done and undone. Then yes, I would use a Stack to hold them.
interface IUndoableAction
{
void Do();
void Undo();
}
Stack<IUndoableAction> Actions;
Each kind of action would implement the Do and Undo methods.
Then, somewhere there would be these two methods:
void PerformAction(IUndoableActionaction)
{
Actions.Push(action);
action.Do();
}
void Undo()
{
var action = Actions.Pop();
action.Undo();
}
As for what to store in the action classes, some actions could just store the old value. However, once I had an action to swap two rows in a spreadsheet. I didn't store the values of every cell in both rows -- I just stored the row indices so they could be swapped back. It could be easy to fill up tons of memory if you stored all of that state for every action.
Then you want a Redo stack as well, and when you undo an action it is pushed onto the redo stack. The redo stack will need to be cleared when a new action is performed, so things don't get out of order.
Probably the most straightforward is to have the undo/redo stack combination.
An alternative is to have an array or list of actions, and just increment/decrement a pointer to an index in the array. When an action is undone, the index is moved back by one, and when the action is redone, the index is moved forward by one.
The advantage here is that you don't require a pop-and-then-push sequence for each action.
Things to consider:
If you undo several times, and then perform an action, all of the
redo actions must be eliminated.
Make sure you check the boundaries and ensure that there is an action available to undo/redo before trying to perform the undo/redo.