Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛАБ № 5 Создание приложений с GUI без среды GU...doc
Скачиваний:
9
Добавлен:
24.11.2019
Размер:
470.53 Кб
Скачать

Сохранение объектов приложения для повторных запусков.

В этом разделе мы рассмотрим способ создания приложения с графическим интерфейсом пользователя, которое при повторном открытии содержит все объекты и данные, созданные при работе с ним ранее. В примерах предыдущих разделов все приложения программировались в файл-функции. Основная функция создавала окно приложения и элементы интерфейса. Подфункции обрабатывали события объектов приложения. Окно приложения создавалось каждый раз заново и, разумеется, все созданные в предыдущих сеансах работы объекты и данные были недоступны.

Есть простой способ решения этой проблемы.

1. Перед завершением работы приложения, оно сохраняется в файле с расширением fig.

2. В начале основной функции приложения проверяется, есть ли файл с именем приложения и расширением fig.

a. Если такой файл есть, то он открывается при помощи функции openfig.

b. Если такого файла нет, то работают команды, создающие окно приложения и элементы интерфейса.

Для реализации данного алгоритма понадобится несколько функций:

 mfilename - возвращает имя последнего запущенного на выполнение m-файла, вызов mfilename из файл-функции позволяет узнать ее имя;

 exist - позволяет выяснить, существует ли файл с определенным именем или нет (ее возможности шире, можно узнать, есть ли определенная переменная, встроенная функция и т.д.);

 openfig - открывает приложение, сохраненное в fig-файле;

 необходимо запрограммировать функцию для события CloseRequestFcn графического окна (возникающего при закрытии приложения), в которой перед закрытием приложение будет сохраняться в файле с расширением fig.

Необходимо учесть, что файл-функция приложения может и не содержаться в текущем каталоге, но путь к каталогу приложения есть в путях поиска MATLAB и приложение запускается вне зависимости от того, какой каталог является текущим. Поэтому, в начале файл-функции желательно точно установить полное имя m-файла c файл-функцией приложения. Для этого следует вызвать функцию mfilename со входным аргументом 'fullpath', тогда она вернет строку, содержащую полное имя m-файла (без расширения).

При проверке существования одноименного файла с расширением fig следует сцепить полученное полное имя со строкой '.fig' (например, при помощи функции strcat для сцепления строк) и воспользоваться функцией exist, которая вернет 0, если такого файла нет. Если же он есть, то открываем его, вызвав openfig со входным аргументом - полным именем (расширение fig можно не указывать). Кстати, функция openfig умеет определять, открыто окно приложения или нет. Это нужно для тех приложений, которые не могут быть одновременно открыты в двух окнах, а при повторном запуске приложения активизируется окно уже работающего приложения. Для этого следует указать второй входной аргумент 'reuse' в функции openfig.

Если нужного файла с расширением fig нет, то создаем окно приложения и элементы интерфейса. Только при создании окна приложения надо не забыть ассоциировать его событие CloseRequestFcn с соответствующей подфункцией и запрограммировать ее. Эта подфункция вызовется перед закрытием окна приложения. В ней надо сохранить приложение в файле с расширением fig (имя снова можно получить при помощи функции mfilename) и удалить графическое окно приложения функцией delete.

Структура основной функции с подфункцией следующая

function имя_основной_функции

% узнаем имя m-файла приложения

ProgName = mfilename('fullpath');

% проверяем, существует ли файл с тем же именем и расширением fig

if exist(strcat(ProgName, '.fig'))

% файл с расширением fig существует, открываем его, reuse надо для того,

% чтобы не допустить повторное открытие окна приложения

openfig(ProgName, 'reuse')

else

% файла с расширением fig не существует, создаем окно приложения

% и элементы интерфейса

% создаем графическое окно, ассоциируем событие CloseRequestFcn

% с соответствующей функцией

hF = figure(..., 'CloseRequestFcn', @winCloseRequestFcn);

% создаем остальные элементы интерфейса

axes(...);

uicontrol(...);

uicontrol(...);

....

end

% функции обработки событий элементов управления

function Oject1Callback(src, evt)

...

function Oject2Callback(src, evt)

...

% функция обработки события CloseRequestFcn, выполняющаяся перед закрытием

% окна приложения

function winCloseRequestFcn(src, evt)

% получаем имя m-файла приложения

ProgName = mfilename('fullpath');

% сохраняем окно приложения в файле с расширением fig

hgsave(ProgName)

% удаляем окно приложения (src содержит указатель на графическое окно)

delete(src)

