【DynamicsAX】X++のメモ : ExcelへExport

X++のメモ。
特定のテーブルからExcelにレコードをエクスポート。jobで実行。参考にさせていただいたサイトはaxaptajob.blogspot.comさん。Thank you so much!

static void ExportToExcel(Args _args)
{
    SalesTable SalesTableLocal; // 今回はSalesTableテーブル
    SysExcelApplication excelApp;
    SysExcelWorkSheet excelWorksheet;
    SysExcelRange excelRange;
    SysExcelCells excelCells;
    SysExcelCell excelCell;
    ComVariant cellValue;
    Int i=1;
    ;
    excelApp = SysExcelApplication::construct();
    cellValue = new ComVariant() ;
    excelApp.workbooks().add();
    excelWorksheet = excelApp.worksheets().itemFromNum( 1 );
    excelCells = excelWorksheet.cells();
    while select * from SalesTableLocal
    {
        excelCells.item(i,1).value(cellValue.bStr(SalesTableLocal.SalesId));
        excelCells.item(i,2).value(cellValue.bStr(SalesTableLocal.SalesName));
        // if field's attribute is 'date', we must use 'cellValue.date()'.
        excelCells.item(i,3).value(cellValue.date(SalesTableLocal.DeliveryDate));
        i++;
    }
    excelApp.visible(true);
    //excelApp.quit();
}

新規jobに上記コードを書いて実行すると、SalesTableのSalesId、SalesName、DeliveryDateがずらっと書かれたExcelシートが表示されます。次回はImportっすね。

【DynamicsAX】X++のメモ

X++のメモ。

その1。BOMテーブルをSELECTするだけ。Jobで新規jobを登録、以下のコードを入力、実行。

static void Job1(Args _args)
{
    BOM bomt;  // BOMテーブルクラス
    int rec;  // レコード数
    ;
    while select bomt {    // BOMテーブルをSELECT
        info("-- record info --");
        info(strfmt("BOMType : %1", bomt.BOMType));    // BOMTypeを表示
        info(strfmt("ItemID  : %1", bomt.ItemId));    // ItemIdを表示
        rec++;    // レコード数をインクリメント
    }
    info("--end--");
    info(strfmt("%1 records was selected", rec));
}

その2。テーブルをjoinした例。BOMTableテーブルとBOMVersionテーブル。

static void Job2(Args _args)
{
    BOMVersion bomv;    // BOMVersionテーブル
    BOMTable bomt;    // BOMTableテーブル
    ;
    while select bomv    // BOMVersionをSELECT
        join bomt    // BOMTableとJOIN
            // JOINの条件
            where bomv.BOMId == bomt.BOMId {
        // BOMIdとSiteIdを表示
        info(strfmt("ItemID, BOMId, SiteId : %1, %2, %3",
            bomv.ItemId, bomv.BOMId, bomt.SiteId));
    }
}

その3。サーバーの指定したフォルダに、テーブルから読み出したレコードをファイル出力するだけ。

