Программирование для Mac OS X Cocoa - делаем калькулятор

В этой статье мы рассмотрим создание довольно простого приложения под Mac OS X -калькулятора.

При этом мы, как и ранее, построим весь интерфейс при помощи Interface Builder'а, а для остального будем использовать среду XCode. Кроме ресурсов в нашем приложении будет и текст программы на Objective-C (можете прочитать статью по Objective-C).

Самым первым нашим шагом будет запуска XCode - для этого щелкните мышью по его иконке в доке. После запуска XCode предложит выбрать тип создаваемого проекта. Выберите Application и в списке Cocoa Application, затем щелкните по кнопке Choose... (или просто нажмите Enter)(рис 1).

Рис 1. Выбор типа приложения.

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

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

Рис 2. Задание имени приложения и его места на диске.

После этого Вы увидите окно созданного проекта (рис 3).

Рис 3. Окно проекта в XCode.

В правой части окна проекта находится таблица со списком файлов проекта, а в левой - вся информация о проекте в виде дерева.

Обратите внимание, что в нашем проекте уже есть один файл на Objective-C - main.m - и один xib файл - MainMenu.xib (В порследней версии XCode и Interface Builder'а вместо традиционного типа nib используется новый тип xib, файлы этго типа являются текстовыми xml-фалйами).

На самом деле каждая Cocoa-программа с графическим интерфейсом обязательно содержит один и тот же файл main.m, содержимое которого приводится ниже.

//
//  main.m
//  Calculator
//
//  Created by Alex Boreskoff on 9/30/06.
//  Copyright __MyCompanyName__ 2006. All rights reserved.
//

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc,  (const char **) argv);
}

Как легко видно это просто стандартная "заглушка". Ее задачей является правильная инициализация приложения, которая производится функцией NSApplicationMain. В эту инициализацию (которая общая для всех приложений) входит создание объекта NSApp класса NSApplication (точнее NSApp указывает на этот объект).

Одной из главных задач объекта класса NSApplication является загрузка главного nib-файла (на самом деле в приложении может быть более одного xib-файла, но при этом один, обычно MainMenu.xib является главным и автоматически загружается при запуске приложения).

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

Одной из интересных особенностей программирования под Mac OS X является крайне редкое создание новых классов на основе NSWindow и NSApplication - практически всегда все необходимые действия могут быть достигнуты путем делегирования и паттерна Observer.

В основе написания приложений с графическим интерфейсом лежит пришедшая из Smalltalk'а концепция MVC (Model, View, Controller.

Согласно этой концепции (как это не удивительно Ruby on Rails при построении Web-страниц использует эту же самую концепцию) выделяются объекты, служащие для показа данных (views), объекты, представляющие данные (models) и объекты, отвечающие за их взаимодействие с пользователем (controllers).

В Mac OS X уже содержится большое количество готовых объектов, предназначенных для показа данных (они обычно унаследованы от класса NSView). В довольно простых приложениях нет необходимости создавать специально модель (хотя для больших приложений это оказывается очень удобным - вся бизнес-логика помещается именно в модели и не вылезает за ее пределы).

Поэтому в простых приложениях обычно используются готовые объекты views, но пишется свой объект-контроллер, отвечающий за взаимодействие с пользователем. Чаще всего такой контроллер наследуется непосредственно от NSView и использует мощные возможности системы по делегированию и оповещению о событиях.

Начнем создание приложение с создания пользовательского интерфейса для него. Для этого вызовем Interface Builder двойным щелчком мыши по файлу MainMenu.xib(English) из списка файлов в правой части окна проекта. При этом запустится Interface Builder, сразу открыв xib-файл из нашего проекта.

Сразу же после запуска Interface Builder-а уберем все лишние окна. Далее откроем окно инспектора (при помощи команды меню Tools/Show Info) и в свойствах окна зададим заголовок Calculator и снимем выделение с пункта "Zoom (and resize)".

Рис 4. Настройка окна калькулятора.

Перетащим поле для ввода текста с палитры компонент, расположив его в самом верху окна. Далее перетащим кнопку и поместим ее в нижний левый угол окна (рис 5.). Изменим текст кнопки на "0", а ее тег (поле Tag:) на 0 и слегка уменьшим ее размер.

Рис 5. Выкладываем компоненты.

После этого выберем из меню Layout команду Embed Objects In и для нее выберем пункт Matrix. В результате будет создан объект класса NSMatrix, представляющий собой прямоугольную матрицу из компонент заданного класса.

Обратите внимание на то, что окно инспектора теперь отражает свойства объекта класса NSMatrix.

Рис 6. Создание блока кнопок.

Рис 7. Свойства матрицы кнопок.

Построим таким образом таблицу из кнопок размера 3х4.

Далее уменьшим размеры окна и выровняем поле ввода как показано на следующем рисунке.

Рис 8. Настройка текстового поля.

Настроим поле ввода при помощи инспектора - уберем выделение с пункта "Editable" - это не позволит явно вводить текст в это поле - и зададим выравнивание текста по правому краю (рис 9).

