IT.Tangdao.Framework 5.1.0

dotnet add package IT.Tangdao.Framework --version 5.1.0
                    
NuGet\Install-Package IT.Tangdao.Framework -Version 5.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IT.Tangdao.Framework" Version="5.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IT.Tangdao.Framework" Version="5.1.0" />
                    
Directory.Packages.props
<PackageReference Include="IT.Tangdao.Framework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IT.Tangdao.Framework --version 5.1.0
                    
#r "nuget: IT.Tangdao.Framework, 5.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IT.Tangdao.Framework@5.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IT.Tangdao.Framework&version=5.1.0
                    
Install as a Cake Addin
#tool nuget:?package=IT.Tangdao.Framework&version=5.1.0
                    
Install as a Cake Tool

IT.Tangdao以下简称唐刀,是我开发的一款适用于WPF的专属框架。

内置了IOC容器ITangdaoContainer、

事件聚合器IEventAggregator、

内容读写器IContentAccess

路径优化类TangdaoPath、

文件监控器IFileMonitor、

文件路径查询器IFileLocator、

自带日志监控ITangdaoLogger、

进程参数传输器ITangdaoParameter、

UI线程直接传输类UIAmbientContext、

简单的翻页导航ISingleRouter、

带有拦截功能和传输功能的导航系统ITangdaoRouter、

虚假数据生成器TangdaoDataFaker、

任务调度器TangdaoTaskScheduler、

异步任务流ITangdaoQueueManager

异步任务控制器ITangdaoController

弱引用事件TangdaoWeakEvent

委托表传输机制IActionTable,复合委托表CompositeActionTable

命令和异步命令TangdaoCommand,TangdaoAsyncCommand

快速静态命令MinidaoCommand

Window管道,WindowPipe

观察者模式事件通知TangdaoMessenger

拦截器、在开发中

此外还内置了常用的WPF转换器和markup扩展

1、启动

1-1、修改App.xaml

<td:TangdaoApplication
    x:Class="TangdaoDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TangdaoDemo"
    xmlns:td="clr-namespace:IT.Tangdao.Framework;assembly=IT.Tangdao.Framework"
    xmlns:vm="clr-namespace:TangdaoDemo.ViewModels">
    <Application.Resources>

    </Application.Resources>
</td:TangdaoApplication>

1-2、修改App.xaml.cs

 public partial class App : TangdaoApplication
 {
     private static readonly ITangdaoLogger Logger = TangdaoLogger.Get(typeof(App));
     protected override void Configure()
     {
         base.Configure();
     }

     protected override void RegisterServices(ITangdaoContainer container)
     {
         container.RegisterdaoSingleton<MainView>();
         container.RegisterdaoSingleton<MainViewModel>();
         Logger.WriteLocal("Tangdao测试代码启动");
     }

     protected override Window CreateWindow()
     {
         return CreateShell<MainView>();
     }

     private void RegisterExceptionEvents()
     {
         TangdaoExceptionHandler handler = new TangdaoExceptionHandler();
         //Task线程内未捕获异常处理事件
         TaskScheduler.UnobservedTaskException += handler.TaskScheduler_UnobservedTaskException;
         //UI线程未捕获异常处理事件(UI主线程)
         this.DispatcherUnhandledException += handler.App_DispatcherUnhandledException;
         //非UI线程未捕获异常处理事件(例如自己创建的一个子线程)
         AppDomain.CurrentDomain.UnhandledException += handler.CurrentDomain_UnhandledException;
     }
 }
2、唐刀命令

正常命令:TangdaoCommand

异步命令:TangdaoAsyncCommand

静态命令:MinidaoCommand

1-1、用法
TangdaoCommand  taodao=new TangdaoCommand(()=>{});
TangdaoAsyncCommand taodaoAsync=new TangdaoAsyncCommand(async () => { });
MinidaoCommand.Create(()=>{});
MinidaoCommand.CreateFromTask(async () => { });
1-2、集成快捷命令
//当ViewModel继承DaoViewModelBase的时候,可以快速创建命令,省去new TangdaoCommand();

public ICommand SaveCommand => this.Cmd(Execute);
3、事件聚合器

IEventAggregator

3-1、用法
 public IEventAggregator _daoEventAggregator;

_daoEventAggregator.Publish<T>();

_daoEventAggregator.Subscribe<T>(Execute);

T:EventBase
4、IOC容器
4-1、容器ITangdaoContainer

修改启动项

public partial class App : TangdaoApplication
 {
     protected override void RegisterServices(ITangdaoContainer container)
     {
         container.RegisterdaoSingleton<MainView>();
         container.RegisterdaoSingleton<MainViewModel>();
         container.RegisterdaoSingleton<HomeViewModel>();
         container.RegisterdaoSingleton<IReadService, ReadService>();
     }

     protected override Window CreateWindow()
     {
         return CreateShell<MainView>();
     }
 }

