How to create undo and redo buttons in GUI that can be pressed multiple times?

Hello everyone! I have a working GUI where I have a undo and redo buttons. I have saved the current and previous states in two variables and implemented them in the respective callbacks. But I can undo and redo for only once. I guess the data is being overwritten everytime. Is there a way around it? Or a easier solution?
Thanks in advance!

 Réponse acceptée

John - without seeing your code, we can't determine why it is allowing you to undo and redo only once. You mention that you have saved the current and previous states in two variables. Are these scalar variables? If I were to implement redo and undo functionality, then I might use an array of some kind that would push the states to the front of the array so that when I call undo, the front would be popped up and the variables (or whatever) updated with the new front of the array. The popped off data would be pushed to the front of the redo array so that if I chose to call redo, then it's front would be popped off. There is lots more that you would have to consider - when should the redo array be emptied, when should the undo array be emptied, etc.

13 commentaires

Hey Geoff. Thanks for the prompt reply.
Basically these variables are holding my pixel matrices. A snippet is attached below :
laststate=pic; %pic is the initial input photo
pic = rgb2gray(pic); % a random effect
currentstate = pic;
imshow(pic);
So, the undo and redo callbacks basically sets the pic value to either laststate or currentstate. And as I have used these variables for several callbacks, they only retain the last values.
Your idea sounds nice. Can you help me with an example? How to pop an array or push back the values when needed? Also, what should be the array size since I don't know how large the pixel matrix will be.
John - you could use a cell array to store the data:
myUndoStack = {};
% push elements to the stack via concatenation
myUndoStack = [pic myUndoStack];
Concatenating it in this way ensures that the latest change is "pushed" to the front. To "pop" this off, you would do something like
if ~isempty(myUndoStack)
poppedItem = myUndoStack{1};
myUndoStack = myUndoStack(2:end);
% do whatever i.e. push the poppedItem to the redoStack
end
We "pop" by just resetting the undo stack to be all elements after the first. Note that this isn't very efficient and you would probably want to set a limit on the stack "depth". You may want to create a "stack" class that will manage the data and handle the "push" and "pop". There may even be something on the File Exchange that does this already.
John Doe
John Doe le 15 Mai 2020
Modifié(e) : John Doe le 15 Mai 2020
Hey Geoff. I've been able to create the undo and redo buttons using your suggestion.
I have a question. Suppose, step A,B,C are done sequentially in my GUI.
  1. Using the undo button, I can go back to step A.
  2. Using the redo button, I can reach the step C for the second time.
  3. But from step C, I cannot go back to B or A again. I figure that is happening because my cells are empty now and there's nothing to pop or push.
How can I solve this issue? How can I reload my previous values so I can jump back and forth using my redo and undo buttons as many times as I want?
Another scenario is if step A,B,C are done :
  1. Using the undo button once, I can go back to B from C.
  2. Then using the redo button once, I can go back to C again.
  3. But pressing the undo again jumps to step A, not step B.
