sources/Google.Solutions.Mvvm/Binding/ViewModelBase.cs (68 lines of code) (raw):

// // Copyright 2019 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you 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. // using System; using System.ComponentModel; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Windows.Forms; namespace Google.Solutions.Mvvm.Binding { /// <summary> /// MVVM view model. /// </summary> public class ViewModelBase : INotifyPropertyChanged, IDisposable { /// <summary> /// View that the view model has been bound to. Null if /// binding has not occurred yet. /// </summary> public IWin32Window? View { get; set; } //--------------------------------------------------------------------- // INotifyPropertyChanged and helpers. //--------------------------------------------------------------------- public event PropertyChangedEventHandler? PropertyChanged; /// <summary> /// Notify observers about a property change. /// </summary> protected void RaisePropertyChange([CallerMemberName] string? propertyName = null) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) { PropertyChanged?.Invoke(this, args); } /// <summary> /// Notify observers about a property change. Using this /// overload avoids having to use a (brittle) string to /// identify a property. /// /// Example: /// RaisePropertyChange((MyViewModel m) => m.MyProperty); /// </summary> protected void RaisePropertyChange<TModel, TProperty>( Expression<Func<TModel, TProperty>> modelProperty) { Debug.Assert(modelProperty.NodeType == ExpressionType.Lambda); if (modelProperty.Body is MemberExpression memberExpression && memberExpression.Member is PropertyInfo propertyInfo) { RaisePropertyChange(propertyInfo.Name); } else { throw new ArgumentException("Expression does not resolve to a property"); } } public bool HasPropertyChangeListeners => this.PropertyChanged != null; //--------------------------------------------------------------------- // Validation. //--------------------------------------------------------------------- internal void Bind(IWin32Window view) { this.View = view; OnValidate(); } internal void Unbind() { Debug.Assert(this.View != null); this.View = null; } /// <summary> /// Check if the view model has been sufficiently initialized to be /// bound to a view. /// </summary> protected virtual void OnValidate() { } //--------------------------------------------------------------------- // IDispose and helpers. //--------------------------------------------------------------------- protected bool Disposed { get; private set; } protected virtual void Dispose(bool disposing) { } public void Dispose() { if (!this.Disposed) { this.Disposed = true; Dispose(disposing: true); GC.SuppressFinalize(this); } else { Debug.Fail("Object has been disposed already"); } } } }