0

I have a Floating-window template in which i load a Message-box by initializing the MessageBoxViewModel object to display the message

I want to close this pop up when user clicks on the Close button. How should i do this. I have written the Close button command in the MessageBoxViewModel .

 public class MessageBoxViewModel : ViewModelBase
{

    public MessageBoxViewModel ( string messageText)
    {
        //  load all the fields
    }




    }
    private string message;

    public string Message
    {
        get
        {
            return message;
        }
        set
        {
            if (value == message)
                return;
            message = value;
            base.OnPropertyChanged("Message");
        }
    }

    #region Commands

    RelayCommand okay;
    public ICommand OKAY
    {
        get
        {
            if (okay == null)
            {
                okay = new RelayCommand(
                    param => this.CallOkay()
                    );
            }
            return okay;
        }
    }

    #endregion

    void CallOkay()
    {
      //  should write logic to close this window

    }
harin04
  • 271
  • 5
  • 16
  • 2
    possible duplicate of [How should the ViewModel close the form?](http://stackoverflow.com/questions/501886/how-should-the-viewmodel-close-the-form) – Sriram Sakthivel Apr 28 '14 at 10:44

3 Answers3

0

The very nature of MVVM stipulates that the model knows nothing about the window that's reading it.

On solution is that the view model throws an event for the Window code to handle.

In your view model code:

public event EventHandler CallOkayRequested;

void CallOkay()
{
  var dg = this.CallOkayRequested;
  if(dg != null) 
  {
     dg(this, EventArgs.Empty);
  }

}

And in your window code, handle this event:

MyMessageBox() 
{
   InitializeComponent();
   ((MessageBoxViewModel)this.DataContext).CallOkayRequested += ModelCallOkayRequested;
}

void ModelCallOkayRequested(object sender, EventArgs args)
{
     this.Close();
}

This might be the best way to do it, if, for example, the View Model is performing some other actions before wanting the dialog to close.


If, however, the view model is doing nothing other than relaying the request, it's less code if you bypass the model altogether and use a standard RoutedUICommand.

In your XAML declare a command binding:

<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" />
</Window.CommandBindings>

Attach this command to your button:

    <Button Command="ApplicationCommands.Close">
        Close
    </Button>

And handle the close method in your window code:

private void CloseCommandExecuted(object sender, EventArgs args)
{
     this.Close();
}
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • I have currently implemented it using events , But i am told using events is against the MVVM principle .So is there an better approach – harin04 Apr 28 '14 at 11:06
  • This top solution would work but you still have a message box view knowing what ViewModel it has in its `DataContext` (i.e. the cast). In the second example, you also have code-behind the view (which is avoidable here - not saying you shouldn't put anything in there EVER) It's possible to avoid code like this in the view by creating better 'glue' between the view/viewmodel. Once you've achieved that, you end up with a reusable solution which just 'automatically' works when you implement the right interface – Charleh Apr 28 '14 at 11:42
0

There are many ways as referenced in Sriram Sakthivel's comment. But using view model event is simplest:

public event Action ViewModelClosed;
void CallOkay()
{
    if (ViewModelClosed != null) ViewModelClosed();
}

in MessageBox's code behind:

...
MessageBoxViewModel vm = new MessageBoxViewModel();
vm.ViewModelClosed += () => this.Close();

Another way:

I always use a layer of message box in my view like this:

<UserControl>
    <Grid>
        <Border>
            <!-- contents of my control -->
        </Border>
        <Border Visibility="{Binding IsVisible, 
                Converter={StaticResource BooleanToVisibilityConverter}}" 
                Background="#4000">
            <!-- contents of my message box -->
        </Border>
    </Grid>
</UserControl>

Add a boolean (IsVisible) property to MessageBoxViewModel and bind the Visibility of MessageBox to it. Then simply change its value in CallOkay()

Bizhan
  • 16,157
  • 9
  • 63
  • 101
  • I have currently implemented it using events , But i am told using events is against the MVVM principle .So is there an better approach – harin04 Apr 28 '14 at 11:06
  • using events in view is forbidden because it allows the non-testable view to modify the fully-testable viewModel unexpectedly. But if view attaches to an event from viewModel, then you're certain that view does not touch a thing from viewModel. – Bizhan Apr 28 '14 at 11:21
0

The approach another MVVM framework uses (Caliburn Micro) is essentially just using events from the VM.

However, to extend the idea into a reusable 'module' Caliburn Micro uses a Conductor class which manages the relationship between the lifecycle of the View and the lifecycle of the ViewModel. An interface on the ViewModel which marks it as 'closable' is required, and you do need to write a conductor specific to the window/dialog implementation you are using (assuming it doesn't subclass from standard Window).

Somewhere in your code you have to create a window and bind it to the viewmodel. This is the place where the conductor should be created to manage the relationship (Caliburn has this in its IWindowManager implementation which provides and binds Window instances to a given VM when the ShowPopup/ShowDialog methods are called)

The conductor may look like (a contrived example):

public class WindowConductor
{
    private ISupportClose _closeable;
    private Window _window;
    private bool _closingFromViewModel;
    private bool _closingFromView;

    public WindowConductor(Window view, ISupportClose closeable)
    {
        _closeable = closeable;
        _window = view;

        _window.Closed += WindowClosed;
        _closeable.Closed += ViewModelClosed;
    }

    public void WindowClosed(object sender, EventArgs e)
    {
        if(_closingFromViewModel) return;

        _closingFromView = true;
        closeable.Close();
    }

    public void ViewModelClosed(object sender, EventArgs e)
    {
        if(_closingFromView) return;

        _closingFromViewModel = true;
        window.Close();
    }
}

Your ISupportClose interface can simply be:

public interface ISupportClose
{
    event EventHandler<CloseEventArgs> Closed;

    void Close(); 
}

Then when you create your windows to display a view for a VM:

public void CreateWindow(viewModel) 
{
    Window window = new Window();
    window.DataContext .. // etc etc bind up the view/model

    // Wrap the window/vm with the conductor if the view supports the interface
    var closeable = viewModel as ISupportClose;

    if(closeable != null)
        new WindowConductor(window, closeable);
}

I always find this very useful as it splits the concerns into smaller chunks. You don't often use more than 1 maybe 2 window implementations in an app anyway.

It may be worth noting that there is a bit of plumbing code behind all this (in fact a base class Screen provides a standard implementation of lifecycle management etc)

If you aren't using an MVVM framework, I'd highly recommend you do so - writing boilerplate 'glue' has been done already by multiple frameworks

Charleh
  • 13,749
  • 3
  • 37
  • 57