Вот пример простого приложения plotandsave, в котором есть область ввода для задания функции и кнопка для построения ее графика на отрезке [-1,1] (см. рис. 6). Каждый новый график добавляется на оси. Если приложение закрыть и потом открыть, то все построенные ранее графики отобразятся и можно добавлять новые.

Рис. 6. Окно приложения plotandsave

function plotandsave

% узнаем имя m-файла приложения

ProgName = mfilename('fullpath');

% проверяем, существует ли файл plotandsave.fig

if exist(strcat(ProgName, '.fig'))

% файл plotandsave.fig существует, открываем его, reuse надо для того,

% чтобы не допустить повторное открытие окна приложения

openfig(ProgName, 'reuse')

else

% файла plotandsave.fig не существует, создаем окно приложения

% и элементы интерфейса

% создаем графическое окно, ассоциируем событие CloseRequestFcn

% с соответствующей функцией winCloseRequestFcn

hF = figure('Name', 'plotandsave', 'NumberTitle','off','Resize','off',...

'MenuBar', 'none', 'Units', 'characters',...

'Position', [10 10 100 30], 'Tag', 'win',...

'CloseRequestFcn', @winCloseRequestFcn);

% создаем оси с тегом axLeft

axes('Position', [0.1 0.15 0.8 0.8], 'Tag', 'axMain',...

'NextPlot', 'add');

uicontrol('Style', 'edit','Units', 'normalized',...

'Position', [0.02 0.02 0.3 0.06],...

'BackgroundColor','w',...

'Tag', 'edtFun');

uicontrol('Style', 'pushbutton', 'Units','normalized',...

'Position', [0.35 0.02 0.1 0.06],...

'String', 'Plot', 'Callback', @BtnPlotCallback,...

'Tag', 'btnPlot');

set(hF, 'HandleVisibility', 'callback');

end

function BtnPlotCallback(src, evt)

% функция обработки события Callback кнопки

% получаем структуру с указателями и данными

handles = guihandles(src);

formula = get(handles.edtFun, 'String');

% вычисляем значения функции на отрезке [-1,1]

[x,y] = fplot(formula, [-1 1]);

% строим график функции (цвет линии выбирается случайным образом)

plot(x, y, 'LineWidth', 3, 'Color', rand(1, 3));

function winCloseRequestFcn(src, evt)

% функция обработки события CloseRequestFcn, выполняющаяся перед закрытием

% окна приложения

% получаем имя m-файла приложения

ProgName = mfilename('fullpath')

% сохраняем окно приложения в файле с расширением fig

hgsave(ProgName)

% удаляем окно приложения (src содержит указатель на графическое окно)

delete(src)

Приложение для предварительного просмотра большого количества графиков

На практике часто возникает необходимость представления результатов вычислений достаточно большого объема в графическом виде, например до 100 графиков. При этом оказывается удобным вывести все результаты в графическое окно на оси небольшого размера для предварительного просмотра и увеличивать график щелчком мыши по нему для вывода его в отдельном графическом окне.

В этом разделе мы рассмотрим создание приложения, окно которого содержит заданное число пар осей, щелчок мышью по которым приводит к отображению отдельного окна с выбранным графиком (см. рисунок ниже).

Приложение для просмотра графиков

Сначала рассмотрим написание такого приложения в самом простом варианте, имеющем некоторые недостатки. Идея заключается в программировании события ButtonDownFcn осей, размещенных в основном (желтом на рисунке) окне приложения. Событие ButtonDownFcn осей возникает при щелчке мышью по ним. При возникновении этого события мы создадим новое графическое окно (на рисунке оно белого цвета) и скопируем на него выбранные оси, задав их размеры так, чтобы они занимали большую часть нового графического окна.

Приложение manyplots состоит из основной функции manyplots и подфункции обработки события ButtonDownFcn, одной для всех пар осей, которая называется mouse_click (эти функции следует сохранить в одном файле manyplots.m).

В основной функции manyplots создается графическое окно желтого цвета (свойство Color установлено в 'y'). Далее в этом графическом окне во вложенных циклах создаются оси. Для создания осей используется функция subplot (если осей достаточно много, то лучше отказаться от subplot и создавать их самостоятельно, об этом написано в конце этого раздела). Функция subplot возвращает указатели на созданные оси, которые записываются в двумерный массив h. Для простоты в этом примере на каждую пару осей выводится ломаная линия случайного цвета. Координаты вершин ломаной и цвет в формате RGB задаются при помощи функции rand, возвращающей массив случайных чисел от 0 до 1. После завершения циклов свойство ButtonDownFcn получает значение указателя на подфункцию mouse_click. Это делается при помощи функции set, в первом входном аргументе которой задается массив указателей на все оси. Таким образом, щелчок по каждой паре осей обрабатывается одной и той же подфункцией.

