MvvmLight + Microsoft.Extensions.DependencyInjection + WpfApp(.NetCore3.1)

git clone MvvmLight失敗,破網絡, 就沒有直接修改源碼的方式來使用了

Nuget安裝MvvmLightLibsStd10

使用GalaSoft.MvvmLight.Command命名空間下的RelayCommand會有一個Bug, CanExecute的返回不會更新UI, 在GalaSoft.MvvmLight.CommandWpf中進行了Fixed, 然而MvvmLightLibsStd10並沒有GalaSoft.MvvmLight.CommandWpf, 直接粗暴的把GalaSoft.MvvmLight.CommandWpf下面的RelayCommand提到工程中

using GalaSoft.MvvmLight.Helpers;
using System;
using System.Threading;
using System.Windows.Input;

namespace TraceApp.LinkLib.Wpf.Extras
{
    /// <summary>
    /// A command whose sole purpose is to relay its functionality to other
    /// objects by invoking delegates. The default return value for the CanExecute
    /// method is 'true'.  This class does not allow you to accept command parameters in the
    /// Execute and CanExecute callback methods.
    /// </summary>
    /// <remarks>If you are using this class in WPF4.5 or above, you need to use the
    /// GalaSoft.MvvmLight.CommandWpf namespace (instead of GalaSoft.MvvmLight.Command).
    /// This will enable (or restore) the CommandManager class which handles
    /// automatic enabling/disabling of controls based on the CanExecute delegate.</remarks>
    public class RelayCommand : ICommand
    {
        private readonly WeakAction _execute;
        private readonly WeakFunc<bool> _canExecute;
        private EventHandler _requerySuggestedLocal;

        /// <summary>
        /// Initializes a new instance of the RelayCommand class that
        /// can always execute.
        /// </summary>
        /// <param name="execute">The execution logic. IMPORTANT: If the action causes a closure,
        /// you must set keepTargetAlive to true to avoid side effects. </param>
        /// <param name="keepTargetAlive">If true, the target of the Action will
        /// be kept as a hard reference, which might cause a memory leak. You should only set this
        /// parameter to true if the action is causing a closure. See
        /// //galasoft.ch/s/mvvmweakaction. </param>
        /// <exception cref="T:System.ArgumentNullException">If the execute argument is null.</exception>
        public RelayCommand(Action execute, bool keepTargetAlive = false)
          : this(execute, (Func<bool>)null, keepTargetAlive)
        {
        }

        /// <summary>Initializes a new instance of the RelayCommand class.</summary>
        /// <param name="execute">The execution logic. IMPORTANT: If the action causes a closure,
        /// you must set keepTargetAlive to true to avoid side effects. </param>
        /// <param name="canExecute">The execution status logic.  IMPORTANT: If the func causes a closure,
        /// you must set keepTargetAlive to true to avoid side effects. </param>
        /// <param name="keepTargetAlive">If true, the target of the Action will
        /// be kept as a hard reference, which might cause a memory leak. You should only set this
        /// parameter to true if the action is causing a closures. See
        /// //galasoft.ch/s/mvvmweakaction. </param>
        /// <exception cref="T:System.ArgumentNullException">If the execute argument is null.</exception>
        public RelayCommand(Action execute, Func<bool> canExecute, bool keepTargetAlive = false)
        {
            if (execute == null)
                throw new ArgumentNullException(nameof(execute));
            this._execute = new WeakAction(execute, keepTargetAlive);
            if (canExecute == null)
                return;
            this._canExecute = new WeakFunc<bool>(canExecute, keepTargetAlive);
        }

