newbeeui 11.2.8

There is a newer version of this package available.
See the version list below for details.
dotnet add package newbeeui --version 11.2.8
                    
NuGet\Install-Package newbeeui -Version 11.2.8
                    
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="newbeeui" Version="11.2.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="newbeeui" Version="11.2.8" />
                    
Directory.Packages.props
<PackageReference Include="newbeeui" />
                    
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 newbeeui --version 11.2.8
                    
#r "nuget: newbeeui, 11.2.8"
                    
#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 newbeeui@11.2.8
                    
#: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=newbeeui&version=11.2.8
                    
Install as a Cake Addin
#tool nuget:?package=newbeeui&version=11.2.8
                    
Install as a Cake Tool

NewBeeUI

NewBeeUI(NB UI,又叫菜鸟UI) 是基于 Avalonia 的 mvu gui 库,它专门针对不会/不喜欢 axaml 的程序员开发,是一个入门非常简单的跨平台 gui 库。

设计原则:

  • 支持 MVU 模式(准确的说,是 VU 模式,M是可选的)
  • 支持 NativeAOT
  • 支持跨平台
  • 尽可能轻量、简单

取名 NewBeeUI/牛逼UI,顾名思义, 是两个意思:

  • 一个是菜鸟、新手的意思,对新人极友好,只需要知道极少的知识,就可以开发一般应用了。
  • 一个是牛逼的意思,方便组合和扩展,对有经验的人来说,也很方便。

相关库:

  • nmarkup: fork 自 Avalonia.Markup.Declarative,进行了少量的修改 NuGet

  • nstyles: 一套可在 NativeAOT 下编译的主题,改自 sukiui。NuGet

  • NStyles.MeterialIcons: 一套可在 NativeAOT 下编译的图标库 NuGet

MVU 开发模式

基础机制

public class HelloView : BaseView
{
    int count = 0;

    protected override object Build()
    {
        return VStack([
                TextBlock().Align(0).Text(() => $"Click {count} times"),
                TextButton("Hello").OnClick(_=>{
                    count++;
                    this.UpdateState();
                })
            ]).Margin(20);
    }
}

NewBeeUI 只约定了状态的更新动作,状态更新内容由数据绑定来触发。通过扩展方法注册 labmda 表达式的属性(上例中 .Text(() ⇒ $"Click {count} times")),会自动添加绑定,当调用 UpdateState() 时,会重新计算表达式的值,并更新 UI。

集合控件

通过 ItemTemplate 扩展方法,可以很方便的创建集合式控件,示例如下:

protected Control BuildMenu()
{
    Control BuildMenuItem(RoutedViewBuilder builder)
    {
        if(builder.IsEmpty())
        {
            return Border().Width(100).Height(1).Align(null,0).Margin(10,0)
                .Background(Brushes.Gray).IsHitTestVisible(false);
        }
        else
        {
            return Grid(cols: "40,100")
                    .Children(
                    [
                        Border(builder.Icon?.Align(0,0)),
                        new TextBlock().Text(builder.Name).Col(1)
                    ]);
        }
    }

    var listBox = new ListBox()
        .HorizontalAlignment(HorizontalAlignment.Center)
        .ItemsSource(() => GetMenuItems())
        .ItemTemplate<RoutedViewBuilder, ListBox>(BuildMenuItem)
        .OnSelectionChanged((e) =>
        {
            if (e.FirstItem() is RoutedViewBuilder builder)
            {
                if(builder.IsEmpty() == false)
                    Router?.Goto(builder);    // 跳转
            }
        });

    return listBox;
}

public List<RoutedViewBuilder> GetMenuItems()
{
    return
    [
        new RoutedViewBuilder("Dashboard", () => new DashboardView())
            .Icon(ViewDashboardOutlineIcon.Instance),
        new RoutedViewBuilder("Hello", () => new HelloView()),
        new RoutedViewBuilder("按钮", () => new ButtonsView()),
        new RoutedViewBuilder("Windows", () => new WindowsView()),
        new RoutedViewBuilder("Test", () => new TestView()),
        new RoutedViewBuilder("Overlay", new OverlayView())
    ];
}

观察机制

默认情况下,UpdateState 只更新当前 view 的状态。可通过 Observe 方法,实现不同 view 之间的级联状态更新。当 A Observe B 时,如果 B 调用了 UpdateState 方法,也会触发 A 调用 UpdateState。

循环绑定也没关系,内部会自动处理,保障 UpdateState 触发时,调用链中的相关 view 只调用一次 UpdateState 方法。

