MATLAB.Exponenta
–Û·Ë͇ Matlab&Toolboxes

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

Создание приложений с GUI без среды GUIDE

Это актуальный вопрос для тех, кто собирается писать сложные многооконные приложения с графическим интерфейсом. Начнем с самого простого примера.

Простой пример - приложение с кнопкой и осями

Требуется написать приложение с графическим интерфейсом, выводящее график функции на оси окна приложения при нажатии на кнопку Plot (см. рис. 1).

Это приложение будет файл-функцией myplotgui с подфункцией BtnPlotCallback обработки события Callback кнопки Plot, которое возникает при нажатии на кнопку (обработку событий можно организовывать и во внешних файлах), но мы пока будем программировать приложение в одной функции с подфункциями обработки событий элементов интерфейса и объектов приложения.


Рис. 1. Окно приложения myplotgui

В файл-функции myplotgui следует:

  1. Создать графическое окно при помощи функции figure
  2. Создать оси, вызвав функцию axes;
  3. Создать кнопку, т.е. графический объект uicontrol, использовав одноименную функцию uicontrol.

При создании кнопки в основной функции необходимо: указать ее положение в графическом окне приложения, задать надпись на ней и, самое главное, связать событие Callback с соответствующей подфункцией обработки события Callback при помощи указателя @ на подфункцию. При программировании обработки событий элементов интерфейса и объектов приложения (не только Callback, но и других) следует учитывать, что соответствующая подфункция обработки события должна иметь как минимум два входных аргумента, назовем их src и evt, как и в справочной системе MATLAB:

function BtnPlotCallback(src, evt)

где src содержит указатель на тот объект, который вызвал событие, а evt является структурой и может быть пустой для некоторых событий. Можно задавать и дополнительные входные аргументы для подфункций обработки событий, что будет рассмотрено ниже. Пока дополнительных аргументов не требуется и при создании кнопки Plot в основной функции мы зададим в качестве значения ее свойства Callback указатель на функцию BtnPlotCallback, т.е. @BtnPlotCallback.

Пока не будем заниматься точным заданием размеров графических объектов и напишем самый простой вариант файл-функции myplotgui с подфункцией

function myplotgui
% создание графического окна с заголовком myplotgui и без надписи Figure 1
hF = figure('Name', 'myplotgui', 'NumberTitle', 'off');
% создание осей
hA = axes('Position', [0.1 0.2 0.8 0.7]);
% создание кнопки и задание свойству Callback значения указателя на подфункцию
hBtnPlot = uicontrol('Style', 'pushbutton', ...
     'Position', [20 20 120 30],...
    'String', 'Plot',...
    'Callback', @BtnPlotCallback);

function BtnPlotCallback(src,evt)
% подфункция обработки события Callback кнопки Plot
surf(peaks(30))

Функцию myplotgui с подфункцией BtnPlotCallback надо сохранить в одном файле myplotgui.m. Теперь для запуска нашего простого приложения с графическим интерфейсом достаточно вызвать его из командной строки (каталог, в котором находится файл myplotgui.m, должен быть текущим):

>> myplotgui

Нажатие на кнопку Plot приводит к появлению графика на осях окна приложения.

Функция uicontrol позволяет создать такие элементы управления, как кнопку, флаг, список и другие. То, что она создает, зависит от значения свойства Style, указанного при создании объекта.

Значение свойства Style Что получается
pushbutton (по умолчанию) кнопка
togglebutton кнопка-переключатель
radiobutton переключатель
checkbox флаг
edit область ввода текса
text статический текст
slider полоса скроллинга
listbox список
popupmenu раскрывающийся список

У всех объектов uicontrol многие остальные свойства имеют схожие значения, например: Position, Units, FontName, FontSize. Некоторые свойства имеют разный смысл. Если значение свойства String кнопки или флага - надпись на кнопке или флаге, то для списка это его содержимое. Кстати, событие Callback не единственно возможное. Назначение всех свойств и событий элементов интерфейса, создаваемых функцией uicontrol, описано в справочной системе:

>> doc uicontrol

Основные из них мы рассмотрим позже.

Задание размеров окна приложения и элементов интерфейса