static server void Job3(Args _args)
{
    TextIo file;
    FileName fileName = @"c:\tmp\test.txt";
    BOMVersion bomv;
    container con;
    FileIoPermission permission;
    #File
    ;
    try {
        permission = new FileIoPermission(fileName, #io_write);
        permission.assert();
        file = new TextIo(fileName, #io_write);
        if (!file) { throw Exception::Error; }
        file.outRecordDelimiter(#delimiterCRLF);
        file.outFieldDelimiter(";");
        while select bomv {
            con = connull();
            con = conins(con, 1, bomv.ItemId);
            con = conins(con, 2, bomv.BOMId);
            file.writeExp(con);
        }
    } catch (Exception::Error) { error("you can't access to file.");}
    CodeAccessPermission::revertAssert();
}

参考資料:Microsoft Dynamics AX 2009 Programming: Getting Started

【iOS】Core Data & TableViewで、確認画面表示

前回のバグはちょっと置いておいて。TableViewで表示されているレコードをタップして、詳細(?)画面を表示させるように出来た。今回参考にさせていただいたのは、独学者の独り言原宏之さん)の記事。ありがとうございます。
まずはスクリーンショットから。Steveさんが2件登録されているのはご愛嬌前回の名残。

んで、上のSteveさんをタップすると

次に、下のSteveさんをタップすると

次にコード。元のViewから今回の詳細画面へ移動する際に使用するメソッドを、以下のように書くとOK。

// レコードを更新したいとき、ここを使おう
// レコードをタップすると、ビューが移動する
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 詳細ページ用に、DetailViewというファイルを用意(.h、.m、.xibを作成しておいた)
    DetailView *detailViewController = [[DetailView alloc]initWithNibName:@"DetailView" bundle:nil];
    // 元のページから、pushで移動
    [self.navigationController pushViewController:detailViewController animated:YES];

    // 最初の画面からindexPath(何行目のデータかってこと)を利用してNSManagedObjectを引き渡す    
    NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
    // nameをキーにしてgetした値を表示
    detailViewController.nameText.text = [[managedObject valueForKey:@"name"] description];
    // 同じくphoneNumberをキーにしてgetした値を表示
    detailViewController.phoneText.text = [[managedObject valueForKey:@"phoneNumber"] description];

    // 終わったらrelease    
    [detailViewController release];
}

ここまで来たら、あとは更新っすね。


ここまで書いて、ふと思い直してというか、理解が足りないと感じた点を。
上記コードは、元画面の実装ファイル(RootViewController.m)に書いた。つまり、「移動先画面でデータを取得」ではなく、「移動元でデータを取得して、取得したデータを移動先に渡す」ということだと思うのだけど、それがピンときていなかった。

【iOS】Core Dataでfetchされたデータの絞り込み検索(バグ?発見)

これじゃまだまだっす...
iPhone Simulatorで以前登録したデータが、起動時には表示されず、検索で文字を入力すると、ひっかかる

なもんで、同じ名前の場合には警告を出すとか禁止するってのが必要
あと、以前登録したレコードを最初に表示させるってのも必要かな

【iOS】Core Dataでfetchされたデータの絞り込み検索(なんとか解決)

なんとも単純な箇所がネックだった。
前回、Core Dataに登録したデータをUISearchBarで絞り込みしようと思い、以下のメソッドを追加した。

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption

上記コードは、Cocoaの日々さんの記事、およびiOS Developer Libraryのサンプルコードを参考にしました。ありがとうございます。

んで

前回のクラッシュは何が原因だったかというと、キャッシュ名でした。元々、

- (NSFetchedResultsController *)fetchedResultsController

というメソッドの中で

NSFetchedResultsController *aFetchedResultsController =
    [[NSFetchedResultsController alloc]
        initWithFetchRequest:fetchRequest
        managedObjectContext:self.managedObjectContext
        sectionNameKeyPath:nil
        cacheName:@"Root"];

という箇所があります。ここで、cacheName:@"Root"でRootと指定しています。しかし、

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
    NSString *query = self.searchDisplayController.searchBar.text;
    if (query && query.length) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains[cd] %@", query];
        [self.fetchedResultsController.fetchRequest setPredicate:predicate];
        
        [NSFetchedResultsController deleteCacheWithName:@"Root"];
    }
    
    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Handle error
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }
}

の中で、deleteCacheWithName:@"Root"というとこ、ここを"UserName"と書いていたんす。これを合わせたら、OKでした。スクリーンショットを以下に。

最初にデータを登録しておきます。

んで、Search欄にフォーカスをあてて、

例えば大文字Sを入力すると...

では次に、大文字Bを入力すると...

どちらにもない文字を入力すると...


と、いうわけで、次は、登録したレコードの更新を。

【iOS】Core Dataでfetchされたデータの絞り込み検索(未解決)

predicateWithFormatが、おそらくSQLでいうところのwhere句に相当するのでは?というところまではあたりはつけたが...

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
    NSString *query = self.searchDisplayController.searchBar.text;
    if (query && query.length) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name contains[cd] %@", query];
        [self.fetchedResultsController.fetchRequest setPredicate:predicate];
        [NSFetchedResultsController deleteCacheWithName:@"UserSearch"];
    }
    
    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Handle error
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }
}

上記コードでビルドしてシミュレートした時。Sを入力した段階では、2件あるレコードともに同じなのだけど、このあとtを入力したら、あっというまにクラッシュ。
キャッシュが悪さをする的な記事がwebにあったのだけど、

[NSFetchedResultsController deleteCacheWithName:@"UserSearch"];

これでキャッシュをデリートしているわけだし...

んー

【iOS】Core Data 2 (入力画面作成)

先日は、Core Dataでデータ登録ができるところまでで終わりました。登録できる値も固定値だったので、全然使い物にならない。
今日は、任意の文字列を登録できるよう、入力画面を作成しました。

参考資料:つくって覚えるObjective-C入門 iOS対応
http://books.ascii.jp/9784048703642/


前回のように、スナップショットを貼付けると、大量な画像になってしまうので、大事な(追加・修正を加えた)コードを主に書くことにしようかと。

まずはプロジェクトに、入力画面用のUIViewControllerサブクラスを追加。その際、xibファイル生成のチェックボックスにもチェックしておく。


まずは、入力画面用のヘッダーファイル。

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@interface InputView : UIViewController {
    
    // 名前と電話番号
    UIButton *addButton;
    UITextField *nameText;
    UITextField *phoneText;
    // データベースアクセス用変数を定義
    NSFetchedResultsController *fetchedResultsController;
}

// 名前と電話番号のプロパティ
// mファイル側で、@synthesizeが必要
@property (nonatomic, retain) IBOutlet UIButton *addButton;
@property (nonatomic, retain) IBOutlet UITextField *nameText;
@property (nonatomic, retain) IBOutlet UITextField *phoneText;
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;

