using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using Nuclex.Support;
using Nuclex.Windows.Forms.ViewModels;
namespace Nuclex.Windows.Forms.Views {
/// Special view form that can display different child views
public class MultiPageViewForm : ViewForm {
/// Initializes a new multi page view window
///
/// Window manager that is used to set up the child views
///
/// Whether page views should be kept alive and reused
public MultiPageViewForm(IWindowManager windowManager, bool cachePageViews = false) {
this.windowManager = windowManager;
this.createViewMethod = typeof(IWindowManager).GetMethod("CreateView");
if(cachePageViews) {
this.cachedViews = new Dictionary();
}
}
/// Called when the control is being disposed
///
/// Whether the call was made by user code (vs. the garbage collector)
///
protected override void Dispose(bool calledExplicitly) {
if(calledExplicitly) {
// Disable the active view, if any
if(this.activePageView != null) {
if(this.childViewContainer != null) {
this.childViewContainer.Controls.Remove(this.activePageView);
}
}
// If caching is disabled, dispose of the active child view, if any
if(this.cachedViews == null) {
if(this.activePageView != null) {
disposeIfSupported(this.activePageView);
this.activePageView = null;
}
} else { // Caching is enabled, dispose of any cached child views
foreach(Control childView in this.cachedViews.Values) {
disposeIfSupported(childView);
}
this.cachedViews.Clear();
this.cachedViews = null;
this.activePageView = null;
}
}
base.Dispose(calledExplicitly);
}
/// Discovers the container control used to host the child views
/// The container control is which the child views will be hosted
///
/// This is supposed to be overriden by the user, simply returning the container
/// control that should host the page views. If it isn't, however, we use some
/// heuristics to figure out the most likely candidate: it should be a container,
/// and it should cover most of the window's client area.
///
protected virtual Control IdentifyPageContainer() {
Size halfWindowSize = Size;
halfWindowSize.Width /= 2;
halfWindowSize.Height /= 2;
// First container control we found -- if we find no likely candidate,
// we simply use the first
Control firstContainer = null;
// Check all top-level controls in the window. If there's a container that
// covers most of the window, it's our best bet
int controlCount = Controls.Count;
for(int index = 0; index < controlCount; ++index) {
Control control = Controls[index];
// Only check container controls
if((control is ContainerControl) || (control is Panel)) {
if(firstContainer == null) {
firstContainer = control;
}
// If this control covers most of the view, it's our candidate!
Size controlSize = control.Size;
bool goodCandidate = (
(controlSize.Width > halfWindowSize.Width) &&
(controlSize.Height > halfWindowSize.Height)
);
if(goodCandidate) {
return control;
}
}
}
// If no candidate was found, return the first container control we encountered
// or create a new UserControl as the container if nothing was found at all.
if(firstContainer == null) {
firstContainer = new Panel();
Controls.Add(firstContainer);
firstContainer.Dock = DockStyle.Fill;
}
return firstContainer;
}
/// Called when the window's data context is changed
/// Window whose data context was changed
/// Data context that was previously used
/// Data context that will be used from now on
protected override void OnDataContextChanged(
object sender, object oldDataContext, object newDataContext
) {
// Kill the currently active view if there was an old view model.
if(oldDataContext != null) {
disableActivePageView();
}
base.OnDataContextChanged(sender, oldDataContext, newDataContext);
// If a valid view model was assigned, create a new view its active page view model
if(newDataContext != null) {
var dataContextAsMultiPageViewModel = newDataContext as IMultiPageViewModel;
if(dataContextAsMultiPageViewModel != null) {
activatePageView(dataContextAsMultiPageViewModel.GetActivePageViewModel());
}
}
}
/// Called when a property of the view model is changed
/// View model in which a property was changed
/// Contains the name of the property that has changed
protected override void OnViewModelPropertyChanged(
object sender, PropertyChangedEventArgs arguments
) {
base.OnViewModelPropertyChanged(sender, arguments);
MultiPageViewModel anyMultiPageViewModel;
if(arguments.AreAffecting(nameof(anyMultiPageViewModel.ActivePage))) {
var viewModelAsMultiPageviewModel = DataContext as IMultiPageViewModel;
if(viewModelAsMultiPageviewModel != null) {
activatePageView(viewModelAsMultiPageviewModel.GetActivePageViewModel());
}
}
}
/// Activates the page view for the specified page view model
///
/// Page view model for which the page view will be activated
///
private void activatePageView(object pageViewModel) {
object activePageViewModel = null;
{
var activePageViewAsView = this.activePageView as IView;
if(activePageViewAsView != null) {
activePageViewModel = activePageViewAsView.DataContext;
}
}
// Try from the cheapest to the most expensive way to get to our goal,
// an activated view suiting the specified view model.
// If we already have the target view model selected, do nothing
if(activePageViewModel == pageViewModel) {
return;
}
// If the page view model for the old and the new page are of the same
// type, we can reuse the currently active page view
if((activePageViewModel != null) && (pageViewModel != null)) {
if(pageViewModel.GetType() == this.activePageView.GetType()) {
var activePageViewAsView = this.activePageView as IView;
if(activePageViewAsView != null) {
activePageViewAsView.DataContext = pageViewModel;
}
return;
}
}
// Worst, but usual, case: the new page view model might require
// a different view. Create or look up the new view and put it in the container
{
disableActivePageView();
this.activePageView = getOrCreatePageView(pageViewModel);
Control pageViewContainer = getPageViewContainer();
pageViewContainer.Controls.Add(this.activePageView);
this.activePageView.Dock = DockStyle.Fill;
}
}
/// Gets the cached child view or creates a new one if not cached
/// View model for which a child view will be returned
/// A child view suitable for the specified view model
private Control getOrCreatePageView(object viewModel) {
Type viewModelType = viewModel.GetType();
Control view;
// If caching is enabled, check if we have a cached view
if(this.cachedViews != null) {
if(this.cachedViews.TryGetValue(viewModelType, out view)) {
return view;
}
}
// Otherwise, call the window manager's CreateView() method
MethodInfo specializedCreateViewMethod = (
this.createViewMethod.MakeGenericMethod(viewModelType)
);
view = (Control)specializedCreateViewMethod.Invoke(
this.windowManager, new object[1] { viewModel }
);
// If caching is enabled, register the view in the cache
if(this.cachedViews != null) {
this.cachedViews.Add(viewModelType, view);
}
return view;
}
/// Disables the currently active page view control
private void disableActivePageView() {
if(this.activePageView != null) {
Control container = getPageViewContainer();
container.Controls.Remove(this.activePageView);
// If we don't reuse views, kill it now
if(this.cachedViews == null) {
disposeIfSupported(this.activePageView);
this.activePageView = null;
} else {
var activePageViewAsView = this.activePageView as IView;
if(activePageViewAsView != null) {
activePageViewAsView.DataContext = null;
}
}
}
}
/// Fetches the container that holds the child views
/// The container for the child views
private Control getPageViewContainer() {
if(this.childViewContainer == null) {
this.childViewContainer = IdentifyPageContainer();
}
return this.childViewContainer;
}
/// Disposes the specified object if it is disposable
/// Object that will be disposed if supported
private static void disposeIfSupported(object potentiallyDisposable) {
var disposable = potentiallyDisposable as IDisposable;
if(disposable != null) {
disposable.Dispose();
}
}
/// Window manager through which the child views are created
private IWindowManager windowManager;
/// Reflection info for the createView() method of the window manager
private MethodInfo createViewMethod;
/// Container in which the child views will be hosted
private Control childViewContainer;
/// Cached views that will be reused when the view model activates them
private Dictionary cachedViews;
/// The currently active child view
private Control activePageView;
}
} // namespace Nuclex.Windows.Forms.Views