Теперь обсудим задание размеров графического окна и элементов интерфейса. Лучше всего сразу выбрать один из подходов для задания положения и размеров осей в пределах графического окна приложения и для расположения кнопок и других элементов интерфейса. По умолчанию, положение осей задается вектором из четырех элементов [x y width height] (x, y - координаты левого нижнего угла осей, width - их ширина, а height - высота) в нормализованных единицах, т.е. считается, что левый нижний угол графического окна имеет координаты (0, 0), а ширина и высота окна принимаются за единицу. Часто для осей лучше устанавливать не свойство Position, а OuterPosition (появилось в MATLAB 7), которое задает положение и размеры осей вместе с разметкой осей, заголовком и подписями к осям. При использовании свойства OuterPosition гарантированно не будет перекрытия осей с другими элементами интерфейса приложения.

Размеры и положение элементов управления uicontrol (кнопок, списков, переключателей, флагов, областей ввода, полос скроллинга) по умолчанию задаются в пикселях. В нашем примере значением свойства Position кнопки Plot был вектор [20 20 120 30], т.е. координаты левого нижнего угла кнопки (20, 20) в окне приложения, ее ширина 120 пикселей, а высота 30 пикселей. В зависимости от того, как должно вести себя графическое окно при изменении размеров, можно выбрать один из следующих вариантов задания единиц измерения и расположения объектов (именно такой подход реализован в среде GUIDE).

1. Графическое окно приложения сразу создается заданного размера и не допускает изменения размеров пользователем. В этом случае в качестве единиц измерения для всех объектов: графического окна, осей, кнопок и т.д. выбираются символы (characters). При этом окно приложения отображается корректно вне зависимости от системных настроек. Для запрета изменения размеров следует установить свойство Resize графического окна в 'off'. Вот пример основной функции приложения myplotgui, рассмотренного выше, которая создает окно, кнопку и оси с размерами, заданными в символах (подфункция BtnPlotCallback остается без изменений):

function myplotgui
% Окно приложения не допускает изменения размеров
% Все единицы измерения - символы
% создание графического окна
hF = figure('Name', 'myplotgui', 'NumberTitle', 'off',...
    'Resize', 'off', 'MenuBar', 'none', 'Units', 'characters');
set(hF, 'Position', [10 10 100 30]); 
% создание осей
hA = axes('Units', 'characters', 'Position', [6 6 80 20]);
% создание кнопки и задание свойству Callback значения указателя на подфункцию
hBtnPlot = uicontrol('Style', 'pushbutton', 'Units', 'characters',...
'Position', [6 2 10 2],'String', 'Plot','Callback', @BtnPlotCallback);

2. Графическое окно приложения должно допускать изменение размеров пользователем и автоматически пропорционально изменять размеры всех объектов. Тогда размеры и положение осей и элементов управления приложения задаются в нормализованных единицах, а размеры и положение графического окна в символах. Для разрешения изменения размеров графического окна, его свойство Resize должно иметь значение 'on' (как по умолчанию). Основная функция приложения myplotgui, рассмотренного выше, может выглядеть таким образом (подфункция BtnPlotCallback остается без изменений):

function myplotgui
% Окно приложения допускает пропорциональное изменение размеров
% Элементы интерфейса заданны в нормализованных единицах измерения
% создание графического окна
hF = figure('Name', 'myplotgui', 'NumberTitle','off',...
    'MenuBar', 'none', 'Units', 'characters');
set(hF, 'Position', [10 10 100 30]); 
% создание осей
hA = axes('Units', 'normalized',...
    'Position', [0.06 0.2 0.8 0.67]);
% создание кнопки и задание свойству Callback значения указателя на подфункцию
hBtnPlot = uicontrol('Style', 'pushbutton', 'Units','normalized',...
'Position', [0.06 0.07 0.1 0.07], 'String', 'Plot',...
'Callback', @BtnPlotCallback);

При таком способе иногда имеет смысл задавать нормализованные единицы измерения шрифта осей и элементов управления. Для этого их свойство FontUnits устанавливается в 'normalized':

hA = axes(...,'FontUnits', 'normalized',...);
hBtnPlot = uicontrol(..., 'FontUnits', ' normalized',...);

Тогда при изменении размеров окна приложения автоматически изменится размер надписей на элементах управления и размер подписей к осям.

3. Объекты изменяют размеры и положение по некоторому алгоритму. Для этого пишется функция обработки события ResizeFcn графического окна, которое возникает при изменении размеров окна приложения. В этой функции программируется способ изменения размеров и положения каждого объекта независимо, причем некоторые объекты могут не менять свои размеры. Этот подход мы рассмотрим отдельно.

