Доброго времени суток!
Уже достаточно давно пытаюсь заставить себя изучить очередной язык/платформу для программирования под Mac OS X/iOS. Интересует именно разработка приложений с нативным GUI, так как консольные приложения можно разрабатывать на чем угодно, начиная с C и C++ и заканчивая модным сейчас Nodejs. Как показала практика, кроссплатформенные фреймворки вроде Qt тут мало подходят, хотя бы потому что не обеспечивают нативный Look and Feel, к которому привыкли пользователи этой ОС.
На хабре есть достаточное количество материалов по языку Objective-C и фреймворку Cocoa. С точки зрения GUI, интерес представляет именно Cocoa, а тут большинство статей ограничивается кнопочками и текстовыми полями. Постараюсь исправить это недоразумение и описать работу с Table View на примере приложения, отображающего список процессов.
Статья не претендует на полноту изложения и абсолютную корректность материала и ориентирована, прежде всего, на начинающих разработчиков. Ошибки и конструктивную критику с радостью выслушаю в комментариях. Кому интересно, добро пожаловать под кат.
Для разработки нам потребуются некоторые знания о Table View. Давайте с них и начнем. Подробное описание работы с Table View приведено в [1], здесь я ограничусь кратким изложением основных идей.
Существует два вида Table View:
В этой статье мы рассмотрим работу с cell-based table view, так как нам достаточно его функций, решение получится более простым и будет работать не только с новыми версиями OS X.
Данные в Table View можно передавать двумя способами:
Cocoa Bindings появился в версии Mac OS X, начиная с версии 10.3. Основная его цель — сократить объем кода контроллера для связи объектов модели (Model) и представления (View).
Cocoa Bindings основан на двух механизмах:
Более подробно о Cocoa Bindings можно почитать в [3].
На этом с теорией закончили, можно переходить к практике.
Открываем Xcode и создаем новый проект «Cocoa Application». Назовем его «Process Monitor». Я использую последнюю на момент публикации версию Xcode (4.2.1), поэтому некоторые шаги могут отличаться.
Открываем файл MainMenu.xib, в нем выбираем Window. Установим для него размеры 480 на 500. Это делается в Size Inspector (значок линейки в правом сайдбаре). Там же установим минимальные размеры — чекбокс и нужные размеры.
Добавляем в окно элемент Table View из Object Library (значок куба в правом нижнем сайдбаре) (поиск по слову «table»). По умолчанию создается таблица cell-based. В этом можно убедиться, если открыть Attributes Inspector (в правом сайдбаре, слева от линейки). Изменяем размеры Table View так, чтобы она занимала все окно. Затем открываем Size inspector и на картинке Autosizing выбираем все элементы — это необходимо, чтобы Table View растягивалась вместе с окном.
Теперь нам необходимо добавить столбцы в таблицу. Будем отображать три столбца — PID процесса, иконку и название процесса. По умолчанию в таблицу добавлено два столбца. Нужно изменить это число на 3. Для этого выбираем таблицу (Table View) кликом в окне. Тут нужно сделать небольшое отступление. Дело в том, что объекты окна вложены друг в друга и выделить нужный совсем не просто. Есть два варианта: это либо выбор нужного элемента в иерархии объектов в окне Objects (думаю, он появился в Xcode 4, раньше я его не видел), либо Command+Control+Shift Click на окне с выбором нужного объекта. После выбора таблицы переходим в Attributes Inspector и устанавливаем значение Columns в 3.
Теперь присвоим имена столбцам. Это делается двойным кликом по заголовку столбца. Имя первого столбца — PID, второй оставим пустым, третий — Process Name.
Изменяем размер столбцов таблицы. Для этого выделяем столбец и в Size Inspector устанавливаем его размеры. Для первого (PID): 60, для второго (Icon): 40, для третьего (Process Name) — все оставшееся место.
Заменим тип второго столбца таблицы с текстового на изображение. Для этого в Object Library найдем Image Cell (поиск по «image») и перетащим ее на второй столбец.
После всех этих действий можно нажать кнопку Run и увидеть такое окно:
Теперь добавим controller, который будет взаимодействовать с таблицей. Самый подходящий тип котроллера для таблицы это NSArrayController. Найдем его в Object Library (поиск по «array») и перетащим в список Objects.
Установим биндинги для таблицы. Для этого выделим Table View и перейдем в Bindings Inspector (предпоследняя вкладка в правом сайдбаре). Там в разделе Table Content выберем Content и установим Bind to: Array Controller с Controller Key: arrangedObjects.
Теперь воспользуемся техникой KVC и укажем какие данные должен отображать каждый столбец. Для этого мы по очереди выделяем каждый столбец (Table Column) и в Bindings Inspector устанавливаем Value в Bind to: Array Controller с Controller Key: arrangedObjects. А для каждого столбца значения Model Key Path соответственно: processIdentifier, icon, localizedName. Почему установлены такие значения, будет описано ниже.
Так мы связали таблицу с контроллером. Осталось связать контроллер с данными.
Первым делом определим эти самые данные. Откроем файл AppDelegate.h и определим свойство contents типа NSArray:
В файле AppDelegate.m напишем:
Теперь вернемся к MainMenu.xib и скажем контроллеру какие данные использовать. Идем в Bindings Inspector и в разделе Controller Content устанавливаем Content Array в Bind to: App Delegate, а Model Key Path: contents.
На этом установка биндингов окончена, нам осталось только заполнить массив contents в AppDelegate нужными данными.
Существует несколько способов получения списка всех запущенных процессов. Об этом я как-нибудь напишу в отдельной статье. А сейчас рассмотрим самый простой и очевидный способ — с использованием Cocoa. Он получает не полный список процессов, но пусть нас это пока не волнует.
Определим в AppDelegate метод updateProcessList:
Сделаем этот метод первым в классе, чтобы была возможность вызывать его из других методов без определения в заголовочном файле.
Немного прокомментирую код. В первой строчке мы получаем объект класса NSWorkspace. Объект этого класса существует в программе в единственном экземпляре, своеобразный синглтон, и получается методом sharedWorkspace.
У этого объекта есть метод runningApplications, возвращающий список запущенных приложений в массиве. Все просто. И этот массив мы используем в качестве данных для контроллера.
Каждый элемент этого массива представляет собой объект класса NSRunningApplication. У этого класса есть несколько свойств, среди которых есть processIdentifier, icon, localizedName. Помните, мы устанавливали их для столбцов таблицы? Так вот, благодаря KVC, во время отображения таблицы будут использоваться эти значения из NSRunningApplication.
Теперь добавим вызов созданного метода во время запуска приложения. Для этого нужно добавить строчку:
в метод applicationDidFinishLaunching.
В Mac OS X принято что приложение не обязательно должно завершаться после того как закрыто последнее окно. В нашем примере в этом большого смысла нет, поэтому добавим автоматическое завершение. Делается это очень просто — добавлением одного метода в AppDelegate.m:
Теперь наше приложение готово. Если все сделано правильно, то после запуска можно увидеть что-то подобное:
В качестве упражнения, предлагаю читателю реализовать периодическое обновление списка процессов. Для этого можно использовать класс NSTimer и метод scheduledTimerWithTimeInterval этого класса [5].
Итак, в этой статье мы рассмотрели процесс создания простого приложения с использованием Table View. Большая часть действий проводилась в Interface Builder и не требовала программирования вообще. Все что можно было упростить, было упрощено — не были рассмотрены контроллеры окон (NSWindowController), контроллеры представлений (NSViewController), управление памятью и многое другое.
В следующей статье я планирую рассмотреть иерархический список (NSOutlineView) для отображения дерева процессов. И, если кода там наберется достаточно, сделаю репозиторий на github'е.
Всем спасибо за внимание!
Upd. Набрался кармы и перенес в Mac OS X.
Уже достаточно давно пытаюсь заставить себя изучить очередной язык/платформу для программирования под Mac OS X/iOS. Интересует именно разработка приложений с нативным GUI, так как консольные приложения можно разрабатывать на чем угодно, начиная с C и C++ и заканчивая модным сейчас Nodejs. Как показала практика, кроссплатформенные фреймворки вроде Qt тут мало подходят, хотя бы потому что не обеспечивают нативный Look and Feel, к которому привыкли пользователи этой ОС.
На хабре есть достаточное количество материалов по языку Objective-C и фреймворку Cocoa. С точки зрения GUI, интерес представляет именно Cocoa, а тут большинство статей ограничивается кнопочками и текстовыми полями. Постараюсь исправить это недоразумение и описать работу с Table View на примере приложения, отображающего список процессов.
Статья не претендует на полноту изложения и абсолютную корректность материала и ориентирована, прежде всего, на начинающих разработчиков. Ошибки и конструктивную критику с радостью выслушаю в комментариях. Кому интересно, добро пожаловать под кат.
Теория
Для разработки нам потребуются некоторые знания о Table View. Давайте с них и начнем. Подробное описание работы с Table View приведено в [1], здесь я ограничусь кратким изложением основных идей.
Типы Table View
Существует два вида Table View:
- cell-based — каждая ячейка таблицы представлена подклассом NSCell. В большинстве случаев этого достаточно, но создает трудности при разработке таблиц с комплексными ячейками, т.к. требует создания подкласса NSCell. В версиях Mac OS X 10.6 и более ранних существовал только этот тип таблиц.
- view-based — каждая ячейка представляет собой отдельный view. Это позволяет строить комплексные таблицы даже с поддержкой анимации без особых трудностей. Данный тип таблицы появился только в Mac OS X 10.7.
В этой статье мы рассмотрим работу с cell-based table view, так как нам достаточно его функций, решение получится более простым и будет работать не только с новыми версиями OS X.
Передача данных в Table View
Данные в Table View можно передавать двумя способами:
- С помощью реализация протоколов (интерфейсов) NSTableViewDelegate, NSTableViewDataSource, а затем связывать Table View с классом, реализующим эти протоколы посредством Interface Builder. Необходимо привязать Outlets dataSource и delegate. А в этом классе в самом простом случае необходимо реализовать два метода:
- numberOfRowsInTableView: — возвращает общее количество строк в таблице
- tableView:objectValueForTableColumn:row: — возвращает элемент для строки, переданной в аргументе row. Номер столбца в этом случае определяется различными способами, используя второй аргумент objectValueForTableColumn.
- С использованием контроллера и биндингов (Bindings). Эта техника основана на паттерне Model-View-Controller (MVC) [2] и технологии Cocoa Bindings. Про MVC, наверное, смысла рассказывать нет — про него все слышали, а кто не слышал, может ознакомиться по приведенной ссылке в Wiki. На Cocoa Bindings остановимся подробнее.
Cocoa Bindings
Cocoa Bindings появился в версии Mac OS X, начиная с версии 10.3. Основная его цель — сократить объем кода контроллера для связи объектов модели (Model) и представления (View).
Cocoa Bindings основан на двух механизмах:
- Key-Value Coding (KVC) — механизм для доступа к полям объекта по именам этих полей.
- Key-Value Observing (KVO) — механизм, позволяющий одним объектам следить за изменениями в других объектах.
Более подробно о Cocoa Bindings можно почитать в [3].
На этом с теорией закончили, можно переходить к практике.
Практика
Открываем Xcode и создаем новый проект «Cocoa Application». Назовем его «Process Monitor». Я использую последнюю на момент публикации версию Xcode (4.2.1), поэтому некоторые шаги могут отличаться.
Создание GUI
Открываем файл MainMenu.xib, в нем выбираем Window. Установим для него размеры 480 на 500. Это делается в Size Inspector (значок линейки в правом сайдбаре). Там же установим минимальные размеры — чекбокс и нужные размеры.
Добавляем в окно элемент Table View из Object Library (значок куба в правом нижнем сайдбаре) (поиск по слову «table»). По умолчанию создается таблица cell-based. В этом можно убедиться, если открыть Attributes Inspector (в правом сайдбаре, слева от линейки). Изменяем размеры Table View так, чтобы она занимала все окно. Затем открываем Size inspector и на картинке Autosizing выбираем все элементы — это необходимо, чтобы Table View растягивалась вместе с окном.
Теперь нам необходимо добавить столбцы в таблицу. Будем отображать три столбца — PID процесса, иконку и название процесса. По умолчанию в таблицу добавлено два столбца. Нужно изменить это число на 3. Для этого выбираем таблицу (Table View) кликом в окне. Тут нужно сделать небольшое отступление. Дело в том, что объекты окна вложены друг в друга и выделить нужный совсем не просто. Есть два варианта: это либо выбор нужного элемента в иерархии объектов в окне Objects (думаю, он появился в Xcode 4, раньше я его не видел), либо Command+Control+Shift Click на окне с выбором нужного объекта. После выбора таблицы переходим в Attributes Inspector и устанавливаем значение Columns в 3.
Теперь присвоим имена столбцам. Это делается двойным кликом по заголовку столбца. Имя первого столбца — PID, второй оставим пустым, третий — Process Name.
Изменяем размер столбцов таблицы. Для этого выделяем столбец и в Size Inspector устанавливаем его размеры. Для первого (PID): 60, для второго (Icon): 40, для третьего (Process Name) — все оставшееся место.
Заменим тип второго столбца таблицы с текстового на изображение. Для этого в Object Library найдем Image Cell (поиск по «image») и перетащим ее на второй столбец.
После всех этих действий можно нажать кнопку Run и увидеть такое окно:
Добавление контроллера и биндингов
Теперь добавим controller, который будет взаимодействовать с таблицей. Самый подходящий тип котроллера для таблицы это NSArrayController. Найдем его в Object Library (поиск по «array») и перетащим в список Objects.
Установим биндинги для таблицы. Для этого выделим Table View и перейдем в Bindings Inspector (предпоследняя вкладка в правом сайдбаре). Там в разделе Table Content выберем Content и установим Bind to: Array Controller с Controller Key: arrangedObjects.
Теперь воспользуемся техникой KVC и укажем какие данные должен отображать каждый столбец. Для этого мы по очереди выделяем каждый столбец (Table Column) и в Bindings Inspector устанавливаем Value в Bind to: Array Controller с Controller Key: arrangedObjects. А для каждого столбца значения Model Key Path соответственно: processIdentifier, icon, localizedName. Почему установлены такие значения, будет описано ниже.
Так мы связали таблицу с контроллером. Осталось связать контроллер с данными.
Первым делом определим эти самые данные. Откроем файл AppDelegate.h и определим свойство contents типа NSArray:
@property (retain) NSArray* contents;
В файле AppDelegate.m напишем:
@synthesize contents;
Теперь вернемся к MainMenu.xib и скажем контроллеру какие данные использовать. Идем в Bindings Inspector и в разделе Controller Content устанавливаем Content Array в Bind to: App Delegate, а Model Key Path: contents.
На этом установка биндингов окончена, нам осталось только заполнить массив contents в AppDelegate нужными данными.
Получение списка запущенных процессов
Существует несколько способов получения списка всех запущенных процессов. Об этом я как-нибудь напишу в отдельной статье. А сейчас рассмотрим самый простой и очевидный способ — с использованием Cocoa. Он получает не полный список процессов, но пусть нас это пока не волнует.
Определим в AppDelegate метод updateProcessList:
- (void)updateProcessList
{
NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
self.contents = [NSArray arrayWithArray:[workspace runningApplications]];
}
Сделаем этот метод первым в классе, чтобы была возможность вызывать его из других методов без определения в заголовочном файле.
Немного прокомментирую код. В первой строчке мы получаем объект класса NSWorkspace. Объект этого класса существует в программе в единственном экземпляре, своеобразный синглтон, и получается методом sharedWorkspace.
У этого объекта есть метод runningApplications, возвращающий список запущенных приложений в массиве. Все просто. И этот массив мы используем в качестве данных для контроллера.
Каждый элемент этого массива представляет собой объект класса NSRunningApplication. У этого класса есть несколько свойств, среди которых есть processIdentifier, icon, localizedName. Помните, мы устанавливали их для столбцов таблицы? Так вот, благодаря KVC, во время отображения таблицы будут использоваться эти значения из NSRunningApplication.
Теперь добавим вызов созданного метода во время запуска приложения. Для этого нужно добавить строчку:
[self updateProcessList];
в метод applicationDidFinishLaunching.
Завершение приложения
В Mac OS X принято что приложение не обязательно должно завершаться после того как закрыто последнее окно. В нашем примере в этом большого смысла нет, поэтому добавим автоматическое завершение. Делается это очень просто — добавлением одного метода в AppDelegate.m:
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return YES;
}
Заключение
Теперь наше приложение готово. Если все сделано правильно, то после запуска можно увидеть что-то подобное:
В качестве упражнения, предлагаю читателю реализовать периодическое обновление списка процессов. Для этого можно использовать класс NSTimer и метод scheduledTimerWithTimeInterval этого класса [5].
Итак, в этой статье мы рассмотрели процесс создания простого приложения с использованием Table View. Большая часть действий проводилась в Interface Builder и не требовала программирования вообще. Все что можно было упростить, было упрощено — не были рассмотрены контроллеры окон (NSWindowController), контроллеры представлений (NSViewController), управление памятью и многое другое.
В следующей статье я планирую рассмотреть иерархический список (NSOutlineView) для отображения дерева процессов. И, если кода там наберется достаточно, сделаю репозиторий на github'е.
Всем спасибо за внимание!
Ссылки
- Table View Programming Guide (en)
- Model–view–controller pattern (en)
- Работа Cocoa Bindings (ru)
- NSWorkspace Class Reference (en)
- NSTask Class Reference (en)
Upd. Набрался кармы и перенес в Mac OS X.