В подфункции mouse_click создается графическое окно белого цвета, его положение и размеры задаются в пикселях. Указатель на него сохраняется в переменной hNewF.

Примечание. Размеры и положение графического окна в пределах экрана монитора определяются значением свойства Position графического окна, которое является вектором из четырех элементов

[x y width height]

Здесь:

  • x, y — координаты левого нижнего угла графического окна;

  • width — ширина графического окна;

  • height — высота графического окна.

По умолчанию, координаты левого нижнего угла графического окна, его ширина и высота задаются в пикселях.

Далее, при помощи функции copyobj в это окно копируются оси, по которым был сделан щелчок мышью. Функция copyobj копирует объект вместе со своими потомками, в данном случае оси копируются вместе с линией графика. Интерфейс функции copyobj таков

NewObject = copyobj(Object, Parent)

Смысл входных и выходных аргументов функции copyobj следующий.

  • Object — указатель на копируемый объект. В нашем примере это входной аргумент src функции mouse_click, который содержит указатель на тот объект, событие ButtonDownFcn которого обрабатывается в данный момент времени, т.е. как раз указатель на те оси основного графического окна, по которым был сделан щелчок мышью.

  • Parent — указатель на родительский объект для копируемого. В нашем примере это hNewF — указатель на созданное графическое окно.

  • NewObject — указатель на новый графический объект, созданный после копирования. В нашем примере указатель на скопированные в новое окно оси записывается в переменную hNewA

После копирования осей в новое графическое окно задаются их размеры и положение

Примечание. Размеры и положение осей в пределах графического окна определяются значением свойства Position осей, которое является вектором из четырех элементов

[x y width height]

Здесь:

  • x, y — координаты левого нижнего угла осей;

  • width — ширина осей;

  • height — высота осей.

По умолчанию, координаты левого нижнего угла осей, их ширина и высота задаются в, так называемых, нормализованных единицах измерения. Т.е. считается, что высота и ширина графического окна равны единице, а x, y, width и height задаются в долях единицы.

Функция manyplots с подфункцией mouse_click

function manyplots

% основная функция приложения

% создание основного графического окна желтого цвета без меню

% с заголовком manyplots

figure('Color', 'y',...

'MenuBar', 'none',...

'Name', 'manyplots',...

'NumberTitle', 'off')

% создание осей в циклах, запись указателей на них в массив h

% и вывод на них графиков

for i = 1 : 4

for j = 1 : 5

h(i, j) = subplot(4, 5, 5*(i-1)+j);

plot(rand(5,1), rand(5,1), 'Color', rand(1,3));

end

end

% связывание подфункции mouse_click с событием ButtonDownFcn осей основного окна

set(h, 'ButtonDownFcn', @mouse_click)

function mouse_click(src, evt)

% подфункция mouse_click обработки события ButtonDownFcn осей основного окна

% создание графического окна белого цвета без меню и заголовка

% и задание его размеров и положения

hNewF = figure('Color', 'w',...

'MenuBar', 'none',...

'Position', [200 200 400 320],...

'NumberTitle', 'off');

% копирование выбранных осей в новое графическое окно

% и сохранение указателя на них в hNewA

hNewA = copyobj(src, hNewF);

% изменение размеров и положения скопированных осей

set(hNewA, 'Position', [0.13 0.11 0.775 0.815])

Созданное приложение manyplots имеет два недостатка.

  1. При щелчке по линиям графиков на осях основного окна ничего не происходит.

  2. При щелчке по скопированным в отдельное окно осям создается новое графическое окно с теми же самыми осями и графиком.

Разберем, почему так происходит.

  1. Щелчок мышью по линии графика не приводит к возникновению события ButtonDownFcn, т.к. оно возникает только при щелчке по свободной части осей. Следовательно, необходимо при создании линии графика связать с ее событием ButtonDownFcn функцию обработки этого события.

  2. При копировании осей в новое графическое окно, значения свойств новых осей совпадают со значениями свойств оригинала. Следовательно, значением свойства ButtonDownFcn новых осей является указатель на подфункцию обработки события mouse_click и при щелчке по новым осям она выполняется, приводя к появлению нового графического окна, содержащего скопированные на него оси. Для избегания этого следует установить скопированным осям значение [] (т.е. пустой массив). Более того, свойству ButtonDownFcn скопированной линии графика следует так же установить в качестве значения пустой массив для того, чтобы при щелчке по скопированной линии не происходило повторное создание графического окна.