状态初始化及更新

NewBeeUI 系统对什么是状态,并没有约定。你可以在 build 发生之前,通过合适的方式,进行初始化动作。

也可以利用 SetState 扩展方法,来进行状态的初始化(或更新),方法原型:

public static TViewModel SetState<TViewModel>(this TViewModel targetView, Action<TViewModel> action, bool setOnce = true) where TViewModel : MvuView

如果通过 SetState 设置了 action,则在 build view 之前,会调用该 action。如果 setOnce = false,则,在每次 UpdateState 调用时,都会调用该 action(如果状态依赖于外部环境,这样做,可以在每次更新之前,同步数据,确保状态保持最新)。

一些简化写法

- Alignment 的简化

通过 Align 扩展方法,简化对 HorizontalAlignment 和 VerticalAlignment 的设置,自动将 int? 转换为 HorizontalAlignment 或 VerticalAlignment。对该值的约定如下:

  • <0: 近 (Left/Top)
  • 0: 居中 (Center)
  • >0: 远 (Right/Bottom)
  • null: Stretch

例如:

TextBlock("XXX").Align(-1,0),
- Grid 的简化

BaseView 提供 HGrid 和 VGrid 两个方法,可以快速创建单行水平或单列垂直的 Grid 布局,每个元素不用指定 Row 或 Col,将自动根据它的位置指定对应的 Row 或 Col。元素也可以为 null。例如:

HGrid("100,*,300",[
    TextBlock("AAA"),
    null,
    TextBlock("CCC"),
]),
- Stack 的简化

BaseView 提供了 HStack 和 VStack 两个方法,简化对 Stack Panel 的使用。

- WhenXXXXX 扩展方法

Avalonia 的很多事件的参数比较复杂,对于常用的事件,NewBeeUI 提供了 WhenXXXXX 扩展方法,简化对这些事件的使用。WhenXXXXX 中传入 Action<T> 中的 T,是当前控件:

public static T WhenLoaded<T>(this T ctrl, Action<T> action) where T : Control
{
    ctrl.OnLoaded((Avalonia.Interactivity.RoutedEventArgs _) => action(ctrl));
    return ctrl;
}

public static T WhenClick<T>(this T ctrl, Action<T> action) where T : Control
{
    ctrl.OnTapped(_ => action(ctrl));
    return ctrl;
}

public static T WhenDoubleClick<T>(this T ctrl, Action<T> action) where T : Control
{
    ctrl.OnDoubleTapped(_ => action(ctrl));
    return ctrl;
}
- OnClick 的简化

增加了以 Action 为参数的 OnClick 扩展方法,可以将无参函数直接传入,可以直接写 a.OnClick(foo) 而不需要写 a.OnClick(_ ⇒ foo()) 了。

路由机制

通过 ViewRouter 提供了路由机制。支持两种路由机制:

  • 跳转到 IRoutedViewBuilder 构建的界面
    • 通过 Goto(IRoutedViewBuilder locator, bool remember = false) 直接跳转到新构建的界面
  • 先通过注册路径,再跳转跳转,如果路径不存在,则显示错误界面
    • 注册:通过 AddRoute 扩展方法将 IRoutedViewBuilder 注册到指定的路径
    • 跳转: 通过 Goto(string route, bool remember = false) 来跳转

提供了 RoutedViewBuilder 实现接口 IRoutedViewBuilder,可直接传入要显示的 view,或产生 view 的方法,如果是后者,则,在路由器跳转时,再创造待显示的 view。

如果跳转时,remember 为 true,则会将路由记录到历史中,用户可以通过 GoBack() 方法返回。

ViewRouter 提供了下面事件,响应路由的改变:

    public Action<ViewRouteUpdateEvent>? OnRouteUpdate { get; set; }
    public Action<RoutedView>? OnViewLeave { get; set; }

桌面端应用示例

在 avalonia 项目中加入 nstyles 主题:

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="NewBeeUI.Demo.App"
             xmlns:ns ="nstyles"
             RequestedThemeVariant="Default">

    <Application.Styles>
        <FluentTheme />
        <ns:NTheme />
    </Application.Styles>
</Application>

启动时,指定相关的 View:

using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using NewBeeUI.Demo.Views;

namespace NewBeeUI.Demo;

public partial class App : Application
{
    public override void Initialize()
    {
        AvaloniaXamlLoader.Load(this);
    }

    public override void OnFrameworkInitializationCompleted()
    {
        // Line below is needed to remove Avalonia data validation.
        // Without this line you will get duplicate validations from both Avalonia and CT
        BindingPlugins.DataValidators.RemoveAt(0);

        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            new MainView().ShowDialog();
        }
        else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
        {
            singleViewPlatform.MainView = new MainView();
        }

        base.OnFrameworkInitializationCompleted();
    }
}

下面是带有完整的菜单及路由跳转的窗口示例:

using NStyles.MeterialIcons;

namespace NewBeeUI.Demo.Views;

public class MainView : BaseView, IWindowView
{
    ViewRouter? Router;

    public WindowInfo WindowInfo { get; }

    protected WindowInfo CreateWindowInfo()
    {
        var window = new NWindowInfo()
        {
            WindowTitle = "标题",
            CanResize = true,
            CanMinimize = true,
            CanClose = true,
            WindowMinWidth = 800,
            WindowMinHeight = 600,
            WindowWidth = 800,
            WindowHeight = 600,
            IsWindowAnimationEnable = true,
            Subtitle = this.BuildSubtitle(),
            RightWindowsBar = new TextBlock()
                .Text("这里可以放置你的按钮")
                .FontSize(14)
                .Margin(10, 0)
                .HorizontalAlignment(HorizontalAlignment.Right)
                .VerticalAlignment(VerticalAlignment.Center)
        };
        window.RightWindowsBar = HStack([
            IconButton(MessageSettingsOutlineIcon.Instance, "设置", ToolTipPosition.Top).Width(24).Height(24).OnClick(_=>{
                new SettingView().ShowDialog("设置");
            }),
            ]);
        return window;
    }

    protected Control BuildSubtitle()
    {
        return new TextBlock().Ref(out SubtitleTextBlock)!
            .FontSize(14)
            .Margin(10, 0)
            .HorizontalAlignment(HorizontalAlignment.Center)
            .VerticalAlignment(VerticalAlignment.Center);
    }

    TextBlock? SubtitleTextBlock;

    public MainView():base()
    {
        this.WindowInfo = CreateWindowInfo();
    }

    protected override object Build()
    {
        var router = BuildViewRouter().Ref(out Router)!;

        return Grid(cols: "Auto, *")
                .Children([
                    BuildMenu(),
                    router.Col(1)
                ]);
    }

    protected ViewRouter BuildViewRouter()
    {
        var r = new ViewRouter().Align(null, null);
        r.OnRouteUpdate = (e) =>
        {
            if (SubtitleTextBlock != null)
            {
                SubtitleTextBlock.Text = $"{e.New?.Name ?? "No Title"}";
            }
        };
        return r;
    }

    protected Control BuildMenu()
    {
        Control BuildMenuItem(RoutedViewBuilder builder)
        {
            if(builder.IsEmpty())
            {
                return Border().Width(100).Height(1).Align(null,0).Margin(10,0)
                    .Background(Brushes.Gray).IsHitTestVisible(false);
            }
            else
            {
                return Grid(cols: "40,100")
                        .Children(
                        [
                            Border(builder.Icon?.Align(0,0)),
                            new TextBlock().Text(builder.Name).Col(1)
                        ]);
            }
        }

        var listBox = new ListBox()
            .HorizontalAlignment(HorizontalAlignment.Center)
            .ItemsSource(() => GetMenuItems())
            .ItemTemplate<RoutedViewBuilder, ListBox>(BuildMenuItem)
            .OnSelectionChanged((e) =>
            {
                if (e.FirstItem() is RoutedViewBuilder builder)
                {
                    if(builder.IsEmpty() == false)
                        Router?.Goto(builder);    // 跳转
                }
            });

        return listBox;
    }

    public List<RoutedViewBuilder> GetMenuItems()
    {
        return
        [
            new RoutedViewBuilder("Dashboard", () => new DashboardView())
                .Icon(ViewDashboardOutlineIcon.Instance),
            new RoutedViewBuilder("按钮", () => new ButtonsView()),
            new RoutedViewBuilder("Windows", () => new WindowsView()),
            new RoutedViewBuilder("Test", () => new TestView()),
            new RoutedViewBuilder("Overlay", new OverlayView())
        ];
    }
}
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos 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
11.3.0.19 242 8/7/2025
11.3.0.16 432 7/21/2025
11.3.0.15 419 7/21/2025
11.3.0.14 335 7/21/2025
11.3.0.9 154 6/14/2025
11.3.0.1 161 5/29/2025
11.2.8 113 9/23/2025
9.0.0.1 138 5/29/2025