2010-02-17 23 views
9

trong ứng dụng ca cao của tôi, tôi cần một NSCell tùy chỉnh cho một NSTableView. Lớp con NSCell này chứa một NSButtonCell tùy chỉnh để xử lý một nhấp chuột (và hai hoặc ba NSTextFieldCells cho nội dung văn bản). Bạn sẽ tìm thấy một ví dụ đơn giản về mã của tôi bên dưới.NSButtonCell bên trong tùy chỉnh NSCell

@implementation TheCustomCell 

- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { 
    // various NSTextFieldCells 
    NSTextFieldCell *titleCell = [[NSTextFieldCell alloc] init]; 
    .... 
    // my custom NSButtonCell 
    MyButtonCell *warningCell = [[MyButtonCell alloc] init]; 
    [warningCell setTarget:self]; 
    [warningCell setAction:@selector(testButton:)]; 
    [warningCell drawWithFrame:buttonRect inView:controlView]; 
} 

Vấn đề tôi đang mắc kẹt với là: (: các NSButtonCell chính xác hơn) bên NSCell này để hoạt động đúng cách tốt nhất/phải để có được nút đó là những gì? "công việc" có nghĩa là: kích hoạt thông báo hành động được chỉ định và hiển thị hình ảnh thay thế khi được nhấp. Ra khỏi hộp, nút không làm bất cứ điều gì khi được nhấp.

Thông tin/bài đọc về chủ đề này khó tìm. Bài viết duy nhất tôi tìm thấy trên mạng đã chỉ cho tôi triển khai

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp; 

Đây có phải là cách chính xác không? Triển khai trackMouse: trong NSCell chứa của tôi? Và sau đó chuyển tiếp sự kiện tới NSButtonCell? Tôi đã có thể mong đợi chính NSButtonCell biết phải làm gì khi nó được click (và tôi thấy trackMouse: phương pháp nhiều hơn trong cunjunction với theo dõi chuột thực sự - không phải là một bánh xe đào tạo cho hành vi bấm 'chuẩn'). Nhưng nó có vẻ như nó không làm điều này khi được bao gồm trong một tế bào chính nó ... Có vẻ như tôi chưa nắm được bức tranh lớn trên các ô tùy chỉnh, ;-)

Tôi rất vui nếu ai đó có thể trả lời điều này (hoặc chỉ cho tôi một số hướng dẫn hoặc tương tự) từ kinh nghiệm của riêng mình - và cho tôi biết nếu tôi đi đúng hướng.

Cảm ơn trước, Tobi

Trả lời

8

Các yêu cầu tối thiểu là:

  • Sau chuột trái xuống trên nút, nó phải xuất hiện bất cứ khi nào ép chuột là trên nó.
  • Nếu con chuột sau đó nhả ra trên nút, ô của bạn phải gửi thông điệp hành động thích hợp.

Để nhấn nút, bạn cần cập nhật thuộc tính highlighted của ô nút thích hợp. Thay đổi trạng thái một mình sẽ không thực hiện được điều này, nhưng những gì bạn muốn là cho nút được đánh dấu nếu, và chỉ khi nào, trạng thái của nó là NSOnState.

Để gửi tin nhắn hành động, bạn cần phải biết khi nào chuột được nhả, sau đó sử dụng -[NSApplication sendAction:to:from:] để gửi tin nhắn.

Để ở vị trí gửi các tin nhắn này, bạn sẽ cần phải móc vào các phương pháp theo dõi sự kiện được cung cấp bởi NSCell. Lưu ý rằng tất cả các phương thức theo dõi đó, ngoại trừ phương thức cuối cùng, -stopTracking:..., trả về một Boolean để trả lời câu hỏi, "Bạn có muốn tiếp tục nhận thông báo theo dõi không?"

Bước cuối cùng là, để được gửi bất kỳ thông báo theo dõi nào, bạn cần triển khai -hitTestForEvent:inRect:ofView: và trả về một bitmask thích hợp của các giá trị NSCellHit.... Cụ thể, nếu giá trị trả về không có giá trị NSCellHitTrackableArea trong đó, bạn sẽ không nhận được bất kỳ thông báo theo dõi nào!

Vì vậy, ở một mức độ cao, thực hiện của bạn sẽ giống như thế:

- (NSUInteger)hitTestForEvent:(NSEvent *)event 
         inRect:(NSRect)cellFrame 
         ofView:(NSView *)controlView { 
    NSUInteger hitType = [super hitTestForEvent:event inRect:cellFrame ofView:controlView]; 

    NSPoint location = [event locationInWindow]; 
    location = [controlView convertPointFromBase:location]; 
    // get the button cell's |buttonRect|, then 
    if (NSMouseInRect(location, buttonRect, [controlView isFlipped])) { 
     // We are only sent tracking messages for trackable areas. 
     hitType |= NSCellHitTrackableArea; 
    } 
    return hitType; 
} 

