YoYo.Toolkit 1.0.1

dotnet add package YoYo.Toolkit --version 1.0.1
                    
NuGet\Install-Package YoYo.Toolkit -Version 1.0.1
                    
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="YoYo.Toolkit" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="YoYo.Toolkit" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="YoYo.Toolkit" />
                    
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 YoYo.Toolkit --version 1.0.1
                    
#r "nuget: YoYo.Toolkit, 1.0.1"
                    
#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 YoYo.Toolkit@1.0.1
                    
#: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=YoYo.Toolkit&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=YoYo.Toolkit&version=1.0.1
                    
Install as a Cake Tool

YoYo.Toolkit

面向 WPF 桌面应用的轻量工具集,为上位机、工控、仪器等「界面 + 设备通信」类项目准备。

全部基于原生 WPF 实现,除 System.Management 外无第三方依赖;不引入 MVVM 框架,也不强制目录约定,可按需取用其中任意一块。

  • 目标框架:net6.0-windows、net8.0-windows
  • 平台:AnyCPU / x64 / x86
  • 许可证:MIT

特性一览

模块 命名空间 内容
导航 YoYo.Toolkit.Navigations BaseNavigation 页面导航,含进入/离开生命周期钩子与强类型参数
窗口 YoYo.Toolkit.Services WindowService 多窗口注册、跨线程关闭
网络 YoYo.Toolkit.Net.Http、.Pings BaseRequest / BaseResponse 请求基类;NetworkPing 连通性检测
控件 YoYo.Toolkit.Controls.* 树形下拉、水印输入框、图标选项、描边文字
进度 YoYo.Toolkit.Controls.Progress ProgressIndicator 20 种动效 + 确定态,StepTracker 流程步骤条,自带主题字典与浅/深令牌,开箱即用且可整体重写
附加行为 YoYo.Toolkit.Input、.Animations PasswordHelper 密码绑定;OSKHelper 触摸屏软键盘
通知 YoYo.Toolkit.Notifies TMessageBox 可换肤消息框
系统 YoYo.Toolkit.System、.WinApis 硬盘序列号、主板 ID、机器名;Kernel32.CopyMemory
加密 YoYo.Toolkit.CryptoServices MD5Helper 摘要;RSAHelper AES/RSA 加解密
工具 YoYo.Toolkit.Time、.Images、.Converters Unix 时间戳、位图加载、常用值转换器

安装

dotnet add package YoYo.Toolkit --version 1.0.1

或在 PackageReference 中指定版本:

<PackageReference Include="YoYo.Toolkit" Version="1.0.1" />

宿主项目需启用 WPF:

<PropertyGroup>
  <UseWPF>true</UseWPF>
</PropertyGroup>

快速开始

1. 页面导航

导航以 IGetService 为底座,视图与 ViewModel 都由外部容器提供,包本身不绑定任何 DI 框架。

public sealed class ShellService : IGetService
{
    private readonly IServiceProvider _provider;
    private FrameworkElement? _view;

    public ShellService(IServiceProvider provider) => _provider = provider;

    public object GetService(Type? t) => _provider.GetService(t!)!;
    public FrameworkElement? GetView() => _view;
    public void SetView(FrameworkElement? view) => _view = view;
}

public sealed class MainNavigation : BaseNavigation
{
    public MainNavigation(IGetService service) : base(service) { }

    public override string NavigationName => "Main";

    public override List<NavigateModel> Navigates { get; } = new()
    {
        new NavigateModel
        {
            ViewType = "HomeView",
            Type = typeof(HomeView),
            VMType = typeof(HomeViewModel),
            Name = "首页",
        },
        new NavigateModel
        {
            ViewType = "DeviceView",
            Type = typeof(DeviceView),
            VMType = typeof(DeviceViewModel),
            Name = "设备",
        },
    };

    public override void InitView() { }
    public override void LogWrite(string? msg) { }
    public override void LogWrite(string? msg, Exception? exception) { }
}

页面 ViewModel 实现 INavigateIn / INavigateIn<T> / INavigateOut,即可在切换时收到回调:

public sealed class DeviceViewModel : INavigateIn<string>, INavigateOut
{
    public void NavigateIn(string? deviceId) { /* 初始化并加载 deviceId */ }
    public void NavigateOut() { /* 停止轮询、释放资源 */ }
}

// 跳转
navigation.GoTo("DeviceView", "SN-0001");
navigation.GoTo("HomeView");

2. HTTP 请求

继承 BaseRequest 描述接口,继承 BaseResponse 描述返回,序列化方式可自行覆盖。

public sealed class LoginRequest : BaseRequest
{
    public override string RootUrl => "https://api.example.com";
    public override string BusinessUrl => "/api/login";
    public override HttpMethodTypes Method => HttpMethodTypes.POST;
    public override Type ResponseType => typeof(LoginResponse);

    public string User { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;

    public override void SerializeObject()
        => Data = JsonSerializer.Serialize(new { User, Password });
}

public sealed class LoginResponse : BaseResponse
{
    public string Token { get; private set; } = string.Empty;

    public override void DeserializeObj()
    {
        using var doc = JsonDocument.Parse(Data);
        var root = doc.RootElement;
        IsOk = root.GetProperty("code").GetInt32() == 0;
        Msg = root.TryGetProperty("msg", out var msg) ? msg.GetString() ?? string.Empty : string.Empty;
        if (IsOk && root.TryGetProperty("token", out var token))
            Token = token.GetString() ?? string.Empty;
    }
}

异步调用(内部使用 BackgroundWorker,回调回到 UI 线程):

var request = new LoginRequest { User = "admin", Password = "123456" };
request.DoServiceAsyncCompleted += response => { /* 处理结果 */ };
request.Error += (message, ex) => { /* 统一错误处理 */ };
request.DoWorkerAsync();

// 同步调用
var syncResponse = request.DoWorkerSync();

支持的请求方式:GET、POST、PUT、DELETE,以及带 Body 的 GetBody。鉴权头可在 SetHeader(HttpClient) 中统一注入;请求前后的等待界面通过重写 ShowProcess() / CloseProcess() 接管,IsShowProcess 控制是否启用。

3. 控件

<Window xmlns:yy="clr-namespace:YoYo.Toolkit.Controls.Text;assembly=YoYo.Toolkit"
        xmlns:busy="clr-namespace:YoYo.Toolkit.Controls.BusyIndicators;assembly=YoYo.Toolkit"
        xmlns:btn="clr-namespace:YoYo.Toolkit.Controls.Button;assembly=YoYo.Toolkit">
  <StackPanel>
    
    <yy:PlaceholderTextBox PlaceHolderText="请输入样本编号" />

    
    <busy:TProgressRing IsActive="True" Width="36" Height="36" />

    
    <btn:IconRadioButton Icon="&#xE7BA;" Content="运行" GroupName="Mode" />

    
    <yy:OutlineText Text="SYSTEM READY" StrokePosition="Outside" />
  </StackPanel>
</Window>

树形下拉要求节点实现 ITreeModel(DisplayName、HasChildren()),选中后可通过 SelectCommand 回传节点:

<combo:TreeComboBox ItemsSource="{Binding Devices}"
                    IsDorpDown="True"
                    IsOnlyLastNode="True"
                    SelectCommand="{Binding SelectDeviceCommand}" />

注意:除 Controls.Progress 之外,本包不内置其它控件的主题资源字典。TProgressRing、IconRadioButton 等通过 DefaultStyleKey 查找样式的控件,需要在宿主应用中提供 Style / ControlTemplate,否则控件不会呈现内容。OutlineText、PlaceholderTextBox 以代码绘制为主,可直接使用。进度控件的样式与模板随包提供(Themes/Generic.xaml),merge 后即可用,详见 8. 进度控件。

4. 消息框

TMessageBox 不依赖系统 MessageBox,外观由你提供的窗口决定:

// 启动时注册弹窗实现,构造函数须为 (string caption, string msg, MessageLevel level)
TMessageBox.Init(typeof(MyMessageDialog));

var result = TMessageBox.ShowMsg("提示", "确认开始运行?", TMessageBox.MessageLevel.Question);
if (result == TMessageBox.TMessageBoxResult.OK)
{
    // ...
}

MyMessageDialog 继承 BaseMessageDialog,在关闭前给 MessageBoxResult 赋值即可。

5. 设备与授权

SystemHelper.Init();                       // 读取硬盘序列号、主板 ID、机器名
var diskId = SystemHelper.HardDiskID;      // 可用于授权绑定
var boardId = SystemHelper.ServerId;
var machine = SystemHelper.ComputerName;

var reachable = await NetworkPing.PingHost("192.168.1.10") is not null;

var md5 = MD5Helper.Md5("hello");
var cipher = RSAHelper.AESEncrypt("plain-text", "your-key");

NetworkPing.PingHost 返回 PingReply?,不可达时返回 null 而不抛异常。 注意 PingReply.Options 可能为 null(本机回环、IPv6 链路本地地址、部分虚拟网卡只回 ICMP 不带 IP 选项), 直接取 reply.Options.Ttl 会抛 NullReferenceException——工具内部已做空值保护,取值失败时退化为占位符。

加解密示例仅作接口参考。用于生产环境前,请自行确认密钥管理、IV 与填充模式是否符合你的安全要求。

6. 密码框绑定

<PasswordBox input:PasswordHelper.Attach="True"
             input:PasswordHelper.Password="{Binding Password, Mode=TwoWay}" />

7. 值转换器

<ResourceDictionary xmlns:conv="clr-namespace:YoYo.Toolkit.Converters;assembly=YoYo.Toolkit">
  <conv:BoolToVisibilityConverter x:Key="BoolToVis" />
  <conv:BoolToReverseConverter x:Key="BoolReverse" />
  <conv:NullToVisibilityConverter x:Key="NullToVis" />
</ResourceDictionary>

8. 进度控件

进度控件是唯一自带主题资源的模块:Themes/Generic.xaml 里两个隐式样式在宿主 merge 后自动生效,开箱即用、无需写样式,同时保留四条自上而下的重写通道。

<Window xmlns:p="clr-namespace:YoYo.Toolkit.Controls.Progress;assembly=YoYo.Toolkit">
  <StackPanel>
    