除了单例注册外,还可以瞬态注册,工厂注册,Key值注册


 container.RegisterdaoScoped<T>();
 container.RegisterdaoTransient<T>();
 container.RegisterdaoSingleton<T>();
 container.AddKeyedTransient<T>();
 container.RegisterdaoSingletonFactory<IWeatherService>(provider =>
new WeatherService(provider.GetService<ITangdaoLogger>(), provider.GetService<IConfig>()));
4-2、解析器ITangdaoProvider
Provider.GetService<T>();
4-3、服务定位器
TangdaoApplication.Provider.GetService(viewModel);
5、插件式注册
5-1、对于.Netframwork版本继承TangdaoModuleBase
 public class DemoModule : TangdaoModuleBase
 {
     public override void RegisterServices(ITangdaoContainer container)
     {
         container.AddKeyedSingleton<IReadContentService, ReadConfigService>("config");
         container.AddKeyedSingleton<IReadContentService, ReadJsonService>("json");
         container.AddKeyedSingleton<IReadContentService, ReadXmlService>("xml");
     }

     public override void OnInitialized(ITangdaoProvider provider)
     {
         base.OnInitialized(provider);
     }
 }
5-2、对于.NetCore版本继承ITangdaoModule
 public class DemoModule : ITangdaoModule
 {
     public override void RegisterServices(ITangdaoContainer container)
     {
         container.AddKeyedSingleton<IReadContentService, ReadConfigService>("config");
         container.AddKeyedSingleton<IReadContentService, ReadJsonService>("json");
         container.AddKeyedSingleton<IReadContentService, ReadXmlService>("xml");
     }

     public override void OnInitialized(ITangdaoProvider provider)
     {
         base.OnInitialized(provider);
     }
 }
5-3、AutoViewAttribute

当对于ViewModel使用特性的时候,自动关联View和ViewModel

 [AutoView]
 public class DefaultViewModel : DaoViewModelBase{}

使用方式需要ContentControl

 <ContentControl Content="{Binding DefaultViewModel}" />

也就是本框架默认使用ViewModel-First

如果使用View-First

写了

<local: DefaultView/>

默认绑定不会生效

6、常用文件的读写
6-1、对XML文件的读写

xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LoginDto xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <UserName>Admin</UserName>
  <Password>123</Password>
  <IsRemember>true</IsRemember>
  <IsAdmin>true</IsAdmin>
  <Role>管理员</Role>
</LoginDto>

cs代码:

 string foldPath = Path.Combine(IgniteInfoLocation.Cache, "LoginInfo.xml");
 var isRememberValue = _access.Default.Read(foldPath).AsXml().SelectNode("IsRemember").Value;

支持XML的序列化和反序列化

 Student student=XmlFolderHelper.Deserialize<Student>(xmlContent);
 string xml=XmlFolderHelper.SerializeXML<Student>(student);

也可以使用缓存模式序列化,只要读过本地的文件,就可以使用缓存转成对象实例

_access.Cache.DeserializeCache<LoginDto>(foldPath.Value, DaoFileType.Xml);
6-2、对Config文件的读写

1、读取默认的App.config配置

<configuration>
	<configSections>
		<section name="Menu" type="System.Configuration.DictionarySectionHandler" />
		<section name="Student" type="System.Configuration.DictionarySectionHandler" />
	</configSections>
	<Menu>
		<add key="0" value="我的样本" />
		<add key="1" value="动态记录" />
		<add key="2" value="存储" />
		<add key="3" value="实验" />
	</Menu>
	<Student>
		<add key="Id" value="1" />
		<add key="Name" value="张三" />
		<add key="Age" value="18" />
		<add key="Source" value="18" />
	</Student>
</configuration>

cs代码:使用SelectAppConfig ,由于读取的是WPF自带的Appconfig,不需要带路径

 _access.Default.Empty().AsConfig().SelectAppConfig(HandlerName);

也可以直接将读取到的数据转成List或ObservableCollection

_access.Default.Empty().AsConfig().SelectAppConfig(HandlerName).ToList(v => new TangdaoMenuItem { MenuName = v }).ToObservableCollection();

2、读取自定义配置config文件

对于.Framework版本

<section name="Tangdao" type="IT.Tangdao.Core.Common.TangdaoMenuSection,IT.Tangdao.Framework" />

对于.NetCore版本

<section name="Tangdao" type="IT.Tangdao.Core.Common.TangdaoMenuSection,IT.Tangdao.Core" />

<Tangdao>
	<menus>
		<add title="首页" value="DefaultViewModel" />
		<add title="用户信息" value="UserInfoViewModel" />
		<add title="设置" value="SetViewModel" />
		<add title="监控" value="MonitorViewModel" />
		<add title="维护" value="MaintionViewModel" />
		<add title="配方" value="RecipeViewModel" />
		<add title="参数" value="ParameBaseViewModel" />
	</menus>
</Tangdao>

cs代码:使用SelectCustomConfig,注意,这里需要先指定路径Read(configPath)

var responseResult = _access.Default.Read(configPath).AsConfig().SelectCustomConfig(readTitle, section);
 if (responseResult.Payload is Dictionary<string, string> data)
 {
     List<HomeMenuItem> menuItems = data.Select(kvp => new HomeMenuItem
     {
         Title = kvp.Key,
         ViewModelName = kvp.Value
     }).ToList();
     return new ReadOnlyCollection<HomeMenuItem>(menuItems);
 }