// 登録ボタンを押した時に実行されるメソッド
-(IBAction)addData;
// ソフトウェアキーボードを閉じる
-(IBAction)closeNameKeyboard;
-(IBAction)closePhoneKeyboard;

@end

氏名と電話番号の入力欄を用意して、登録用ボタンも必要。@propertyで宣言してやることで、ゲッターセッターを用意せずとも変数にアクセスできる。登録ボタン押下時のメソッドと、ソフトウェアキー入力後の閉じるアクションも用意。


上記の実装ファイルの中から、大事な箇所を抜き出し。まずは登録ボタンを押したときのメソッド。

// 登録ボタンを押した時の挙動
-(void)addData{

    // insertNewObjectメソッドを参考、っていうかコピペ
    // コピペした際には、fetchedResultsControllerが定義されていないはずなので、要定義
    // それとデフォルトでは、self.fetchedResultsController となっていたのを修正(self.をはずす)
    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
    
    // nameを追加する
    // テキストフィールドで入力した値を使用する
    [newManagedObject setValue:nameText.text forKey:@"name"];
    // phoneNumberを追加する
    // テキストフィールドで入力した値を使用する
    [newManagedObject setValue:phoneText.text forKey:@"phoneNumber"];

    NSLog(@"addData method executed.");

    // Save the context.
    NSError *error = nil;
    if (![context save:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    // 前画面へ戻る
    [self.navigationController popViewControllerAnimated:YES];
}

上のコードで、self付きのままにしてたら、クラッシュした覚えがある。


次に、氏名と電話番号入力欄に入力を終えたときの、ソフトウェアキーボードを閉じるとこ。

// 氏名入力のソフトェアキーボードを閉じる
-(void)closeNameKeyboard{
    [nameText resignFirstResponder];
}

// 電話番号入力のソフトェアキーボードを閉じる
-(void)closePhoneKeyboard{
    [phoneText resignFirstResponder];
}


んで、オイラが一番悩んだところがここ。使い終わった変数をreleaseするとこ。これを正しく書かないと、入力後の画面戻り終えたとき、容赦なくクラッシュ。

- (void)dealloc
{
    [addButton release];
    [nameText release];
    [phoneText release];
    [fetchedResultsController release];
    [super dealloc];
}

オイラはreleaseと書くべきところをついついdeallocと書いていて、クラッシュしまくってました。releaseです。


今度は、前回あったファイルに戻る(RootViewController.h)

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "InputView.h"

@interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate> {
    
}

// 入力画面へ移動する
-(void)move2inputView;

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;

@end

前回はなかった、入力画面へ移動するというメソッドを用意。


次に、実装ファイル(RootViewController.m)。入力画面へ移動のためのメソッド。読み込むnib(xib)の名前を書いてallocとinit。

// 入力画面へ移動、のためのメソッド
-(void)move2inputView{
    
    // tableView:didSelectRowAtIndexPath:メソッドからコピペ、参考に
    InputView *detailViewController = [[InputView alloc] initWithNibName:@"InputView" bundle:nil];
    
    // 入力画面で入力してた値を、元の画面へ渡すのに必要
    // 左辺 = 入力画面、右辺 = 元画面
    detailViewController.fetchedResultsController = self.fetchedResultsController;
    
    [self.navigationController pushViewController:detailViewController animated:YES];
    [detailViewController release];
}


ビューが読み込まれたときのメソッド。上で書いたメソッドを呼び出すように。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // 編集ボタンを用意
    self.navigationItem.leftBarButtonItem = self.editButtonItem;

    // プラスボタンを押したときの挙動
    // 新規オブジェクト登録ではなく、入力画面を呼び出すように修正(move2inputView呼び出し)
    UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(move2inputView)];

    // 新規作成ボタン(プラスボタン)
    self.navigationItem.rightBarButtonItem = addButton;
    [addButton release];
}


セルに、何を表示させるか、のメソッド。

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
    // セルに、nameとphoneNumberの値を表示させる
    cell.textLabel.text = [managedObject valueForKey:@"name"];
    cell.detailTextLabel.text = [managedObject valueForKey:@"phoneNumber"];

}


コードについてはこれくらい。Macでの開発の困難さ(簡単さ)は、ソースコードよりもむしろ、Interface Builder(以降IB)での操作に原因があると思うのだけど。なもんで、ちょいと画像を載せます。下記画像は、テキストフィールドやボタンと、変数・メソッドなどを、IBで接続しているところ。


そんなこんなで、出来ました。入力画面を作って、任意の文字列をどんどん入力できるようになりました。


さて。次に試すとしたら、どうしよう。どんどん登録したレコードをCSVにして、サーバに送信、とか?ほかにも使っていないUIがたくさんあるので、そちらを利用した場合とか。