В любом случае, перед выводом окна приложения на экран полезно узнать размеры экрана монитора. Размеры экрана монитора являются значением свойства ScreenSize корневого объекта Root, указатель на который всегда равен нулю. Для получения значения свойства графического объекта используется функция get:

s = get(0, 'ScreenSize')

В вектор s записываются координаты нижнего левого угла, ширина и высота экрана. По умолчанию, размеры экрана возвращаются в пикселях и левый нижний угол имеет координаты (1,1). Для получения размеров экрана в других единицах измерения необходимо предварительно изменить значение свойства Units корневого объекта и не забыть вернуть ему прежнее значение, т.к. многие функции предполагают, что его свойство Units имеет значение 'pixels' (как по умолчанию):

un = get(0, 'Units')
set(0, 'Units', 'characters')
s = get(0, 'ScreenSize')
set(0, 'Units', un)
Задание дополнительных параметров в функциях обработки событий

В предыдущем разделе мы рассмотрели самый простой способ задания функций обработки событий для объектов приложения с графическим интерфейсом, при котором предполагается, что функции имеют только два входных аргумента: src (указатель на объект, событие которого выполняется) и evt (структура, используемая в некоторых событиях). Тогда при создании объекта достаточно его свойству, связанному с событием, установить в качестве значения указатель на подфункцию, например:

h = uicontrol(...,'Callback', @ObjectCallback)

а сама функция ObjectCallback должна иметь заголовок

function ObjectCallback(src, evt)

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

h = uicontrol(...,'Callback', {@ObjectCallback, par1, par2, ...})

function ObjectCallback(src, evt, par1, par2, ...)

Вот простой пример приложения myplotgui2, в котором есть две пары осей и две кнопки, нажатие на каждую кнопку приводит к построению графика на осях, расположенных над этой кнопкой (см. рис. 2).


Рис. 2. Окно приложения myplotgui2

Подфункция обработки события BtnPlotCallback одна для левой и правой кнопки. Третьим входным аргументом функции BtnPlotCallback является указатель на нужные оси, которые при помощи функции axes делаются текущими при нажатии на кнопку:

function myplotgui2
% создаем графическое окно 
hF = figure('Name', 'myplotgui2', 'NumberTitle', 'off',...
    'MenuBar', 'none', 'Units', 'characters');
set(hF, 'Position', [10 10 100 30]); 
% создаем две пары осей
hA1 = axes('Position', [0.1 0.2 0.3 0.7]);
hA2 = axes('Position', [0.6 0.2 0.3 0.7]);
% создаем две кнопки, для каждой кнопки функция BtnPlotCallback
% вызывается со своим третьим входным аргументом
hBtnPlot1 = uicontrol('Style', 'pushbutton', 'Units', 'normalized',...
    'Position', [0.1 0.05 0.3 0.05],...
    'String', 'Plot', 'Callback', {@BtnPlotCallback, hA1});
hBtnPlot2 = uicontrol('Style', 'pushbutton','Units','normalized',...
    'Position', [0.6  0.05 0.3 0.05],...
    'String', 'Plot', 'Callback', {@BtnPlotCallback, hA2});

function BtnPlotCallback(src, evt, h)
% подфункция обработки события нажатия на кнопку
axes(h) % делаем нужную пару осей текущими
surf(peaks(30)) % строим график
Скрытие указателей объектов приложения с GUI

Для чего желательно скрывать указатели на объекты приложения с графическим интерфейсом? Если приложение работает и пользователь набирает в командной строке, например bar([1 2 3 1 2]), то столбцевая диаграмма построится именно в окне приложения, а вовсе не в отдельном графическом окне (проверьте для приведенных выше приложений myplotgui, myplotgui1 и myplotgui2). Графические функции MATLAB строят график в существующем окне или существующих осях, а новое графическое окно или новые оси создаются тогда, когда окна или осей нет, или MATLAB их не видит. То есть скрытие указателей нужно для предотвращения случайного изменения вида приложения пользователем.

Для скрытия указателей самый простой способ сделать их доступными только при обработке событий объектов приложения. В приведенных выше примерах для этого достаточно в конце основной функции установить свойство HandleVisibility графического окна в 'callback'.

function myplotgui
.........
set(hF, 'HandleVisibility', 'callback');