Рис 9. Задание атрибутов текстового поля.

Далее займемся настройкой отдельных кнопок из построенной матрицы. Для доступа к отдельной кнопке (а не всей матрице целиком) служит двойной щелчок мышью по нужной кнопке.

Выбирая таким образом по очереди все кнопки настроим их параметры следующим образом - в качестве текста зададим одну из цифр (чтобы получилось так как на рис 10), а в качестве тега для каждой кнопки укажем значение соответствующей ей цифры (рис 11). Кроме того утсановим для получившеся матрицы кнопок режим (Mode) Highlight, вместо установленного по умолчанию List.

Рис 10. Выделение отдельной кнопки из матрицы.

Рис 11. Настройка атрибутов отдельной кнопки из матрицы.

Мы сразу же добавим поддержку клавиатуры, используя так называемые key equivalent'ы, рассмотренным нами в предыдущей статье. Для этого щелкнув мышью по полю Key Equiv. (после чего вокруг поля появится голубая рамка) нажмем на клавишу, которую мы хотим выбрать в качестве key equivalent'а.

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

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

Для этого двойным щелчком мыши выделите нижнюю левую кнопку в матрице и в окне инспектора снимите выделения с пункта "Enabled" и поставьте выделение у пункта "Transparent".

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

Далее сделаем матрицу из двух кнопок, расположенных одна над другой с текстом "С" и "СА" (рис 13).

Рис 12. Окно калькулятора с цифровыми кнопками и кнопками С и СА.

Нашим следующим шагом будет создание класса контроллера. Для этого в палитре объектов Library откроем раздел Objects & Controllers и перетащим синий кубик, представляющий собой объект класса NSObject в окно Main Menu.xib (English)(рис 13).

Рис 13. Создание класса на основе класса NSObject

Выделив этот объект в окне инспектора откроем закладку Identity (предпоследняя). Далее в поле Class изменим название класса с NSObject на CalcController.

Рис 14. Свойства класса CalcController.

Далее при помощи кнопок + в разделах Class Actions и Class Outlets добавим нашему классу CalcController один outlet с именем text, задав в качестве его класса NSTextField и следующие методы - (раздел Class Actions ) - operation:, digit:, clear: и clearAll:.

Первый из этих методов (operation:) будет вызываться при нажатии кнопки, соответствующей какой-либо арифметической операции. Метод digit: будет вызываться при нажатии какой-либо из цифровых кнопок, назначение методов clear: и clearAll: понятно из названия.

После того, как мы задали какие outlet'ы и методы нам нужны, при помощи команды меню File/Write Class Files... создадим файлы CalcController.h и CalcController.m.

Рис 15. Создание файлов для класса CalcController.

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

Рис 16. Добавление созданных файлов в проект.

После этого вернемся обратно в XCode и убедимся, что файлы CalcController.h и CalcController.m действительно добавились к проекту.

Откроем файл CalcController.h - для этого можно либо однократным щелчком мыши по нему открыть файл в нижней части окна проекта (рис 17) или двойным щелчком открыть файл в отдельном окне.

Рис 17. Окно проекта с открытым файлом CalcController.h.

Обратите внимание, что в файле CalcController.h содержится полное описание класса CalcController, как мы его определили в Interface Builder'е.

Изменим файл CalcController.h как показано на следующем листинге.

/* CalcController */

#import <Cocoa/Cocoa.h>

enum
{
    OP_PLUS   = 1001,
    OP_MINUS  = 1002,
    OP_MULT   = 1003,
    OP_DIV    = 1004,
    OP_INV    = 1005,
    OP_EQUALS = 1006
};

@interface CalcController : NSObject
{
    IBOutlet NSTextField * text;
    BOOL                   enterFlag;
    BOOL                   yFlag;
    int                    operation;
    double                 x, y;
}
- (IBAction) clear: (id)sender;
- (IBAction) clearAll: (id)sender;
- (IBAction) digit: (id)sender;
- (IBAction) operation: (id)sender;
- (void) displayX;
@end

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

Обратите внимание на тип IBAction - на самом деле это void, но использование этого имени помогает Interface Builder'у выделить среди списка всех методов, те, которые могут выступать в качестве целей для визуальных компонент (в нашем случае - для кнопок).

Откроем теперь файл CalcController.m. Как видно он содержит тела для всех заданным нами методов, но эти тела пусты. Поэтому нашим следующим шагом будет наполнение файла CalcController.m реализациями методов.

Ниже приводится нужное нам содержимое файла CalcController.m (все файлы проекта доступны для скачивания по ссылке в конце статьи).

#import "CalcController.h"

@implementation CalcController

- (IBAction)clear:(id)sender
{
    x = 0;

    [self displayX];
}

- (IBAction)clearAll:(id)sender
{
    x         = 0;
    y         = 0;
    yFlag     = NO;
    enterFlag = NO;

    [self displayX];
}

