1

This is a similar problem encountered by many others where guidata(hOjbect, handles) does not seem to update a value. I'm using it with a listener, and am not sure how to proceed.

In my gui_OpeningFcn I have the following line:

addlistener(handles.s, 'name', 'PostSet', @(s,e)updatefilesave(hObject, [], handles));

This sets the listener appropriately and it does call updatefilesave when name is modified. However, inside updatefilesave is the following code:

handles.fileUnsaved = true;
guidata(hObject, handles);

Inside the function, both lines work. When I breakpoint on the first line and step, fileUnsaved gets set to true. After I step the second line (while still inside the updatefilesave function), handles.fileUnsaved is still set to true.

However, when I step out of the function, the green arrow gets put on to the addlistener line in the gui_OpeningFcn function. at this level, handles.fileUnsaved is now set back to false.

How do I get handles to update when using a listener?

EDIT

What I'm trying to do is know when input fields have changed so I can prompt the user to save their work before closing the program. I check the fileUnsaved flag in the CloseRequestFcn and if it is true, I ask the user if they want to save before exiting.

function namebox_Callback(hObject, eventdata, handles)

newName = handles.namebox.String;
if ~isempty(newName)
    handles.s.name = newName; % (listener gets triggered here post set)
end

handles.namebox.String = handles.s.name;

