Динамическое изменение размеров окна как в System Preferences

Многие наверное обращали внимание как изящно сделана в Mac OS X System Preferences работа с настройками - выбор одной из иконок приводит к плавному изменению размеров окна и содержимое окна изменяется на настройки для данного раздела.

Рис 1. System Preferences.

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

Рис 2.

Создадим новый проект Resizer типа Cocoa Application и сразу же перейдем к редактированию ресурсов (файла - MainMenu.nib) - основная работа будет именно там.

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

Для этого в окне MainMenu.nib откроем закладку Classes, выберем класс NSObject и нажатием Enter создадим новый класс, унаследованный от NSView. Сразу же переименуем его в MyController.

Следующим шагом будет добавление этому классу outlet'ов и методов (action'ов).

Для этого откроем панель инспектора (при помощи Shift-Command-I), перейдем в нем к разделу Attributes и добавим outlet'ы, показанные на следующем рисунке.

Рис 3. Набор outlet'ов для контроллера.

Аналогичным образом добавим контроллеру три метода - setContent:, setContentToView: и restoreContent.

Рис 4. Методы контроллера.

После этого при помощи команды меню Classes/Create Files For MyController создадим файлы MyController.h и MyController.m (и сразу же добавим их к проекту) и инстанциируем объект этого класса - после этого у нас в закладке Instances появится объект MyController.

Далее приведем главное окно к виду, показанному на следующем рисунке, т.е. изменим его размер, уберем признак Zoom (and resize) и разместим в нем кнопки с надписями "1", "2" И "3". Для каждой из этих кнопок установим соответствующее значение тега (1, 2 и 3).

Рис 5. Главное окно.

Далее как раз и начинается самое интересное - перетащим компоненту CustomView с палитры компонент прямо в раздел Instances.

Повторим эту операцию еще два раза, в результате чего у нас в разделе Instances будут три перетащенных объекта View1, View2 и View3.

Рис 6.

Обратите внимание, что каждому такому перетащенному объекту будет соответствовать свое окно в Interface Builder'е, с заголовком соответствующему имени объекта (т.е. View1, View2 и View3) - компонента, т.е. объект, унаследованный от NSView должна содержаться внутри какого-то окна.

Собственно эти компоненты и представляют собой содержимое главного окна для каждого из трех состояний. Мы можем произвольно менять из размеры, добавлять в них другие компоненты и т.п.

Пока мы ограничимся только изменением размера и добавлением кнопки "Back" в правый нижний угол.

Рис 7. Вид Interface Builder'а со всеми окнами.

После всего этого нам осталось установить связи для кнопок и контроллера.

Каждую из трех кнопок на главном окне подключим к методу контроллера setContent:.

Для каждой из компонент View1, View2 и View3 подключим кнопку "Back" к методу контроллера restoreContent:.

После этого подключим outlet'ы (выходы) контроллера view1, view2, view3, btn1, btn2 и btn3 к соответствующим объектам. Также подключим outlet window к главному окну.

Сохраним наши изменения в nib-файле и вернемся обратно в XCode. Откроем файлы MyController.h и MyController.m.

Добавим классу MyController две instance-переменные - contentView (типа NSView *) и saveFrame (типа NSRect). Ниже приводится листинг окончательного варианта файла MyController.h.

#import <Cocoa/Cocoa.h>

@interface MyController : NSObject
{
    IBOutlet NSButton * btn1;
    IBOutlet NSButton * btn2;
    IBOutlet NSButton * btn3;
    IBOutlet NSView   * view1;
    IBOutlet NSView   * view2;
    IBOutlet NSView   * view3;
    IBOutlet NSWindow * window;
    
    NSView * contentView;
    NSRect   saveFrame;
}
- (IBAction) setContent:(id)sender;
- (IBAction) setContentToView: (NSView *) view;
- (IBAction) restoreContent: (id) sender;
@end

После этого осталось только реализовать методы класса MyController. Основным методом является setContentToView:.

Данный метод основан на том, что каждое окно содержит т.н. contentView - визуальную компоненту, соответствующую клиентской части окна. Любая добавляемая в окно компонента на самом деле добавляется именно в contentView.

Класс NSWindow предоставляет два метода для работы с contentView - contentView setContentView:. Первый из них возвращает указатель на текущий contentView, а второй позволяет задать окну другой contentView.

Таким образом, все что должен делать метод контроллера setContentToView: это задать для окна новый contentView (в его качестве будут выступать view1, view2 и view3) и изменить размеры окна чтобы они соответствовали размерам нового contentView.

Для изменения размера окна удобно воспользоваться методом setFrame:display:animate: класса NSWindow. При этом если в качестве последнего параметра задать YES, то изменение размера произойдет в виде плавной анимации.

Ниже приводится реализация данного метода контроллера.

- (IBAction) setContentToView: (NSView *) view
{
    NSRect  frame    = [view frame];
    NSRect  winFrame = [window frame];
    NSRect  newFrame = NSMakeRect ( winFrame.origin.x, winFrame.origin.y-frame.size.height+winFrame.size.height, frame.size.width, frame.size.height );
        
    [window setContentView: view];
    [window setFrame: newFrame display: YES animate: YES];
}

Задачей метода setContent: (а именно он будет вызываться при нажатии кнопок переключения) является определение того, какая именно кнопка послала данное сообщение (что делается анализом ее тега), определения в какое состояние нам нужно перейти и вызов метода setContentToView: с соответствующим аргументом.

Поскольку мы хотим предоставить возможность возврата в исходное состояние (при помощи кнопки "Back"), то нам необходимо также запомнить первоначальный contentView и текущий размер окна.

- (IBAction) setContent: (id)sender
{
    if ( contentView == nil )
        contentView = [[window contentView] retain];

    saveFrame   = [window frame];
        
    int tag = [sender tag];
    
    if ( tag == 1 )
        [self setContentToView: view1];
    else
    if ( tag == 2 )
        [self setContentToView: view2];
    else
    if ( tag == 3 )
        [self setContentToView: view3];
}

Задачей метода restoreContent: является восстановление исходного размера и contentView окна.

- (IBAction) restoreContent: (id) sender
{
    [window setContentView: contentView];
    [window setFrame: saveFrame display: YES animate: YES];
}

Ниже приводится полный текст файла MyController.m.

#import "MyController.h"

@implementation MyController

- (IBAction) setContent: (id)sender
{
    if ( contentView == nil )
        contentView = [[window contentView] retain];

    saveFrame   = [window frame];
        
    int tag = [sender tag];
    
    if ( tag == 1 )
        [self setContentToView: view1];
    else
    if ( tag == 2 )
        [self setContentToView: view2];
    else
    if ( tag == 3 )
        [self setContentToView: view3];
}

- (IBAction) setContentToView: (NSView *) view
{
    NSRect  frame    = [view frame];
    NSRect  winFrame = [window frame];
    NSRect  newFrame = NSMakeRect ( winFrame.origin.x, winFrame.origin.y-frame.size.height+winFrame.size.height, frame.size.width, frame.size.height );
        
    [window setContentView: view];
    [window setFrame: newFrame display: YES animate: YES];
}

- (IBAction) restoreContent: (id) sender
{
    [window setContentView: contentView];
    [window setFrame: saveFrame display: YES animate: YES];
}

@end

По этой ссылке можно скачать весь исходный код к этой статье.

Valid HTML 4.01 Transitional

Напиши мне