Теперь все графические команды, выполняемые из командной строки, создадут новое графическое окно со своими осями и выведут график на них.

Надо иметь ввиду, что у корневого объекта Root есть свойство ShowHiddenHandles, которое по умолчанию установлено в 'off' и видимость указателей определяется значением свойства HandleVisibility каждого графического окна. Если установить свойство ShowHiddenHandles корневого объекта в 'on', то все указатели будут видны вне зависимости от значения свойства HandleVisibility графического окна.

Получение указателей на объекты приложения в функциях обработки событий, функция guihandles.

В подфункциях обработки события элементов интерфейса и объектов приложения с графическим интерфейсом часто требуется получить указатели на другие элементы интерфейса и объекты. В примере myplotgui2 предыдущего раздела мы обошлись всего одной функцией обработки события Callback кнопок для демонстрации вызова функции обработки события с дополнительными параметрами. Для выполнения различных действий при нажатии на правую и левую кнопки приложения (см. рис. 2) придется написать две функции обработки событий Callback - каждую для своей кнопки. При этом возникает следующий вопрос: как в функции обработки события Callback кнопки узнать указатель на нужную пару осей окна приложения.

В MATLAB имеется функция guihandles, которая возвращает структуру с полями, содержащими указатели на все объекты графического окна приложения (поля структуры отделяются точкой от ее имени, например handles.axLeft - поле axLeft структуры handles). Поля этой структуры совпадают со значениями свойства Tag объектов. Поэтому, при создании объекта требуется задавать их свойству Tag некоторое уникальное значение. Причем это значение MATLAB должен воспринимать как имя переменной (чтобы оно могло быть полем структуры). Т.е. например, 'axLeft' или 'ax_Left' могут быть значениями свойства Tag, а '5axLeft' или 'ax-Left' нет. Входным аргументом функции guihandles может являться указатель на любого потомка графического окна приложения. Итак, если при создании осей их свойству Tag было присвоено значение axLeft:

axes(...,'Tag', 'axLeft',...)

то для получения указателя на них в некоторой функции обработки события другого графического объекта следует использовать guihandles, возвращающую структуру с указателями (имя handles для структуры используется в справочной системе MATLAB и в среде GUIDE, поэтому мы сохраним его):

	function ObjectCallback(src, evt)
...
handles = guihandles(src);
% теперь handles.axLeft содержит указатель на оси 
... 

При этом следует иметь ввиду, что при графическом выводе высокоуровневые функции MATLAB (plot, surf и др.) изменяют значения всех свойств осей, кроме Position. Значением свойства Tag становится пустая строка (как по умолчанию). Так происходит потому, что свойство осей NextPlot по умолчанию установлено в 'replace'. Поэтому при создании осей его следует установить в 'replacechildren':

axes(...,'NextPlot', 'replacechildren', 'Tag', 'axLeft',...)

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

В следующем примере приведено приложение, которое выглядит так же, как и предыдущее приложение myplotgui2, но для обработки события Callback каждой кнопки запрограммирована своя функция, в которой нужные оси делаются текущими.

function myplotgui3
% создаем графическое окно с тегом win
hF = figure('Name', 'myplotgui3', 'NumberTitle','off',...
    'MenuBar', 'none', 'Units', 'characters',...
    'Position', [10 10 100 30], 'Tag', 'win');
% создаем оси с тегом axLeft
axes('Position', [0.1 0.2 0.3 0.7], 'Tag', 'axLeft',...
    'NextPlot', 'replacechildren');
% создаем оси с тегом axRight
axes('Position', [0.6 0.2 0.3 0.7],'Tag', 'axRight',...
    'NextPlot', 'replacechildren');
% создаем кнопку с тегом btnLeft
uicontrol('Style', 'pushbutton', 'Units','normalized',...
    'Position', [0.1 0.05 0.3 0.05],...
    'String', 'Plot', 'Callback', @BtnLeftCallback,...
    'Tag', 'btnLeft');
% создаем кнопку с тегом btnRight
uicontrol('Style', 'pushbutton','Units', 'normalized',...
    'Position', [0.6  0.05 0.3 0.05],...
    'String', 'Plot', 'Callback', @BtnRightCallback,...
    'Tag', 'btnRight');
% скрываем указатель на окно приложения 
set(hF, 'HandleVisibility', 'callback');
	