Вышеописанные улучшения реализованы в основной функции manyplots2 с подфункцией mouse_click. В функции manyplots2 при создании линий функцией plot сохраняются указатели на них в массиве hL. Далее с событием ButtonDownFcn связывается та же самая функция mouse_click. В функции mouse_click происходит проверка типа объекта, событие ButtonDownFcn которого выполняется в данный момент времени. Если объект не является осями (а значит, линией), то вызывается функция get для получения указателя на его родительские оси. Далее происходит копирование и установка значения [] свойству ButtonDownFcn новых осей и их потомков.

Функция manyplots2 с подфункцией mouse_click

function manyplots2

% основная функция приложения

% создание основного графического окна желтого цвета без меню

% с заголовком manyplots

figure('Color', 'y',...

'MenuBar', 'none',...

'Name', 'manyplots',...

'NumberTitle', 'off')

% создание осей в циклах, запись указателей на них в массив h и вывод на них графиков,

% запись указателей на линии в массив hL

for i = 1 : 4

for j = 1 : 5

h(i, j) = subplot(4, 5, 5*(i-1)+j);

hL(i, j) = plot(rand(5,1), rand(5,1), 'Color', rand(1,3));

end

end

% связывание подфункции mouse_click с событием ButtonDownFcn осей основного окна

set(h, 'ButtonDownFcn', @mouse_click)

% связывание подфункции mouse_click с событием ButtonDownFcn линий

set(hL, 'ButtonDownFcn', @mouse_click)

function mouse_click(src, evt)

% подфункция mouse_click обработки события ButtonDownFcn осей основного окна

% создание графического окна белого цвета без меню и заголовка

% и задание его размеров и положения

hNewF = figure('Color', 'w',...

'MenuBar', 'none',...

'Position', [200 200 400 320],...

'NumberTitle', 'off');

% получение типа объекта, по которому был сделан щелчок мышью

src_type = get(src, 'Type');

% проверка, является ли объект осями

if ~isequal(src_type, 'axes')

% объект не является осями (следовательно это линия)

% записываем в src указатель на оси (родительский объект линии)

src = get(src, 'Parent');

end

% копирование выбранных осей в новое графическое окно

% и сохранение указателя на них в hNewA

hNewA = copyobj(src, hNewF);

% изменение размеров и положения скопированных осей

% и задание значения [] их свойству ButtonDownFcn

set(hNewA, 'Position', [0.13 0.11 0.775 0.815], ...

'ButtonDownFcn', [])

% получение указателей на потомков скопированных осей

% и задание значения [] их свойству ButtonDownFcn

hC = get(hNewA, 'Children');

set(hC, 'ButtonDownFcn', [])

Еще одним недостатком нашего приложения, который проявляется в случае большого числа пар осей в основном окне, является использование функции subplot. Дело в том, что функция subplot работает достаточно медленно. Сравним, например, создание 100 пар осей без разметки при помощи функции subplot и при помощи функции axes (с вычислением положения и размеров каждой пары осей по очевидным формулам).

Следующий код вызывает subplot в цикле и замеряет время.

tic

figure

h = zeros(100,1);

for i = 1 : 100

h(i) = subplot(10,10,i);

end

set(h, 'XTickLabel', [],...

'YTickLabel',[])

toc

Получается около 2,5 секунд.

Откажемся теперь от subplot и будем создавать оси при помощи функции axes, вычисляя их положение и размеры в двух вложенных циклах.

tic

figure

Delta = 0.005;

n=10;

Width = (1 - (n+1)*Delta)/n;

Height = Width;

for i = 1 : n

for j = 1 : n

y = 1 - (Height+Delta)*i;

x = Delta + (Width+Delta)*(j-1);

axes('Position', [x, y, Width, Height],...

'XTickLabel', [], 'YTickLabel',[],...

'Box', 'on');

end

end

toc

Время работы этого кода меньше 0.1 сек. При увеличении числа осей разница становится еще более существенной.

Таким образом, если предполагается предварительный просмотр достаточно большого количества графиков, то соответствующие оси лучше создавать при помощи axes, а не subplot.

Приложение manyplots2 легко переделать и для предварительного просмотра содержимого графических файлов текущего каталога. Пример такого приложения jpgshow с комментариями приведен ниже.

Приложение jpgshow для предварительного просмотра jpg файлов

