【问题标题】:How to draw an outline on the screen around a given rectangle?如何围绕给定的矩形在屏幕上绘制轮廓?
【发布时间】:2021-01-09 09:12:34
【问题描述】:

我的目标是在另一个应用程序的窗口周围画一个轮廓。类似于 Microsoft Teams 在桌面共享期间绘制轮廓的方式。

例如,如图所示,Safari 窗口周围的轮廓。

使用 macOS 的 Quartz Window Services 和辅助功能 API,我能够获取 Safari 的坐标(x、y、宽度、高度),并且我订阅了窗口发生的事件,例如调整大小或窗口改变其位置.

// How I get window coordinates

- (CGRect)selectedAppCoordinates:(long)windowId {
  NSLog(@"selectedAppCoordinates - IN");
  CGRect rect;
  CGWindowID windowid[1];
  windowid[0] = windowId;
  CFArrayRef windowArray =
      CFArrayCreate(nullptr, (const void **)windowid, 1, nullptr);
  CFArrayRef windowsdescription =
      CGWindowListCreateDescriptionFromArray(windowArray);
  CFDictionaryRef windowdescription = (CFDictionaryRef)CFArrayGetValueAtIndex(
      (CFArrayRef)windowsdescription, 0);
  if (CFDictionaryContainsKey(windowdescription, kCGWindowBounds)) {
    CFDictionaryRef bounds = (CFDictionaryRef)CFDictionaryGetValue(
        windowdescription, kCGWindowBounds);
    if (bounds) {
      if (CGRectMakeWithDictionaryRepresentation(bounds, &rect)) {

        NSLog(@"x: %f, y: %f, width: %f, height: %f", rect.origin.x,
              rect.origin.y, rect.size.width, rect.size.height);
      }
    }
  }
  CFRelease(windowArray);
  NSLog(@"selectedAppCoordinates - OUT");

  return rect;
}

对我来说,问题是绘制轮廓。我对原生 macOS 开发经验很少,如果有任何帮助,我将不胜感激。

我应该如何绘制这个轮廓,在需要时更新和隐藏它?

【问题讨论】:

  • 我现在没有时间写一个正确的答案,所以我会在评论中回答。据我所知,最简单的方法是制作一个具有透明背景的无边框窗口,然后在该窗口的边缘周围绘制一个红色矩形。使您的窗口浮动在其他窗口之上,并使其与目标窗口的大小和位置相匹配(加上适当的边距)。如果您保持同步,它将看起来像围绕目标窗口绘制的边框。或者四个不透明的无边框窗口,每个边缘一个,如果您愿意的话。
  • 谢谢@bhaller。我正在尝试编写类似于您建议的代码,但我在创建/更新/隐藏大纲窗口时遇到了困难,因为我对 macOS 开发完全不熟悉。我只是想为需要本地化的 Electron.js 应用程序执行此辅助功能。我希望一个慷慨的陌生人能通过实际的代码演示来启发我如何做到这一点。
  • 这不是你想要的,但这段代码可能会让你开始:github.com/bhaller/Jiggler/blob/master/JigglerOverlayWindow.m。这将创建一个无边框的窗口,里面有一个视图。视图派生自 NSImageView,这是您不想要的;只需使用您自己的 NSView,并在它的 drawRect: 方法中使用 NSFrameRect() 来框出您从 [self bounds] 获得的视图边界,其颜色类似于 [NSColor redColor]。对不起;如果您在努力后仍然卡住,请在此处发布问题,我会尽力提供帮助,但我现在没有时间写完整的答案。
  • 谢谢。我试试看。

标签: objective-c macos cocoa core-graphics


【解决方案1】:

根据 bhaller 之前给出的建议,下面的演示使用第二个底层窗口,其中包含一个彩色填充视图,并且比主窗口略大,因此它的边缘是可见的。底层窗口通过窗口代理绑定到主窗口,以同步更改位置/大小,并且可以显示或隐藏。该演示可以在 Xcode 中运行,只需将主文件替换为以下代码并另外删除 Apple 提供的 AppDelegate 文件(以避免重复符号错误):

#import <Cocoa/Cocoa.h>

@interface CustomView : NSView
@end

@implementation CustomView
 
- (id)initWithFrame:(NSRect)frameRect{
 if ((self = [super initWithFrame:frameRect]) != nil) {
 // Add initialization code here
 }
 return self;
}
 
- (void)drawRect:(NSRect)rect {
 // ****** Background ***** //
 [[NSColor redColor] set];
 [NSBezierPath fillRect:rect];
}
 
 // ----- Use this if you want 0,0 (origin) to be top, left ---- //
 // ----- Otherwise origin will be at bottom, left (Unflipped) ----- //
 -(BOOL)isFlipped
 {
 return YES;
 }
@end

@interface WindowDelegate : NSObject <NSWindowDelegate>
 @property(strong) NSWindow *underlayWnd;
@end

@implementation WindowDelegate
 
