1

I am trying to implement save/load functions in a MATLAB (R2009a) UI. My object implements a layout function that generates a user interface for the object. I am trying to implement the callbacks for the save/load buttons. The save button works and save the object out to a MAT file which can be loaded later.

My problem is implementing the callback for the load button. I cannot seem to get the load to load the data from the MAT file and update the properties of the new object. Any suggestions on where I am going wrong along with suggestions on how I might proceed is greatly appreciated.

The important code is my class definition file of course my actual object implements many more properties and methods but here is a skeleton of what I have

classdef myObj<handle
   properties
       image % property holds a matlab image matrix
       objCount % number of objects in image
   end

   properties(Transient=true)
       parent
       children
   end

   methods
       function myObj
           % empty constructor
       end

       function load_object(self)
            % ask user for file
            [fileName, pathToFile]=uigetfile('*.mat','Select .mat file');
            tmp = load(fullfile(pathToFile,fileName);
            if isfield(tmp,'obj')
                self = tmp.obj;
            end
       end
       LayoutFcn(self) % UI layout function
   end
end

The UI layout is defined in a seperate file LayoutFcn.m which basically looks like

function LayoutFcn(self)
% create figure window and add various UI elements
...
% create load button
self.children(end+1) = uipushtool('Parent',hToolbar, ... % set parent to current toolbar
                   'CData',iconRead('open-document.png'), ... % read icon image from file
                   'Tag','uiLoad', ...
                    'ClickedCallback',@(hObj,event)loadingMyObject(self,hObj,event));

% create save button
self.children(end+1) = uipushtool('Parent',hToolbar, ... % set parent to current toolbar
                   'CData',iconRead('save-document.png'), ... % read icon image from file
                   'Tag','uiSave', ...
                    'ClickedCallback',@(hObj,event)savingMyObject(self,hObj,event));

...
end

function loadingMyObject(self,hObj,event)
     self.load_object; % call load_object method defined above in class definition
end

function savingMyObject(self,hObj,event)
     [fileName,pathName]=uiputfile('.mat','Save object to MAT file');
      obj = self;
      save(fullfile(pahtName,fileName),'obj')
end 

Note: I am using MATLAB R2009a.

The code doesn't throw any errors. The way I wrote the code the parent object (represented by self) does not get updated after the call to LOAD in the method load_object. SO, this has the desired effect:

>> var = myObj;
>> var.load_object;

However, if I use the loadingMyObject callback defined in LayoutFcn.m in this fashion

>> var = myObjl
>> var.LayoutFcn
-> click Load button to call _loadingMyObject_

doesn't affect var properties. That is var will still have its default property values after clicking the Load button.

  1. Changing the load methods to use set as suggested by gnovice throws the following error

    ??? Error using ==> set Conversion to double from FujiCalibration is not possible.

    even though I have set/get methods for each property; as in

    method set.image(self,II)
       % ... some data validation code ...
       self.image = II
    end
    
  2. Using a loop to set each field as suggested by Mr Fooz is not really an option as my full class has public constant that throw an error when they are set.

I am looking for a solution that would avoid me having to hand code setting each field individually. Although at this point it seems like the only possibility.

Community
  • 1
  • 1
Azim J
  • 8,260
  • 7
  • 38
  • 61
  • Are you getting a specific error message, or does it just do nothing? It would help to know exactly how it is failing. Also, there is one typo: a missing ")" for the LOAD command in load_object. – gnovice Mar 19 '09 at 00:11

3 Answers3

1

Don't assign values to self. All that does is replace the binding to the self variable in the scope of the method call. It does not call a magical copy constructor to replace the object reference in the caller. Instead, copy the fields into self. Try something like:

if isfield(tmp,'obj')
   self.image = tmp.obj.image;
   self.objCount = tmp.obj.objCount;
end
Mr Fooz
  • 109,094
  • 6
  • 73
  • 101
  • Thank you for your answer. Yes that is one way to go. I was thinking I could do it without having to individual reassign each field. – Azim J Mar 19 '09 at 03:25
  • Well, if you want to trust the data file (generally a bad idea), you could iterate over the fields in obj and assign them in a loop. See the FIELDNAMES function. for fn = fieldnames(tmp.obj), assert(isfield(self, fn{1}); self.(fn{1}) = tmp.obj.(fn{1}); end – Mr Fooz Mar 19 '09 at 12:45
  • yes that was one thought but my object has some public constants so I can't just loop over all the fields as you suggest. – Azim J Mar 19 '09 at 14:55
  • Could you modify the serialization mechanism so that the public constants aren't saved? – Mr Fooz Mar 19 '09 at 15:47
  • I will have to look into how to do that. My guess would be through MATLAB's saveobj/loadobj methods. – Azim J Mar 19 '09 at 16:02
  • Creating saveobj/loadobj methods which do not save or load public constants is a good idea. – gnovice Mar 19 '09 at 17:06
1

I believe Mr Fooz is right. The self variable passed to load_object is an object of type "myObj", but the line:

self = tmp.obj;

is simply overwriting the self variable with the structure stored in tmp.obj. Doing:

self.image = tmp.obj.image;

should instead invoke a set operator for the image property of object self. In the MATLAB documentation there is a sample class definition with a method called "set.OfficeNumber" that illustrates this.

In addition, the following line in your function savingMyObject may be unnecessary:

obj = self;

I think it might make most sense (and make the code a little clearer) if you used the name "obj" in place of the word "self" within your class code (as the documentation tends to do). "self" doesn't appear to be any kind of special keyword in MATLAB (like it may be in other languages). It's just another variable as far as I can tell. =)

EDIT #1:

If the prospect of having to set each property individually in your load_object method doesn't sound like fun, one way around it is if you have a SET method for your object that is designed like the SET method for handle graphics. That SET command can accept a structure input where each field name is a property name and each field value is the new value for that property. Then you would have one call like:

set(self,tmp.obj);

Quite a bit shorter, especially if you have lots of properties to set. Of course, you'd then have to write the new SET method for your object, but the shortened syntax may be worth the extra work if it comes in handy elsewhere too. =)

EDIT #2:

You may be able to use the loop Mr Fooz suggested in conjunction with a try/catch block:

fn = fieldnames(tmp.obj);
for i = 1:numel(fn),
  try
    self.(fn{i}) = tmp.obj.(fn{i});
  catch
    % Throw a warning here, or potentially just do nothing.
  end
end
Community
  • 1
  • 1
gnovice
  • 125,304
  • 15
  • 256
  • 359
  • I agree that the line obj=self is unnecessary but is used for some backward compatibility to older code that expects the MAT file to have a _obj_ variable. – Azim J Mar 19 '09 at 03:18
  • It seems that the class methods need to have a _Self_ (in Mathwork's doc they use _obj_). I had assumed it was a pointer to the current instance of the class. Guess I need to go back and read the doc's again. – Azim J Mar 19 '09 at 03:21
  • Ah, I understand now why you need the line "obj=self". Your code for loading the MAT file also expects there to be a variable named "obj". Unfortunately, MATLAB doesn't really have pointers, per se. The handle class allows you to treat objects as pointer-like, but it can get a little confusing. – gnovice Mar 19 '09 at 04:09
  • hmmm, tried the set(self,tmp.obj) suggestions but it throws a "conversion to double from MyObj is not possible" error. – Azim J Mar 19 '09 at 14:53
  • Did you write a SET method for MyObj? That's the caveat here... objects don't have one by default. The default SET command is for handle graphics, and it accepts a handle which is a double precision value. You would have to make your own that mimics the default SET behavior. – gnovice Mar 19 '09 at 15:12
  • ah, I hadn't written an overload for SET. I thought I could get away with the set.image type methods I had written. Guess not – Azim J Mar 19 '09 at 16:00
1

Combining Mr Fooz's and gnovice's suggestions, I added a SET function with the following definition

function set(self,varargin)
    if isa(varargin{1},'FujiCalibration')
        tmp = varargin{1};
        fns = fieldnames(self);
        for i = 1:length(fns)
            if strcmpi(fns{i}(1:2),'p_')
                self.(fns{i}) = tmp.(fns{i});
            end
        end
        self.calibImage = tmp.calibImage;
    else
        proplist=fields(self);
        for n=1:2:length(varargin)
            tmp = proplist(strcmpi(proplist,varargin{n}));
            value = varargin{n+1};
            switch length(tmp)
                case 0
                    msg = char(strcat('There is no ''', varargin{n}, '''property'));
                    error('FujiCalibration:setPropertyChk',msg)
                case 1
                    tmp = char(tmp);
                    self.(tmp) = value;
            end
        end  
    end
end

I then modified the load_object method as suggested by gnovice by changing

self = tmp.obj

to

set(self,tmp.obj).

I need to make sure that properties with values I want to persist are prefixed with 'p_'.

Thanks to gnovice and Mr Fooz for their answers.

Community
  • 1
  • 1
Azim J
  • 8,260
  • 7
  • 38
  • 61