Сначала при помощи функции dir определяется содержимое текущего каталога. Функция dir возвращает массив структур files, каждая из которых содержит информацию о файле, либо подкаталоге текущего каталога. При этом, если поле isdir структуры равно 1, то это подкаталог. В цикле перебирается содержимое текущего каталога и обрабатываются только имена файлов. Для получения расширения файла используется функция fileparts. Если расширение совпадает с .jpg (функция strcmpi сравнивает две строки без учета регистра), то имя файла заносятся в массив ячеек jpg_names.

Если в текущем каталоге был найден хоть один файл с расширением jpg, то происходит вычисление, на сколько осей по вертикали и горизонтали следует разбить графическое окно и во вложенных циклах происходит создание текущей пары осей и вывод на них изображения. Считывание содержимого графического файла осуществляется функцией imread, которая возвращает массив с графическими данными.

Далее, для вывода на оси изображения вызывается функция imshow. Она возвращает указатель на изображение, который сохраняется в векторе hIMG. Функция drawnow задействована для вывода считанного изображения на оси перед продолжением работы приложения. Без функции drawnow сначала бы завершилось чтение всех файлов jpg, а только затем произошел бы графический вывод, что может быть плохо в случае достаточно большого числа файлов jpg, или при достаточно большом их размере.

После вывода всех изображений с их событием ButtonDownFcn связывается функция mouse_click. В функции mouse_click создается новое графическое окно, на него копируются оси с изображением (указатель на оси является указателем на предка изображения). Для потомков новых осей свойству ButtonDownFcn устанавливается в качестве значения пустой массив для того, чтобы не происходил повторный вызов функции mouse_click при щелчке по новому изображению.

Функция jpgshow с подфункцией mouse_click

function jpgshow

% получаем список файлов и подкаталогов текущего каталога

files = dir;

% узнаем длину списка

file_num = length(dir);

jpg_num = 0;

% проходим в цикле по содержимому каталога

for k = 1 : file_num

% определяем: files(k) это файл или каталог

if ~files(k).isdir

% files(k) это файл, получаем его имя и расширение

[pathstr, name, ext, versn] = fileparts(files(k).name);

% проверяем, будет ли files(k) файлом jpg

if strcmpi(ext, '.jpg')

% files(k) является файлом *.jpg, увеличиваем счетчик на 1

jpg_num = jpg_num + 1;

% добавляем имя файла в массив ячеек имен jpg-файлов

jpg_names{jpg_num} = [name ext];

end

end

end

% проверяем, есть ли файлы jpg

if jpg_num > 0

% jpg-файлы есть, вычисляем разбиение графического окна на оси

n = ceil(sqrt(jpg_num));

% создаем нулевой массив для хранения указателей на рисунки

hIMG = zeros(1, jpg_num);

% создаем графическое окно

figure('Color', 'y',...

'MenuBar', 'none',...

'Name', 'jpgshow',...

'NumberTitle', 'off')

% задаем расстояние между осями (в нормализованных единицах)

Delta = 0.005;

% вычисляем ширину и высоту каждой пары осей

Width = (1 - (n+1)*Delta)/n;

Height = Width;

% в циклах создаем оси в графическом окне

for i = 1 : n

for j = 1 : n

% вычисляем координаты левого нижнего угла осей

y = 1 - (Height+Delta)*i;

x = Delta + (Width+Delta)*(j-1);

% находим номер текущего рисунка

im_num = (i-1)*n+j;

% проверяем, не превысили ли число рисунков

if im_num <= jpg_num

% создаем оси

axes('Position', [x, y, Width, Height])

% читаем файл jpg

IMG = imread(jpg_names{im_num});

% выводим рисунок на оси и сохраняем указатель

% на него в массиве hIMG

hIMG(im_num) = imshow(IMG);

% обновляем оси

drawnow

end

end

end

% связываем с событием ButtonDownFcn рисунка

% функцию mouse_click

set(hIMG, 'ButtonDownFcn', @mouse_click)

end

function mouse_click(src, evt)

% функция обработки события ButtonDownFcn изображений

% создаем графическое окно

hNewF = figure('Color', 'w',...

'MenuBar', 'none',...

'Position', [200 200 400 320],...

'NumberTitle', 'off');

% получаем указатель на оси, т.е. на предка изображения

hAxes = get(src, 'Parent');

% копируем оси в новое графическое окно

hNewA = copyobj(hAxes, hNewF);

% задаем размеры и положение новых осей

set(hNewA, 'Position', [0.13 0.11 0.775 0.815])

% получаем указатели на потомков осей

hC = get(hNewA, 'Children');

% задаем их свойству ButtonDownFcn значение пустой массив

set(hC, 'ButtonDownFcn', [])