function BtnLeftCallback(src, evt)
% подфункция обработки события нажатия на левую кнопку
% записываем в структуру handles указатели на объекты приложения
handles = guihandles(src);
% сейчас в поле axLeft структуры handles находится указатель на левые оси
% делаем их текущими
axes(handles.axLeft)
barh(rand(5)) % строим график

function BtnRightCallback(src, evt)
% подфункция обработки события нажатия на правую кнопку
% записываем в структуру handles указатели на объекты приложения
handles = guihandles(src);
% сейчас в поле axRight структуры handles находится указатель на правые оси
% делаем их текущими
axes(handles.axRight)
bar(rand(5)) % строим график

В заключение этого раздела приведем чуть более сложный пример приложения с графиеским интерфейсом пользователя myplotgui5, окно которого изображено на рис. 5. В приложении myplotgui5 имеется строка ввода для задания функции одной переменной (в соответствии с правилами MATLAB, например: x^2*cos(3*x)). Эта область ввода имеет тег edtFun. В подфункциях обработки события Callback кнопок нужные оси (левые или правые) делаются текущими. Далее, если на нажатой пользователем кнопке находится надпись "Plot", то на соответствующих осях, расположенных над кнопкой, строится график заданной функции на отрезке [-3, 3] и надпись на кнопке меняется на "Clear". Если на кнопке уже написано "Clear", то очищаются соответствующие оси и надпись меняется на "Plot".


Рис. 3. Окно приложения myplotgui5
function myplotgui5
% создаем графическое окно с тегом win
hF = figure('Name', 'myplotgui3', 'NumberTitle','off',...
    'MenuBar', 'none', 'Units', 'characters',...
    'Position', [10 10 100 30], 'Tag', 'win');
% создаем оси с тегом axLeft
axes('Position', [0.1 0.2 0.3 0.7], 'Tag', 'axLeft',...
    'NextPlot', 'replacechildren');
% создаем оси с тегом axRight
axes('Position', [0.6 0.2 0.3 0.7], 'Tag', 'axRight',...
    'NextPlot', 'replacechildren');
% создаем кнопку с тегом btnLeft
uicontrol('Style', 'pushbutton', 'Units', 'normalized',...
    'Position', [0.1 0.1 0.3 0.05],...
    'String', 'Plot', 'Callback', @BtnLeftCallback,...
    'Tag', 'btnLeft');
% создаем кнопку с тегом btnRight
uicontrol('Style', 'pushbutton','Units', 'normalized',...
    'Position', [0.6  0.1 0.3 0.05],...
    'String', 'Plot', 'Callback', @BtnRightCallback,...
    'Tag', 'btnRight');
% создаем область ввода текста с тегом edtFun
uicontrol('Style', 'edit', 'Units', 'normalized',...
    'Position', [0.1  0.01 0.8 0.05],...
    'BackgroundColor', 'w',...
    'Tag', 'edtFun');
% скрываем указатель на окно приложения 
set(hF, 'HandleVisibility', 'callback');
	
function BtnLeftCallback(src, evt)
% подфункция обработки события нажатия на левую кнопку
% записываем в структуру handles указатели на объекты приложения
handles = guihandles(src);
% сейчас в поле axLeft структуры handles находится указатель на левые оси
% делаем их текущими
axes(handles.axLeft)
% Проверяем, совпадает ли надпись на кнопке с Plot
if isequal(get(src, 'String'), 'Plot')
    % надпись на кнопке Plot
    % берем текст из строки ввода (выражение для функции)
    str = get(handles.edtFun, 'String');
    % строим график на левых осях (они текущие)
    fplot(str, [-3 3])
    % изменяем надпись на кнопке на Clear
    set(src, 'String', 'Clear')
else
    % очищаем левые оси (они текущие)
    cla
    % изменяем надпись на кнопке на Plot
    set(src, 'String', 'Plot')
end
   
function BtnRightCallback(src, evt)
% подфункция обработки события нажатия на правую кнопку
% записываем в структуру handles указатели на объекты приложения
handles = guihandles(src);
% сейчас в поле axRight структуры handles находится указатель на правые оси
% делаем их текущими
axes(handles.axRight)
% Проверяем, совпадает ли надпись на кнопке с Plot
if isequal(get(src, 'String'), 'Plot')
    % надпись на кнопке Plot
    % берем текст из строки ввода (выражение для функции)
    str = get(handles.edtFun, 'String');
    % строим график на правых осях (они текущие)
    fplot(str, [-3 3])
    % изменяем надпись на кнопке на Clear
    set(src, 'String', 'Clear')