6-3、对Json文件的读写
var json = _access.Default.Read(foldPath).AsJson(); 

6-4、对ini文件的读写

ini文件内容

[parameters] 
api_url=http://58.211.68.202:8082
sockt_port=7181
boot_up=1
socket_port=7181
 var result = _access.Default.Read(foldPath).AsIni().SelectIni("socket_port");

对XML各种文件子节点的读取

<?xml version="1.0" encoding="utf-8"?>
<Student target="学生">
	<Id target="009">1</Id>
	<Age>18</Age>
	<Name>李四</Name>
</Student>

实体类为

 [XmlRoot("Student")]
 public class Student
 {
     [XmlAttribute("target")]
     public string Target { get; set; }

     [XmlElement("Id")]
     public int Id { get; set; }

     [XmlElement("Age")]
     public int Age { get; set; }

     [XmlElement("Name")]
     public string Name { get; set; }
    
 }

使用SelectNode读取xml节点

例如xml文档如下

<?xml version="1.0" encoding="utf-8"?>
<UserInfo>
  <Login Id="0">
    <UserName>Admin</UserName>
    <Password>2</Password>
    <Role>管理员</Role>
    <IsAdmin>True</IsAdmin>
    <IP>192.168.0.1</IP>
  </Login>
  <Register Id="1">
    <UserName>Ad</UserName>
    <Password>12</Password>
    <Role>普通用户</Role>
    <IsAdmin>False</IsAdmin>
    <IP>127.0.0.1</IP>
  </Register>
</UserInfo>

使用方式

// 正确调用(多节点必须指定索引)
var ip1 = _access.Default.Read("Path").AsXml().SelectNode("IP").Value;

var ipAll = _access.Default.Read("Path").AsXml().SelectNodes(); 

优化繁琐的读取,不需要知道类的所有属性

  var readResult = _access.Default.Read("Path").AsXml().SelectNodes("ProcessItem", x => new ProcessItem
  {
      Name = x.Element("Name")?.Value,
      IsFeeding = (bool)x.Element("IsFeeding"),
      IsBoardMade = (bool)x.Element("IsBoardMade"),
      IsBoardCheck = (bool)x.Element("IsBoardCheck"),
      IsSeal = (bool)x.Element("IsSeal"),
      IsSafe = (bool)x.Element("IsSafe"),
      IsCharge = (bool)x.Element("IsCharge"),
      IsBlanking = (bool)x.Element("IsBlanking"),
  });

直接通过反射+泛型

 var readResult = _access.Default.Read("Path").AsXml().SelectNodes<ProcessItem>();
7、扩展
7-1、基于ViewModel命名约定的隐式键展开容器字典
 public class Test
 {
     public static MultiKeyDictionary<string> keyValuePairs = new MultiKeyDictionary<string>();

     public static MultiKeyDictionary<string> Print()
     {
         keyValuePairs["Sample"] = "Test";
         //keyValuePairs["SampleView"] = "Test";
         // keyValuePairs["SampleViewModel"] = "Test";

         return keyValuePairs;
     }
 }

内部跟据View和ViewModel后缀自动绑定字典

增加自定义排序字典TangdaoSortedDictionary,以及对它的扩展

7-2、增加另外一种全新的方式去发送数据
MainViewModel: 发送
private void Execute()
{
      ITangdaoParameter tangdaoParameter = new TangdaoParameter();
      tangdaoParameter.Add("001",Name);
      this.RunSameLevelWindowAsync<LoginView>(tangdaoParameter);
}
LoginViewModel:接收
 public void Response(ITangdaoParameter tangdaoParameter)
 {
     Name = tangdaoParameter.Get<string>("001");
 }

对PLC的读取进行了扩展

注意:已迁移到IT.Tangdao.Core.Bridge.dll或IT.Tangdao.Core.Bridge.dll

  container.RegisterPlcServer(plc => 
  {
      plc.PlcType= PlcType.Siemens;
      plc.PlcIpAddress = "127.0.0.1";
      plc.Port = "502";

  });

  container.RegisterType<IPlcReadService,PlcReadService>();
  var plcservice=provider.Resolve<IPlcReadService>();
  plcservice.ReadAsync("DM200");

链式创建对象

var maybe = TangdaoOptional<string>.Some("Hello")
                                   .Where(s => s.Length > 3)
                                   .Select(s => s.ToUpper())
                                   .ValueOrDefault("NONE");

Console.WriteLine(maybe);   // HELLO
8、组件通信
//同级别窗体通信 
//tangdaoParameter为发送的数据,可以在打开窗体的时候直接发送数据过去
this.RunSameLevelWindowAsync<LoginView>(tangdaoParameter);

//父子窗体通信
this.RunChildWindowAsync<LoginView>();
8-1、线程之间的数据传输 AmbientContext
//值类型使用
AmbientContext.Set(123);
AmbientContext.Get<int>();

//引用类型使用
Student student=new Student(1,"张三");
AmbientContext.SetCurrent(student);
AmbientContext.Get<Student>();

//按指定Key值传输数据
AmbientContext.SetCurrent("学生",student);
AmbientContext.Get<Student>("学生");

8-2、进程之间的数据传输 TangdaoContext

功能更加强大,可以进行数据传输,委托传输,字典传输,命令传输

TangdaoContext.SetTangdaoParameter<T>();

TangdaoContext.GetTangdaoParameter<T>();
8-3、Socket通信,注意:此功能已迁移到IT.Tangdao.Core.Bridge.dll或IT.Tangdao.Core.Bridge.dll
 string connUri = "tcp://127.0.0.1:502";
 var uri = new TangdaoUri(connUri);

 Context = TangdaoChannelBuilder.Build(NetMode.Client, connUri);
 container.RegisterdaoSingleton<ITangdaoChannel>();
 container.RegisterdaoSingleton<ITangdaoRequest>();
 container.RegisterdaoSingleton<ITangdaoResponse>();

9、日志TangdaoLogger

日志默认是写在桌面上的

 private static readonly ITangdaoLogger Logger = TangdaoLogger.Get(typeof(Bootstrapper));
 Logger.WriteLocal($"注册成功");

会在桌面生成Tangdaos目录的日志

也可以自定义配置日志的路径:

下面代码放在启动项即可


 LogEntry logEntry = new LogEntry()
 {
     SaveDir = ConfigurationManager.AppSettings["LoggerPath"],
     LogFormat = LogFormat.Json
 };
 LogEnsureConfig.Load(logEntry);

现在日志有三种格式,txt文本,xml文本和json文本

主要的设计模式及其应用:

1. 静态工厂模式
  • 应用 : TangdaoLogger.Get<TType>() 方法
  • 作用 :通过类型获取对应的日志记录器实例,简化了日志记录器的创建过程
2. 单例模式
  • 应用 : TangdaoLogger 类使用 ConcurrentDictionary 缓存每个类型的日志记录器实例
  • 作用 :确保每个类型只有一个日志记录器实例,减少内存占用
3. 策略模式
  • 应用 : LogHelper.SaveLogToFile 方法根据不同的日志格式采用不同的保存策略
  • 作用 :支持多种日志格式(XML、JSON、文本),易于扩展新的格式
4. 生产者-消费者模式
  • 应用 : LoggerHandler 使用 BlockingCollection 来处理日志
  • 作用 :实现了日志的异步处理,提高了应用性能
5. 扩展方法模式
  • 应用 : TangdaoLoggerExtension 提供了 WriteLocal 扩展方法
  • 作用 :为 ITangdaoLogger 接口添加了额外的功能,而不需要修改接口定义
6. 配置模式
  • 应用 :使用 LogEnsureConfig 来加载和管理日志配置
  • 作用 :集中管理日志配置,提供了统一的配置接口
7. 命令模式
  • 应用 : LogItem 类封装了日志的所有信息
  • 作用 :将日志信息封装成一个对象,便于在系统中传递和处理
8. 依赖注入模式
  • 应用 : TangdaoLogger.LoggerFactory 属性
  • 作用 :允许用户自定义日志记录器的创建方式,提高了系统的灵活性
9. 枚举策略模式
  • 应用 :使用 LogFormat 枚举代替字符串常量
  • 作用 :提高了类型安全性,避免了字符串拼写错误
10. 序列化模式
  • 应用 :使用 TangdaoXmlSerializer 和 JsonConvert 进行序列化
  • 作用 :支持不同格式的日志输出,便于日志的存储和分析
10、自动生成器

可以自动生成虚假数据,用于平时调试

在WPF可以这样使用

public class MainWindowViewModel : BindableBase
 {
     private ObservableCollection<Student> _students;

     public ObservableCollection<Student> Students
     {
         get => _students;
         set => SetProperty(ref _students, value);
     }

     public MainWindowViewModel()
     {
         Loaded();
     }

     private void Loaded()
     {
         var generator = new TangdaoDataFaker<Student>();
         List<Student> randomStudents = generator.Build(10);
         Students = new ObservableCollection<Student>(randomStudents);
     }
 }
 public class Student
 {
     public int Id { get; set; }

     [DaoFakeDataInfo("姓名")] // 使用 DaoName 枚举生成姓名
     public string Name { get; set; }

     public int Age { get; set; }

     [DaoFakeDataInfo("爱好")] // 使用 DaoHobby 枚举生成爱好
     public string Hobby { get; set; }

     [DaoFakeDataInfo("城市")] // 使用 ChineseCities 数组生成城市
     public string Address { get; set; }
 }
11、增加路由导航
1、简单的导航,具有翻页功能ISingleRouter

使用方式,与ISingleNavigateView配合使用

使用IOC容器注册所有的视图

