如何在 Cocoa 中显示和管理简单的应用程序模式对话框

发布于 2025-01-04 07:47:07 字数 5096 浏览 3 评论 0原文

我不确定我是否以正确的方式做事,或者我是否把一切都搞砸了。

我创建了一个非常简单的测试应用程序(不是基于文档的),用于学习如何在 Cocoa 应用程序中使用应用程序模式对话框。

在应用程序项目“TestModalDialog”中,我添加了一个简单的 MainMenu.xib,其中包含默认视图和一个按钮“显示对话框”。我创建了第二个名为 TheDialog.xib 的 XIB,其中包含“取消”和“确定”按钮。该 xib 有一个从 NSWindowController 派生的类作为其所有者,称为“TheDialogController”;窗口插座和代表连接到控制器。

在主视图上选择“显示对话框”将启动该对话框。选择“取消”或“确定”将关闭该对话框。这是非常简单的代码:

//  TestModalDialogAppDelegate.h  
//  TestModalDialog  

#import <Cocoa/Cocoa.h>  

@class TheDialogController;  

@interface TestModalDialogAppDelegate : NSObject <NSApplicationDelegate>  
{  
  NSWindow *window;  
  TheDialogController* theDialogController;  
}  

@property (assign) IBOutlet NSWindow *window;  
- (IBAction)showDialog:(id)sender;  

@end  

//  TestModalDialogAppDelegate.m  
//  TestModalDialog  

#import "TestModalDialogAppDelegate.h"  
#import "TheDialogController.h"  

@implementation TestModalDialogAppDelegate  

@synthesize window;  

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification  
{  
  theDialogController= [[TheDialogController alloc] init];  
}  

- (void)dealloc  
{
  if(nil != theDialogController)   
    [theDialogController release];  

  [super dealloc];  
}  