else
    % очищаем правые оси (они текущие)
    cla
    % изменяем надпись на кнопке на Plot
    set(src, 'String', 'Plot')
end
Сохранение данных, полученных в функции обработки событий, функция guidata.

Если приложение с графическим интерфейсом программируется в файл-функции, в которой основная функция создает окно приложения и элементы управления, а подфункции предназначены для обработки событий элементов управления и объектов приложения, то часто возникает следующий вопрос. Как сохранить некоторые данные, полученные в ходе выполнения одной функции обработки события, и передать их в другую функцию? Запись данных в переменную ничего не дает, поскольку переменные подфункции являются локальными и по завершении работы подфункции они удаляются из памяти.

Один из возможных подходов к решению этой проблемы состоит в создании структуры, поля которой содержат некоторые общие данные. Назовем эту структуру handles, как и в предыдущем разделе. Для сохранения этой структуры служит функция guidata, которая часто используется в сочетании с функцией guihandles, предназначенной для получения указателей на объекты приложения. Для получения структуры handles в функции обработки события достаточно вызвать функцию guidata со входным аргументом - указателем на тот объект, событие которого выполняется (он и будет потомком графического окна приложения). После этого в структуре handles можно создать новое поле, например data, записать в него необходимые данные и сохранить обновленную структуру handles при помощи функции guidata

function ObjectCallback(src,evt)
 ....
% имеются некоторые данные, записанные в массив A
% получаем структуру handles
handles = guidata(src);
% добавляем в нее поле data
handles.data = A;
% сохраняем структуру
guidata(handles, src)
...

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

В качестве простого примера приведем приложение presscount, содержащее всего одну кнопку, надпись на которой содержит число нажатий на кнопку (см. рис. 4).


Рис. 4. Окно приложения presscount

В основной функции приложения presscount создается графическое окно и кнопка с надписью "You have not press me yet". Затем создается структура handles с одним полем times, которому присваивается 0 (пока кнопка ни разу не нажималась). Структура handles сохраняется при помощи функции guidata.

При каждом нажатии на кнопку в функции BtnCallback обработки события Callback кнопки вызывается функция guidata для получения структуры handles, значение ее поля times увеличивается на единицу и структура handles сохраняется. Остается изменить надпись на кнопке, прибегнув к функции num2str для преобразования числа в строку и сцеплению строк при помощи квадратных скобок.

function presscount
% создаем графическое окно
hF = figure('Name', 'presscount', 'NumberTitle','off',...
    'MenuBar', 'none', 'Units', 'characters',  'Position', [10 10 100 5]);
% создаем кнопку
uicontrol('Style', 'pushbutton', 'Units','normalized',...
    'Position', [0.1 0.1 0.8 0.8],...
    'String', 'You have not press me yet',...
    'FontSize', 16, 'Callback', @BtnCallback);
% создаем структуру handles с полем times, в которое записываем 0
% (кнопка нажималась 0 раз)
handles.times = 0;
% сохраняем структуру handles
guidata(hF, handles)
% скрываем указатели на объекты приложения
set(hF, 'HandleVisibility', 'callback');

function BtnCallback(src, evt)
% подфункция обработки нажатия на кнопку
% получаем структуру handles
handles = guidata(src);
% увеличиваем значение ее поля times на единицу
handles.times = handles.times + 1;
% сохраняем структуру handles
guidata(src, handles);
% изменяем надпись на кнопке
str = ['You pressed me ' num2str(handles.times) ' times'];
set(src, 'String', str)

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

Обмен данными между функциями обработки событий. Обработка событий объектов, создаваемых приложением.

В этом разделе мы рассмотрим чуть более сложный пример, чем в предыдущем, поясняющий решение двух вопросов:

1) как организовать обмен данными и указателями на объекты приложения между функциями обработки событий;
2) как обрабатывать события, возникающие от тех объектов, которые были созданы в ходе работы приложения.

Обсудим эти вопросы на примере приложения plotlines, окно которого приведено на рис. 5.


Рис. 5. Окно приложения plotlines

Приложение plotlines содержит оси, область ввода для задания функции в соответствиями с правилами MATLAB и кнопку, нажатие на которую приводит к построению графика введенной функции на отрезке [-1,1]. График каждой новой функции добавляется на оси. Приложение plotlines запоминает все введенные формулы и при щелчке мышью по любой линии на осях в области ввода появляется соответствующая ей формула.