    <p:ProgressIndicator Kind="Arc" IsActive="True" Width="40" Height="40" />
    <p:ProgressIndicator Kind="Signal" IsActive="True" StatusText="正在采集" Width="190" Height="44" />

    
    <p:ProgressIndicator Kind="FlowBar" IsIndeterminate="False"
                         Minimum="0" Maximum="100" Value="62"
                         Width="180" Height="20" />

    
    <p:StepTracker Steps="{Binding Steps}" CurrentIndex="{Binding CurrentStep}" />
  </StackPanel>
</Window>

StepTracker 默认 HorizontalAlignment="Stretch":每个步骤占一个等宽单元格(内部是 UniformGrid + 左右半段连接线),步数与标签长度变化都不会撑破容器,也不会把末步文字裁掉。竖排时把 Orientation 设为 Vertical 即可,模板会自动切换成"节点在上、标签在下"的项模板。

20 种不确定动效
分组 Kind
环形(12) Arc 旋转弧环、MarchingRing 行进虚线环、DualRing 对流双环、GlowRing 流光环、Sonar 声纳波纹、Radar 雷达扫描臂、Breathing 呼吸光球、Petal 花瓣绽放、Orbit 轨道点、TickRing 刻度游标环、Comet 流星尾迹、DotMatrix 点阵涟漪
条形与波形(6) FlowBar 流动扫光条、CounterFlow 对向游走、Scan 屏幕扫描、Skeleton 骨架扫光、Wave 行波线、Signal 信号带
行内小型(2) BounceDots 三点脉动、Typing 打字省略号

其中 14 套是纯声明式(模板里的 Storyboard 推导动画),6 套由 ProgressPainter 自绘参数化阵列(Signal / Ticks / TicksProgress / Wave / Matrix / Comet),阵列的相位由 Phase 属性驱动,因此新增一种阵列只需加一个 Render* 方法和一个枚举值,不必再写一套 XAML。

确定态

IsIndeterminate="False" 时按 Minimum / Maximum / Value 计算 Percent。内置三套确定态模板:FlowBar.Determinate(条形)、Arc.Determinate(环形弧)、TickRing.Determinate(分段刻度),中央自动显示百分比。其它 Kind 会自动回落到这三者之一(DeterminateFallback)。

边界行为:0 时弧线与分段全部不点亮、条形宽度为 0;1 时整圆闭合;Maximum == Minimum 时 Percent 返回 0,不抛除零异常。

状态与动效强度

State 走 VisualStateManager,状态组固定名为 ProgressStates,成员为 Idle / Running / RunningReduced / Success / Error / Paused。

