Dialogs, Coroutines and strange behaviour

Sep 2, 2010 at 2:09 AM

This post may take a little while to get through, so please bear with me.  I have adapted the "Soup to Nuts" article to create a dialog helper framework.  Which works a treat except for one odd little side effect.  After showing the dialog 2-3 times, some artifact is remaining on the screen that does not allow me to interact with the controls anymore.  The artifact must be the background rectangle that is shown when the dialog box is shown.

Firstly I have an IDialog interface that is used as a method to know when the user has pressed the OK or Cancel buttons (or however the developer would like to treat it)

    public class DialogResultCompletionEventArgs : EventArgs
    {
        public Exception Error;
        public bool? DialogResult;
    }

    public interface IDialog
    {
        event EventHandler<DialogResultCompletionEventArgs> Completed;
    }
Next I have the ShowDialog helper class which implements IResult interface.  It also has a requirement that the ViewModel passed in is a Screen that implements IDialog.
    public class ShowDialog : IResult
    {
        private static readonly ILog Log = LogManager.GetLog( typeofShowDialog ) );

        readonly Type dialogType;
        readonly string name;

        [Import]
        public IWindowManager WindowManager { getset; }

        public ShowDialog( string name )
        {
            this.name = name;
        }

        public ShowDialog( Type dialogType )
        {
            this.dialogType = dialogType;
        }

        public void Execute( ActionExecutionContext context )
        {
            var theObject = !string.IsNullOrEmpty( name )
                ? IoC.Get<object>( name )
                : IoC.GetInstance( dialogType, null );
            // Needs to be a screen so we can close it
            Screen theScreen = theObject as Screen;
            ifnull == theScreen ) 
            {
                throw new InvalidOperationException();
            }
            // Needs to be an IDialog so we can hook on the Completed event
            IDialog theDialog = theObject as IDialog;
            ifnull == theDialog )
            {
                throw new InvalidOperationException();
            }

            theDialog.Completed += new EventHandler<DialogResultCompletionEventArgs>( OnDialogCompleted );
            WindowManager.ShowDialog( theDialog, null );
        }

        void OnDialogCompleted( object sender, DialogResultCompletionEventArgs e )
        {
            Screen theScreen = sender as Screen;
            ifnull == theScreen ) throw new ArgumentException"sender" );
            theScreen.CanClose( a => 
            {
                    if( !a ) return;
            });
            theScreen.TryClose();

            ResultCompletionEventArgs args = new ResultCompletionEventArgs();
            args.WasCancelled = !(e.DialogResult.HasValue && e.DialogResult.Value );
            args.Error = e.Error;
            Completed( this, args );
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

        public static ShowDialog Of<T>()
        {
            if( !typeofScreen ).IsAssignableFrom( typeof( T ) ) ) throw new ArgumentExceptionstring.Format( "{0} needs to inherit from {1}"typeof( T ).Name, typeofScreen ).Name ) );
            iftypeof( T ).GetInterface( typeofIDialog ).Name, false ) == null ) throw new ArgumentExceptionstring.Format( "{0} needs to implment {1}"typeof( T ).Name, typeofIDialog ).Name ) );
            return new ShowDialogtypeof( T ) );
        }

        public static ShowDialog Named( string name )
        {
            return new ShowDialog( name );
        }
    }
So assuming that I have a LostPasswordView & LostPasswordViewModel, I can simply call:
    
    yield return ShowDialog.Of<LostPasswordViewModel>();
Now can somebody explain what is going wrong?  My LostPasswordView is a ChildWindow, and my LostPasswordViewModel is a Screen that implements IDialog.  
The problem is that after 2-3 calls to lost password, the ChildWindow seems to leave artifacts.  Is this my implementation issue or the Silverlight Control Toolkit.
If it is the later, then you are free to use the above code however you like.
Cheers,
Scott
Coordinator
Sep 2, 2010 at 2:43 AM

I *think* this is an issue with the ChildWindow. I've seen some similar strange behavior happen with the BusyIndicator as well...I'm not 100% sure though. Can you send a really simple solution that demonstrates the issue?

Sep 2, 2010 at 2:56 AM

I will do one up and sent it through

Sep 2, 2010 at 3:43 AM

A quick sample can be seen here.  It only takes 2 window shows for it to show the effect.

http://cid-d2a2864a727653a2.office.live.com/self.aspx/.Public/TestApplications/ChildWindowDialogTest.zip

Sep 2, 2010 at 4:32 AM