        /// <summary>
        /// Occurs when changes occur that affect whether the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (this._canExecute == null)
                    return;
                EventHandler eventHandler = this._requerySuggestedLocal;
                EventHandler comparand;
                do
                {
                    comparand = eventHandler;
                    eventHandler = Interlocked.CompareExchange<EventHandler>(ref this._requerySuggestedLocal, comparand + value, comparand);
                }
                while (eventHandler != comparand);
                CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (this._canExecute == null)
                    return;
                EventHandler eventHandler = this._requerySuggestedLocal;
                EventHandler comparand;
                do
                {
                    comparand = eventHandler;
                    eventHandler = Interlocked.CompareExchange<EventHandler>(ref this._requerySuggestedLocal, comparand - value, comparand);
                }
                while (eventHandler != comparand);
                CommandManager.RequerySuggested -= value;
            }
        }

        /// <summary>
        /// Raises the <see cref="E:GalaSoft.MvvmLight.CommandWpf.RelayCommand.CanExecuteChanged" /> event.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">This parameter will always be ignored.</param>
        /// <returns>true if this command can be executed; otherwise, false.</returns>
        public bool CanExecute(object parameter)
        {
            if (this._canExecute == null)
                return true;
            if (this._canExecute.IsStatic || this._canExecute.IsAlive)
                return this._canExecute.Execute();
            return false;
        }

        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">This parameter will always be ignored.</param>
        public virtual void Execute(object parameter)
        {
            if (!this.CanExecute(parameter) || this._execute == null || !this._execute.IsStatic && !this._execute.IsAlive)
                return;
            this._execute.Execute();
        }
    }

    /// <summary>
    /// A generic command whose sole purpose is to relay its functionality to other
    /// objects by invoking delegates. The default return value for the CanExecute
    /// method is 'true'. This class allows you to accept command parameters in the
    /// Execute and CanExecute callback methods.
    /// </summary>
    /// <typeparam name="T">The type of the command parameter.</typeparam>
    /// <remarks>If you are using this class in WPF4.5 or above, you need to use the
    /// GalaSoft.MvvmLight.CommandWpf namespace (instead of GalaSoft.MvvmLight.Command).
    /// This will enable (or restore) the CommandManager class which handles
    /// automatic enabling/disabling of controls based on the CanExecute delegate.</remarks>
    public class RelayCommand<T> : ICommand
    {
        private readonly WeakAction<T> _execute;
        private readonly WeakFunc<T, bool> _canExecute;

        /// <summary>
        /// Initializes a new instance of the RelayCommand class that
        /// can always execute.
        /// </summary>
        /// <param name="execute">The execution logic. IMPORTANT: If the action causes a closure,
        /// you must set keepTargetAlive to true to avoid side effects. </param>
        /// <param name="keepTargetAlive">If true, the target of the Action will
        /// be kept as a hard reference, which might cause a memory leak. You should only set this
        /// parameter to true if the action is causing a closure. See
        /// //galasoft.ch/s/mvvmweakaction. </param>
        /// <exception cref="T:System.ArgumentNullException">If the execute argument is null.</exception>
        public RelayCommand(Action<T> execute, bool keepTargetAlive = false)
          : this(execute, (Func<T, bool>)null, keepTargetAlive)
        {
        }

        /// <summary>Initializes a new instance of the RelayCommand class.</summary>
        /// <param name="execute">The execution logic. IMPORTANT: If the action causes a closure,
        /// you must set keepTargetAlive to true to avoid side effects. </param>
        /// <param name="canExecute">The execution status logic.  IMPORTANT: If the func causes a closure,
        /// you must set keepTargetAlive to true to avoid side effects. </param>
        /// <param name="keepTargetAlive">If true, the target of the Action will
        /// be kept as a hard reference, which might cause a memory leak. You should only set this
        /// parameter to true if the action is causing a closure. See
        /// //galasoft.ch/s/mvvmweakaction. </param>
        /// <exception cref="T:System.ArgumentNullException">If the execute argument is null.</exception>
        public RelayCommand(Action<T> execute, Func<T, bool> canExecute, bool keepTargetAlive = false)
        {
            if (execute == null)
                throw new ArgumentNullException(nameof(execute));
            this._execute = new WeakAction<T>(execute, keepTargetAlive);
            if (canExecute == null)
                return;
            this._canExecute = new WeakFunc<T, bool>(canExecute, keepTargetAlive);
        }

        /// <summary>
        /// Occurs when changes occur that affect whether the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (this._canExecute == null)
                    return;
                CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (this._canExecute == null)
                    return;
                CommandManager.RequerySuggested -= value;
            }
        }

        /// <summary>
        /// Raises the <see cref="E:GalaSoft.MvvmLight.CommandWpf.RelayCommand`1.CanExecuteChanged" /> event.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">Data used by the command. If the command does not require data
        /// to be passed, this object can be set to a null reference</param>
        /// <returns>true if this command can be executed; otherwise, false.</returns>
        public bool CanExecute(object parameter)
        {
            if (this._canExecute == null)
                return true;
            if (this._canExecute.IsStatic || this._canExecute.IsAlive)
            {
                if (parameter == null && typeof(T).IsValueType)
                    return this._canExecute.Execute(default(T));
                if (parameter == null || parameter is T)
                    return this._canExecute.Execute((T)parameter);
            }
            return false;
        }

        /// <summary>
        /// Defines the method to be called when the command is invoked.
        /// </summary>
        /// <param name="parameter">Data used by the command. If the command does not require data
        /// to be passed, this object can be set to a null reference</param>
        public virtual void Execute(object parameter)
        {
            object parameter1 = parameter;
            if (parameter != null && parameter.GetType() != typeof(T) && parameter is IConvertible)
                parameter1 = Convert.ChangeType(parameter, typeof(T), (IFormatProvider)null);
            if (!this.CanExecute(parameter1) || this._execute == null || !this._execute.IsStatic && !this._execute.IsAlive)
                return;
            if (parameter1 == null)
            {
                if (typeof(T).IsValueType)
                    this._execute.Execute(default(T));
                else
                    this._execute.Execute(default(T));
            }
            else
                this._execute.Execute((T)parameter1);
        }
    }
}

