BezierPathViewを対応させる【実践的Macintoshプログラミング解説】

印刷用表示 |テキストサイズ 小 |中 |大 |

CoreData版 Repeating Motif Generator の開発 Repeating Motif Wonderland CoreData 実践的 Macintosh プログラミング解説

LinkIconホーム

更新日 2009-05-24

ビューを原図とミキサーに対応させる

BezierPathViewを対応させる

 原図とミキサーのモデルを追加したので、次はビューを原図とミキサーに対応させます。以前はBezierPathViewのサブクラスとしてOscillatorViewとLissajousViewを作っていましたが、LissajousViewはキーパスの設定を行なうメソッドしかありませんでした。

 キーパスの設定はツールチップの文字列を使って行なうという変則的な事をやっていたのですが、これをやめて外部から設定する様に変更します。したがってMasterMotifViewやMixerViewといったサブクラスを作る必要がなくなりました。

 ただしOscillatorViewはクリックに応答して選択枠を描画するという独自の機能を持っているので、サブクラス化は必要です。よってOscillatorViewはそのまま残し、キーパスの設定部分のみ変更します。

BezierPathViewの初期化方法を変更する

 第三のプロジェクトでBezierPathViewを作りました。そこではツールチップに設定した文字列の最初の文字でビューの種類を見分け、バインディングに使うキーパスを設定したり、X軸用のビューであるかどうかを設定したりしていました。

 ツールチップに設定した文字列で設定を変えるという方法はトリッキーな感じがして元々あまり気に入らなかったのですが、それに加えて動作確認中にツールチップがちらついたり、下のウィンドウに配置されたビューのツールチップが表示されてしまったりといった問題も発生してしまいました。そこで初期設定を別の方法で行なう事にしました。

 これまでは自分の初期化は自分で行なう方針でしたが、これをやめてEnclosureViewに設定してもらう事にします。そこでバインディングに使うキーパスと、X軸用のビューであるかどうかを設定するメソッドsetKeyPath:isXView:を用意します。

 インターフェイスファイルにメソッドの宣言を追加します。

//
//  BezierPathView.h
//  RepeatingMotifGenerator
//
//  Copyright NovemberKou 2008. All rights reserved.
//

#import <Cocoa/Cocoa.h>


@interface BezierPathView : NSView
{
    IBOutlet    NSArrayController   *controller;
    
    BOOL            isXView, multipleSelection, noSelection;
    NSBezierPath    *dataPath, *displayPath;
}

- (NSBezierPath *)dataPath;
- (void)setDataPath:(NSBezierPath *)newPath;

- (void)setKeyPath:(NSString *)keyPath isXView:(BOOL)isX;

@end

 setKeyPath:isXView:メソッドを実装します。実施している内容は、isXViewフラグの設定と、dataPathのバインディングの設定だけです。".bezierPath"は必ず末尾につくとわかっているので、引数には含めずこのメソッドで付け足す事にしました。

//
//  BezierPathView.m
//

- (void)setKeyPath:(NSString *)keyPath isXView:(BOOL)isX
{
    isXView = isX;
    [self bind:@"dataPath"
      toObject:controller
   withKeyPath:[keyPath stringByAppendingString:@".bezierPath"]
       options:nil];
}

 以前はawakeFromNibでdataPathのバインディングを設定していましたが、setKeyPath:isXView:メソッドで設定する様に変更したので、その部分を削除します。

//
//  BezierPathView.m
//

- (void)awakeFromNib
{
    [self bind:@"dataPath"
      toObject:controller
   withKeyPath:[self keyPath]
       options:nil];

    [self bind:@"selectionIndexes"
      toObject:controller
   withKeyPath:@"selectionIndexes"
       options:nil];
}

EnclosureViewでBezierPathViewを初期化する

 EnclosureViewでBezierPathViewを初期化する為にアウトレットを追加、変更します。OscillatorViewは数が二個から六個に増えたので、名称を変更します。そしてlissajousViewを削除して、代わりに原図とミキサーのビューを追加します。viewMasterが原図のビュー、viewXとviewYがそれぞれX軸とY軸のミキサー用のビューです。

//
//  EnclosureView.h
//  RepeatingMotifGenerator
//
//  Copyright NovemberKou 2008. All rights reserved.
//

#import <Cocoa/Cocoa.h>


@class  OscillatorView, BezierPathView;

@interface EnclosureView : NSView
{
    IBOutlet    OscillatorView      *x1View,*x2View,*x3View;
    IBOutlet    OscillatorView      *y1View,*y2View,*y3View;
    IBOutlet    BezierPathView      *viewX,*viewY,*viewMaster;
    IBOutlet    OscillatorView      *xView, *yView;
    IBOutlet    NSView              *lissajousView;

    IBOutlet    NSArrayController   *controller;
    IBOutlet    NSTextField         *frequencyTextField, *phaseLagTextField;
    IBOutlet    NSSlider            *frequencySlider, *phaseLagSlider;
    IBOutlet    NSStepper           *phaseLagStepper;

    int     whiteSpace,viewMargin;

    OscillatorView  *selectedView;
}

- (void)selectOscillator:(OscillatorView *)sender;

@end

 awakeFromNibに初期化コードを追加します。ついでにOscillatorViewの選択を明示的に行なう様に変更しました。

//
//  EnclosureView.m
//

- (void)awakeFromNib
{
    selectedView = nil;
    [self selectOscillator:x1View];

    [self frameDidChange:nil];

    [viewMaster setKeyPath:@"selection" isXView:NO];
    [viewX setKeyPath:@"selection.x" isXView:YES];
    [viewY setKeyPath:@"selection.y" isXView:NO];
    [x1View setKeyPath:@"selection.x.osc1" isXView:YES];
    [x2View setKeyPath:@"selection.x.osc2" isXView:YES];
    [x3View setKeyPath:@"selection.x.osc3" isXView:YES];
    [y1View setKeyPath:@"selection.y.osc1" isXView:NO];
    [y2View setKeyPath:@"selection.y.osc2" isXView:NO];
    [y3View setKeyPath:@"selection.y.osc3" isXView:NO];
}

OscillatorViewの変更点

 OscillatorViewがクリックされてビューの選択が切り替わった時、スライダーやテキストフィールドといった他のビューのバインディングを切替える必要があります。その切替えに自身のキーパスを提供する必要があるので、インスタンス変数を追加してキーパスを保持する様にします。

 例えばキーパスが"selection.x.osc1.bezierPath"の場合、"selection.x.osc1"がsetKeyPath:isXView:の引数として渡されるので、これを保持します。実際の切替えはEnclosureViewが行ないますので、oscillatorKeyPathメソッドで参照できる様にします。

//
//  OscillatorView.h
//  RepeatingMotifGenerator
//
//  Copyright NovemberKou 2008. All rights reserved.
//

#import <Cocoa/Cocoa.h>
#import "BezierPathView.h"


@interface OscillatorView : BezierPathView
{
    BOOL        selected;
    NSString    *oscillatorKeyPath;
}

- (NSString *)oscillatorKeyPath;

- (BOOL)selected;
- (void)setSelected:(BOOL)isSelected;

@end

 keyPathメソッドは不要になったので削除します。

 初期化メソッドとdeallocメソッドを追加します。初期化メソッドではインスタンス変数oscillatorKeyPathの初期値をセットしていますが、何もセットしないと起動時にスライダーやテキストフィールドのバインディングを設定するコードでエラーが発生するので、その対策としてセットしています。deallocメソッドでは保持していたキーパスを解放します。

 setKeyPath:isXView:メソッドをオーバーライドして、キーパスを保持するコードを追加しています。

//
//  OscillatorView.m
//

- (NSString *)keyPath
{
    NSString    *viewName;

    viewName = [[self toolTip] substringWithRange:NSMakeRange(0,1)];
    isXView = [viewName isEqualToString:@"x"];
    selected = isXView;

    return  [NSString stringWithFormat:@"selection.%@.bezierPath",viewName];
}

#pragma mark -
#pragma mark Initializer and Deallocator

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if(self)
    {
        selected = NO;
        oscillatorKeyPath = @"selection.x.osc1";
    }
    return self;
}

- (void)dealloc
{
    [oscillatorKeyPath release];
    [super dealloc];
}

#pragma mark -
#pragma mark Accessor methods

- (NSString *)oscillatorKeyPath     {return  oscillatorKeyPath;}

- (void)setKeyPath:(NSString *)keyPath isXView:(BOOL)isX
{
    [super setKeyPath:keyPath isXView:isX];
    [keyPath retain];
    [oscillatorKeyPath release];
    oscillatorKeyPath = keyPath;
}