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 /// http://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 /// http://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 /// http://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 /// http://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="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://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="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://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() { } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2018-05-29 pybind11简介