В основной функции приложения создаются:
  • окно приложения с тегом win;
  • оси c тегом axMain и свойством NextPlot, установленным в add, для добавления линий;
  • кнопка с тегом btnPlot и функцией BtnPlotCallback обработки события Callback;
  • область ввода текста с тегом edtFun.

Указатели на объекты приложения plotlines хранятся в структуре handles, которая создается в конце основной функции приложения при помощи функции guihandles. Напомним, что названия полей структуры handles совпадают с тегами объектов, т.е. handles.win содержит указатель на графическое окно приложения, handles.axMain - на оси, handles.btnPlot - на кнопку и handles. edtFun - на область ввода текста (не все указатели могут понадобиться, однако, лучше всегда при создании объектов приложения давать им уникальные теги).

Затем в структуру handles добавляется два поля:
  • Funs - для хранения введенных формул в массиве ячеек, каждая ячейка содержит строку с формулой;
  • Lines - для хранения указателей на построенные линии в векторе.

Начальными значениями этих полей должны быть пустые массивы: handles.Funs - пустой массив ячеек {}, handles.Lines - пустой числовой массив []. После добавления полей Funs и Lines в структуру handles и инициализации их значений вызывается функция guidata для сохранения структуры handles.

В функции BtnPlotCallback обработки события Callback кнопки вызывается функция guidata для записи в handles структуры данных приложения. Входным аргументом функции guidata должен быть указатель на объект приложения, в нашем случае это указатель на кнопку, который содержится в первом входном аргументе src функции BtnPlotCallback. Далее определяется номер новой линии графика (число построенных линий, т.е. длина массива указателей на линии handles.Lines, плюс один). В новую ячейку массива handles.Funs заносится содержимое строки ввода на текущий момент (указатель на нее находится в handles.edtFun) и заданная функция визуализируется. Функция flpot не строит график, она только вычисляет функцию на отрезке [-1, 1] и записывает абсциссы и ординаты в массивы x и y. Объект линия получается в результате работы функции plot, которая возвращает указатель на линию. Он добавляется в новый элемент массива handles.Lines.

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

function plotlines
% создаем графическое окно с тегом win
hF = figure('Name', 'plotlines', 'NumberTitle','off',...
    'MenuBar', 'none', 'Units', 'characters',...
    'Position', [10 10 100 30], 'Tag', 'win');
% создаем оси с тегом axMain, свойство NextPlot = 'add' для добавления новых линий
axes('Position', [0.1 0.15 0.8 0.8], 'Tag', 'axMain',...
    'NextPlot', 'add');
% создаем область ввода с тегом edtFun
uicontrol('Style', 'edit', 'Units', 'normalized',...
    'Position', [0.02 0.02 0.3 0.06],...
    'BackgroundColor', 'w',...
    'Tag', 'edtFun');
% создаем кнопку с тегом btnPlot и функцией BtnPlotCallback обработки
% события Callback
uicontrol('Style', 'pushbutton', 'Units','normalized',...
    'Position', [0.35 0.02 0.1 0.06],...
    'String', 'Plot', 'Callback', @BtnPlotCallback,...
    'Tag', 'btnPlot');
% записываем указатели на объекты в структуру handles
handles = guihandles(hF);
% добавляем в структуру handles поле Funs для записи строк с формулами
% в массив ячеек и присваиваем ему пустой массив ячеек
handles.Funs = {};
% добавляем в структуру handles поле Lines для записи указателей на линии
% и присваиваем ему пустой числовой массив
handles.Lines=[];
% сохраняем обновленную структуру
guidata(hF, handles)
% скрываем указатели
set(hF, 'HandleVisibility', 'callback');
	
function BtnPlotCallback(src, evt)
% функция обработки события Callback кнопки
% получаем структуру с указателями и данными
handles = guidata(src);
% определяем номер новой линии
k = length(handles.Lines)+1;
% заносим в новую ячейку поля Funs структуры handles строку с формулой
handles.Funs{k} = get(handles.edtFun, 'String')
% вычисляем значения функции на отрезке [-1,1] 
[x,y] = fplot(handles.Funs{k}, [-1 1]);
% строим график функции, указатель на линию графика записываем в новую 
% ячейку массива handles.Lines
handles.Lines(k) = plot(x,y,'LineWidth',3);
% связываем функцию LineButtonDownFcn с событием, которое возникает при
% нажатии кнопки мыши на линии
set(handles.Lines(k), 'ButtonDownFcn', @LineButtonDownFcn)
% сохраняем структуру handles
guidata(src, handles)