一不做二不休, 發現MvvmLightLibsStd10中也沒有DispatcherHelper, 也提取出來

using System;
using System.Text;
using System.Windows.Threading;

namespace TraceApp.LinkLib.Wpf.Extras
{
    /// <summary>
    /// Helper class for dispatcher operations on the UI thread.
    /// </summary>
    public static class DispatcherHelper
    {
        /// <summary>
        /// Gets a reference to the UI thread's dispatcher, after the
        /// <see cref="M:GalaSoft.MvvmLight.Threading.DispatcherHelper.Initialize" /> method has been called on the UI thread.
        /// </summary>
        public static Dispatcher UIDispatcher { get; private set; }

        /// <summary>
        /// Executes an action on the UI thread. If this method is called
        /// from the UI thread, the action is executed immendiately. If the
        /// method is called from another thread, the action will be enqueued
        /// on the UI thread's dispatcher and executed asynchronously.
        /// <para>For additional operations on the UI thread, you can get a
        /// reference to the UI thread's dispatcher thanks to the property
        /// <see cref="P:GalaSoft.MvvmLight.Threading.DispatcherHelper.UIDispatcher" /></para>.
        /// </summary>
        /// <param name="action">The action that will be executed on the UI
        /// thread.</param>
        public static void CheckBeginInvokeOnUI(Action action)
        {
            if (action == null)
                return;
            DispatcherHelper.CheckDispatcher();
            if (DispatcherHelper.UIDispatcher.CheckAccess())
                action();
            else
                DispatcherHelper.UIDispatcher.BeginInvoke((Delegate)action);
        }

        private static void CheckDispatcher()
        {
            if (DispatcherHelper.UIDispatcher == null)
            {
                StringBuilder stringBuilder = new StringBuilder("The DispatcherHelper is not initialized.");
                stringBuilder.AppendLine();
                stringBuilder.Append("Call DispatcherHelper.Initialize() in the static App constructor.");
                throw new InvalidOperationException(stringBuilder.ToString());
            }
        }

        /// <summary>Invokes an action asynchronously on the UI thread.</summary>
        /// <param name="action">The action that must be executed.</param>
        /// <returns>An object, which is returned immediately after BeginInvoke is called, that can be used to interact
        /// with the delegate as it is pending execution in the event queue.</returns>
        public static DispatcherOperation RunAsync(Action action)
        {
            DispatcherHelper.CheckDispatcher();
            return DispatcherHelper.UIDispatcher.BeginInvoke((Delegate)action);
        }