- (IBAction)digit:(id)sender
{
    if ( enterFlag )
    {
        y         = x;
        x         = 0;
        enterFlag = NO;
    }

    x = (10.0*x) + [[sender selectedCell] tag];

    [self displayX];
}

- (IBAction) operation: (id)sender
{
    if ( yFlag )
    {
        switch ( operation )
        {
            case OP_PLUS:
                x = y + x;
                break;

            case OP_MINUS:
                x = y - x;
                break;

            case OP_MULT:
                x = y * x;
                break;

            case OP_DIV:
                x = y / x;
                break;
        }
    }

    y         = x;
    yFlag     = YES;
    operation = [[sender selectedCell] tag];
    enterFlag = YES;

    [self displayX];
}

- (void) displayX
{
    NSString * str = [NSString stringWithFormat: @"%15.10g", x];

    [text setStringValue: str];
}
@end

Обратите внимание на реализацию метода digit:. Данный метод будет вызываться при нажатии любой из цифровых кнопок, при этом в качестве параметра (sender) будет передаваться адрес посылающего объекта, в качестве которого будет выступать вся матрица кнопок.

Однако матрица отслеживает активный объект (который можно получить послав сообщение selectedCell), а уже у него можно узнать его тег (послав ему сообщение tag).

Таким образом [[sender selectedCell] tag] возвращает тег нажатой цифровой кнопки, что в нашем случае просто равно значению цифры с данной кнопки.

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

Теперь соединим объект класса NSMatrix, содержащий кнопки с цифрами, с только что созданным объектом CalcController. В окне инспектора выберем метод (в закладке Target/Actions) digit: и осуществим подключение при помощи кнопки Connect.

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

Аналогичным образом подключим кнопки С и СА к методам clear: и clearAll: (для подключения отдельной кнопки, а не всей матрицы, необходимо сначала выделить нужную кнопку двойным щелчком мыши).

Далее протянем связь от объекта CalcController к текстовому полю и осуществим подключение поля к outlettext.

Рис 18. Подключение outlettext к текстовому полю.

Теперь, сохранив nib-файл, можно вернуться в XCode и щелчком мыши по кнопке "Build And Go" собрать и запустить наше приложение.

Рис 19. Работающий калькулятор.

При помощи мыши можно набирать числа (нажимая на кнопки калькулятора) и очищать числовое поле калькулятора. Пора научить его и считать :)))

Закроем приложение при помощи Command-Q и снова перейдем в Interface Builder. На этот раз нам нужно будет добавить блок кнопок, соответствующих арифметическим операциям, кнопке изменения знака ("+/-") и кнопке "=".

Как и ранее используем для этого матрицу кнопок, задав для каждой из них нужный текст. В качестве тегов поставим кнопкам значения, заданные соответствующим enum'ом - 1001 ("+"), 1002 ("-"), 1003 ("*") и т.д.

Рис 20. Задание атрибутов для кнопки "+/-".

В качестве следующего шага подключим матрицу кнопок, соответствующих операциям к методу operation: объекта CalcController.

Однако пока наш калькулятор не поддерживает операцию смены знака (кнопка "+/-"). Для ее поддержки добавим новый метод в класс CalcController. Для этого перейдем обратно в XCode и добавим следующий метод в файл CalcController.m:

- (IBAction) invSign: (id) sender
{
    x = -x;
    
    [self displayX];
}

Аналогично добавим описание этого метода в файл CalcController.h.

Теперь мы изменили интерфейс класса CalcController и, чтобы мы могли подсоединить кнопку к добавленному методу, необходимо сообщить Interface Builder'у об изменении интерфейса класса CalcController.

Для этого переключимся в Interface Builder и воспользуемся командой Classes/Read Files....

Рис 21. Обновление данных о классе путем чтения заголовочного файла.

В появившемся диалоге выберем файл CalcController.h.

В результате Interface Builder разберет файл CalcController.h и уточнит свою информацию о соответствующем классе. Теперь в списке методов класса CalcController есть добавленный метод invSign: и мы можем подключить кнопку "+/-" к нему.

Рис 22. Обновленный список методов класса CalcController.

Дальше подключим кнопки к методома объекта CalcController. Сперва протянем связть от кнопки CA к объекту CalcController и выберем в списке метод clearAll:.

Рис 23. Подключение кнопки CA.

Далее вделим матрицу цифровых кнопок и подключим ее к методу digit: CalcController'а.

Рис 24. Подключение кнопки CA.

Далее сперва всю матрицу кнопок с операциями подключим к методу operation:, после чего первую кнопку (+/-) отдельно подколючим к методу invSign:

Рис 25. Подключение кнопки +/-.

Если теперь собрать и запустить приложение, то мы получил полноценный калькулятор

Рис 26. Окончательный вид работающего калькулятора.

Обратите внимание, на то, что использование нами объекта CalcController фактически является случаем шаблона Директор (см. Приемы объектно-ориентированного проектирования. Паттерны проектирования. Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влисидес ).

Весь проект можно скачать по этой ссылке .

Valid HTML 4.01 Transitional

Напиши мне
Используются технологии uCoz