XAML Code:

 
 <ContentControl
     HorizontalContentAlignment="Stretch"
     VerticalContentAlignment="Stretch"
     Content="{Binding CurrentView}" />

 
 <Border
     Grid.Row="1"
     Padding="10"
     Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
     <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
         
         <Button
             Width="100"
             Command="{Binding PreviousCommand}"
             Content="◄ 上一页"
             IsEnabled="{Binding CanPrevious}" />

         
         <ToggleButton
             Width="200"
             Margin="20,0"
             Background="{Binding IsAutoRotating, Converter={StaticResource BoolToColorConverter}}"
             Command="{s:Action ToggleAutoCarousel}"
             Content="{Binding AutoRotateStatusText}"
             IsChecked="{Binding IsAutoRotating, Mode=OneWay}" />

         
         <Button
             Width="100"
             Command="{Binding NextCommand}"
             Content="下一页 ►"
             IsEnabled="{Binding CanNext}" />
     </StackPanel>
 </Border>

CS Code:

 public class GlobalPhotoViewModel : Screen
 {
     private readonly ISingleRouter _router;

     public ISingleNavigateView CurrentView => _router.CurrentView;
     public bool CanPrevious => _router.CanPrevious;
     public bool CanNext => _router.CanNext;
     public bool IsAutoRotating => _router.IsAutoRotating;
     public string AutoRotateStatusText => _router.IsAutoRotating ? "自动轮播开启中" : "自动轮播已禁用";

     public GlobalPhotoViewModel(ISingleRouter router)
     {
         _router = router;
         _router.PropertyChanged += OnRouterPropertyChanged;
         _router.NavigationChanged += OnRouterNavigationChanged;
     }

     public void Previous()
     {
         _router.Previous();
     }

     public void Next()
     {
         _router.Next();
     }

     public void ToggleAutoCarousel() => _router.ToggleAutoCarousel();

     private void OnRouterPropertyChanged(object sender, PropertyChangedEventArgs e)
     {
         // 将路由器的属性变化转发到视图模型
         NotifyOfPropertyChange(e.PropertyName);

         if (e.PropertyName == nameof(ISingleRouter.IsAutoRotating))
         {
             NotifyOfPropertyChange(nameof(AutoRotateStatusText));
         }
     }

     private void OnRouterNavigationChanged(object sender, EventArgs e)
     {
         NotifyOfPropertyChange(nameof(CurrentView));
         NotifyOfPropertyChange(nameof(CanPrevious));
         NotifyOfPropertyChange(nameof(CanNext));
     }

     protected override void OnDeactivate()
     {
         _router.IsAutoRotating = false;
         base.OnDeactivate();
     }
 }
2、工业级别导航,具有拦截器ITangdaoRouter

使用时与ITangdaoPage配合

XAML Code:

  
  <ContentControl Grid.Row="0" Content="{Binding Router.CurrentView}" />

  
  <StackPanel
      Grid.Row="1"
      HorizontalAlignment="Right"
      Orientation="Horizontal">

      <Button
          Margin="2"
          Command="{Binding GoBackCommand}"
          Content="◄"
          IsEnabled="{Binding Router.CanGoBack}"
          ToolTip="上一页" />
      <Button
          Margin="2"
          Command="{Binding GoForwardCommand}"
          Content="►"
          IsEnabled="{Binding Router.CanGoForward}"
          ToolTip="下一页" />
      <Button
          Margin="5"
          Command="{s:Action GoToVacuumGaugeView}"
          Content="真空表" />
      <Button
          Margin="5"
          Command="{s:Action GoToDigitalSmartGaugeView}"
          Content="数字智能测量仪" />
  </StackPanel>

CS Code:

 public class MainViewModel : BaseDeviceViewModel
 {
     public ITangdaoRouter Router { get; set; }
     public IContainer _container;

     public MainViewModel(ITangdaoRouter router) 
     {
         Router = router;
         _container = container;
         Router.RouteComponent = this;
         Router.RegisterPage<Test1ViewModel>();
         Router.RegisterPage<Test2ViewModel>();
         Router.RegisterPage<Test3ViewModel>();
         GoBackCommand = MinidaoCommand.Create(ExecuteGoBack);
         GoForwardCommand = MinidaoCommand.Create(ExecuteGoForward);
     }

     private void ExecuteGoForward()
     {
         Router.GoForward();
     }

     private void ExecuteGoBack()
     {
         Router.GoBack();
     }

     public ICommand GoBackCommand { get; set; }
     public ICommand GoForwardCommand { get; set; }

     public void GoToTest1View()
     {
         //Router.NavigateTo<Test1ViewModel>();或者
         Router.NavigateTo("Test1ViewModel");
     }

     public void GoToTest2View()
     {
         //Router.NavigateTo<Test2ViewModel>();或者
         Router.NavigateTo("Test2ViewModel");
     }
     public void GoToTest3View()
     {
         //Router.NavigateTo<Test3ViewModel>();或者
         Router.NavigateTo("Test3ViewModel");
     }
   
 }

 [AutoWireView]
 public class Test1ViewModel : DaoViewModelBase, ITangdaoPage
 {
     public string PageTitle => "测试1";

     public bool CanNavigateAway()
     {
         return true;
     }

     public void OnNavigatedFrom()
     {
     }

     public void OnNavigatedTo(ITangdaoParameter parameter = null)
     {
     }
 }
12、时间轮
class Program
{
    static async Task Main(string[] args)
    {
        // 创建时间轮实例
        var timeWheel = new TimeWheel<Student>();
        timeWheel.Start(); // 启动时间轮
        
        // 创建几个学生
        var student1 = new Student { Id = 1, Name = "张三", Grade = 3 };
        var student2 = new Student { Id = 2, Name = "李四", Grade = 2 };
        var student3 = new Student { Id = 3, Name = "王五", Grade = 1 };
        
        Console.WriteLine($"当前时间: {DateTime.Now:HH:mm:ss}");
        
        // 添加任务:5秒后打印学生信息
        await timeWheel.AddTaskAsync(5, student1, async s => 
        {
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 处理学生1: {s}");
            await Task.Delay(100); // 模拟异步工作
        });
        
        // 添加任务:10秒后升级学生年级
        await timeWheel.AddTaskAsync(10, student2, async s => 
        {
            s.Grade++;
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {s.Name}升级到{s.Grade}年级");
            await Task.Delay(100);
        });
        
        // 添加任务:15秒后发送通知
        await timeWheel.AddTaskAsync(15, student3, async s => 
        {
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 发送通知给{s.Name}的家长");
            await Task.Delay(100);
        });
        
        // 防止程序退出
        Console.ReadLine();
    }
}
13、文本监控

在程序启动时注册事件

  protected override void Configure()
  {
      // 启动监控服务
      var monitorService = Provider.GetService<IFileMonitor>();
      monitorService.FileChanged += OnFileChanged;
      monitorService.StartMonitoring();
  }

  private void OnFileChanged(object sender, DaoFileChangedEventArgs e)
  {
      Logger.WriteLocal($"XML 文件变化: {e.FilePath}, 变化类型: {e.ChangeType},变化详情:{e.ChangeDetails},old:{e.OldContent},new:{e.NewContent}");
  }

注册代码

 container.RegisterdaoSingleton(new FileMonitorConfig() 
 {
     MonitorRootPath = ConfigurationManager.AppSettings["MontionPath"],
     IncludeSubdirectories = true,
     MonitorFileTypes = new List<DaoFileType>
         {
             DaoFileType.Xml,
         },
     DebounceMilliseconds = 800,
     FileReadRetryCount = 3
 });
 container.RegisterdaoSingletonFactory<IFileMonitor>(provider =>
 {
     return new FileMonitor
     {
         
     };
 });
14、任务调度器
  TangdaoTaskScheduler.Execute(dao: daoTask =>
  {
      
  });

         
  TangdaoTaskScheduler.Execute(daoAsync: daoTask =>
  {
     
  });

  TangdaoTaskScheduler.Execute(daoAsync => { }, dao => { });
15、Markup的扩展
1、对Combobox进行定制列表
public class ComboboxOptions
{
    public static void SetTheme()
    {
        OptionListExtension.OptionsPool["Accuracy"] = new[] { "X1", "Y1", "X2", "Y2" };
    }
}

在启动时自定义设置自己需要的字典

然后当使用Combobox的下拉列表时候,可缓存直接使用,当多个视图都需要使用同一个下拉列表的时候非常方便

  xmlns:markup="clr-namespace:IT.Tangdao.Core.Markup;assembly=IT.Tangdao.Core"
   <ComboBox x:Name="com" ItemsSource="{markup:OptionList Key=Accuracy}"  />

并且你什么都不写的时候我具有缺省默认数据

 OptionsPool["Default"] = new[] { "全部", "Load", "Upload" };

如果你不想在程序启动使用代码复用,也可以直接写

<ComboBox ItemsSource="{markup:OptionList Values='X1,Y1,X2,Y2'}" />
2、 Bool-String转换器,可以指定显示结果

常规的bool转string需要在ViewModel设置想要动态改变还需要写触发器,我可以自己指定

 <TextBlock Text="{marup:BoolToStringMode Binding={Binding BoolValue}, FalseValue='失败', TrueValue='成功'}" />
3、列表绑定枚举时使用
  <daoMarkup:EnumBindSource x:Key="plcType" EnumType="{x:Type shared:PlcType}" />
4、获取当前控件所在的Window实例
  <Button
      Width="100"
      Height="30"
      Margin="20,20,20,-100"
      HorizontalAlignment="Center"
      Background="{StaticResource OceanBlueBrush}"
      Command="{s:Action ExecuteCancel}"
      CommandParameter="{markup:GetWindow}"
      Content="取消"
      IsCancel="True" />
6、列表中的按钮绑定

在一般情况下绑定比较复杂

对比如下:

<DataGridTemplateColumn Width="200" Header="操作">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Orientation="Horizontal">
                <Button
                   Width="70"
                   Margin="0,0,15,0"
                   Background="LightGreen"
                   Command="{markup:AncestorBinding Path=DataContext.UpdateUserCommand}"
                   CommandParameter="{Binding Id}"
                   Content="修改"
                   FontSize="14"
                   Foreground="Black" />
                <Button
                   Width="70"
                   Background="Red"
                   Command="{Binding DataContext.DeleteUserCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"
                   CommandParameter="{Binding Id}"
                   Content="删除"
                   FontSize="14"
                   Foreground="Black" />
            </StackPanel>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
16、路径处理
场景 用途 如何使用
加载本地资源文件(如JSON、图片、配置) 避免硬编码路径,支持相对解决方案路径 TangdaoPath.Solution().Combine("Assets", "Icons", "logo.png").Build()
日志文件输出路径 确保日志写入到临时目录或指定目录 TangdaoPath.Temp().Combine("logs", "app.log").Build()
用户导出/保存文件 提供默认路径,避免平台差异 TangdaoPath.GetEnvironmentDirectory("EXPORT_DIR").Combine("report.xlsx")
插件加载路径 动态加载插件DLL,避免路径错误 TangdaoPath.Solution().Combine("Plugins", "PluginA.dll").Build()
配置文件路径 支持CI/CD中配置路径注入 TangdaoPath.GetThisFilePath().Parent().Combine("settings.json")
单元测试中模拟路径 避免使用真实文件系统 使用 AbsolutePath 和 RelativePath 构造虚拟路径,不依赖磁盘
1、WPF中加载图片资源
// ViewModel 或代码背后
var imagePath = TangdaoPath
    .Solution()
    .Combine("Assets", "Images", "user_avatar.png")
    .Build();

if (imagePath.FileExists)
{
    AvatarImageSource = new BitmapImage(new Uri(imagePath.Value));
}
2、

1、使用 PathTemplate 动态生成路径

var template = PathTemplate.Create("{Solution}/Exports/{UserId}/{Date}/report.xlsx");

var path = template.Resolve(new
{
    Solution = TangdaoPath.Instance.GetSolutionDirectory().Value,
    UserId = 12345,
    Date = DateTime.Now.ToString("yyyy-MM-dd")
});

// 输出:C:\Projects\MySolution\Exports\12345\2025-10-10\report.xlsx

2、 邮件 / 短信模板

var mailTmpl = PathTemplate.Create(
    "亲爱的 {UserName},您的验证码是 {Code},{Expire} 分钟内有效。");
var body = mailTmpl.Resolve(new { UserName = "张三", Code = "628491", Expire = "5" });
Console.WriteLine(body);
// 输出:亲爱的 张三,您的验证码是 628491,5 分钟内有效。

3、代码片段生成器(T4 替代)

var propTmpl = PathTemplate.Create("public {Type} {Name} { get; set; }");
var props = new[] { new { Type = "string", Name = "FirstName" },
                    new { Type = "int",    Name = "Age" } };
foreach (var p in props)
    Console.WriteLine(propTmpl.Resolve(p));
// 输出:
// public string FirstName { get; set; }
// public int Age { get; set; }
3、缓存解决方案目录,避免重复查找
// 全局初始化一次
var solutionDir = TangdaoPath.GetSolutionDirectory();

// 后续使用
var configPath = solutionDir.Combine("config", "appSettings.json");
var dbPath = solutionDir.Combine("data", "local.db");
4、清理临时文件
var tempDir = TangdaoPath.GetTempDirectory().Combine("MyApp");
if (tempDir.DirectoryExists)
{
    Directory.Delete(tempDir.Value, true);
}
5、文件导出
 public void Export()
 {
     try
     {
         var template = PathTemplate.Create("{Solution}/Exports/{Date}/report.txt");
         var exportPath = template.Resolve(new
         {
             Solution = TangdaoPath.GetSolutionDirectory().Value,
             Date = DateTime.Now.ToString("yyyy-MM-dd")
         });

         Directory.CreateDirectory(exportPath.Parent().Value);
         File.WriteAllText(exportPath.Value, $"Hello Stylet! Exported at {DateTime.Now}");
     }
     catch (Exception ex)
     {
     }
 }
6、创建备份
// 使用 Combine 构建路径
AbsolutePath sourcePath = TangdaoPath
    .GetThisFilePath()
    .Parent()                           // 移除文件名
    .Combine("..")                      // 上级目录
    .Combine("Models")                  // Models 文件夹
    .Combine("User.cs");                // 具体文件

// 使用 Backup 创建备份
 var backup = sourcePath.Backup(".bak");
17、自定义排序

带后期增加接口优化

List<Student> students = new List<Student>
{
    new Student { Id=4, Name = "张三1111", Education = "一本" },
    new Student { Id=5, Name = "李四2222", Education = "大专" },
    new Student { Id=2, Name = "王五3333", Education = "研究生" },
    new Student { Id=1, Name = "张三4444", Education = "一本" },
    new Student { Id=0, Name = "李四5555", Education = "大专" },
    new Student { Id=6, Name = "王五6666", Education = "研究生" },
};

var priority = new Dictionary<string, int>
{
    ["大专"] = 1,
    ["一本"] = 2,
    ["研究生"] = 3
};

var comparer = TangdaoSortProvider.Priority<Student>(s => s.Education, priority);
18、特性

AutoRegisterAttribute:自动注册特性