+ (BOOL)prefersTrackingUntilMouseUp { 
    // you want a single, long tracking "session" from mouse down till up 
    return YES; 
} 

- (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView { 
    // use NSMouseInRect and [controlView isFlipped] to test whether |startPoint| is on the button 
    // if so, highlight the button 
    return YES; // keep tracking 
} 

- (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView { 
    // if |currentPoint| is in the button, highlight it 
    // otherwise, unhighlight it 
    return YES; // keep on tracking 
} 

- (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView:(NSView *)controlView mouseIsUp:(BOOL)flag { 
    // if |flag| and mouse in button's rect, then 
    [[NSApplication sharedApplication] sendAction:self.action to:self.target from:controlView]; 
    // and, finally, 
    [buttonCell setHighlighted:NO]; 
} 
+0

Ở đâu trong đó bạn có nói với bảng để nói với dataSource của nó rằng nút đã được kiểm tra? – Richard

+0

@ Jeremy W. Sherman: Đây có lẽ là một câu hỏi ngu ngốc, nhưng làm thế nào để bạn có được "nút của button | buttonRect |"? Tôi đã thử nhiều thứ khác nhau như [nút khung], nhưng điều đó dường như không hoạt động ... – houbysoft

5

Mấu chốt của NSCell lớp con là để tách trách nhiệm về dựng hình và xử lý các yếu tố giao diện người dùng thông thường (điều khiển) từ visual- và sự kiện hệ thống phân cấp trách nhiệm của NSView lớp. Việc ghép đôi này cho phép mỗi người cung cấp chuyên môn hóa và biến đổi lớn hơn mà không phải gánh nặng người khác. Nhìn vào số lượng lớn các trường hợp NSButton người ta có thể tạo trong Cocoa. Hãy tưởng tượng số lượng các lớp con NSButton có thể tồn tại nếu chức năng chia tách này không hoạt động!

Sử dụng ngôn ngữ mẫu thiết kế để mô tả vai trò: NSControl hoạt động như mặt tiền, ẩn chi tiết thành phần của ứng dụng từ khách hàng và chuyển sự kiện và hiển thị thông báo cho trường hợp NSCell hoạt động đại diện.

NSCell lớp con của bạn bao gồm các trường hợp NSCell lớp con khác trong thành phần của nó, họ không còn trực tiếp nhận các thông báo sự kiện từ NSControl dụ mà là trong hệ thống phân cấp xem. Do đó, để các trường hợp ô này nhận các thông báo sự kiện từ chuỗi trả lời sự kiện (của hệ thống phân cấp khung nhìn), cá thể di động của bạn cần truyền theo các sự kiện có liên quan đó. Bạn đang tạo lại tác phẩm của phân cấp NSView.

Đây không hẳn là điều xấu. Bằng cách sao chép hành vi của NSControl (và số NSView siêu lớp) nhưng ở dạng NSCell, bạn có thể lọc các sự kiện được chuyển đến các ô con theo vị trí, loại sự kiện hoặc các tiêu chí khác. Hạn chế là sao chép công việc của NSView/NSControl trong việc xây dựng cơ chế quản lý lọc &.

Vì vậy, trong việc thiết kế giao diện của bạn, bạn cần phải xem xét liệu các NSButtonCell (và NSTextFieldCell s) là tốt hơn hết trong NSControl s trong hệ thống phân cấp xem bình thường, hoặc như các tế bào tiểu trong NSCell lớp con của bạn. Tốt hơn là tận dụng chức năng đã tồn tại cho bạn trong một codebase hơn là phát minh lại nó (và tiếp tục duy trì nó sau này) một cách không cần thiết.

+1

Cảm ơn bạn, Huperniketes - một take tốt về chủ đề này! – Tobidobi

+0

@Huperniketes vì ​​vậy nếu tôi định triển khai một ô tùy chỉnh cho bảng (trước Lion với khả năng mới của bảng NSView), tôi buộc phải bắt chước hành vi của mỗi điều khiển mà tôi nhúng và tôi chỉ sử dụng ô cho phần bản vẽ thực sự. Đúng không? – David

+1

@David, tôi không rõ ràng về câu hỏi của bạn. Nếu bạn hỏi về việc nhúng một lớp con NSView vào một ô tùy chỉnh trong một khung nhìn bảng, ô của bạn sẽ cần phải tuân theo giao thức NSControl, hoặc xử lý các thông điệp từ chính lớp con xem, chuyển chúng vào khung nhìn bảng hoặc để chúng bị bướng bỉnh. Bạn không cần phải nhúng một lớp con NSControl vào một ô tùy chỉnh vì bạn thường có thể sử dụng chính ô điều khiển. Theo như hành vi của tế bào, bạn có thể sử dụng ô để vẽ các phần, tìm các phần được nhấp vào, v.v. – Huperniketes

Các vấn đề liên quan