I figure that is happening as B is already popped once, so A will be popped next. How can I maintain the sequnce or reload the B value?
Thanks in advance.
Hi John - I guess it all depends on how you've implemented the pushing and popping of the elements from the stacks. In your first scenario, let's suppose A, B, and C are done sequentially, then your stacks should look like
myUndoStack = {C, B, A};
myRedoStack = {};
If we undo back to A, then the stacks should (?) look like
myUndoStack = {A};
myRedoStack = {B, C};
and using redo to get back to C we shoud have
myUndoStack = {C, B, A};
myRedoStack = {};
By keeping the two stacks in sync, you should be able to keep undoing and redoing as long as you want. I think the rule would be if you pop off something from the undo stack then it should be pushed to the redo stack. And vice versa too.
You do need to consider how these stacks get cleared. If my state is
myUndoStack = {A};
myRedoStack = {B, C};
and I go to step D, then it might look like
myUndoStack = {D, A};
myRedoStack = {};
since we've taken a "new" path and so our redo stack should be cleared.
For your second scenario, let's suppose A, B, and C are done sequentially, then the stacks should look like
myUndoStack = {C, B, A};
myRedoStack = {};
Going back to B via undo should give us
myUndoStack = {B, A};
myRedoStack = {C};
and pressing redo should give us
myUndoStack = {C, B, A};
myRedoStack = {};
It could be that too much might be taken off of the stacks. If you undo back to a step, then that step shoud be at the front of the undo stack. Maybe that isn't happening?
Yeah, I think that's the issue.
if ~isempty(undostack)
pic = undostack{1};
redostack = [pic redostack]; %pushing the element in redostack
undostack = undostack(2:end);
end
Here is my callback for undo button. After each undo, I have pushed the element into the redostack. Previously, my redostack was also concatenated like my undo button. Now, I have kept it empty and coded like this :
if ~isempty(redostack)
pic = redostack{1};
undostack = [undostack pic]; %restoring the element in undostack
redostack = redostack(2:end);
end
But I think I have messed up somewhere and doing a silly mistake. The output is not sequential for redoing. Plus, it only reaches step B, not step C.
Also,
redostack = redostack(2:end);
This line was written as previously I concatenated my outputs in redo. Suppose, there is nothing to redo. Won't this line give error?
For the code
if ~isempty(redostack)
pic = redostack{1};
undostack = [undostack pic]; %restoring the element in undostack
redostack = redostack(2:end);
end
shouldn't pic be put to the front of the undostack since it is the last action made?
if ~isempty(redostack)
pic = redostack{1};
undostack = [pic undostack]; %restoring the element in undostack
redostack = redostack(2:end);
end
As for
redostack = redostack(2:end);
it seems to work even if redostack is empty or has only one element so this should be safe to use. If you are unsure, then you can add some code to guard against this sort of thing:
if ~isempty(redostack)
pic = redostack{1};
undostack = [pic undostack]; %restoring the element in undostack
if length(redostack) > 1
redostack = redostack(2:end);
else
redostack = {};
end
end
Yes, you are right, Geoff. I fixed the code for undo and redo using your advice.
if ~isempty(undostack)
pic = undostack{1};
undostack = undostack(2:end);
redostack = [pic redostack];
end
if ~isempty(redostack)
pic = redostack{1};
redostack = redostack(2:end);
undostack = [pic undostack];
end
Jumping back and forth between the undo and redo works now and goes all the steps. But to activate undo and redo, somehow I need to press the push button twice, then it works. I think something like this is happening in the undostack and redostack:
undostack = [1 1 2 3]; %just an example
I am maybe popping off the same thing twice or pushing the same thing in front for undostack/redostack, that is why I see no change. Any suggestions on how to resolve this issue?
You'd probably need to post your code so that I can see what is happening. At what point do you push something on to the undo stack? You can try using the MATLAB debugger with breakpoints to see what is happening (or just log the state of the stacks to the console log).
Geoff, here is my code. The undo works just fine. Load the image, apply the gray effect, then apply the binary effect. Then pressing undo takes me back to the previous step till it shows the original base image.
But when I again press the redo button, I expect the button to re-apply the previous effect(s) /trace back what I undo-ed , but it doesn't work like that. Using your advice, I have pushed the popped item to the redostack, but I think I have messed up a step or something. Kindly have a look and give me your suggestions.
John - I think that the problem is with the undo function callback
% --- Executes on button press in pushbutton4.
function pushbutton4_Callback(hObject, eventdata, handles)
% hObject handle to pushbutton4 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
global undostack pic redostack
if ~isempty(undostack)
pic = undostack{1};
undostack = undostack(2:end);
redostack = [pic redostack];
end
imshow(pic);
In the above code, we are taking the pic from the undo stack, pushing it to the redo stack, and then displaying that image. But the image we want to push to the redo stack should be the current image. The code may look like this
% --- Executes on button press in pushbutton4.
function pushbutton4_Callback(hObject, eventdata, handles)
% hObject handle to pushbutton4 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
global undostack pic redostack
if ~isempty(undostack)
redostack = [pic redostack]; % <--- save current image to redo stack
pic = undostack{1};
undostack = undostack(2:end);
end
imshow(pic);
Likewise, when redoing, the image pushed to the undo stack should be the image that is currently being shown and not the image that we are to show.
% --- Executes on button press in pushbutton5.
function pushbutton5_Callback(hObject, eventdata, handles)
% hObject handle to pushbutton5 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
global pic redostack undostack
if ~isempty(redostack)
undostack = [pic undostack]; % <--- save current image to undo stack
pic = redostack{1};
redostack = redostack(2:end);
end
imshow(pic);
I think that part of the confusion was how I described how this might work. I envisioned having the current image always at the top of the undo stack rather than separate.
Also, you don't need to use global variables. For GUIs created with GUIDE, you can use the handles structure to save data to and extract from. For example, in the load function callback
% --- Executes on button press in pushbutton1.
function pushbutton1_Callback(hObject, eventdata, handles)
% hObject handle to pushbutton1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
handles.undostack={};
handles.redostack={};
%redostack= [pic redostack];
path = imgetfilz();
axes(handles.axes1);
handlespic = imread(path);
%undostack = [pic undostack];
imshow(handles.pic);
guidata(hObject, handles); % <--- important! need to do this every time
You would then access these variables (in all other callbacks) via the handles structure. Note that whenver you update a field in this structure, you need to call
guidata(hObject, handles);
so that all other callbacks receive the updated structure.
Thanks Geoff! It's working nicely now and the tip about using the handles really made the code look neater.
If you have any other tips regarding GUIDE, please do share.
Also, I was wondering if there was a way to activate the redo push button only if the undo button has been pressed? If undo is not pressed, one cannot press the redo button. Is this possible for push buttons?
Members like you are the main reason which make this MATLAB community so happening! Thanks for sticking with this thread for the past few days.
Not sure which button is redo, but you can set the Enable property to off in GUIDE by double clicking on the button and changing the Enable property to off. Now when they run it, it will be disabled. Then in the callback for the undo button, you can enable the redo button.
handles.btnRedo.Enable = 'on';
Any time you want to disable it again, you can do this:
handles.btnRedo.Enable = 'off';
Of course, change the name to whatever you called the redo button.
Thanks for Accepting and Voting for Geoff's answer. It's how people earn reputation points and is a good way to thank answerers (of which Geoff is one of the best).
John Doe
John Doe le 17 Mai 2020
Modifié(e) : John Doe le 18 Mai 2020
Hey, Image Analyst. Tried your technique and it works fine. Thanks!

Connectez-vous pour commenter.

Plus de réponses (0)

Catégories

En savoir plus sur Loops and Conditional Statements dans Centre d'aide et File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by