AutoWireViewAttribute:对ViewModel标注特性,自动关联View和ViewModel

 [AutoRegister(Mode = RegisterMode.Singleton, Order = 6)]
 [AutoWireView]
 public class TaskViewModel : DaoViewModelBase{}

注册的时候,可以指定注册的模式和顺序,数字越小,最先注册

19、弱引用TangdaoWeakEvent
1、MessageReceived处理普通事件
TangdaoWeakEvent.Instance.OnMessageReceived += (sender, obj) => { };
TangdaoWeakEvent.Instance.Publish("Open", Message);
2、OnKeyMessageReceived 处理带Key的事件
TangdaoWeakEvent.Instance.OnKeyMessageReceived += (sender, obj) => { };
TangdaoWeakEvent.Instance.Publish("Open", KeyMessage);
3、OnHandlerTableReceived 处理事件列表
TangdaoWeakEvent.Instance.OnHandlerTableReceived += (sender, obj) => { };
TangdaoWeakEvent.Instance.Publish("Open", HandlerTable);
20、观察者模式事件通知TangdaoMessenger

当时有内部框架的IOC时,不需要外部委托SetResolver,当使用其他的IOC时需要通过SetResolver来解析

TangdaoMessenger.SetResolver(reg => Container.Get(reg.RegisterType) as INoticeObserver);

注册的不同写法:

1、

TangdaoMessenger.Instance.ChainRegister(typeof(LightViewModel), typeof(ElectViewModel), typeof(PressureViewModel));

2、

TangdaoMessenger.Instance.ChainRegister().Add(typeof(LightViewModel)).Add(typeof(ElectViewModel)).Add(typeof(PressureViewModel));

使用场景:

 public class ElectViewModel : BaseViewModel, IMessageObserver
 {
     private string _tag;

     public string Tag
     {
         get => _tag;
         set => SetAndNotify(ref _tag, value);
     }

      public void UpdateNotice(NoticeContext context)
      {
         Tag = context.Tag;
      }
 }

一键通知:

 MessengerContext Context = new MessengerContext();
 Context.CurrentTime = DateTime.Now;
 Context.Tag = "数据改变了";
 TangdaoMessenger.Instance.NotifyAllObservers(Context);

单独通知:

 MessengerContext Context = new MessengerContext();
 Context.CurrentTime = DateTime.Now;
 Context.Tag = "单独数据改变";
 TangdaoMessenger.Instance.NotifyObserverByKey("LightViewModel", Context);

注销通知:

 TangdaoMessenger.Instance.Unregister("Key");

注销所有通知:

TangdaoMessenger.Instance.Unregister();
21、委托表传输机制IActionTable

1、使用时需要保持单例唯一性,这里跟据你使用的IOC决定注册

 container.RegisterdaoSingleton<IActionTable, ActionTable>();

2、带参数使用

 VM1:
 public void TestWindow()
 {
     _actionTable.Register("Open", x =>
     {
         if (x.Result == ActionStatus.Success)
         {
             MessageBox.Show("成功返回");
         }
     });
 }
 VM2:
 public void Open()
 {
     ActionResult result = new ActionResult();
     result.Result = ActionStatus.Success;
     _handlerTable.GetResultHandler("Open").Invoke(result);
 }

3、不带参数使用

 VM1:
 public void TestWindow()
 {
     _actionTable.Register("Open", () =>
     {
         
     });
 }
 VM2:
 public void Open()
 {
     _handlerTable.GetHandler("Open").Invoke();
 }
22、Window管道
 public override void ConfigureWindowPipe(IWindowBuilder windowBuilder)
 {
     windowBuilder.UseGuard<LoginSignGuard>(rules =>
     {
         rules.UseLogin<LoginView>();
         rules.SetCancel(true);
     });
 }
重大迁移通知
旧项 新项
DaoViewModelBase ViewModelBase
IDaoEventAggregator IEventAggregator
DaoEventBase EventBase
IT.Tangdao.Framework.DaoExtension IT.Tangdao.Framework.Extensions
IT.Tangdao.Framework.EventArs IT.Tangdao.Framework.Events
IT.Tangdao.Core.DaoExtension IT.Tangdao.Core.Extensions
IT.Tangdao.Core.EventArs IT.Tangdao.Core.Events
Product Compatible and additional computed target framework versions.
.NET Framework net46 is compatible.  net461 is compatible.  net462 was computed.  net463 was computed.  net47 is compatible.  net471 is compatible.  net472 is compatible.  net48 is compatible.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.1.0 79 9/28/2026
5.0.16 120 6/19/2026
5.0.15 111 6/3/2026
5.0.14 108 6/2/2026
5.0.13 102 5/31/2026
5.0.12 140 5/12/2026
5.0.10 108 5/8/2026
5.0.9 94 5/7/2026
5.0.8 96 5/5/2026
5.0.7 98 4/30/2026
5.0.6 111 4/16/2026
5.0.5 114 4/14/2026
5.0.4 109 4/11/2026
5.0.3 104 4/8/2026
5.0.2 103 4/7/2026
5.0.1 104 4/6/2026
5.0.0 113 3/31/2026
Loading failed

重构命名空间