guidata(hObject, handles); % (namebox's local handles with fileUnsaved set to false gets put into hObject)

This is why I cannot call handles = guidata(hObject) in the CloseRequestFcn. The only way to stop this is to call handles = guidata(hObject) in the namebox callback before I call guidata(hObject, handles). But doing that everywhere would defeat the point of using listeners. I would just go and set fileUnsaved to true in every callback function (about 50 of them).

toshiomagic
  • 1,335
  • 1
  • 12
  • 39

2 Answers2

1

In general, if you want to have a function that you call from one callback to modify handles and then have those changes be made available to the calling function, you'll need to not only save the handles struct in the called function (so that they are available to other callbacks), but you have to re-load the handles struct in the calling function otherwise the calling function is just going to simply use it's own local (and unmodified) copy of handles since it has no way to know that it was modified.

function main_callback(hObject, eventData, handles)

    % Set the value to one thing
    handles.value = false;

    sub_callback(hObject, eventData, handles);

    % Check that the value is STILL false
    disp(handles.value)

    % Load in the change so that handles gets updated
    handles = guidata(hObject);

end

function sub_callback(hObject, eventData, handles)
    handles.value = true;

    % Save the value
    guidata(hObject, handles);
end

The other option, is to have your other function actually return the modified handles

function handles = sub_callback(hObject, eventData, handles)
    handles.value = true;

    guidata(hObject, value);
end

And then from within the calling function you can use the output argument to overwrite the local handles variable

handles = sub_callback(hObject, eventData, handles);

Now to your specific question regarding addlistener, since the callback is executed in an "asynchronous" sense, it doesn't really make sense to return a value. What I would recommend though is to reload the handles data (as shown in the first example), before you go to use handles again (where you expect it to be changed) to ensure that you have the most up-to-date version.

Suever
  • 64,497
  • 14
  • 82
  • 101
  • This basically means I have to modify the addlistener line to include some sort of `handles = guidata(hObject)` call. Is there a way to nest multiple function calls in the addlistener function input? like `@(s,e)updatefilesave(hObject, [], handles);handles=guidata(hObject)` – toshiomagic Jan 16 '17 at 18:32
  • @toshiomagic No. As I said in the last part of my answer, you can't return a value from `addlistener` and since it's executed asynchronously, putting `handles = guidata(hObject)` in your code *after* you assign the listener won't work. You need to put `handles = guidata(hObject)` in your code in the line before you access `handles` and expect it to be different – Suever Jan 16 '17 at 18:34
  • Well darn it. That basically defeats the purpose of the listener. If I have to go to every function where `s` changes and say `handles = guidata(hObject)` I might as well just go to every place and say `handles.fileUnsaved = true`. – toshiomagic Jan 16 '17 at 18:39
  • @toshiomagic Well it's a little unclear exactly what you're trying to do here. Can you show us the rest of your opening fcn? I'm not sure why you need to access the modified handles from within the same function because if the `name` property of `handles.s` changes, that would mean that you explicitly changed it, no? – Suever Jan 16 '17 at 18:46
  • I'm trying to detect when a few input values get changed. If they do get changed, I want the flag `fileUnsaved` to get set to true. That way when the user tries to close the gui without saving the latest changes, they are prompted to save. I thought the easiest way to do this would be to add some listeners that all point to a function that sets `fileUnsaved` to true. – toshiomagic Jan 16 '17 at 19:02
  • @toshiomagic Right, so when you perform the save, change the flag to `true` and use `guidata` to save it. When you go to close the GUI, load the current value with `guidata` and any changes inside of the listener will be reflected. – Suever Jan 16 '17 at 19:14
  • I can't do that because fileUnsaved won't update. That's why I asked this question. – toshiomagic Jan 16 '17 at 19:21
  • @toshiomagic Please show the actual code that you are referring to. You should have `handles = guidata(hObject)` at the top of your `CloseRequestFcn` to get the latest values. Simply passing `handles` to the close request function isn't going to work since that's a local copy – Suever Jan 16 '17 at 19:29
  • @toshiomagic Another alternative is to use a class wrapper around your `guidata` like the `structobj` approach I've shown in [this answer](http://stackoverflow.com/questions/41466664/structure-of-handles-of-guis-matlab/41467048#41467048) – Suever Jan 16 '17 at 19:34
  • The problem is that I call guidata(hObject, handles) immediately after I change handles.s.name. See question. – toshiomagic Jan 16 '17 at 19:34
  • @toshiomagic I've seen the question. Please provide the code where you set your `CloseRequestFcn`. Are you doing that within the `Opening_fcn`? I'm guessing that you have something like `set(gcf, 'CloseRequestFcn', @(s,e)closerequest(hObject, [], handles))` and you're expecting that last input to `closerequest` to contain the updated `struct`. This doesn't work like that since it will pass the *local copy* of `handles`. Instead, your first line of `closerequest` should be `handles = guidata(hObject)` to *get the latest copy that is modified*. – Suever Jan 16 '17 at 19:37
  • @toshiomagic Why are you saving `handles` at the end of `namebox_Callback`? You don't need to. It wasn't modified. That is the source of your problem. You are over-writing the modified `handles` with the local unmodified version – Suever Jan 16 '17 at 19:42
  • While you are right, there are other instances where I have to call guidata(hObject, handles) inside of callback functions. So the problem is the same. – toshiomagic Jan 16 '17 at 19:44
  • @toshiomagic In those cases you should use the `structobj` approach or something similar that I liked above where you have a `handle` class as your `guidata` object so that it's passed by reference and any changes that are made (wherever they are) will automatically be propagated to all other references to the same object. Alternaly, since you're changing the `name` property manually, just set the flag manually as well. – Suever Jan 16 '17 at 19:48
  • `s` is a handle class. So I guess I'm basically already doing that. – toshiomagic Jan 16 '17 at 19:54
  • @toshiomagic No. `handles` has to be a `handle` class. If your flag lived *inside* of `s` then that would be fine but since you're trying to store it in `handles` directly, it doesn't work. – Suever Jan 16 '17 at 19:55
  • Well crap, then I'll just put it inside of `s`. Who cares? – toshiomagic Jan 16 '17 at 20:00
  • @toshiomagic Yes that's probably the easiest option. I don't care, I'm just trying to help you figure out how to do what you're trying to do. – Suever Jan 16 '17 at 20:01
1

I stumbled upon this thread having a similar issue, but for me modifying handles inside the listener seems to be working.

In the outside function, I have something like this:

handles.myListener = addlistener(ObjectThrowingEvent,'EventName', @(src,evt)ListenerFunction(src, evnt, hObject));
guidata(hObject,handles);

Then in the inside function

function ListenerFunction(ObjectThrowingEvent, eventData, hObject)
  handles = guidata(hObject)
  % a bunch of stuff happens, including updates to the handles structure
  guidata(hObject, handles);

To me it looks like the difference is that I'm passing in hObject and looking up the handles from hObject in the listener. Even though the listener is asynchronous, it's being passed hObject which I think just points to the current state of the figure rather than some local non-updated copy.

I'd be curious if this works for you. It seems to be working in my code so far.

leoshmu
  • 131
  • 3