ガンプラ

お年玉として、甥っ子に正月にガンプラを買ってあげた。

彼の話によると、最近のガンダムの敵は火星人なのだそうだ。

../../../_images/martian.jpg

昔のガンダムのイメージとはかなりかけ離れている感じがする。

validateUserInterfaceItem(4)

前回の更に続き。

MTLControlItemEnablerとは何か?

MTLControlItemEnablerはコードを簡潔に表記する事を目的に書かれました。 UIの挙動を宣言的に表記出来ます。

例えば、メソッド”xyzAction:”を呼出すボタンのenable/disableを制御するには、”canXyzAction:”を定義します。

“canXyzAction:”の返り値でenable/disableを制御します。

- (IBAction) xyzAction:(id)sender
{
    // 色々と実行
}

- (BOOL) canXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return YES or NO;
}

“canXyzAction:”の呼び出しは、”validateUserInterfaceItem:”で”controlItemEnabler:”を呼出すと内部で適当に呼出します。

- (BOOL)validateUserInterfaceItem:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return [self controlItemEnabler:anItem];
}

MTLControlItemEnablerを拡張する

さらに今回のコードでは、もう一歩進めて以下のUIの挙動を変更出来ます。

  • state
NSMixedState, NSOffState, NSOnState等の値でチェックBOXやラジオボタンのON/OFFやメニューのチェック等を制御します。
  • image
buttunやToolboxItemやMenuItemの画像を制御します。例えば、音楽プレーヤーアプリのPlay/Stopを一つのボタンので行い画像を随時入れ替える場合等に使えます。
  • title
ButtonやMenuItemのタイトル文字列を変更出来ます。
  • label
toolbarItemは何故かtitle属性がなくlabel属性になっていたので追加。

メソッド”xyzAction:”が有った場合は、以下のようにメソッド名にprefixを付けたメソッドを作成します。

- (IBAction) xyzAction:(id)sender
{
    // 色々と実行
}

- (BOOL) canXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return YES or NO;
}

- (NSIntger) stateOfXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return NSMixedState;//NSMixedState or NSOffState or NSOnState;
}

- (NSImage*) imageOfXyzAction:(id)anItem
{
    if( func() )
        return [NSImage imageNamed:NSImageNameIconViewTemplate];
    else
        return [NSImage imageNamed:NSImageNameListViewTemplate];
}

- (NSString*) titleOfXyzAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return [NSString stringWithFormat:@"xyz(%li)", 1/*適当な数字とか*/];
}

サンプルコード

プロジェクトファイルはここ、

EnablerTest-3.zip

最初は以下のようになってます。

../../../_images/ss011.tiff

action1のボタン(titleOfで置換えているので、enable(x)となってるが)を押すと、titleやimageが変わります。

../../../_images/ss021.tiff

action2のボタンを押すと、また変わります。

../../../_images/ss031.tiff

action3を押すともとに戻ります。

コードの解説は特に無し。zipを解凍してみてみてください。

validateUserInterfaceItem(3)

前回の更に続き。

今回は、NSControlの方を変更するのではなく、”validateUserInterfaceItem:”の記述の仕方の説明。

しかも、ノーマルな”validateUserInterfaceItem:”の記述方法ではなくて、私がより簡潔だと考えている記述方法です。

validateUserInterfaceItem:とは何か?

UIの有効無効を制御するCocoa標準の手法です。validateUserInterfaceItem登場以前には、toolbarやmenu等でそれぞれ異なったメソッドを使用していた。

現在は、validateUserInterfaceItemを実装する事を推奨している。

validateUserInterfaceItemの役割は、

  • UI有効無効を制御
  • UIにチェックマークを付ける
  • UIにtooltipの設定

等です。

Cocoa標準の表記法では

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;
    if([anItem action] == @selector(saveAction:))
    {
        theResult = YES or NO;
    }
    else if([anItem action] == @selector(action2:))
    {
       theResult = YES or NO;
    }
    return theResult; // or NO
}

と、こんな感じで表記します。

validateUserInterfaceItem:の表記法の問題点は何か?

プログラムコードを書く時には、関連するコードは近い場所に書くべきと言われています。 しかし、この標準的な書き方では関係ある物はまとめたいのに、actionのメソッドと有効にするためのロジックが以下のように離れてしまいます。

// UIから実行したいメソッド
- (IBAction) saveAction:(id)sender
{
    .
    .
    .
}
.
.
.
- (IBAction) action2:(id)sender
{
    .
    .
    .
}
.
.
.

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;

    // saveAction:のenable/disableの制御
    if([anItem action] == @selector(saveAction:))
    {
        theResult = YES or NO;
    }
    // action2:のenable/disableの制御
    else if([anItem action] == @selector(action2:))
    {
        .
        .
        .
        .
        .
        .
    }
    return theResult;
}

また、validateUserInterfaceItem:メソッドの中の、if-else-ifが延々と続くのも可読性が悪くなります。

解決方法は?

解決方法は単純です。

UIから呼出されるaction:メソッドとUIのenable/disableを決定するロジックを記述するcanActionメソッドの2つを対にして実装します。 これで2つのコードが離れる事を防げます。

次に、if-else-ifが延々と続くのを防ぐ為に、validateUserInterfaceItem:でaction:メソッドからcanActionメソッドを動的に探すコードを書けば解決出来ます。

これが、

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    BOOL theResult = NO;

    if([anItem action] == @selector(saveAction:))
    {
        theResult = [self canSaveAction:anItem];
    }
    else if([anItem action] == @selector(action2:))
    {
        theResult = [self canAction2:anItem];
    }
    else if([anItem action] == @selector(action3:))
    {
        theResult = [self canAction3:anItem];
    }
    else if([anItem action] == @selector(action4:))
    {
        theResult = [self canAction4:anItem];
    }

    return theResult; // or NO
}

こうなります。

#import "NSObject(MTLControlItemEnabler).h"
    .
    .
    .
- (BOOL)validateUserInterfaceItem:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    // controlItemEnablerで全部面倒見ます
    return [self controlItemEnabler:anItem];
}

actionとcanActionメソッドは、例えば以下のように一カ所に表記出来ます。

- (IBAction) saveAction:(id)sender
{
    NSError *error = nil;

    if (![[self managedObjectContext] save:&error])
    {
        [[NSApplication sharedApplication] presentError:error];
    }
}

- (BOOL) canSaveAction:(NSObject<NSValidatedUserInterfaceItem>*)anItem
{
    return [[self managedObjectContext] hasChanges];
}

サンプルコードは、

EnablerTest-2.zip

validateUserInterfaceItem(2)

前回の続き。サンプルアプリを作った。

MTLButtonがToolbarItemやMenuItemのように、UIのenable/disableを自動で行うのを確認出来る。

AppDelegateのvalidateUserInterfaceItemで順繰りに特定のactionを持つToolbarItemやMenuItemをenable/disableを行っている。

MTLButtonではWindowがアップデートされるタイミングで、このvalidateUserInterfaceItemを呼出して自分自身のenable/disableを変更している。

Action1のselectorを持つUIがenableになっている。NSButtonはvalidateUserInterfaceItemに対応していないので、全てenableになっている。しかし、MTLButton, ToolbarItem, MenuItemはAction1しかenableになっていない。

../../../_images/ss01.tiff

action2をenableにした場合。

../../../_images/ss02.tiff

action3をenableにした場合。

../../../_images/ss03.tiff

サンプルコードは、

EnablerTest.zip