WPF之MVVM框架Stylet使用

2025-05-05  本文已影响0人  小羊爱学习

介绍:

Stylet是一款比较轻量级的MVVM框架,适合一些小型或中型项目上使用,上手比Prism快,大家根据具体情况选择使用。

地址

Github地址
使用说明

Nuget包

创建启动项Bootstrapper

    public class Bootstrapper : Bootstrapper<MainShellViewModel>
    {
        /// <summary>
        /// ioc容器注册
        /// </summary>
        /// <param name="builder"></param>
        protected override void ConfigureIoC(IStyletIoCBuilder builder)
        {
            base.ConfigureIoC(builder);
            // 注册其他服务
        }

        /// <summary>
        /// 其他配置项
        /// </summary>
        protected override void Configure()
        {
            base.OnStart();
        }

重新设置Application的启动项

这里我们有两种方式设置启动项,推荐使用第一种,因为ApplicationLoader 是Stylet提供的专用加载器,完全遵循Stylet的设计规范,可以避免手动调用可能导致的时序问题。

<Application x:Class="StyletDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:StyletDemo"
             xmlns:s="https://github.com/canton7/Stylet" Startup="Application_Startup">
    <Application.Resources>
        <s:ApplicationLoader>
            <s:ApplicationLoader.Bootstrapper>
                <local:Bootstrapper/>
            </s:ApplicationLoader.Bootstrapper>
        </s:ApplicationLoader>
    </Application.Resources>
</Application>
    public partial class App : Application
    {
        public App()
        {

        }
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            //var bootstrapper = new Bootstrapper();
            //bootstrapper.Start(e.Args);
        }
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            var bootstrapper = new Bootstrapper();
            bootstrapper.Start(e.Args);
        }
    }

后面使用到ViewModel需要继承Conductor<IScreen>.Collection.OneActive时,这种方式就会报错The ViewManager resource is unassigned. This should have been set by the Bootstrapper,因此不推荐第二种方式。

image.png
Stylet的网站也可以看到,人家也是使用第一种方式的。

使用

<Window x:Class="StyletDemo.MainShellView"
        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"
        xmlns:local="clr-namespace:StyletDemo"
        mc:Ignorable="d"
        xmlns:s="https://github.com/canton7/Stylet" 
        d:DataContext="{d:DesignInstance Type=local:MainShellViewModel}"
        Title="MainShellView" Height="500" Width="800">
    <TabControl>
        <TabItem Header="Step 1">
            <Canvas>
                <ProgressBar Value="{Binding ProgressValue}" Visibility="{Binding ProgressVisibility}" Maximum="100" Height="26" Width="213" Canvas.Left="86" Canvas.Top="42"/>
                <CheckBox Content="Show" IsChecked="{Binding IsProgressShow}" Canvas.Left="86" Canvas.Top="98"/>
                <Slider Value="{Binding ProgressValue}" Maximum="100" Canvas.Left="86" Canvas.Top="142" Width="213"/>


                <TextBox Text="{Binding InputString, UpdateSourceTrigger=PropertyChanged}" Canvas.Left="86" Canvas.Top="234" Width="213" Height="20"/>
                <TextBlock  Text="{Binding OutputString}" Canvas.Left="86" Canvas.Top="275"/>
                <Button Content="Show" Command="{s:Action ShowString}" Canvas.Left="86" Canvas.Top="314" Width="85" Height="31"/>

                <ListBox ItemsSource="{Binding StringList}" SelectedItem="{Binding SelectedString}" Canvas.Left="518" Canvas.Top="39" Width="121" Height="147"/>
                <Button Content="Add String" Command="{s:Action AddString}" Canvas.Left="518" Canvas.Top="204" Width="121" Height="26"/>
                <Button Content="Delete String" Command="{s:Action DeleteString}" Canvas.Left="518" Canvas.Top="244" Width="121" Height="26"/>

                <TextBox TextChanged="{s:Action TextChanged}" Width="213" Canvas.Left="86" Canvas.Top="380"/>
            </Canvas>
        </TabItem>
    </TabControl>