Okay, I think that I have figured it out.  I was not detaching my IDialog.Completed event handler when I was done (so I just keep attaching it every time I show a dialog). 

looks like this now:

        void OnDialogCompleted( object sender, DialogResultEventArgs e )
        {
            callback( sender, e );

            Screen theScreen = sender as Screen;
            ifnull == theScreen ) throw new ArgumentException"sender" );
            theScreen.CanClose( a =>
            {
                if( !a ) return;
            } );
            theScreen.TryClose();

            ( (IDialog)sender ).Completed -= new EventHandler<DialogResultEventArgs>( OnDialogCompleted );

            ResultCompletionEventArgs args = new ResultCompletionEventArgs();
            args.WasCancelled = !( e.DialogResult.HasValue && e.DialogResult.Value );
            args.Error = e.Error;
            Completed( this, args );
        }
I have also updated the methods to pass across a call back so that you can get to the results.  Here is the updated ShowDialog class:
    public class ShowDialog : IResult
    {
        private static readonly ILog Log = LogManager.GetLog( typeofShowDialog ) );

        readonly Type dialogType;
        readonly string name;
        readonly Action<objectDialogResultEventArgs> callback;

        [Import]
        public IWindowManager WindowManager { getset; }

        public ShowDialog( string name, Action<objectDialogResultEventArgs> callback )
        {
            this.name = name;
            this.callback = callback;
        }

        public ShowDialog( Type dialogType, Action<objectDialogResultEventArgs> callback )
        {
            this.dialogType = dialogType;
            this.callback = callback;
        }

        public void Execute( ActionExecutionContext context )
        {
            var theObject = !string.IsNullOrEmpty( name )
                ? IoC.Get<object>( name )
                : IoC.GetInstance( dialogType, null );
            // Needs to be a screen so we can close it
            Screen theScreen = theObject as Screen;
            ifnull == theScreen )
            {
                throw new InvalidOperationException();
            }
            // Needs to be an IDialog so we can hook on the Completed event
            IDialog theDialog = theObject as IDialog;
            ifnull == theDialog )
            {
                throw new InvalidOperationException();
            }

            theDialog.Completed += new EventHandler<DialogResultEventArgs>( OnDialogCompleted );
            WindowManager.ShowDialog( theDialog, null );
        }

        void OnDialogCompleted( object sender, DialogResultEventArgs e )
        {
            callback( sender, e );

            Screen theScreen = sender as Screen;
            ifnull == theScreen ) throw new ArgumentException"sender" );
            theScreen.CanClose( a =>
            {
                if( !a ) return;
            } );
            theScreen.TryClose();

            ( (IDialog)sender ).Completed -= new EventHandler<DialogResultEventArgs>( OnDialogCompleted );

            ResultCompletionEventArgs args = new ResultCompletionEventArgs();
            args.WasCancelled = !( e.DialogResult.HasValue && e.DialogResult.Value );
            args.Error = e.Error;
            Completed( this, args );
        }

        public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

        public static ShowDialog Of<T>( Action<objectDialogResultEventArgs> callback )
        {
            if( !typeofScreen ).IsAssignableFrom( typeof( T ) ) ) throw new ArgumentExceptionstring.Format( "{0} needs to inherit from {1}"typeof( T ).Name, typeofScreen ).Name ) );
            iftypeof( T ).GetInterface( typeofIDialog ).Name, false ) == null ) throw new ArgumentExceptionstring.Format( "{0} needs to implment {1}"typeof( T ).Name, typeofIDialog ).Name ) );
            return new ShowDialogtypeof( T ), callback );
        }

        public static ShowDialog Named( string name, Action<objectDialogResultEventArgs> callback )
        {
            return new ShowDialog( name, callback );
        }
    }
Sep 2, 2010 at 5:55 AM

Note: make sure you remove the code behind OKButton_Click and CancelButton_Click handlers.  They really get in the way.

Coordinator
Sep 2, 2010 at 12:31 PM

Glad you figured it out :)

Feb 9, 2011 at 2:36 PM

Hi, Thread got me interested, and the effect is rather odd.

But I am very sure now that Schappel is right, and the button click events must be taken off the buttons in ChildWindowView.xaml, AND additionally the CreationPolicy for ChildWindowViewModel must be NonShared.
If Shared, the ShellView button and TextBlock become disabled after two presses. I assume this is because MEF keeps the instance of ChildWindow even though CM has closed it (Constructor only runs once) and somewhere the instance is not correctly returned to a default state before re-activating - but I can't find it!

