Cocoa Programming II : NSTableView Advance

หลังจากได้ลองเขียน NSTableView ดูกันแล้ว วันนี้ก็ยังคงต่อด้วยเรื่องของ NSTableView เหมือนเดิมแต่ว่าเป็นขั้น Advance มากกว่าเดิม

More about datasource

จากโปรแกรมแสดงตารางรายชื่อครั้งก่อน จะเห็นว่าเราได้ set datasource มาที่ AppController และเราก็เขียน implement delegate ของ datasource ที่ AppController จะเห็นว่าวิธีแบบนี้ไม่ค่อยจะยืดหยุ่นมากนัก เพราะเนื่องจากว่าเราให้ AppController เป็นทั้งตัวควบคุม interface และยังให้เป็น datasource

สมมติว่าเราต้องการมี table มากกว่า 1 table และแต่ละ table มี datasource ที่มีข้อมูลต่างกัน เราก็คงต้องเพิ่ม datasource ให้เท่ากับจำนวนของ table ซึ่งนั่นก็แปลว่าเราก็ต้องสร้าง AppController instance มาหลายๆอัน มันก็คงจะไม่ใช่วิธีที่ดีนัก และที่สำคัญตัวโปรแกรมจะมองว่า AppController instance ที่เราสร้างขึ้นมาใหม่ มันเป็นตัวเดียวกัน ดังนั้นข้อมูลใน table A และ B ก็จะเหมือนกัน ( ดูรูปประกอบ ) ฉนั้นแล้วการที่เราให้ AppController เป็น datasource โดยตรงก็คงไม่ดีแน่

งั้นแทนที่เราจะให้ Class AppController เป็น datasource โดยตรง เราก็เปลี่ยนให้ AppController มี member เป็น datasource ก็น่าจะดีกว่า

จากรูปข้างบน จะเห็นว่า เราก็สามารถมีหลายๆ datasource ที่ต่างกันได้ แล้วเราจะเขียน class ออกมาได้ยังไง ? งั้นมาดูตัวอย่างจริงกันเลยดีกว่า

สมมติว่า เราต้องการ เขียนโปรแกรมแสดงรายชื่อเหมือนเดิม แต่เราต้องการแยก class datasource ออกมาจาก AppController โดยให้ชื่อว่า StudentDataSource

@interface StudentDataSource : NSObject
{
        NSMutableArray *m_arrayNameData;
}
-(void) AddStudentRecord:(NSString*) name;
- (int) numberOfRowsInTableView:(NSTableView*) table;
- (id)  tableView:(NSTableView *)aTableView 
        objectValueForTableColumn:(NSTableColumn *)
        aTableColumn row:(int)rowIndex;
@end

จาก Class StudentDataSource ข้างบนเราก็ได้ให้ NSMutableArray เป็นตัวเก็บข้อมูล รายชื่อ และในส่วนของ implement ก็เขียนได้ลักษณะแบบนี้

@implementation StudentDataSource
-(id)init
{
	[super init];
	m_arrayNameData = [NSMutableArray array];
	[m_arrayNameData retain];
	return self;
}
-(void) dealloc
{
	[m_arrayNameData release];
	[super dealloc];
}
 
-(int) numberOfRowsInTableView:(NSTableView*) table
{
	return [m_arrayNameData count];
}
 
-(id) tableView:(NSTableView*)table
      objectValueForTableColumn:(NSTableColumn*) tableColumn
      row:(int) rows
{
	return [m_arrayNameData objectAtIndex:rows];
}
 
-(void) AddStudentRecord:(NSString*) name
{
	if( [name length] != 0 )
		[m_arrayNameData addObject:name];
}
 
@end

ก็เป็นว่าตอนนี้เรามี Class ที่เป็น DataSource ต่อไปก็แก้ไข AppController ของเรา แบบนี้

@interface AppController : NSObject
{
    IBOutlet NSTableView *m_tableView;
    IBOutlet NSButton *m_button;
    IBOutlet NSTextField *m_textFields;
 
    StudentDataSource *m_dataSource;
}
 
-(IBAction) AddRecord:(id) sender;
 
@end

จะเห็นว่า เรามี member ที่เป็น StudentDataSource เข้ามาด้วย และในส่วน implement ก็จะได้แบบนี้

@implementation AppController
-(id) init
{
	[super init];
	m_dataSource = [[StudentDataSource alloc] init];
	[m_dataSource retain];
	return self;
}
 
-(void) awakeFromNib
{
	[m_tableView setDataSource:m_dataSource];
}
-(void) dealloc
{
	[m_dataSource release];
	[super dealloc];
}
 
-(IBAction) AddRecord:(id) sender
{
	NSString* name = [m_textFields stringValue];
	[m_dataSource AddStudentRecord:name];
	[m_tableView reloadData];
}
@end

จาก Code ข้างบนจะมีส่วนที่สำคัญคือ awakeFromNib จะเห็นว่าเมื่อโปรแกรมเริ่มทำงาน เราก็ Set DataSource ให้กับ m_tableView ในทันที และเราก็ไม่ต้องไป link ที่ interface builder เลย

ลอง Download Source ไปลอง  Compile ดูครับ โปรแกรมก็ทำงานเหมือนเดิมแต่ ต่างกันตรงที่เราแยก datasource ออกมาให้เป็น member ของ AppController ต่อไปถ้าหากเราต้องการ มีหลายๆตาราง และ datasource ที่ต่างกัน ก็สามารถทำได้แล้วครับ

Editable Cell

โปรแกรมที่เราได้ลองเขียนนั้น เป็นแค่การอ่านข้อมูลมาแสดงผลอย่างเดียว ไม่สามารถ แก้ไขข้อมูลภายใน Cell ได้ ถ้าหากเราต้องการให้ table ของเราสามารถแก้ไขได้ เราต้อง implement delegate method เพิ่มอีก 1 นั่นก็คือ

- (void)tableView :( NSTableView *)aTable setObjectValue :( id)aData
forTableColumn :( NSTableColumn *)aCol
row :( int)aRow;

และจากโปรแกรมข้างบน ก็สามารถเขียนเพ่ิมให้ table ของเราสามารถแก้ไขข้อมูลได้ ดังตัวอย่าง code ข้างล่าง

- (void)tableView:(NSTableView *)aTable setObjectValue:(id)aData
        forTableColumn:(NSTableColumn *)aCol
        row:(int)aRow
{
   NSString *text = aData;
   if( [[m_arrayNameData objectAtIndex: aRow] isEqualToString:text] == NO)
   {
	[m_arrayNameData replaceObjectAtIndex:aRow withObject:aData];
   }
}

ก็ลองโหลด Source Code ไปลอง Compile เล่นดู

More Column

จากตัวอย่างที่เขียนไปเราได้สร้างตารางที่มีแค่ 1 column เท่านั้น ถ้าหากต้องการสร้าง หลายๆ Column แล้วเราก็เพียงแค่ไปเปลี่ยน ค่าของจำนวน column ใน interface builder แบบรูปข้างล่าง

การสร้าง interface  ก็คงไม่ยากอะไร แต่ปัญหาคือ เราจะเขียน Code ให้มันแสดง cell ที่ตำแหน่ง column ที่เราต้องการได้อย่างไร ?

วีธีการก็คือ เราต้อง กำหนดชื่อ ให้กับ Column ก่อน แล้วก็แก้ไข code ในส่วนของ

-(id) tableView:(NSTableView*)    table
objectValueForTableColumn:(NSTableColumn*) tableColumn
row:(int) rows

สมมติว่า เราต้องการแก้ไข โปรแกรม แสดงรายชื่อนักเรียน ให้มีอีก 1 Column เพื่อแสดงสาขาที่เรียนของนักเขียนด้วย หน้าตาโปรแกรม เป็นลักษณะแบบนี้

เราก็ทำการแก้ไข datasource ของเราให้มี array เพ่ิมขึ้นมาอีก 1 เพื่อเอาไว้เก็บ สาขาที่เรียนของนักเรียน อาจจะเขียนใหม่ได้ว่า

@interface StudentDataSource : NSObject
{
   NSMutableArray *m_arrayNameData;
   NSMutableArray *m_arrayDepartmentData;
}
-(void) AddStudentRecord:(NSString*)name 
        andDepartment:(NSString*) department;
@end

และเราก็เขียน method เพิ่ิมเติม

-(IBAction) AddRecord:(id) sender
{
	NSString* name = [m_textFields stringValue];
	NSString* department = [m_textFields stringValue];
 
	[m_dataSource AddStudentRecord:name andDepartment:department];
	[m_tableView reloadData];
}

และส่วนที่สำคัญที่สุดคือ tableView: objectValueForTableColumn: row: เราต้องเขียนให้มันส่ง ตำแหน่ง cell ใน column ที่ต้องการให้เป็นแบบนี้

-(id) tableView:(NSTableView*)	table
      objectValueForTableColumn:(NSTableColumn*) tableColumn
      row:(int) rows
{
	NSString *columnName;
	columnName = [tableColumn identifier];
	if( [columnName isEqualToString:@"name"] == YES )
		return [m_arrayNameData objectAtIndex:rows];
	else
		return [m_arrayDepartMentData objectAtIndex:rows];
}

จาก code ข้างบนเราสามารถอ้างอิงถึง column ได้โดยการใช้ column identifier เป็นการตรวจสอบว่า เป็น column ของเราที่ต้องการหรือเปล่า  อย่างตัวอย่างเราก็ได้ให้ เปรียบเทียบว่าเป็น column “name” หรือไม่ และการใส่ค่าให้กับ column identifier เราทำได้โดยการใช้ interface builder และใส่ค่าให้กับ column เหมือนรูปข้างล่าง

แล้วก็ Compile ดูครับ เพียงเท่านี้ก็เป็นอันเรียบร้อย โปรแกรมก็จะออกมาแบบนี้

สำหรับ Source ก็โหลดได้นี่เลย Download Source Code

Technorati Tags: , , , ,


One response so far, want to say something?

  1. Avatar

    lmstart says:

    ผมติดปัญหาหนึ่งอะครับ เรื่องการโหลดTableView ที่มี Row มากกว่า25 ใน Iphone ครับ ถ้ามันมีมากกว่ามันก็ crashes เลย ทำไงดีอะครับ

Leave a Reply

You must be logged in to post a comment.