</Window>
    public class MainShellViewModel : Stylet.Screen
    {
        public int ProgressValue { get; set; }
        public bool IsProgressShow { get; set; } = true;
        public Visibility ProgressVisibility => IsProgressShow ? Visibility.Visible : Visibility.Collapsed;


        public string? InputString { get; set; }
        public string? OutputString { get; set; }
        public void ShowString()
        {
            OutputString = $"Your string is : {InputString}";
        }
        //在方法名称前加一个Can表示防卫属性,通过CanShowString属性可以控制该按钮的IsEnabled状态。
        public bool CanShowString => !string.IsNullOrEmpty(InputString);


        public BindableCollection<string> StringList { get; set; } = new BindableCollection<string>();
        public string? SelectedString { get; set; }
        public void AddString()
        {
            StringList.Add($"Item{StringList.Count + 1}");
        }
        public void DeleteString()
        {
            if (SelectedString != null)
            {
                StringList.Remove(SelectedString);
            }

        }
        public bool CanDeleteString => SelectedString != null;


        public void TextChanged()
        {
            Debug.WriteLine("TextChanged");
        }
    }

注意点:
Stylet的Command命令或者其他自定义命令实现,不是直接Bingding了,而是使用Action来绑定。在方法名称前加一个Can表示防卫属性,通过CanShowString属性可以控制该按钮的IsEnabled状态。
某些控件没有Command属性,也是用同样方法调用:

<TextBox TextChanged="{s:Action TextChanged}" IsEnabled={Binding IsTextBoxEnabled} />

public void TextChanged()
{
      Debug.WriteLine("TextChanged");
}

此时,防卫属性是不能用的,如果需要,可以定义一个普通的bool类型变量Binding到控件的IsEnabled属性即可。

服务注册

启动项的ConfigureIoC方法里面,提供服务的注册。不过它自带的IOC容器可选生命周期比较有限,没有prism或者asp.netcore自带的容器丰富。不过注册的写法看起来很直观,builder.Bind<接口>().To<实现>().生命周期模式。

public class Bootstrapper : Bootstrapper<MainShellViewModel>
{
    /// <summary>
    /// ioc容器注册
    /// </summary>
    /// <param name="builder"></param>
    protected override void ConfigureIoC(IStyletIoCBuilder builder)
    {
        base.ConfigureIoC(builder);
        // 注册其他服务
        builder.Bind<IEmailService>().To<EmailService>().InSingletonScope();
         //builder.Bind<IEmailService>().ToAbstractFactory();
    }

    /// <summary>
    /// 其他配置项
    /// </summary>
    protected override void Configure()
    {
        base.OnStart();
    }
}

ToAbstractFactory

    public interface IEmailService
    {
        SecondShellViewModel SecondShellViewModel();
        string GetName(string str)
        {
            return "123";
        }
    }
        private void ApplyParam(string? obj)
        {
           SecondShellViewModel viewmodel =  _emailService.SecondShellViewModel();
            var str = _emailService.GetName("1111");
            Debug.WriteLine(str);
        }

这样我们就可以根据项目情况,决定要不要再去创建接口的实现。

依赖注入使用

为方便功能演示,我们再次创建MainView和MainViewModel来演示

<Window x:Class="StyletDemo.MainView"
        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"
        xmlns:local="clr-namespace:StyletDemo"
        xmlns:s="https://github.com/canton7/Stylet"
        mc:Ignorable="d"
        Title="MainView" Height="450" Width="800">
    <Grid>
        <Button Width="100" Height="50" Content="按钮" Command="{s:Action BtnClick}"/>
    </Grid>
</Window>
namespace StyletDemo
{
    public class MainViewModel : Stylet.Screen
    {
        private readonly IWindowManager _windowManager;
        private readonly IEmailService _emailService;
        public MainViewModel(IWindowManager windowManager, IEmailService emailService)
        {
            _windowManager = windowManager;
            _emailService = emailService;
        }

        public void BtnClick()
        {
            Debug.WriteLine("按钮点击事件");
            _emailService.SendEmail("jsc", "这是邮件内容");

            _windowManager.ShowWindow(new MainShellViewModel());

        }
    }
}

IWindowManager :

IWindowManager是Stylet框架中用于管理窗口的核心接口,支持通过ViewModel打开、关闭窗口和对话框,保持ViewModel与视图的解耦。提供ShowWindow()(非模态显示)、ShowDialog()(模态显示)和ShowMessageBox()(消息框)方法,简化窗口操作。

_windowManager.ShowWindow(new AboutViewModel());
bool? result = _windowManager.ShowDialog(new SettingsViewModel());
if (result == true) { /* 处理用户确认操作 */ }
public void CloseWindow() {
    this.RequestClose();
}
//按钮文本:通过 ButtonLabels 字典修改按钮显示文本。
//图标与声音:通过 IconMapping 和 SoundMapping 配置图标和提示音。
_windowManager.ShowMessageBox(
    "保存成功!", 
    "提示", 
    MessageBoxButton.OK, 
    MessageBoxImage.Information
);

