2012-02-09 11 views
6

No estoy seguro de estar haciendo las cosas bien o si lo tengo todo pirateado.Cómo mostrar y administrar un cuadro de diálogo simple de aplicación-modal en Cocoa

Tengo una aplicación de prueba realmente simple (no basada en documentos) que he creado para aprender a trabajar con diálogos de aplicación-modal en aplicaciones Cocoa.

Con el proyecto de aplicación "TestModalDialog", tengo un MainMenu.xib simple con la vista por defecto y un botón, "Mostrar cuadro de diálogo", agregué. Creé un segundo XIB llamado TheDialog.xib que tiene los botones "Cancelar" y "Aceptar". Ese xib tiene como propietario una clase derivada de NSWindowController llamada "TheDialogController"; la salida de la ventana y el delegado están conectados al controlador.

La selección de "Mostrar cuadro de diálogo" en la vista principal iniciará el cuadro de diálogo. La selección de "Cancelar" o "Aceptar" cancelará el diálogo. Aquí está el código bastante simple:

// 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 

tuve problemas con la modalidad - antes de poner en userClickedCloseOrOk y las pruebas, si el usuario pulsa el botón de cierre (punto rojo superior izquierda), el cuadro de diálogo se cerraría, pero el la sesión modal aún se estaba ejecutando.

Me di cuenta de que podía dejar el botón de cerrar el cuadro de diálogo para comenzar, pero con eso, ¿qué es lo que he demostrado que es una buena forma de captar ese escenario, o hay una forma mejor? O, ¿estoy haciendo algo mal para empezar, que me crea ese problema?

Cualquier consejo sería apreciado.

NOTA - Código del ejemplo original comentado y reemplazado por código basado en la respuesta. También se agregó un nuevo controlador de notificaciones.

+0

¿Hay una manera mejor que windowDidBecomeKey: o windowDidBecomeMain: saber cuándo se ha mostrado la ventana en una sesión modal? Lo pregunto porque me gustaría saber cuándo comienza realmente la sesión modal. Podría crear/destruir la ventana cada vez, y luego usar windowDidLoad, pero eso no parece muy eficiente. También podría enviar una bandera para indicarle a la ventana que está a punto de mostrar en una sesión modal, y administrarla desde cualquiera de las notificaciones; de nuevo, se siente como un truco. O, ¿debería usar una hoja en su lugar? – GTAE86

Respuesta

2

En los métodos okDialog: y closeDialog: llaman close en lugar de performClose: para evitar la ventana llamando al método windowShouldClose: delegado. De esta manera no necesita userClickedCloseOrOk.

También creo que desea llamar al stopModalWithCode: en lugar de stopModal ya que en la aplicación delegado parece que está interesado en el resultado. Y puede llamar al stopModal o al stopModalWithCode: en lugar de abortModal porque siempre está en el runloop cuando lo llama (se cancela cuando se encuentra fuera del runloop modal, como en otro thread o runloop del temporizador).

En windowShouldClose: está realizando una acción (abortModal) cuando solo debería responder a la pregunta "¿se cerrará esta ventana?". El método delegado windowWillClose: es donde debe realizar acciones si es necesario.

Las hojas son útiles cuando hay una ventana y le dice al usuario que no pueden hacer nada con ella hasta que completen lo que está en la hoja. Las ventanas de aplicación modal son útiles cuando tiene varias ventanas con las que el usuario no puede interactuar hasta que completa lo que está en la ventana modal o donde hay un error que involucra a toda la aplicación pero que no está vinculado al contenido de una ventana. En su HIG Apple sugiere evitar el uso de ventanas de aplicación modal siempre que sea posible.

+0

Cambiar de performClose para cerrar realmente no cambió nada. La razón por la que tengo el cheque para userClickedCloseOrOk en la ventanaShouldClose es capturar cuando el usuario presiona el botón de cerrar en el marco (el botón de cierre estándar). Cuando lo encuentro puedo llamar a abortModal, o incluso a StopModal. De lo contrario, la sesión modal no termina y tengo que forzar la salida de la aplicación. Lo que estoy haciendo parece un truco, y parece que no sería necesario si administro el diálogo modal de la manera "correcta", sea lo que sea. Gracias por la aclaración sobre abortModal vs. stopModal. – GTAE86

+0

Aaahhh Creo que veo más de lo que quieres decir ahora: mueve el elemento modal a windowWillClose, y no lo necesitaré en ningún otro lado. Luego puedo establecer un código según corresponda en okDialog: o closeDialog: in y simplemente llamar a stopModalWithCode :. Bonito. – GTAE86

1

he hecho solo estado luchando con el mismo problema y encontré este enlace:

Stopping modal when window is closed (Cocoa)

+0

Algo así como "Estoy haciendo lo correcto en primer lugar". Al principio me costó trabajo usar una hoja o un cuadro de diálogo modal. No pude encontrar fácilmente una buena respuesta. Luché por un día sobre cómo lograr que la cosa se comporte del todo. Todo se reducía a averiguar cómo conectar la salida de la ventana al controlador ... y averiguar qué pregunta realmente hacer. – GTAE86

+0

También consideré el uso de hojas, pero quería que mi ventana se moviera y no se adjuntara a la ventana principal. Si usa exposición OS X con hojas, no verá la ventana modal como una extra. Llámame nerd, pero no quería eso :) – guitarflow

Cuestiones relacionadas