        /// <summary>
        /// This method should be called once on the UI thread to ensure that
        /// the <see cref="P:GalaSoft.MvvmLight.Threading.DispatcherHelper.UIDispatcher" /> property is initialized.
        /// <para>In a Silverlight application, call this method in the
        /// Application_Startup event handler, after the MainPage is constructed.</para>
        /// <para>In WPF, call this method on the static App() constructor.</para>
        /// </summary>
        public static void Initialize()
        {
            if (DispatcherHelper.UIDispatcher != null && DispatcherHelper.UIDispatcher.Thread.IsAlive)
                return;
            DispatcherHelper.UIDispatcher = Dispatcher.CurrentDispatcher;
        }

        /// <summary>
        /// Resets the class by deleting the <see cref="P:GalaSoft.MvvmLight.Threading.DispatcherHelper.UIDispatcher" />
        /// </summary>
        public static void Reset()
        {
            DispatcherHelper.UIDispatcher = (Dispatcher)null;
        }
    }
}
Nuget安裝Microsoft.Extensions.DependencyInjection
創建一個類AppLocator, 在App.Xaml中申明為全局資源來做Service的註冊來View的綁定

AppLocator

using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Trace.Model;
using TraceApp.DB;
using TraceApp.ViewModel;

namespace TraceApp
{
    public class AppLocator
    {
        public IServiceProvider ServiceProvider { get; private set; }
        public IConfiguration Configuration { get; private set; }

        public AppLocator()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", true, true);
            Configuration = builder.Build();

            var collection = new ServiceCollection();
            ConfigureServices(collection);
            ServiceProvider = collection.BuildServiceProvider();
        }

        private void ConfigureServices(IServiceCollection services)
        {
            // Configuration
            services.Configure<AppSettings>(Configuration.GetSection(nameof(AppSettings)));
            // Database
            services.AddDbContext<MyContext>(options =>
            {
                options.UseSqlite("Data Source=TraceApp.db3");
                options.UseLoggerFactory(LoggerFactory.Create(builder =>
                {
                    builder.AddFilter((category, level) =>
                        category == DbLoggerCategory.Database.Command.Name
                        && level == LogLevel.Information);
                    builder.AddConsole();
                }));
            });

            #region Register ViewModel
            services.AddSingleton<MainWindowViewModel>();
            #endregion
        }

        #region ViewModel's DataContexts
        public MainWindowViewModel MainWindow => ServiceProvider.GetService<MainWindowViewModel>();
        #endregion
    }
}
運行, 效果是自己想要的

附上App.Xaml、MainWindow.Xaml、MainWindowViewModel.cs的代碼

App.Xaml

<Application  x:Class="TraceApp.App"
             xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TraceApp">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!--Handy Control-->
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <local:AppLocator x:Key="AppLocator"/>
        </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.Xaml

<Window x:Class="TraceApp.View.MainWindow"
        xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="//schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        DataContext="{Binding Source={StaticResource AppLocator}, Path=MainWindow}"
        Title="MainWindow" d:DesignHeight="500" d:DesignWidth="800">
    <Grid>
        <WrapPanel>
            <TextBlock Text="{Binding Title}"/>
            <Button Content="測試命令" Command="{Binding TestCommand}"/>
            <CheckBox IsChecked="{Binding Enable, Mode=TwoWay}"/>
        </WrapPanel>
    </Grid>
</Window>

MainWindowViewModel

using GalaSoft.MvvmLight;
using HandyControl.Controls;
using TraceApp.LinkLib.Wpf.Extras;

namespace TraceApp.ViewModel
{
    public class MainWindowViewModel : ViewModelBase
    {
        private string _title = "Wpf Mvvm Application";
        public string Title
        {
            get => _title;
            set => Set(ref _title, value);
        }

        private bool _enable = false;
        public bool Enable { get => _enable; set => Set(ref _enable, value); }

        public RelayCommand TestCommand => new RelayCommand(() => { MessageBox.Show("Test!"); }, 
            () => Enable);

        public MainWindowViewModel()
        {
            
        }
    }
}
沒有了Nuget安裝MvvmLight在.NetCore中使用的Warnning, 強迫症的心情愉快了很多

Tags: