newbeeui 11.2.8
See the version list below for details.
dotnet add package newbeeui --version 11.2.8
NuGet\Install-Package newbeeui -Version 11.2.8
<PackageReference Include="newbeeui" Version="11.2.8" />
<PackageVersion Include="newbeeui" Version="11.2.8" />
<PackageReference Include="newbeeui" />
paket add newbeeui --version 11.2.8
#r "nuget: newbeeui, 11.2.8"
#:package newbeeui@11.2.8
#addin nuget:?package=newbeeui&version=11.2.8
#tool nuget:?package=newbeeui&version=11.2.8
NewBeeUI
NewBeeUI(NB UI,又叫菜鸟UI) 是基于 Avalonia 的 mvu gui 库,它专门针对不会/不喜欢 axaml 的程序员开发,是一个入门非常简单的跨平台 gui 库。
设计原则:
- 支持 MVU 模式(准确的说,是 VU 模式,M是可选的)
- 支持 NativeAOT
- 支持跨平台
- 尽可能轻量、简单
取名 NewBeeUI/牛逼UI,顾名思义, 是两个意思:
- 一个是菜鸟、新手的意思,对新人极友好,只需要知道极少的知识,就可以开发一般应用了。
- 一个是牛逼的意思,方便组合和扩展,对有经验的人来说,也很方便。
相关库:
nmarkup: fork 自 Avalonia.Markup.Declarative,进行了少量的修改
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 | Versions 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. |
-
net9.0
- Avalonia (>= 11.2.8)
- nmarkup (>= 11.2.8)
- nstyles (>= 11.2.8)
- NStyles.MeterialIcons (>= 11.2.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.