属性变化检测:thit.Bind

 public class TestViewModel : Stylet.Screen
 {
     public string btnStr { get; set; } = "default";

     protected override void OnViewLoaded()
     {
         base.OnViewLoaded();
         Debug.WriteLine("OnViewLoaded");
     }
     protected override void OnInitialActivate()
     {
         base.OnInitialActivate();
         Debug.WriteLine("OnInitialActivate");


         this.Bind(
             s => btnStr,
             (o, e) =>
             {
                 Debug.WriteLine($"Bind btnStr:{btnStr}");// 打印 123
             }
         );
     }
     public TestViewModel()
     {

     }
     public void BtnClick()
     {
         Debug.WriteLine("点击按钮");
         btnStr = "123";
     }
 }

生命周期:

Stylet的Screen提供了以下生命周期的方法:

Conductors:

Conductors的主要接口是 IConductor<T>,它提供了以下方法:

Stylet 内置了一些Conductors,这些Conductors都源自 Screen。

具体区别和使用就看一下Stylet 文档吧。这里就不一一去解释了。这里就说一下如何使用Conductor<T>中提供的ActiveItem来实现页面切换。
TestView.xmal页面:

<Window x:Class="MyNamespace.ConductorViewModel"
        xmlns:s="https://github.com/canton7/Stylet" ....>
    <Grid Width="800" Height="450">
        <Button Width="50" Height="30" Content="page" HorizontalAlignment="Left" VerticalAlignment="Top" Command="{s:Action BtnClick}" CommandParameter="page"/>
        <Button Width="50" Height="30" Content="usercontrol" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,100,0,0" Tag="2" Command="{s:Action BtnClick}" CommandParameter="other"/>
        <Border Width="500" Height="350" BorderThickness="1" BorderBrush="Red" CornerRadius="10">
            <ContentControl Width="500" Height="400" s:View.Model="{Binding ActiveItem}"/>
            <!--<ContentControl Width="500" Height="400" s:View.Model="{Binding ItemView}"/>-->
        </Border>
    </Grid>
</Window>

TestViewModel 代码:

 public class TestViewModel : Conductor<IScreen>.Collection.OneActive
 {
     public IWindowManager _windowManager;
     public IViewFactory _viewFactory;

     public PageViewModel? ItemView { get; private set; }

     public TestViewModel(IWindowManager windowManager, IViewFactory viewFactory)
     {
         _windowManager = windowManager;
         _viewFactory = viewFactory;
     }

     public void BtnClick(string param)
     {
         Debug.WriteLine("点击按钮");

         //ItemView = _viewFactory.PageViewModel();

         if (param.Equals("page"))
         {
             //ActiveItem = _viewFactory.PageViewModel();
             ActivateItem(_viewFactory.PageViewModel());// 推荐
         }
         else
         {
             //ActiveItem = _viewFactory.UCSettingViewModel();
             ActivateItem(_viewFactory.UCSettingViewModel());// 推荐
         }



     }
 }

事件

在Stylet中提供了EventAggregator类,它是一个去中心化、弱绑定、基于发布/订阅的事件管理器。

    public class MyOtherEvent
    {
        public string? Address { get; set; }
    }

    public class MyEvent
    {
        public string? Name { get; set; }
    }
    public class PageViewModel : Stylet.Screen, IHandle<MyEvent>, IHandle<MyOtherEvent>
    {
        private readonly IEventAggregator _eventAggregator;
        public PageViewModel(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator;
            eventAggregator.Subscribe(this);
        }

        public void Handle(MyEvent message)
        {
            Debug.WriteLine($"订阅者收到事件1:{message.Name}");
        }

        public void Handle(MyOtherEvent message)
        {
            Debug.WriteLine($"订阅者收到事件2:{message.Address}");
        }
    }
    public class TestViewModel : Conductor<IScreen>.Collection.OneActive
    {
        public IWindowManager _windowManager;
        public IEventAggregator _eventAggregator;


        public TestViewModel(IWindowManager windowManager, IEventAggregator eventAggregator)
        {
            _windowManager = windowManager;
            _eventAggregator = eventAggregator;
        }

        // 点击发布事件
        public void BtnEventClick()
        {
            MyEvent myEvent = new MyEvent();
            myEvent.Name = "jsc";
            _eventAggregator.Publish(myEvent);

            MyOtherEvent myOtherEvent = new MyOtherEvent();
            myOtherEvent.Address = "江苏镇江";
            _eventAggregator.Publish(myOtherEvent);
        }
    }
上一篇 下一篇

猜你喜欢

热点阅读