[NSAlert beginSheetModalForWindow:…];を待ちます
-
03-07-2019 - |
質問
このようなNSAlertを表示すると、すぐに応答が得られます。
int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
response = [alert runModal];
問題は、これがアプリケーションモーダルであり、私のアプリケーションがドキュメントベースであることです。次のようなシートを使用して、現在のドキュメントのウィンドウにアラートを表示します。
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;
}
これに関する唯一の問題は、 beginSheetModalForWindow:
がすぐに戻るため、ユーザーに確実に質問して応答を待つことができないことです。タスクを2つの領域に分割できれば大した問題にはなりませんが、できません。
(ツリー内にある)約40の異なるオブジェクトを処理するループがあります。 1つのオブジェクトが失敗した場合、アラートを表示してユーザーに続行するか中止する(現在のブランチで処理を続行する)かを尋ねますが、アプリケーションはドキュメントベースであるため、Apple Human Interface Guidelinesはアラートが発生したときにシートを使用するよう指示していますドキュメント固有。
アラートシートを表示して応答を待つにはどうすればよいですか
解決
残念ながら、ここでできることはあまりありません。基本的に、決定を下す必要があります。非同期の方法でオブジェクトを処理できるようにアプリケーションを再設計するか、アプリケーションのモーダルアラートを表示する非承認の非推奨アーキテクチャを使用します。
実際の設計に関する情報やこれらのオブジェクトの処理方法を知らなくても、さらに情報を提供することは困難です。しかし、私の頭の上では、いくつかの考えがあります:
- 何らかの種類の実行ループ信号またはキューを介してメインスレッドと通信する別のスレッドでオブジェクトを処理します。ウィンドウのオブジェクトツリーが中断された場合、メインスレッドに中断されたことを通知し、メインスレッドからの処理(このブランチの続行または中止)に関する情報を待機します。メインスレッドはドキュメントモーダルウィンドウを表示し、ユーザーが何をするかを選択した後にプロセススレッドに信号を送ります。
ただし、これは必要なもののために本当に複雑すぎるかもしれません。その場合、非推奨の使用法に従うことをお勧めしますが、それはユーザーの要件によって異なります。
他のヒント
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];
このコードは、 GitHub から入手でき、現在のバージョンは完全を期して以下に掲載されています。
ヘッダーファイル NSAlert + SynchronousSheet.h
:
#import <Cocoa/Cocoa.h>
@interface NSAlert (SynchronousSheet)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow;
-(NSInteger) runModalSheet;
@end
実装ファイル 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
解決策は電話することです
[NSApp runModalForWindow:alert];
beginSheetModalForWindowの後。また、「ダイアログが閉じました」をキャッチするデリゲートを実装する必要があります。アクション、および応答で[NSApp stopModal]を呼び出します。
この問題を解決するNSAlertカテゴリがあります(フレデリックが提案し、Laurent Pが改善したソリューションでPhilippが提案したとおりです。デリゲートの代わりにコードブロックを使用しているため、もう一度簡略化しています) 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
これを探している人がいたら(私はそうしました)、次のようにして解決しました:
@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
NSAlertを同期的に実行するのは次のように簡単です:
AlertSync* sync = [[AlertSync alloc] initWithAlert: alert asSheetForWindow: window];
int returnCode = [sync run];
[sync release];
説明したように、再入可能性の問題が発生する可能性があることに注意してください。これを行う場合は注意してください。
ここに私の答えがあります:
グローバルクラス変数「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;
}
何か助けになるといいのですが、 乾杯 -ハンズ
これは、上記のLaurentなどのバージョンで、Xcode 6.4用のSwift 1.2(現在の最新の作業バージョン)に翻訳され、アプリでテストされています。この作業に貢献してくれたすべての人に感謝します! Appleからの標準的な文書では、少なくとも私が見つけることができる場所ではなく、これについてどうするかについての手がかりは与えられませんでした。
1つの謎が残っています。最終関数で二重感嘆符を使用しなければならなかった理由です。 NSApplication.mainWindowは、オプションのNSWindow(NSWindow?)であるはずですよね?しかし、2番目の '!'を使用するまで、コンパイラはエラーを表示しました。
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!!)
}
}
Windowsとは異なり、モーダルダイアログをブロックする方法はないと思います。入力(ユーザーがボタンをクリックするなど)はメインスレッドで処理されるため、ブロックする方法はありません。
タスクについては、メッセージをスタックに渡してから、中断したところから続行する必要があります。
1つのオブジェクトが失敗した場合、ツリー内のオブジェクトの処理を停止し、失敗したオブジェクトを書き留めて(順序があり、中断したところから再開できると仮定して)、シートをスローします。ユーザーがシートを閉じるときに、 dturnEndSelector:
メソッドに、 returnCode
に応じて、中断したオブジェクトまたは中断していないオブジェクトから処理を再開させます。
- (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;
}
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
役立つこと。