macos 等待 [NSAlert beginSheetModalForWindow:...];
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/604768/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Wait for [NSAlert beginSheetModalForWindow:...];
提问by dreamlax
When I display an NSAlert like this, I get the response straight away:
当我像这样显示 NSAlert 时,我会立即得到响应:
int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
response = [alert runModal];
The problem is that this is application-modal and my application is document based. I display the alert in the current document's window by using sheets, like this:
问题是这是应用程序模式,而我的应用程序是基于文档的。我使用工作表在当前文档的窗口中显示警报,如下所示:
int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
[alert beginSheetModalForWindow:aWindow
modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:&response];
//elsewhere
- (void) alertDidEnd:(NSAlert *) alert returnCode:(int) returnCode contextInfo:(int *) contextInfo
{
*contextInfo = returnCode;
}
The only issue with this is that beginSheetModalForWindow:
returns straight away so I cannot reliably ask the user a question and wait for a response. This wouldn't be a big deal if I could split the task into two areas but I can't.
唯一的问题是它会立即beginSheetModalForWindow:
返回,因此我无法可靠地向用户提问并等待回复。如果我可以将任务分成两个区域,这不是什么大问题,但我不能。
I have a loop that processes about 40 different objects (that are in a tree). If one object fails, I want the alert to show and ask the user whether to continue or abort (continue processing at the current branch), but since my application is document based, the Apple Human Interface Guidelines dictate to use sheets when the alert is specific to a document.
我有一个循环处理大约 40 个不同的对象(在树中)。如果一个对象失败,我希望警报显示并询问用户是继续还是中止(在当前分支继续处理),但由于我的应用程序是基于文档的,Apple Human Interface Guidelines 规定在警报出现时使用工作表特定于文档。
How can I display the alert sheet and wait for a response?
如何显示警报表并等待响应?
采纳答案by Jason Coco
Unfortunately, there is not much you can do here. You basically have to make a decision: re-architect your application so that it can process the object in an asynchronous manner or use the non-approved, deprecated architecture of presenting application modal alerts.
不幸的是,您在这里无能为力。您基本上必须做出决定:重新架构您的应用程序,以便它可以以异步方式处理对象,或者使用呈现应用程序模式警报的未经批准、已弃用的架构。
Without knowing any information about your actual design and how you processes these objects, it's hard to give any further information. Off the top of my head, though, a couple of thoughts might be:
如果不了解有关您的实际设计以及您如何处理这些对象的任何信息,就很难提供任何进一步的信息。不过,在我的脑海中,有几个想法可能是:
- Process the objects in another thread that communicates with the main thread through some kind of run loop signal or queue. If the window's object tree gets interrupted, it signals the main thread that it was interrupted and waits on a signal from the main thread with information about what to do (continue this branch or abort). The main thread then presents the document-modal window and signals the process thread after the user chooses what to do.
- 在通过某种运行循环信号或队列与主线程通信的另一个线程中处理对象。如果窗口的对象树被中断,它会通知主线程它被中断并等待来自主线程的信号,并提供有关要做什么(继续此分支或中止)的信息。然后,主线程呈现文档模式窗口,并在用户选择要执行的操作后向进程线程发送信号。
This may be really over-complicated for what you need, however. In that case, my recommendation would be to just go with the deprecated usage, but it really depends on your user requirements.
然而,这对于您需要的东西来说可能真的过于复杂了。在这种情况下,我的建议是使用已弃用的用法,但这实际上取决于您的用户需求。
回答by Philipp
We created a category on NSAlert
to run alerts synchronously, just like application-modal dialogs:
我们创建了一个类别NSAlert
来同步运行警报,就像应用程序模式对话框一样:
NSInteger result;
// Run the alert as a sheet on the main window
result = [alert runModalSheet];
// Run the alert as a sheet on some other window
result = [alert runModalSheetForWindow:window];
The code is available via GitHub, and the current version posted below for completeness.
该代码可通过GitHub 获得,为了完整起见,下面发布了当前版本。
Header file NSAlert+SynchronousSheet.h
:
头文件NSAlert+SynchronousSheet.h
:
#import <Cocoa/Cocoa.h>
@interface NSAlert (SynchronousSheet)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow;
-(NSInteger) runModalSheet;
@end
Implementation file NSAlert+SynchronousSheet.m
:
实施文件NSAlert+SynchronousSheet.m
:
#import "NSAlert+SynchronousSheet.h"
// Private methods -- use prefixes to avoid collisions with Apple's methods
@interface NSAlert ()
-(IBAction) BE_stopSynchronousSheet:(id)sender; // hide sheet & stop modal
-(void) BE_beginSheetModalForWindow:(NSWindow *)aWindow;
@end
@implementation NSAlert (SynchronousSheet)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow {
// Set ourselves as the target for button clicks
for (NSButton *button in [self buttons]) {
[button setTarget:self];
[button setAction:@selector(BE_stopSynchronousSheet:)];
}
// Bring up the sheet and wait until stopSynchronousSheet is triggered by a button click
[self performSelectorOnMainThread:@selector(BE_beginSheetModalForWindow:) withObject:aWindow waitUntilDone:YES];
NSInteger modalCode = [NSApp runModalForWindow:[self window]];
// This is called only after stopSynchronousSheet is called (that is,
// one of the buttons is clicked)
[NSApp performSelectorOnMainThread:@selector(endSheet:) withObject:[self window] waitUntilDone:YES];
// Remove the sheet from the screen
[[self window] performSelectorOnMainThread:@selector(orderOut:) withObject:self waitUntilDone:YES];
return modalCode;
}
-(NSInteger) runModalSheet {
return [self runModalSheetForWindow:[NSApp mainWindow]];
}
#pragma mark Private methods
-(IBAction) BE_stopSynchronousSheet:(id)sender {
// See which of the buttons was clicked
NSUInteger clickedButtonIndex = [[self buttons] indexOfObject:sender];
// Be consistent with Apple's documentation (see NSAlert's addButtonWithTitle) so that
// the fourth button is numbered NSAlertThirdButtonReturn + 1, and so on
NSInteger modalCode = 0;
if (clickedButtonIndex == NSAlertFirstButtonReturn)
modalCode = NSAlertFirstButtonReturn;
else if (clickedButtonIndex == NSAlertSecondButtonReturn)
modalCode = NSAlertSecondButtonReturn;
else if (clickedButtonIndex == NSAlertThirdButtonReturn)
modalCode = NSAlertThirdButtonReturn;
else
modalCode = NSAlertThirdButtonReturn + (clickedButtonIndex - 2);
[NSApp stopModalWithCode:modalCode];
}
-(void) BE_beginSheetModalForWindow:(NSWindow *)aWindow {
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
@end
回答by Laurent
Here is a NSAlert category that solves the issue (as suggested by Philipp with the solution proposed by Frederick and improved by Laurent P.: I use a code block instead of a delegate, so it is simplified once again).
这是解决该问题的 NSAlert 类别(如 Philipp 建议的解决方案,由 Frederick 提出并由 Laurent P. 改进:我使用代码块而不是委托,因此再次简化)。
@implementation NSAlert (Cat)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow
{
[self beginSheetModalForWindow:aWindow completionHandler:^(NSModalResponse returnCode)
{ [NSApp stopModalWithCode:returnCode]; } ];
NSInteger modalCode = [NSApp runModalForWindow:[self window]];
return modalCode;
}
-(NSInteger) runModalSheet {
return [self runModalSheetForWindow:[NSApp mainWindow]];
}
@end
回答by Frederik Slijkerman
The solution is to call
解决方法是调用
[NSApp runModalForWindow:alert];
after beginSheetModalForWindow. Also, you need to implement a delegate that catches the "dialog has closed" action, and calls [NSApp stopModal] in response.
在 beginSheetModalForWindow 之后。此外,您需要实现一个捕获“对话框已关闭”操作的委托,并调用 [NSApp stopModal] 作为响应。
回答by mjmt
Just in case anyone comes looking for this (I did), I solved this with the following:
以防万一有人来找这个(我做了),我用以下方法解决了这个问题:
@interface AlertSync: NSObject {
NSInteger returnCode;
}
- (id) initWithAlert: (NSAlert*) alert asSheetForWindow: (NSWindow*) window;
- (NSInteger) run;
@end
@implementation AlertSync
- (id) initWithAlert: (NSAlert*) alert asSheetForWindow: (NSWindow*) window {
self = [super init];
[alert beginSheetModalForWindow: window
modalDelegate: self didEndSelector: @selector(alertDidEnd:returnCode:) contextInfo: NULL];
return self;
}
- (NSInteger) run {
[[NSApplication sharedApplication] run];
return returnCode;
}
- (void) alertDidEnd: (NSAlert*) alert returnCode: (NSInteger) aReturnCode {
returnCode = aReturnCode;
[[NSApplication sharedApplication] stopModal];
}
@end
Then running an NSAlert synchronously is as simple as:
然后同步运行 NSAlert 就像这样简单:
AlertSync* sync = [[AlertSync alloc] initWithAlert: alert asSheetForWindow: window];
int returnCode = [sync run];
[sync release];
Note there is potential for re-entrancy issues as discussed, so be careful if doing this.
请注意,如所讨论的那样,可能会出现重入问题,因此在执行此操作时要小心。
回答by Bibiko
here is my answer:
这是我的回答:
Create a global class variable 'NSInteger alertReturnStatus'
创建一个全局类变量 'NSInteger alertReturnStatus'
- (void)alertDidEndSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
[[sheet window] orderOut:self];
// make the returnCode publicly available after closing the sheet
alertReturnStatus = returnCode;
}
- (BOOL)testSomething
{
if(2 != 3) {
// Init the return value
alertReturnStatus = -1;
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:NSLocalizedString(@"Warning", @"warning")];
[alert setInformativeText:@"Press OK for OK"];
[alert setAlertStyle:NSWarningAlertStyle];
[alert setShowsHelp:NO];
[alert setShowsSuppressionButton:NO];
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(alertDidEndSheet:returnCode:contextInfo:) contextInfo:nil];
// wait for the sheet
NSModalSession session = [NSApp beginModalSessionForWindow:[alert window]];
for (;;) {
// alertReturnStatus will be set in alertDidEndSheet:returnCode:contextInfo:
if(alertReturnStatus != -1)
break;
// Execute code on DefaultRunLoop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
// Break the run loop if sheet was closed
if ([NSApp runModalSession:session] != NSRunContinuesResponse
|| ![[alert window] isVisible])
break;
// Execute code on DefaultRunLoop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
[NSApp endModalSession:session];
[NSApp endSheet:[alert window]];
// Check the returnCode by using the global variable alertReturnStatus
if(alertReturnStatus == NSAlertFirstButtonReturn) {
return YES;
}
return NO;
}
return YES;
}
Hope it'll be of some help, Cheers --Hans
希望它会有所帮助,干杯——汉斯
回答by Michael L. Mehr
This is the version of Laurent, et al., above, translated into Swift 1.2 for Xcode 6.4 (latest working version as of today) and tested in my app. Thanks to all those who contributed to make this work! The standard documentation from Apple gave me no clues as to how go about this, at least not anywhere that I could find.
这是上面的 Laurent 等人的版本,已转换为 Xcode 6.4 的 Swift 1.2(截至今天的最新工作版本)并在我的应用程序中进行了测试。感谢所有为这项工作做出贡献的人!Apple 的标准文档没有给我任何关于如何解决这个问题的线索,至少在我能找到的任何地方都没有。
One mystery remains to me: why I had to use the double exclamation point in the final function. NSApplication.mainWindow is supposed to be just an optional NSWindow (NSWindow?), right? But the compiler gave the error shown until I used the second '!'.
对我来说还有一个谜团:为什么我必须在最后一个函数中使用双感叹号。NSApplication.mainWindow 应该只是一个可选的 NSWindow(NSWindow?),对吧?但是编译器给出了显示的错误,直到我使用第二个“!”。
extension NSAlert {
func runModalSheetForWindow( aWindow: NSWindow ) -> Int {
self.beginSheetModalForWindow(aWindow) { returnCode in
NSApp.stopModalWithCode(returnCode)
}
let modalCode = NSApp.runModalForWindow(self.window as! NSWindow)
return modalCode
}
func runModalSheet() -> Int {
// Swift 1.2 gives the following error if only using one '!' below:
// Value of optional type 'NSWindow?' not unwrapped; did you mean to use '!' or '?'?
return runModalSheetForWindow(NSApp.mainWindow!!)
}
}
回答by marcelosarquis
You can use dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
:
您可以使用dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"alertMessage"];
[alert addButtonWithTitle:@"Cancel"];
[alert addButtonWithTitle:@"Ok"];
dispatch_async(dispatch_get_main_queue(), ^{
[alert beginSheetModalForWindow:progressController.window completionHandler:^(NSModalResponse returnCode) {
if (returnCode == NSAlertSecondButtonReturn) {
// do something when the user clicks Ok
} else {
// do something when the user clicks Cancel
}
dispatch_group_leave(group);
}];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//you can continue your code here
Hope that helps.
希望有帮助。
回答by Vadim
Swift 5:
斯威夫特 5:
extension NSAlert {
/// Runs this alert as a sheet.
/// - Parameter sheetWindow: Parent window for the sheet.
func runSheetModal(for sheetWindow: NSWindow) -> NSApplication.ModalResponse {
beginSheetModal(for: sheetWindow, completionHandler: NSApp.stopModal(withCode:))
return NSApp.runModal(for: sheetWindow)
}
}
回答by Herman Reintjes
- (bool) windowShouldClose: (id) sender
{// printf("windowShouldClose..........\n");
NSAlert *alert=[[NSAlert alloc ]init];
[alert setMessageText:@"save file before closing?"];
[alert setInformativeText:@"voorkom verlies van laatste wijzigingen"];
[alert addButtonWithTitle:@"save"];
[alert addButtonWithTitle:@"Quit"];
[alert addButtonWithTitle:@"cancel"];
[alert beginSheetModalForWindow: _window modalDelegate: self
didEndSelector: @selector(alertDidEnd: returnCode: contextInfo:)
contextInfo: nil];
return false;
}