Home > .NET 4.0, C#, Idea, Tips > [EN] Asynchronous closing in #MVVM using #DevExpress #WPF controls and #RxFramework

[EN] Asynchronous closing in #MVVM using #DevExpress #WPF controls and #RxFramework

Probably you know the idea of asynchronously closing tabs. For example you can find this feature in IE, Firefox Chrome or in other modern prowsers. When you close a tab, and then switch to another tab, then the previous tab can close in some seconds and in this time you are making some work on next tab. You can use asynchronous close to force some operations in background like saving the content (which can take time) and what is more You want to have responsive UI.

I had a need to use this process in one project. While closing a tab I want to save some settings to a data store (this process can take some seconds) and after pressing the close button I want to switch to other tabs and do some other stuff. What is more, I want to have a responsive UI. The project that I’m working on uses WPF DevExpress controls and DockLayoutManager with DocumentPanels inside it. Everything looks nice unless there are some limitations or porblems.

1. First problem

The DocumentPanel in WPF DevExpress does not have Close Command or even Close Event. The Close event is handled in DockLayoutManagers by DockItemClosing Event. (See this).

2. Second problem

The event handler does only know, that he fires the close event. You can close a specified panel programatically by using LayoutManager’s DockController Close method which takes a BaseLayoutItem that should be closed (in our case DocumentPanel). You cannot set in the event an information that the close event was fired by some other control.

3. Third problem

There should be a communication between the DocumentPanel (well by ViewModel of the View inside DocumentPanel) and the DockLayoutManager.

To overcome the problems first I created an interface that will be implemented by some ViewModels used in DocumentPanel (some not all, because only in some ViewModels the Asynchronous operation is necessary). The interface looks like this:

public interface IAsyncClose
{
 void RequestClose();
 CloseStatusEnum CloseStatus { get; }
 IObservable<IAsyncClose> Closeable { get; }
}

The method RequestClose() will be responsible for informing the ViewModel that he should be closed, this method will create some background operations like saving etc. Later the method will contain a RequestStatus parameter that will inform what kind of close request it is (like REQUEST – informing that the user clicked the X button on the tab, SAVE – informaing that all changes should be saved automatically etc.).

Next I created this code, behind the View which contains the DockLayoutManager control (no MVVM at this time).

private CloseManager _closeManager;
public MainWindow()
{
 InitializeComponent();
 _closeManager = new CloseManager(layoutManager);
}
// ... SOME CODE
private void LayoutManager_OnDockItemClosing(object sender, ItemCancelEventArgs e)
{
 var documentPanel = e.Item as DocumentPanel;
  if (documentPanel != null)
  {
   var panelView = documentPanel.Content as FrameworkElement;
    if (panelView != null)
    {
     if (!_closeManager.CanClose(documentPanel))
      {
        e.Cancel = true;
        e.Handled = true;
      }
    }
  }
}
// ... SOME CODE

This class contains a CloseManager instance which is responsible for allowing DocumentPanel closing and observing the asynchronous close response from the ViewModel. The method LayoutManager_OnDockItemClosing is executed when the X button was clicked on DocumentPanel and here we are asking the manager if the panel can be closed, when the manager refuses then we are cancelling the close event.

Now let’s look inside the CloseManager class. First let’s start at CanClose method.

private Dictionary<DocumentPanel, CloseHandler>  _closeHandlers = new Dictionary<DocumentPanel, CloseHandler>();
public bool CanClose(DocumentPanel documentPanel)
{
  var closeable = (documentPanel.Content as FrameworkElement).DataContext as IAsyncPanelClose;
  if (closeable != null)
  {
    CloseHandler closeHandler;
    if (_closeHandlers.TryGetValue(documentPanel, out closeHandler))
    {
      if (closeHandler.ShouldBeClosed())
      {
        _closeHandlers.Remove(documentPanel);
        return true;
      }
    }
    return false;
  }
  return true;
}

This method will allow to close the panel immediatly – only when the ViewModel (the DataContext inside the DocumentPanel) does not implement our IAsyncClose interface. When the ViewModel implements the interface then we can analyze the close a bit more. We allow to close the panel only if the CloseHandler allow us to do this by returning true from ShouldBeClosed() method.

What is a CloseHandler ?

This is a helper class inside the CloseManager that is created for each panel in the tab. This class is created by Observe method in CloseManager. This method should be executed when a DocumentPanel is added to DockLayoutManager.

public void Observe(DocumentPanel documentPanel)
{
  var frameworkElement = documentPanel.Content as FrameworkElement;
  if (frameworkElement != null)
  {
    var closeable = frameworkElement.DataContext as IAsyncPanelClose;
    if (closeable != null)
    {
      if (!_closeHandlers.ContainsKey(documentPanel))
      {
        var closeHandler = new CloseHandler(documentPanel, RemovePanelFromMamager);
        _closeHandlers.Add(documentPanel, closeHandler);
        closeHandler.Subscribe();
      }
    }
  }
}

As you can see, this method creates an instance and executes CloseHandler’s  Subscribe() method. This method creates a subscribtion for our observer ViewModel (_asyncPanelClose). We observe only two statuses.

public void Subscribe()
{
  _subscription = _asyncPanelClose.Closeable.Subscribe(x =>
  {
    if (x.CloseStatus == CloseStatusEnum.Close)
    {
      Unsubscribe();
      _shouldBeClosed = true;
      _sendClose(_documentPanel);
    }
    else if (x.CloseStatus == CloseStatusEnum.Canceled)
    {
      _shouldBeClosed = false;
      RequestSent = false;
    }
  });
}

The most important is Close status. When we catch this status then we Unsubscribe from ViewModel observations, remember that this panel shouldBeClosed and after that we send to our CloseManager a request to close our documentPanel. Now let’s look at our ShouldBeClosed method that is executed in our CloseManager.

public bool ShouldBeClosed()
{
  if (!_shouldBeClosed && !RequestSent)
  {
    RequestSent = true;
    _asyncPanelClose.RequestClose();
  }
  return _shouldBeClosed;
}

When we are executing this method at first time, then we send request to our ViewModel, any next time (unless the request was cancelled) we return the information if our panel should be closed.

And how looks our ViewModel ?

As I mentioned at beggining. We have to implement IAsyncClose interface in the ViewModel.

public void RequestClose()
{
  CloseStatus = CloseStatusEnum.Requested;
  // OUR ASYNC OPERATION
}
private readonly Subject<IAsyncPanelClose> _subject = new Subject<IAsyncPanelClose>();
public IObservable<IAsyncPanelClose> Closeable { get { return _subject.AsObservable(); } }
private CloseStatusEnum _closeStatusEnum;
public CloseStatusEnum CloseStatus
{
  get { return _closeStatusEnum; }
  set
  {
    _closeStatusEnum = value;
    // ...
    _subject.OnNext(this);
  }
}

The RequestClose method should at beginning set a Requested close status to inform that we are closing the ViewModel. Later we can set some other status in background worker or we can immediately set our desired status and close it synchronously.

You can see the result in sample project. When you check the allow close then the viewmodel can be closed from outside (by clicking the X button) – it will close in some seconds. You can check No asynchronous to close the tab synchronously. Also you can close the tab by clicking the Close View Model button in the tab.

Source code: http://cid-e2eed297c7a63060.office.live.com/self.aspx/Publiczny/Samples/DXPanelGroupExample.zip

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: