#objective-c #cocoa #draggable #nsview
#objective-c #cocoa #перетаскиваемый #nsview
Вопрос:
В приложении на основе Cocoa у меня есть холст для рисования, унаследованный от NSView, а также прямоугольник, также унаследованный от NSView. Перетаскивание прямоугольника внутри холста не является проблемой:
-(void)mouseDragged:(NSEvent *)theEvent {
NSPoint myOrigin = self.frame.origin;
[self setFrameOrigin:NSMakePoint(myOrigin.x [theEvent deltaX],
myOrigin.y - [theEvent deltaY])];
}
Работает как по волшебству. Проблема, с которой я сталкиваюсь сейчас: Как я могу предотвратить перемещение прямоугольника за пределы холста?
Итак, прежде всего, я хотел бы исправить это только для левой границы, впоследствии адаптировав другие края. Моя первая идея такова: «проверьте, является ли начало координат прямоугольника отрицательным». Но: как только оно станет отрицательным, прямоугольник больше нельзя будет перемещать (естественно). Я решил это, переместив прямоугольник на нулевое смещение x в ветке else. Это работает, но это … некрасиво.
Итак, я немного озадачен этим, есть какие-нибудь подсказки? Определенно, решение действительно близко и просто. Настолько просто, что я не могу в этом разобраться (как всегда с простыми решениями ;).
С уважением
Mac
Ответ №1:
Я бы предложил не использовать deltaX
и deltaY
; попробуйте использовать местоположение события в супервизоре. Вам понадобится ссылка на вложенный просмотр.
// In the superview
- (void)mouseDragged:(NSEvent *)event {
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
// Could also add the width of the moving rectangle to this check
// to keep any part of it from going outside the superview
mousePoint.x = MAX(0, MIN(mousePoint.x, self.bounds.size.width));
mousePoint.y = MAX(0, MIN(mousePoint.y, self.bounds.size.height));
// position is a custom ivar that indicates the center of the object;
// you could also use frame.origin, but it looks nicer if objects are
// dragged from their centers
myMovingRectangle.position = mousePoint;
[self setNeedsDisplay:YES];
}
Вы бы выполнили, по сути, ту же проверку границ mouseUp:
.
ОБНОВЛЕНИЕ: Вам также следует ознакомиться с руководством по программированию View, в котором рассказывается о создании перетаскиваемого представления: Создание пользовательского представления.
Пример кода, который должен быть полезным, хотя и не имеет строго отношения к вашему первоначальному вопросу:
В DotView.m:
- (void)drawRect:(NSRect)dirtyRect {
// Ignoring dirtyRect for simplicity
[[NSColor colorWithDeviceRed:0.85 green:0.8 blue:0.8 alpha:1] set];
NSRectFill([self bounds]);
// Dot is the custom shape class that can draw itself; see below
// dots is an NSMutableArray containing the shapes
for (Dot *dot in dots) {
[dot draw];
}
}
- (void)mouseDown:(NSEvent *)event {
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
currMovingDot = [self clickedDotForPoint:mousePoint];
// Move the dot to the point to indicate that the user has
// successfully "grabbed" it
if( currMovingDot ) currMovingDot.position = mousePoint;
[self setNeedsDisplay:YES];
}
// -mouseDragged: already defined earlier in post
- (void)mouseUp:(NSEvent *)event {
if( !currMovingDot ) return;
NSPoint mousePoint = [self convertPoint:[event locationInWindow]
fromView:nil];
spot.x = MAX(0, MIN(mousePoint.x, self.bounds.size.width));
spot.y = MAX(0, MIN(mousePoint.y, self.bounds.size.height));
currMovingDot.position = mousePoint;
currMovingDot = nil;
[self setNeedsDisplay:YES];
}
- (Dot *)clickedDotForPoint:(NSPoint)point {
// DOT_NUCLEUS_RADIUS is the size of the
// dot's internal "handle"
for( Dot *dot in dots ){
if( (abs(dot.position.x - point.x) <= DOT_NUCLEUS_RADIUS) amp;amp;
(abs(dot.position.y - point.y) <= DOT_NUCLEUS_RADIUS)) {
return dot;
}
}
return nil;
}
Точка.h
#define DOT_NUCLEUS_RADIUS (5)
@interface Dot : NSObject {
NSPoint position;
}
@property (assign) NSPoint position;
- (void)draw;
@end
Точка.m
#import "Dot.h"
@implementation Dot
@synthesize position;
- (void)draw {
//!!!: Demo only: assume that focus is locked on a view.
NSColor *clr = [NSColor colorWithDeviceRed:0.3
green:0.2
blue:0.8
alpha:1];
// Draw a nice border
NSBezierPath *outerCirc;
outerCirc = [NSBezierPath bezierPathWithOvalInRect:
NSMakeRect(position.x - 23, position.y - 23, 46, 46)];
[clr set];
[outerCirc stroke];
[[clr colorWithAlphaComponent:0.7] set];
[outerCirc fill];
[clr set];
// Draw the "handle"
NSRect nucleusRect = NSMakeRect(position.x - DOT_NUCLEUS_RADIUS,
position.y - DOT_NUCLEUS_RADIUS,
DOT_NUCLEUS_RADIUS * 2,
DOT_NUCLEUS_RADIUS * 2);
[[NSBezierPath bezierPathWithOvalInRect:nucleusRect] fill];
}
@end
Как вы можете видеть, Dot
класс очень легкий и использует пути Безье для рисования. Супервизор может обрабатывать взаимодействие с пользователем.
Комментарии:
1. Хм, хорошо… итак, еще два вопроса к этому: (1) Почему я не должен использовать дельты? (2) При использовании superview мне всегда придется проверять, перетаскиваю ли я прямоугольник, т. Е. находится ли моя мышь над прямоугольником. Если у меня несколько прямоугольников, это кажется мне довольно неэффективным, или я что-то неправильно понял?
2. @macs: причина, по которой вы не должны использовать дельты, заключается в том, что это приводит к проблеме, с которой вы сталкиваетесь! Что касается второго вопроса, я на самом деле почти предположил, что вы, возможно, захотите пересмотреть rectangle как
NSView
подкласс. Если у вас есть только один, супервизору несложно выполнить отрисовку. Если вы планируете использовать их много, то производительность становится проблемой; см. раздел Оптимизация отображения .3. Спасибо за дополнительное объяснение. Я собираюсь реализовать ваши предложения.
4. @macs: Если вы планируете использовать много прямоугольников, то кому-то придется выполнять тесты попадания, либо вам, либо AppKit. Сохранение массива
NSRects
позволяет легко определить, какой из них пользователь хочет переместить.5. @Josh: В итоге приложение будет иметь несколько форм. Поскольку я новичок в Objective-C, первое, что я хотел выяснить, это лучший способ рисования фигур и как их перетаскивать и так далее. Как следует из руководства по программированию: наличие большого количества представлений не очень эффективно, поэтому впоследствии я собираюсь заменить их облегченным пользовательским классом.