If anyone wants a working copy of it let me know - it's a very interesting example of Coroutines and IResult. Thanks Schappel

John

 

Feb 17, 2011 at 11:23 PM

Hi jradxl,

I'd like to have a working copy as it would avoid re-inventing the wheel.

Thanks

Tiago Freitas Leal

Jul 20, 2011 at 11:35 PM
Edited Jul 20, 2011 at 11:45 PM

Hello,

As I understand it, in case I wish to get different dialogs with different results, I have to create new classes for each Dialog with theire different Results (ShowDialog, DialogResultEventArgs, IDialog) .
Is there a way to make the DialogResultEventArgs in the above mentioned approach more generic.

I want do define the DialogResult like: 

ShowDialog Of<LoginViewModel>(Action<object, DialogResultLoginInfo> or
ShowDialog Of<TransferListViewModel>(Action<object, DialogResultTransferList> 

Thanx in advance

Hans

Jul 21, 2011 at 10:16 AM

It has been a while since I have actually had to touch this (as in I use the dialoging all the time, but have not had to alter the following code), but here is what I have ended up using:

public interface IDialogManager : IResult
{
IDialog Instance { get; set; }
void Show( IDialog dialog, Action<object, DialogResultEventArgs> callback );
void Show( Action<object, DialogResultEventArgs> callback );
}

public class DialogManager : IDialogManager
{
private static readonly ILog Log = LogManager.GetLog( typeof( DialogManager ) );

public Action<object, DialogResultEventArgs> callback;
public IDialog Instance{ get; set; }

internal object Window;

public void Execute( ActionExecutionContext context )
{
var window = EnsureWindow( Instance );
Instance.Completed += OnDialogCompleted;

var windowManager = (IWindowManager)IoC.GetInstance( typeof( IWindowManager ), null );
windowManager.ShowDialog( window, null );
}

private object EnsureWindow( IDialog instance )
{
Window = ViewLocator.LocateForModel( instance, null, null ) as Window ?? (object)new DialogViewModel { Child = instance };
return Window;
}

private void OnDialogCompleted( object sender, DialogResultEventArgs eventArgs )
{
if( null != callback )
{
callback( sender, eventArgs );
}

var theScreen = sender as Screen;
if( null == theScreen ) throw new ArgumentException( "sender" );
theScreen.CanClose( a => { if( !a ) return; } );

( (IDialog)sender ).Completed -= OnDialogCompleted;

theScreen.TryClose();
if( Window is Conductor<IDialog> )
{
( (Conductor<IDialog>)Window ).TryClose();
}
if( Window is Window )
{
( (Window)Window ).Close();
}

var args = new ResultCompletionEventArgs
{
WasCancelled = !( eventArgs.DialogResult.HasValue && eventArgs.DialogResult.Value ),
Error = eventArgs.Error
};
Completed( this, args );
}

private static void ValidateParameters( Type type )
{
if( type.GetInterface( typeof( IScreen ).Name, false ) == null )
{
throw new ArgumentException( string.Format( "{0} needs to implement {1}", type.Name, typeof( IScreen ).Name ) );
}
if( type.GetInterface( typeof( IDialog ).Name, false ) == null )
{
throw new ArgumentException( string.Format( "{0} needs to implement {1}", type.Name, typeof( IDialog ).Name ) );
}
}

public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

public void Show( Action<object, DialogResultEventArgs> callback )
{
if( null == this.Instance )
{
throw new InvalidOperationException( "Instance has not been specified" );
}
this.callback = callback;
this.Execute( null );
}

public void Show( IDialog dialog, Action<object, DialogResultEventArgs> callback )
{
this.Instance = dialog;
this.callback = callback;
this.Execute( null );
}

public static IDialogManager Of<T>( Action<object, DialogResultEventArgs> callback )
{
ValidateParameters( typeof( T ) );
var dialogManager = IoC.Get<IDialogManager>();
dialogManager.Instance = (IDialog)IoC.GetInstance( typeof( T ), null );
return dialogManager;
}

public static void From( IDialog dialog, Action<object, DialogResultEventArgs> callback )
{
ValidateParameters( dialog.GetType() );

var dialogManager = IoC.Get<IDialogManager>();
dialogManager.Show( dialog, callback );
}

public static IDialogManager Named( string name, Action<object , DialogResultEventArgs> callback )
{
var dialogManager = IoC.Get<IDialogManager>();
var instance = IoC.Get<object>( name );
ValidateParameters( instance.GetType() );
dialogManager.Instance = (IDialog)instance;
return dialogManager;
}
}