- (IBAction)showDialog:(id)sender  
{  
  if(nil == theDialogController)  
  {  
    NSAlert* alert= [NSAlert alertWithMessageText:@"Dialog Error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"The dialog controller was not allocated."];  
    [alert runModal];  
    return;  
  }

  NSInteger result= [NSApp runModalForWindow:[theDialogController window]]; 
  // Do something with result.... 
}  
@end  

//  TheDialogController.h  
//  TestModalDialog  

#import <Cocoa/Cocoa.h>  

@interface TheDialogController : NSWindowController  
{  
  // BOOL userClickedCloseOrOk; // Removed based on answer.  
  // Should declare a common define - just being lazy.  
  NSInteger userClickedOk;  // Added based on answer.  
  UInt16 timesShown;   
}  

- (IBAction)showWindow:(id)sender;  
- (IBAction)closeDialog:(id)sender;  
- (IBAction)okDialog:(id)sender;  
//- (BOOL)windowShouldClose:(id)sender;   // Removed based on answer.  
- (void)windowWillClose:(NSNotification*)notification;  // Added based on answer.   
- (void)windowDidBecomeKey:(NSNotification*)notification;  // To set title when modal.  
@end  

//  TheDialogController.m  
//  TestModalDialog  

#import "TheDialogController.h"  

@implementation TheDialogController  

- (id)init  
{  
  self = [super initWithWindowNibName:@"TheDialog"];  

  userClickedOk= 0;  // Added based on answer.  
  // userClickedCloseOrOk= FALSE;  // Removed based on answer.  

  return self;  
}  

-(void)dealloc  
{  
  // Do member cleanup if needed.
  [super dealloc];  
}  

- (void)windowDidLoad  
{  
  [super windowDidLoad];  

  // Initialize as needed....  
  [[self window] center];  // Center the window.  
}  

// Does not show with runModalForWindow.  
- (IBAction)showWindow:(id)sender  
{  
  // Just playing with the window title....  
  ++timesShown;  
  NSString* newTitle= [NSString stringWithFormat:@"Shown %d Times", timesShown];  
  [[self window] setTitle:newTitle];  
  return [super showWindow:sender];  
}  

// This method no longer used for this solution based on the answer.  
//- (BOOL)windowShouldClose:(id)sender  
//{  
//  if(!userClickedCloseOrOk)  // The user did not click one of our buttons.  
//    [NSApp abortModal];  
//  else  
//    userClickedCloseOrOk= FALSE;  // Clear for next time.  
//  
//  return TRUE;  
//}  

// Added based on answer.  
- (void)windowWillClose:(NSNotification*)notification  
{  
  [NSApp stopModalWithCode:userClickedOk];  
  userClickedOk= 0;  // Reset for next time.  
}  

// Note - the title will update every time the window becomes key. To do the  
// update only once per modal session, a flag can be added. There might be a better  
// notification to catch.  
- (void)windowDidBecomeKey:(NSNotification*)notification  
{  
  ++timesShown;  
  NSString* newTitle= [NSString stringWithFormat:@"Shown %d Times", timesShown];  
  [[self window] setTitle:newTitle];  
}  

- (IBAction)closeDialog:(id)sender  
{  
  //userClickedCloseOrOk= TRUE;  // Removed based on answer.  
  //[NSApp abortModal];  // Removed based on answer.  
  //[[self window] performClose:self];  // Removed based on answer.  
  [[self window] close];  // Know we want to close - based on answer.  
}  

- (IBAction)okDialog:(id)sender  
{  
  userClickedOk= 1;  // Added based on answer.  
  //userClickedCloseOrOk= TRUE;  // Removed based on answer.  
  //[NSApp stopModal];  // Removed based on answer.  
  //[[self window] performClose:self];  // Removed based on answer.  
  [[self window] close];  // Know we want to close - based on answer.  
}    

@end  

我在模态方面遇到了麻烦 - 在放入 userClickedCloseOrOk 和测试之前,如果用户点击关闭按钮(左上角红点),对话框将关闭,但模态会话仍在运行。

我意识到我可以先将关闭​​按钮从对话框中删除,但是有了它,我已经展示了捕捉该场景的好方法,还是有更好的方法?或者,我一开始就做错了什么,这给我带来了这个问题?

任何建议将不胜感激。

注意 - 原始示例中的代码被注释掉并替换为基于答案的代码。还添加了一个新的通知处理程序。

I am not sure I am doing things the right way, or if I have it all hacked up.

I have a really simple test application (not document-based) I created for learning how to work with application-modal dialogs in Cocoa applications.

With the application project "TestModalDialog", I have a simple MainMenu.xib with the default view and a button, "Show Dialog", I added. I created a second XIB called TheDialog.xib that has "Cancel" and "OK" buttons. That xib has as its owner a class derived from NSWindowController called "TheDialogController"; the window outlet and delegate are connected to the controller.

Selection of "Show Dialog" on the main view will launch the dialog. Selection of "Cancel" or "OK" will dismiss the dialog. Here is the pretty simple code:

//  TestModalDialogAppDelegate.h  
//  TestModalDialog  

#import <Cocoa/Cocoa.h>  

@class TheDialogController;  

@interface TestModalDialogAppDelegate : NSObject <NSApplicationDelegate>  
{  
  NSWindow *window;  
  TheDialogController* theDialogController;  
}  

@property (assign) IBOutlet NSWindow *window;  
- (IBAction)showDialog:(id)sender;  

@end  

//  TestModalDialogAppDelegate.m  
//  TestModalDialog  

#import "TestModalDialogAppDelegate.h"  
#import "TheDialogController.h"  

@implementation TestModalDialogAppDelegate  

@synthesize window;  

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification  
{  
  theDialogController= [[TheDialogController alloc] init];  
}  

- (void)dealloc  
{
  if(nil != theDialogController)   
    [theDialogController release];  

  [super dealloc];  
}  

- (IBAction)showDialog:(id)sender  
{  
  if(nil == theDialogController)  
  {  
    NSAlert* alert= [NSAlert alertWithMessageText:@"Dialog Error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"The dialog controller was not allocated."];  
    [alert runModal];  
    return;  
  }

  NSInteger result= [NSApp runModalForWindow:[theDialogController window]]; 
  // Do something with result.... 
}  
@end  

//  TheDialogController.h  
//  TestModalDialog  

#import <Cocoa/Cocoa.h>  

@interface TheDialogController : NSWindowController  
{  
  // BOOL userClickedCloseOrOk; // Removed based on answer.  
  // Should declare a common define - just being lazy.  
  NSInteger userClickedOk;  // Added based on answer.  
  UInt16 timesShown;   
}  

- (IBAction)showWindow:(id)sender;  
- (IBAction)closeDialog:(id)sender;  
- (IBAction)okDialog:(id)sender;  
//- (BOOL)windowShouldClose:(id)sender;   // Removed based on answer.  
- (void)windowWillClose:(NSNotification*)notification;  // Added based on answer.   
- (void)windowDidBecomeKey:(NSNotification*)notification;  // To set title when modal.  
@end  

//  TheDialogController.m  
//  TestModalDialog  

#import "TheDialogController.h"  

@implementation TheDialogController  

- (id)init  
{  
  self = [super initWithWindowNibName:@"TheDialog"];  

  userClickedOk= 0;  // Added based on answer.  
  // userClickedCloseOrOk= FALSE;  // Removed based on answer.  

  return self;  
}  

-(void)dealloc  
{  
  // Do member cleanup if needed.
  [super dealloc];  
}  

- (void)windowDidLoad  
{  
  [super windowDidLoad];  

  // Initialize as needed....  
  [[self window] center];  // Center the window.  
}  

// Does not show with runModalForWindow.  
- (IBAction)showWindow:(id)sender  
{  
  // Just playing with the window title....  
  ++timesShown;  
  NSString* newTitle= [NSString stringWithFormat:@"Shown %d Times", timesShown];  
  [[self window] setTitle:newTitle];  
  return [super showWindow:sender];  
}  

// This method no longer used for this solution based on the answer.  
//- (BOOL)windowShouldClose:(id)sender  
//{  
//  if(!userClickedCloseOrOk)  // The user did not click one of our buttons.  
//    [NSApp abortModal];  
//  else  
//    userClickedCloseOrOk= FALSE;  // Clear for next time.  
//  
//  return TRUE;  
//}  

// Added based on answer.  
- (void)windowWillClose:(NSNotification*)notification  
{  
  [NSApp stopModalWithCode:userClickedOk];  
  userClickedOk= 0;  // Reset for next time.  
}  

// Note - the title will update every time the window becomes key. To do the  
// update only once per modal session, a flag can be added. There might be a better  
// notification to catch.  
- (void)windowDidBecomeKey:(NSNotification*)notification  
{  
  ++timesShown;  
  NSString* newTitle= [NSString stringWithFormat:@"Shown %d Times", timesShown];  
  [[self window] setTitle:newTitle];  
}  

- (IBAction)closeDialog:(id)sender  
{  
  //userClickedCloseOrOk= TRUE;  // Removed based on answer.  
  //[NSApp abortModal];  // Removed based on answer.  
  //[[self window] performClose:self];  // Removed based on answer.  
  [[self window] close];  // Know we want to close - based on answer.  
}  

- (IBAction)okDialog:(id)sender  
{  
  userClickedOk= 1;  // Added based on answer.  
  //userClickedCloseOrOk= TRUE;  // Removed based on answer.  
  //[NSApp stopModal];  // Removed based on answer.  
  //[[self window] performClose:self];  // Removed based on answer.  
  [[self window] close];  // Know we want to close - based on answer.  
}    

@end  

I had trouble with the modality - before I put in userClickedCloseOrOk and tests, if the user hit the close button (upper-left red dot), the dialog would close but the modal session was still running.

I realize I could just leave the close-button off the dialog to start with, but with it there, is what I have demonstrated a good way to catch that scenario, or is there a better way? Or, am I doing something wrong to start with, which creates that problem for me?

Any advice would be appreciated.

NOTE - Code from the original example commented out and replaced with code based on the answer. Also added a new notification handler.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

怪我太投入 2025-01-11 07:47:07

okDialog:closeDialog: 方法中调用 close 而不是 performClose: 以避免窗口调用 >windowShouldClose: 委托方法。这样您就不需要userClickedCloseOrOk

另外,我认为您想要调用 stopModalWithCode: 而不是 stopModal 因为在应用程序委托中您似乎对结果感兴趣。你可以调用 stopModalstopModalWithCode: 而不是 abortModal 因为当你调用它时你总是处于运行循环中(abort 是当你在模态运行循环之外,就像在另一个线程或计时器的运行循环中一样)。

windowShouldClose: 中,您正在执行一个操作 (abortModal),而您应该只回答“此窗口是否应该关闭”的问题。如果需要,您应该在 windowWillClose: 委托方法中执行操作。

当只有一个窗口时,工作表非常有用,它告诉用户在完成工作表中的所有内容之前不能使用该窗口执行任何操作。当您有多个窗口时,用户在完成模式窗口中的任何操作之前无法与其交互,或者存在涉及整个应用程序但与一个窗口的内容无关的错误时,应用程序模式窗口非常有用。 Apple 在其 HIG 中建议尽可能避免使用应用程序模式窗口。

In the okDialog: and closeDialog: methods call close instead of performClose: to avoid the window calling the windowShouldClose: delegate method. That way you don't need userClickedCloseOrOk.

Also I think you want to be calling stopModalWithCode: instead of stopModal since in the app delegate you seem to be interested in the result. And you can call stopModal or stopModalWithCode: instead of abortModal because you are always in the runloop when you call it (abort is for when you are outside of the modal runloop, like in another thread or timer's runloop).

In windowShouldClose: you are doing an action (abortModal) when you should only be answering the question "should this window close". The windowWillClose: delegate method is where you should do actions if you need to.

Sheets are useful when there is one window and it tells the user they can't do anything with it until they complete whatever is in the sheet. Application-modal windows are useful when you have multiple windows that the user can't interact with until they complete whatever is in the modal window or where there is an error that involves the whole application but is not tied to the content of one window. In their HIG Apple suggests avoiding the use of Application-modal windows whenever possible.

小帐篷 2025-01-11 07:47:07

我实际上一直在努力解决同样的问题,并找到了这个链接:

Stopping modal when窗口已关闭(可可)

I have actually just been struggling with the same problem and found this link :

Stopping modal when window is closed (Cocoa)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文