- (void)windowDidResize:(NSNotification *)notification {
NSRect frameR = NSMakeRect([notification.object frame].origin.x - 10, [notification.object frame].origin.y - 10, [notification.object frame].size.width + 20, [notification.object frame].size.height + 20);
[_underlayWnd setFrame:frameR display:YES];
}
 
- (void)windowDidEndLiveResize:(NSNotification *)notification {
 NSRect frameR = NSMakeRect([notification.object frame].origin.x - 10, [notification.object frame].origin.y - 10, [notification.object frame].size.width + 20, [notification.object frame].size.height + 20);
[_underlayWnd setFrame:frameR display:YES];
}
 
- (void)windowDidMove:(NSNotification *)notification {
 NSRect frameR = NSMakeRect([notification.object frame].origin.x - 10, [notification.object frame].origin.y - 10, [notification.object frame].size.width + 20, [notification.object frame].size.height + 20);
 [_underlayWnd setFrame:frameR display:YES];
}
 
- (void)windowDidMiniaturize:(NSNotification *)notification {
NSRect frameR = NSMakeRect([notification.object frame].origin.x - 10, [notification.object frame].origin.y - 10, [notification.object frame].size.width + 20, [notification.object frame].size.height + 20);
 [_underlayWnd setFrame:frameR display:YES];
}
  
@end
 
@interface AppDelegate : NSObject <NSApplicationDelegate>
{
 NSWindow *window;
 NSWindow *underlayWnd;
 WindowDelegate *windowDelegate;
 CustomView *view;
}
- (void) myShowAction;
- (void) myHideAction;
- (void) buildMenu;
- (void) buildWindow;
@end

@implementation AppDelegate

-(void) myHideAction {
 [underlayWnd orderOut:nil];
 }

-(void) myShowAction {
 [underlayWnd orderFront:nil];
 [window makeKeyAndOrderFront: nil];
}

- (void) buildMenu {
NSMenu *menubar = [NSMenu new];
NSMenuItem *menuBarItem = [NSMenuItem new];
[menubar addItem:menuBarItem];
[NSApp setMainMenu:menubar];
NSMenu *appMenu = [NSMenu new];
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(terminate:) keyEquivalent:@"q"];
[appMenu addItem:quitMenuItem];
[menuBarItem setSubmenu:appMenu];
}

- (void) buildWindow {

windowDelegate = [[WindowDelegate alloc]init];

#define _wndW  700
#define _wndH  550

window = [[NSWindow alloc] initWithContentRect: NSMakeRect( 0, 0, _wndW, _wndH )
styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
backing: NSBackingStoreBuffered defer: NO];

[window center];
[window setTitle: @"Test window"];
[window setDelegate: windowDelegate];
[window makeKeyAndOrderFront: nil];

NSRect frameR = NSMakeRect([window frame].origin.x - 10, [window frame].origin.y - 10, [window frame].size.width + 20, [window frame].size.height + 20);
underlayWnd = [[NSWindow alloc] initWithContentRect:frameR styleMask: NSWindowStyleMaskBorderless backing: NSBackingStoreBuffered defer: NO];
[underlayWnd setIgnoresMouseEvents:true];
[underlayWnd orderWindow:NSWindowBelow relativeTo:0];

// **** Custom View **** //
view = [[CustomView alloc]initWithFrame:NSMakeRect(0,0,frameR.size.width, frameR.size.height)];
[view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
[[underlayWnd contentView] addSubview:view]
[windowDelegate setUnderlayWnd:underlayWnd];

// **** Hide Button **** //
NSButton *hideBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 30, 30, 165, 30 )];
[hideBtn setBezelStyle:NSBezelStyleRounded ];
[hideBtn setTitle: @"Hide underlay window"];
[hideBtn setAction: @selector (myHideAction)];
[[window contentView] addSubview: hideBtn];

// **** Show Button **** //
NSButton *showBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 210, 30, 165, 30 )];
[showBtn setBezelStyle:NSBezelStyleRounded ];
[showBtn setTitle: @"Show underlay window"];
[showBtn setAction: @selector (myShowAction)];
[[window contentView] addSubview: showBtn];

// **** Quit btn **** //
NSButton *quitBtn = [[NSButton alloc]initWithFrame:NSMakeRect( _wndW - 50, 5, 40, 40 )];
[quitBtn setBezelStyle:NSBezelStyleCircular ];
[quitBtn setTitle: @"Q" ];
[quitBtn setAutoresizingMask: NSViewMinXMargin];
[quitBtn setAction:@selector(terminate:)];
[[window contentView] addSubview: quitBtn];
}

- (void) applicationWillFinishLaunching: (NSNotification *)notification
{
[self buildMenu];
[self buildWindow];
}

- (void) applicationDidFinishLaunching: (NSNotification *)notification
{
}
@end

int main (){
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
return 0;
}

【讨论】:

  • 谢谢。我目前正在消化这些信息并尝试将其整合到我的项目中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-05
  • 1970-01-01
  • 2019-11-24
  • 1970-01-01
  • 2012-02-03
  • 2018-02-13
相关资源
最近更新 更多