#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
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 {
#region struct RedrawLockScope
/// Prevents controls from redrawing themselves for a while
private struct RedrawLockScope : IDisposable {
/// Window message that enables or disables control redraw
private const int WM_SETREDRAW = 11;
/// Sends a window message to the specified window
/// Window a message will be sent to
/// ID of the message that will be sent
/// First argument to the window procedure
/// Second argument to the window procedure
/// The return value of the window procedure
[DllImport("user32")]
public static extern int SendMessage(
IntPtr windowHandle, int messageId, bool firstArgument, int secondArgument
);
/// Stops redrawing the specified control
/// Control to stop redrawing
public RedrawLockScope(Control control) {
if(Environment.OSVersion.Platform == PlatformID.Win32NT) {
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
this.control = control;
} else {
this.control = null;
}
}
/// Enables redrawing again when the lock scope is disposed
public void Dispose() {
if(this.control != null) {
SendMessage(this.control.Handle, WM_SETREDRAW, true, 0);
this.control.Invalidate(true);
}
}
/// Control that has been stopped from redrawing itself
private Control control;
}
#endregion // struct RedrawLockScope
/// Initializes a new multi page view window for the windows forms designer
public MultiPageViewForm() {
this.createViewMethod = typeof(IWindowManager).GetMethod(nameof(IWindowManager.CreateView));
}
/// 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(nameof(IWindowManager.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);
if(arguments.AreAffecting(nameof(MultiPageViewModel