หลังจากได้ลองเขียน 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








lmstart says:
ผมติดปัญหาหนึ่งอะครับ เรื่องการโหลดTableView ที่มี Row มากกว่า25 ใน Iphone ครับ ถ้ามันมีมากกว่ามันก็ crashes เลย ทำไงดีอะครับ
August 15, 2011, 12:06 pm