function LineButtonDownFcn(src, evt)
% обработка нажатия кнопки мыши на линии, в src - указатель 
% на текущую линию, событие ButtonDownFcn которой обрабатывается
% получаем структуру handles с указателями и данными
handles = guidata(src);
% ищем номер текущей линии с указателем src
k = find(handles.Lines == src);
% записываем в строку ввода формулу, соответствующую текущей линии графика
set(handles.edtFun, 'String', handles.Funs{k})

Вообще говоря, этот пример приведен только для демонстрации обмена данными между функциями обработки событий. Приложение plotlines можно было запрограммировать проще. У объектов, в том числе и линий, есть свойство UserData, значением которого могут быть данные, ассоциированные с объектом. В нашем случае достаточно при построении линии задать соответствующую строку с формулой в качестве значения ее свойства UserData. В функции LineButtonDownFcn, обрабатывающей нажатие кнопки мыши на линии, значение свойства UserData текущей линии (указатель на нее - во входном аргументе src) записывается в область ввода. В таком варианте функция guidata не нужна - обмен данными между подфункциями производится при помощи свойства UserData объекта.

Вот листинг измененной функции с подфункциями.

function plotlines
% создаем графическое окно с тегом win
hF = figure('Name', 'plotlines', 'NumberTitle','off',...
    'MenuBar', 'none', 'Units', 'characters',...
    'Position', [10 10 100 30], 'Tag', 'win');
% создаем оси с тегом axMain, свойство NextPlot = 'add' для добавления новых линий
axes('Position', [0.1 0.15 0.8 0.8], 'Tag', 'axMain',...
    'NextPlot', 'add');
% создаем область ввода с тегом edtFun
uicontrol('Style', 'edit','Units', 'normalized',...
    'Position', [0.02 0.02 0.3 0.06],...
    'BackgroundColor','w',...
    'Tag', 'edtFun');
% создаем кнопку с тегом btnPlot и функцией BtnPlotCallback обработки
% события Callback
uicontrol('Style', 'pushbutton', 'Units','normalized',...
    'Position', [0.35 0.02 0.1 0.06],...
    'String', 'Plot', 'Callback', {@BtnPlotCallback},...
    'Tag', 'btnPlot');
% скрываем указатели
set(hF, 'HandleVisibility', 'callback');
	
function BtnPlotCallback(src, evt)
% функция обработки события Callback кнопки
% получаем структуру указателей на объекты приложения
handles = guihandles(src);
% записываем в formula содержимое строки ввода
formula = get(handles.edtFun, 'String')
% строим график функции, заданной строкой formula
[x,y] = fplot(formula, [-1 1]);
hL = plot(x,y,'LineWidth',3);
% связываем функцию LineButtonDownFcn с событием, возникающим при нажатии
% кнопки мыши на линии
set(hL, 'ButtonDownFcn', @LineButtonDownFcn)
% записываем в свойство UserData линии строку с соответствующей ей формулой
set(hL, 'UserData', formula)

function LineButtonDownFcn(src, evt)
% обработка нажатия кнопки мыши на линии, в src - указатель 
% на текущую линию, событие ButtonDownFcn которой обрабатывается
% получаем структуру handles с указателями и данными
handles = guihandles(src);
% считываем значение свойства UserData линии, оно содержит формулу
formula = get(src, 'UserData');
% записываем формулу в строку ввода
set(handles.edtFun, 'String', formula)

В MATLAB 7 структура графических объектов несколько изменилась по сравнению с предыдущими версиями. В частности, появились рисованные объекты, которые строятся высокоуровневыми графическими функциями (например, plot), в отличие от базовых, которые получаются при вызове низкоуровневых функций (например line). У рисованных объектов линий (Lineseries) есть свойство DisplayName, значением которого может быть строка (именно эта строка будет отображаться в легенде рядом с образцом линии при использовании функции legend). В нашем примере можно было задействовать свойство DisplayName вместо UserData.

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

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

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

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)
    

  • Поиск по сайту:

    Система Orphus

    Яндекс.Метрика