  • IsActive 是 State 的便捷开关:State 仍为 Idle 而 IsActive="True" 时按 Running 处理。
  • Motion="Reduced" 走 RunningReduced 分支,模板里只放静态态即可实现「减少动效」的无障碍降级。
  • Success / Error 用于跑完的收尾态,兜底模板会显示勾 / 叉。
换肤:只换令牌,不动模板
<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      
      <ResourceDictionary Source="pack://application:,,,/YoYo.Toolkit;component/Themes/Progress.Tokens.Dark.xaml" />
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

令牌分四组,浅深两版键名完全一致:Progress.Brush.*(Accent / AccentSoft / Secondary / Track / Guide / Success / Error / OnAccent / Surface / Ink / InkSecondary / InkDim)、Progress.Thickness.*、Progress.Duration.*、Progress.Opacity.* / Progress.Size.* / Progress.Font.Caption。

只想改主色而不换整套皮肤时,直接在宿主资源里覆盖单个键,或对单实例设 AccentBrush:

<p:ProgressIndicator Kind="Arc" IsActive="True" AccentBrush="#B4700F" />
自定义模板的四条通道

按优先级从高到低,命中即止:

通道 做法 适用
1. 显式 Template <p:ProgressIndicator.Template> 或样式 Setter 单处特例
2. TemplateKey TemplateKey="My.BrandLoading",指向宿主任意 ControlTemplate 设计稿新增动效,不扩充枚举
3. 覆盖 Progress.Template.{Kind}.{Indeterminate\|Determinate} 在宿主资源里定义同名键 整套替换某个 Kind
4. 覆盖 Progress.Template.Default 兜底模板 全局统一风格

第 3、4 条能生效,靠的是 WPF 对 MergedDictionaries 的倒序查找:ProgressTheme 把自带的模板字典 Insert(0, ...) 插到宿主字典列表最前面,宿主后 merge 的同名资源因此优先命中。

自己写模板时遵守三条约定即可正常驱动动画:

<ControlTemplate x:Key="My.BrandLoading" TargetType="{x:Type p:ProgressIndicator}">
  <Grid x:Name="PART_Root" Background="Transparent">
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="ProgressStates">
        <VisualState x:Name="Idle" />
        <VisualState x:Name="Running">
          <Storyboard>
            <DoubleAnimation Storyboard.TargetName="Spin"
                             Storyboard.TargetProperty="(RotateTransform.Angle)"
                             From="0" To="360"
                             Duration="{DynamicResource Progress.Duration.Base}"
                             RepeatBehavior="Forever" />
          </Storyboard>
        </VisualState>
        <VisualState x:Name="RunningReduced" />
        <VisualState x:Name="Success" />
        <VisualState x:Name="Error" />
        <VisualState x:Name="Paused" />
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <Ellipse Width="40" Height="40" StrokeThickness="3"
             Stroke="{Binding AccentBrush, RelativeSource={RelativeSource TemplatedParent}}"
             StrokeDashArray="2 3" RenderTransformOrigin="0.5,0.5">
      <Ellipse.RenderTransform>
        <RotateTransform x:Name="Spin" />
      </Ellipse.RenderTransform>
    </Ellipse>
  </Grid>
</ControlTemplate>
  1. 根元素必须是 Grid(或任意 FrameworkElement),第一列放视觉、第二列放状态文案。
  2. 状态组必须叫 ProgressStates,六个状态名一个都不能少——VisualStateManager 找不到对应状态时会静默跳过,动画看起来就「不动」。
  3. Storyboard.TargetProperty 指向 RotateTransform / ScaleTransform / TranslateTransform 时,路径要带类型限定符((RotateTransform.Angle) 而不是 Angle)。不带限定符在部分模板里能过,但换到另一套变换就会在 OnApplyTemplate 阶段抛 无法解析属性路径中的所有属性引用。

⚠️ 命名空间冲突:本包根命名空间是 YoYo.Toolkit,其下已有 YoYo.Toolkit.System 等子命名空间。宿主工程若命名为 YoYo.Toolkit.Sample、YoYo.Toolkit.Demo 这类以 YoYo.Toolkit 为前缀的名字,XAML 编译时会把你写的 System.Windows.* 解析到 YoYo.Toolkit.System,报 CS0234: 命名空间"YoYo.Toolkit.System"中不存在类型或命名空间名"Windows"。宿主工程请避开 YoYo.Toolkit.* 前缀。

许可证

MIT © 2025 在梦想路上狂奔的蜗牛

Product Compatible and additional computed target framework versions.
.NET net6.0-windows7.0 is compatible.  net7.0-windows was computed.  net8.0-windows was computed.  net8.0-windows7.0 is compatible.  net9.0-windows was computed.  net10.0-windows 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
1.0.1 0 11/15/2026
1.0.0 50 11/13/2026

1.0.1:完善示例程序与双主题令牌,进度控件补充 20 种不确定动效与确定态展示,修正 TreeComboBox、NetworkPing、WindowService 若干细节;README 增加界面预览。含导航框架、HTTP 请求基类、WPF 控件集、设备信息与加密工具;进度控件自带浅/深令牌与可覆盖模板。