Changed license to Apache License 2.0
This commit is contained in:
parent
857917aad5
commit
0037b7de46
47 changed files with 5942 additions and 5849 deletions
|
@ -1,38 +1,37 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2019 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Nuclex.Windows.Forms {
|
||||
|
||||
/// <summary>Enables consumer to look up the currently active window</summary>
|
||||
public interface IActiveWindowTracker {
|
||||
|
||||
/// <summary>The currently active top-level or modal window</summary>
|
||||
/// <remarks>
|
||||
/// If windows live in multiple threads, the property change notification for
|
||||
/// this property, if supported, might be fired from a different thread.
|
||||
/// </remarks>
|
||||
Form ActiveWindow { get; }
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms
|
||||
#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.Windows.Forms;
|
||||
|
||||
namespace Nuclex.Windows.Forms {
|
||||
|
||||
/// <summary>Enables consumer to look up the currently active window</summary>
|
||||
public interface IActiveWindowTracker {
|
||||
|
||||
/// <summary>The currently active top-level or modal window</summary>
|
||||
/// <remarks>
|
||||
/// If windows live in multiple threads, the property change notification for
|
||||
/// this property, if supported, might be fired from a different thread.
|
||||
/// </remarks>
|
||||
Form ActiveWindow { get; }
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms
|
||||
|
|
|
@ -1,41 +1,40 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2019 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Windows.Forms.Views {
|
||||
|
||||
/// <summary>View with support for data binding</summary>
|
||||
public interface IView {
|
||||
|
||||
/// <summary>Provides the data binding target for the view</summary>
|
||||
/// <remarks>
|
||||
/// This property is identical to the same-named one in WPF, it provides
|
||||
/// the view model to which the view should bind its controls.
|
||||
/// </remarks>
|
||||
object DataContext { get; set; }
|
||||
|
||||
// Whether the view owns its view model and it needs to be disposed after
|
||||
// the view ceases to exist
|
||||
//bool IsOwnedByView { get; set; }
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
#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;
|
||||
|
||||
namespace Nuclex.Windows.Forms.Views {
|
||||
|
||||
/// <summary>View with support for data binding</summary>
|
||||
public interface IView {
|
||||
|
||||
/// <summary>Provides the data binding target for the view</summary>
|
||||
/// <remarks>
|
||||
/// This property is identical to the same-named one in WPF, it provides
|
||||
/// the view model to which the view should bind its controls.
|
||||
/// </remarks>
|
||||
object DataContext { get; set; }
|
||||
|
||||
// Whether the view owns its view model and it needs to be disposed after
|
||||
// the view ceases to exist
|
||||
//bool IsOwnedByView { get; set; }
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
|
|
|
@ -1,366 +1,385 @@
|
|||
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 {
|
||||
|
||||
/// <summary>Special view form that can display different child views</summary>
|
||||
public class MultiPageViewForm : ViewForm {
|
||||
|
||||
#region struct RedrawLockScope
|
||||
|
||||
/// <summary>Prevents controls from redrawing themselves for a while</summary>
|
||||
private struct RedrawLockScope : IDisposable {
|
||||
|
||||
/// <summary>Window message that enables or disables control redraw</summary>
|
||||
private const int WM_SETREDRAW = 11;
|
||||
|
||||
/// <summary>Sends a window message to the specified window</summary>
|
||||
/// <param name="windowHandle">Window a message will be sent to</param>
|
||||
/// <param name="messageId">ID of the message that will be sent</param>
|
||||
/// <param name="firstArgument">First argument to the window procedure</param>
|
||||
/// <param name="secondArgument">Second argument to the window procedure</param>
|
||||
/// <returns>The return value of the window procedure</returns>
|
||||
[DllImport("user32")]
|
||||
public static extern int SendMessage(
|
||||
IntPtr windowHandle, int messageId, bool firstArgument, int secondArgument
|
||||
);
|
||||
|
||||
/// <summary>Stops redrawing the specified control</summary>
|
||||
/// <param name="control">Control to stop redrawing</param>
|
||||
public RedrawLockScope(Control control) {
|
||||
if(Environment.OSVersion.Platform == PlatformID.Win32NT) {
|
||||
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
|
||||
this.control = control;
|
||||
} else {
|
||||
this.control = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Enables redrawing again when the lock scope is disposed</summary>
|
||||
public void Dispose() {
|
||||
if(this.control != null) {
|
||||
SendMessage(this.control.Handle, WM_SETREDRAW, true, 0);
|
||||
this.control.Invalidate(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Control that has been stopped from redrawing itself</summary>
|
||||
private Control control;
|
||||
|
||||
}
|
||||
|
||||
#endregion // struct RedrawLockScope
|
||||
|
||||
/// <summary>Initializes a new multi page view window for the windows forms designer</summary>
|
||||
public MultiPageViewForm() {
|
||||
this.createViewMethod = typeof(IWindowManager).GetMethod(nameof(IWindowManager.CreateView));
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new multi page view window</summary>
|
||||
/// <param name="windowManager">
|
||||
/// Window manager that is used to set up the child views
|
||||
/// </param>
|
||||
/// <param name="cachePageViews">Whether page views should be kept alive and reused</param>
|
||||
public MultiPageViewForm(IWindowManager windowManager, bool cachePageViews = false) {
|
||||
this.windowManager = windowManager;
|
||||
this.createViewMethod = typeof(IWindowManager).GetMethod(nameof(IWindowManager.CreateView));
|
||||
|
||||
if(cachePageViews) {
|
||||
this.cachedViews = new Dictionary<Type, Control>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called when the control is being disposed</summary>
|
||||
/// <param name="calledExplicitly">
|
||||
/// Whether the call was made by user code (vs. the garbage collector)
|
||||
/// </param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Discovers the container control used to host the child views</summary>
|
||||
/// <returns>The container control is which the child views will be hosted</returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Called when the window's data context is changed</summary>
|
||||
/// <param name="sender">Window whose data context was changed</param>
|
||||
/// <param name="oldDataContext">Data context that was previously used</param>
|
||||
/// <param name="newDataContext">Data context that will be used from now on</param>
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Called when a property of the view model is changed</summary>
|
||||
/// <param name="sender">View model in which a property was changed</param>
|
||||
/// <param name="arguments">Contains the name of the property that has changed</param>
|
||||
protected override void OnViewModelPropertyChanged(
|
||||
object sender, PropertyChangedEventArgs arguments
|
||||
) {
|
||||
base.OnViewModelPropertyChanged(sender, arguments);
|
||||
|
||||
if(arguments.AreAffecting(nameof(MultiPageViewModel<object>.ActivePage))) {
|
||||
var viewModelAsMultiPageviewModel = DataContext as IMultiPageViewModel;
|
||||
if(viewModelAsMultiPageviewModel != null) {
|
||||
if(InvokeRequired) {
|
||||
Invoke(
|
||||
new Action<object>(activatePageView),
|
||||
viewModelAsMultiPageviewModel.GetActivePageViewModel()
|
||||
);
|
||||
} else {
|
||||
activatePageView(viewModelAsMultiPageviewModel.GetActivePageViewModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Currently active page view control</summary>
|
||||
protected Control ActivePageView {
|
||||
get { return this.activePageView; }
|
||||
}
|
||||
|
||||
/// <summary>The view model running the currently active page</summary>
|
||||
protected object ActivePageViewModel {
|
||||
get {
|
||||
var activePageViewAsView = this.activePageView as IView;
|
||||
if(activePageViewAsView == null) {
|
||||
return null;
|
||||
} else {
|
||||
return activePageViewAsView.DataContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Activates the page view for the specified page view model</summary>
|
||||
/// <param name="pageViewModel">
|
||||
/// Page view model for which the page view will be activated
|
||||
/// </param>
|
||||
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
|
||||
{
|
||||
if(pageViewModel == null) {
|
||||
disableActivePageView();
|
||||
} else {
|
||||
Control pageViewContainer = getPageViewContainer();
|
||||
using(new RedrawLockScope(pageViewContainer)) {
|
||||
disableActivePageView();
|
||||
|
||||
this.activePageView = getOrCreatePageView(pageViewModel);
|
||||
pageViewContainer.Controls.Add(this.activePageView);
|
||||
this.activePageView.Dock = DockStyle.Fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the cached child view or creates a new one if not cached</summary>
|
||||
/// <param name="viewModel">View model for which a child view will be returned</param>
|
||||
/// <returns>A child view suitable for the specified view model</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Disables the currently active page view control</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fetches the container that holds the child views</summary>
|
||||
/// <returns>The container for the child views</returns>
|
||||
private Control getPageViewContainer() {
|
||||
if(this.childViewContainer == null) {
|
||||
this.childViewContainer = IdentifyPageContainer();
|
||||
}
|
||||
|
||||
return this.childViewContainer;
|
||||
}
|
||||
|
||||
/// <summary>Disposes the specified object if it is disposable</summary>
|
||||
/// <param name="potentiallyDisposable">Object that will be disposed if supported</param>
|
||||
private static void disposeIfSupported(object potentiallyDisposable) {
|
||||
var disposable = potentiallyDisposable as IDisposable;
|
||||
if(disposable != null) {
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Window manager through which the child views are created</summary>
|
||||
private IWindowManager windowManager;
|
||||
/// <summary>Reflection info for the createView() method of the window manager</summary>
|
||||
private MethodInfo createViewMethod;
|
||||
|
||||
/// <summary>Container in which the child views will be hosted</summary>
|
||||
private Control childViewContainer;
|
||||
/// <summary>Cached views that will be reused when the view model activates them</summary>
|
||||
private Dictionary<Type, Control> cachedViews;
|
||||
/// <summary>The currently active child view</summary>
|
||||
private Control activePageView;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
#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 {
|
||||
|
||||
/// <summary>Special view form that can display different child views</summary>
|
||||
public class MultiPageViewForm : ViewForm {
|
||||
|
||||
#region struct RedrawLockScope
|
||||
|
||||
/// <summary>Prevents controls from redrawing themselves for a while</summary>
|
||||
private struct RedrawLockScope : IDisposable {
|
||||
|
||||
/// <summary>Window message that enables or disables control redraw</summary>
|
||||
private const int WM_SETREDRAW = 11;
|
||||
|
||||
/// <summary>Sends a window message to the specified window</summary>
|
||||
/// <param name="windowHandle">Window a message will be sent to</param>
|
||||
/// <param name="messageId">ID of the message that will be sent</param>
|
||||
/// <param name="firstArgument">First argument to the window procedure</param>
|
||||
/// <param name="secondArgument">Second argument to the window procedure</param>
|
||||
/// <returns>The return value of the window procedure</returns>
|
||||
[DllImport("user32")]
|
||||
public static extern int SendMessage(
|
||||
IntPtr windowHandle, int messageId, bool firstArgument, int secondArgument
|
||||
);
|
||||
|
||||
/// <summary>Stops redrawing the specified control</summary>
|
||||
/// <param name="control">Control to stop redrawing</param>
|
||||
public RedrawLockScope(Control control) {
|
||||
if(Environment.OSVersion.Platform == PlatformID.Win32NT) {
|
||||
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
|
||||
this.control = control;
|
||||
} else {
|
||||
this.control = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Enables redrawing again when the lock scope is disposed</summary>
|
||||
public void Dispose() {
|
||||
if(this.control != null) {
|
||||
SendMessage(this.control.Handle, WM_SETREDRAW, true, 0);
|
||||
this.control.Invalidate(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Control that has been stopped from redrawing itself</summary>
|
||||
private Control control;
|
||||
|
||||
}
|
||||
|
||||
#endregion // struct RedrawLockScope
|
||||
|
||||
/// <summary>Initializes a new multi page view window for the windows forms designer</summary>
|
||||
public MultiPageViewForm() {
|
||||
this.createViewMethod = typeof(IWindowManager).GetMethod(nameof(IWindowManager.CreateView));
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new multi page view window</summary>
|
||||
/// <param name="windowManager">
|
||||
/// Window manager that is used to set up the child views
|
||||
/// </param>
|
||||
/// <param name="cachePageViews">Whether page views should be kept alive and reused</param>
|
||||
public MultiPageViewForm(IWindowManager windowManager, bool cachePageViews = false) {
|
||||
this.windowManager = windowManager;
|
||||
this.createViewMethod = typeof(IWindowManager).GetMethod(nameof(IWindowManager.CreateView));
|
||||
|
||||
if(cachePageViews) {
|
||||
this.cachedViews = new Dictionary<Type, Control>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called when the control is being disposed</summary>
|
||||
/// <param name="calledExplicitly">
|
||||
/// Whether the call was made by user code (vs. the garbage collector)
|
||||
/// </param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Discovers the container control used to host the child views</summary>
|
||||
/// <returns>The container control is which the child views will be hosted</returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Called when the window's data context is changed</summary>
|
||||
/// <param name="sender">Window whose data context was changed</param>
|
||||
/// <param name="oldDataContext">Data context that was previously used</param>
|
||||
/// <param name="newDataContext">Data context that will be used from now on</param>
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Called when a property of the view model is changed</summary>
|
||||
/// <param name="sender">View model in which a property was changed</param>
|
||||
/// <param name="arguments">Contains the name of the property that has changed</param>
|
||||
protected override void OnViewModelPropertyChanged(
|
||||
object sender, PropertyChangedEventArgs arguments
|
||||
) {
|
||||
base.OnViewModelPropertyChanged(sender, arguments);
|
||||
|
||||
if(arguments.AreAffecting(nameof(MultiPageViewModel<object>.ActivePage))) {
|
||||
var viewModelAsMultiPageviewModel = DataContext as IMultiPageViewModel;
|
||||
if(viewModelAsMultiPageviewModel != null) {
|
||||
if(InvokeRequired) {
|
||||
Invoke(
|
||||
new Action<object>(activatePageView),
|
||||
viewModelAsMultiPageviewModel.GetActivePageViewModel()
|
||||
);
|
||||
} else {
|
||||
activatePageView(viewModelAsMultiPageviewModel.GetActivePageViewModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Currently active page view control</summary>
|
||||
protected Control ActivePageView {
|
||||
get { return this.activePageView; }
|
||||
}
|
||||
|
||||
/// <summary>The view model running the currently active page</summary>
|
||||
protected object ActivePageViewModel {
|
||||
get {
|
||||
var activePageViewAsView = this.activePageView as IView;
|
||||
if(activePageViewAsView == null) {
|
||||
return null;
|
||||
} else {
|
||||
return activePageViewAsView.DataContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Activates the page view for the specified page view model</summary>
|
||||
/// <param name="pageViewModel">
|
||||
/// Page view model for which the page view will be activated
|
||||
/// </param>
|
||||
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
|
||||
{
|
||||
if(pageViewModel == null) {
|
||||
disableActivePageView();
|
||||
} else {
|
||||
Control pageViewContainer = getPageViewContainer();
|
||||
using(new RedrawLockScope(pageViewContainer)) {
|
||||
disableActivePageView();
|
||||
|
||||
this.activePageView = getOrCreatePageView(pageViewModel);
|
||||
pageViewContainer.Controls.Add(this.activePageView);
|
||||
this.activePageView.Dock = DockStyle.Fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the cached child view or creates a new one if not cached</summary>
|
||||
/// <param name="viewModel">View model for which a child view will be returned</param>
|
||||
/// <returns>A child view suitable for the specified view model</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Disables the currently active page view control</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fetches the container that holds the child views</summary>
|
||||
/// <returns>The container for the child views</returns>
|
||||
private Control getPageViewContainer() {
|
||||
if(this.childViewContainer == null) {
|
||||
this.childViewContainer = IdentifyPageContainer();
|
||||
}
|
||||
|
||||
return this.childViewContainer;
|
||||
}
|
||||
|
||||
/// <summary>Disposes the specified object if it is disposable</summary>
|
||||
/// <param name="potentiallyDisposable">Object that will be disposed if supported</param>
|
||||
private static void disposeIfSupported(object potentiallyDisposable) {
|
||||
var disposable = potentiallyDisposable as IDisposable;
|
||||
if(disposable != null) {
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Window manager through which the child views are created</summary>
|
||||
private IWindowManager windowManager;
|
||||
/// <summary>Reflection info for the createView() method of the window manager</summary>
|
||||
private MethodInfo createViewMethod;
|
||||
|
||||
/// <summary>Container in which the child views will be hosted</summary>
|
||||
private Control childViewContainer;
|
||||
/// <summary>Cached views that will be reused when the view model activates them</summary>
|
||||
private Dictionary<Type, Control> cachedViews;
|
||||
/// <summary>The currently active child view</summary>
|
||||
private Control activePageView;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
|
|
|
@ -1,89 +1,88 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2019 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Nuclex.Support;
|
||||
|
||||
namespace Nuclex.Windows.Forms.Views {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for MVVM user controls that act as views connected to a view model
|
||||
/// </summary>
|
||||
public class ViewControl : UserControl, IView {
|
||||
|
||||
/// <summary>Initializes a new view control</summary>
|
||||
public ViewControl() {
|
||||
this.onViewModelPropertyChangedDelegate = OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>Called when the control's data context is changed</summary>
|
||||
/// <param name="sender">Control whose data context was changed</param>
|
||||
/// <param name="oldDataContext">Data context that was previously used</param>
|
||||
/// <param name="newDataContext">Data context that will be used from now on</param>
|
||||
protected virtual void OnDataContextChanged(
|
||||
object sender, object oldDataContext, object newDataContext
|
||||
) {
|
||||
var oldViewModel = oldDataContext as INotifyPropertyChanged;
|
||||
if(oldViewModel != null) {
|
||||
oldViewModel.PropertyChanged -= this.onViewModelPropertyChangedDelegate;
|
||||
}
|
||||
|
||||
var newViewModel = newDataContext as INotifyPropertyChanged;
|
||||
if(newViewModel != null) {
|
||||
newViewModel.PropertyChanged += this.onViewModelPropertyChangedDelegate;
|
||||
InvalidateAllViewModelProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Refreshes all properties from the view model</summary>
|
||||
protected void InvalidateAllViewModelProperties() {
|
||||
OnViewModelPropertyChanged(this.dataContext, PropertyChangedEventArgsHelper.Wildcard);
|
||||
}
|
||||
|
||||
/// <summary>Called when a property of the view model is changed</summary>
|
||||
/// <param name="sender">View model in which a property was changed</param>
|
||||
/// <param name="arguments">Contains the name of the property that has changed</param>
|
||||
protected virtual void OnViewModelPropertyChanged(
|
||||
object sender, PropertyChangedEventArgs arguments
|
||||
) { }
|
||||
|
||||
/// <summary>Provides the data binding target for the view</summary>
|
||||
public object DataContext {
|
||||
get { return this.dataContext; }
|
||||
set {
|
||||
if(value != this.dataContext) {
|
||||
object oldDataContext = this.dataContext;
|
||||
this.dataContext = value;
|
||||
OnDataContextChanged(this, oldDataContext, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Active data binding target, can be null</summary>
|
||||
private object dataContext;
|
||||
/// <summary>Delegate for the OnViewModelPropertyChanged() method</summary>
|
||||
private PropertyChangedEventHandler onViewModelPropertyChangedDelegate;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
#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.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Nuclex.Support;
|
||||
|
||||
namespace Nuclex.Windows.Forms.Views {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for MVVM user controls that act as views connected to a view model
|
||||
/// </summary>
|
||||
public class ViewControl : UserControl, IView {
|
||||
|
||||
/// <summary>Initializes a new view control</summary>
|
||||
public ViewControl() {
|
||||
this.onViewModelPropertyChangedDelegate = OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>Called when the control's data context is changed</summary>
|
||||
/// <param name="sender">Control whose data context was changed</param>
|
||||
/// <param name="oldDataContext">Data context that was previously used</param>
|
||||
/// <param name="newDataContext">Data context that will be used from now on</param>
|
||||
protected virtual void OnDataContextChanged(
|
||||
object sender, object oldDataContext, object newDataContext
|
||||
) {
|
||||
var oldViewModel = oldDataContext as INotifyPropertyChanged;
|
||||
if(oldViewModel != null) {
|
||||
oldViewModel.PropertyChanged -= this.onViewModelPropertyChangedDelegate;
|
||||
}
|
||||
|
||||
var newViewModel = newDataContext as INotifyPropertyChanged;
|
||||
if(newViewModel != null) {
|
||||
newViewModel.PropertyChanged += this.onViewModelPropertyChangedDelegate;
|
||||
InvalidateAllViewModelProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Refreshes all properties from the view model</summary>
|
||||
protected void InvalidateAllViewModelProperties() {
|
||||
OnViewModelPropertyChanged(this.dataContext, PropertyChangedEventArgsHelper.Wildcard);
|
||||
}
|
||||
|
||||
/// <summary>Called when a property of the view model is changed</summary>
|
||||
/// <param name="sender">View model in which a property was changed</param>
|
||||
/// <param name="arguments">Contains the name of the property that has changed</param>
|
||||
protected virtual void OnViewModelPropertyChanged(
|
||||
object sender, PropertyChangedEventArgs arguments
|
||||
) { }
|
||||
|
||||
/// <summary>Provides the data binding target for the view</summary>
|
||||
public object DataContext {
|
||||
get { return this.dataContext; }
|
||||
set {
|
||||
if(value != this.dataContext) {
|
||||
object oldDataContext = this.dataContext;
|
||||
this.dataContext = value;
|
||||
OnDataContextChanged(this, oldDataContext, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Active data binding target, can be null</summary>
|
||||
private object dataContext;
|
||||
/// <summary>Delegate for the OnViewModelPropertyChanged() method</summary>
|
||||
private PropertyChangedEventHandler onViewModelPropertyChangedDelegate;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
|
|
|
@ -1,89 +1,88 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2019 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Nuclex.Support;
|
||||
|
||||
namespace Nuclex.Windows.Forms.Views {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for MVVM windows that act as views connected to a view model
|
||||
/// </summary>
|
||||
public class ViewForm : Form, IView {
|
||||
|
||||
/// <summary>Initializes a new view control</summary>
|
||||
public ViewForm() {
|
||||
this.onViewModelPropertyChangedDelegate = OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>Provides the data binding target for the view</summary>
|
||||
public object DataContext {
|
||||
get { return this.dataContext; }
|
||||
set {
|
||||
if(value != this.dataContext) {
|
||||
object oldDataContext = this.dataContext;
|
||||
this.dataContext = value;
|
||||
OnDataContextChanged(this, oldDataContext, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called when the window's data context is changed</summary>
|
||||
/// <param name="sender">Window whose data context was changed</param>
|
||||
/// <param name="oldDataContext">Data context that was previously used</param>
|
||||
/// <param name="newDataContext">Data context that will be used from now on</param>
|
||||
protected virtual void OnDataContextChanged(
|
||||
object sender, object oldDataContext, object newDataContext
|
||||
) {
|
||||
var oldViewModel = oldDataContext as INotifyPropertyChanged;
|
||||
if(oldViewModel != null) {
|
||||
oldViewModel.PropertyChanged -= this.onViewModelPropertyChangedDelegate;
|
||||
}
|
||||
|
||||
var newViewModel = newDataContext as INotifyPropertyChanged;
|
||||
if(newViewModel != null) {
|
||||
newViewModel.PropertyChanged += this.onViewModelPropertyChangedDelegate;
|
||||
InvalidateAllViewModelProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Refreshes all properties from the view model</summary>
|
||||
protected void InvalidateAllViewModelProperties() {
|
||||
OnViewModelPropertyChanged(this.dataContext, PropertyChangedEventArgsHelper.Wildcard);
|
||||
}
|
||||
|
||||
/// <summary>Called when a property of the view model is changed</summary>
|
||||
/// <param name="sender">View model in which a property was changed</param>
|
||||
/// <param name="arguments">Contains the name of the property that has changed</param>
|
||||
protected virtual void OnViewModelPropertyChanged(
|
||||
object sender, PropertyChangedEventArgs arguments
|
||||
) { }
|
||||
|
||||
/// <summary>Active data binding target, can be null</summary>
|
||||
private object dataContext;
|
||||
/// <summary>Delegate for the OnViewModelPropertyChanged() method</summary>
|
||||
private PropertyChangedEventHandler onViewModelPropertyChangedDelegate;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
#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.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Nuclex.Support;
|
||||
|
||||
namespace Nuclex.Windows.Forms.Views {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for MVVM windows that act as views connected to a view model
|
||||
/// </summary>
|
||||
public class ViewForm : Form, IView {
|
||||
|
||||
/// <summary>Initializes a new view control</summary>
|
||||
public ViewForm() {
|
||||
this.onViewModelPropertyChangedDelegate = OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>Provides the data binding target for the view</summary>
|
||||
public object DataContext {
|
||||
get { return this.dataContext; }
|
||||
set {
|
||||
if(value != this.dataContext) {
|
||||
object oldDataContext = this.dataContext;
|
||||
this.dataContext = value;
|
||||
OnDataContextChanged(this, oldDataContext, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called when the window's data context is changed</summary>
|
||||
/// <param name="sender">Window whose data context was changed</param>
|
||||
/// <param name="oldDataContext">Data context that was previously used</param>
|
||||
/// <param name="newDataContext">Data context that will be used from now on</param>
|
||||
protected virtual void OnDataContextChanged(
|
||||
object sender, object oldDataContext, object newDataContext
|
||||
) {
|
||||
var oldViewModel = oldDataContext as INotifyPropertyChanged;
|
||||
if(oldViewModel != null) {
|
||||
oldViewModel.PropertyChanged -= this.onViewModelPropertyChangedDelegate;
|
||||
}
|
||||
|
||||
var newViewModel = newDataContext as INotifyPropertyChanged;
|
||||
if(newViewModel != null) {
|
||||
newViewModel.PropertyChanged += this.onViewModelPropertyChangedDelegate;
|
||||
InvalidateAllViewModelProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Refreshes all properties from the view model</summary>
|
||||
protected void InvalidateAllViewModelProperties() {
|
||||
OnViewModelPropertyChanged(this.dataContext, PropertyChangedEventArgsHelper.Wildcard);
|
||||
}
|
||||
|
||||
/// <summary>Called when a property of the view model is changed</summary>
|
||||
/// <param name="sender">View model in which a property was changed</param>
|
||||
/// <param name="arguments">Contains the name of the property that has changed</param>
|
||||
protected virtual void OnViewModelPropertyChanged(
|
||||
object sender, PropertyChangedEventArgs arguments
|
||||
) { }
|
||||
|
||||
/// <summary>Active data binding target, can be null</summary>
|
||||
private object dataContext;
|
||||
/// <summary>Delegate for the OnViewModelPropertyChanged() method</summary>
|
||||
private PropertyChangedEventHandler onViewModelPropertyChangedDelegate;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Windows.Forms.Views
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue