zoukankan      html  css  js  c++  java
  • 从零开始搭建Wpf基础10-接入Api数据与获取真实菜单

    前言:接入后台数据,前后台实体类都基本一直,稍微各有各的扩展,后台可以采用modelfirst,前言还是采用dbfrist好了,直接生成实体类。

    第零步:可能你还没有数据库脚本,项目里提供一个sqlserver的执行语句(以下也是以sqlserver为例),如果你想使用sqlite,那么项目里有加好数据的Api.db,你把连接字符串改成连接Api.db的即可。

    第一步:新建实体类项目AIStudio.Wpf.Entity.Models,安装Microsoft.EntityFrameworkCore.SqlServer,Microsoft.EntityFrameworkCore.Tools,Microsoft.EntityFrameworkCore.Design。

    然后在程序包控制台输入: Scaffold-DbContext "Server=.;Database=Colder.Admin.AntdVue;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -UseDatabaseNames 生成成功(是不是很省事):

    第二步:新增Operator类,保存登录者的信息,并实现接口IOperator。

    public class Operator : IOperator
    {
        public string UserId { get { return Property?.Id; } }
        /// <summary>
        /// 当前操作者UserName
        /// </summary>
        public string UserName { get; set; }

        public string Avatar { get; set; }

        public Base_User Property { get; set; }

        public List<string> Permissions { get; set; }

        //菜单树
        public ObservableCollection<AMenuItem> MenuItems { get; set; }

        //打平用于查询的菜单
        public ObservableCollection<AMenuItem> SearchMenus { get; set; }

    }
    public interface IOperator
    {
        /// <summary>
        /// 当前操作者UserId
        /// </summary>
        string UserId { get; }
        string UserName { get; set; }
        string Avatar { get; set; }
        /// <summary>
        /// 当前操作者
        /// </summary>
        Base_User Property { get; set; }

        List<string> Permissions { get; set; }

        //菜单树
        ObservableCollection<AMenuItem> MenuItems { get; set; }

        //打平用于查询的菜单
        ObservableCollection<AMenuItem> SearchMenus { get; set; }

        #region 操作方法

        #endregion
    }

    别忘了在App.xaml.cs注册

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.Register<IDataProvider, ApiDataProvider>();
        containerRegistry.RegisterSingleton<IOperator, Operator>();
    }

    第三步:将登录信息保存,并用获取到的后台菜单。

     private async void InitMenu()
    {
        var userinfo = await _dataProvider.GetData<UserInfoPermissions>("/Base_Manage/Home/GetOperatorInfo");
        if (!userinfo.Success)
        {
            throw new System.Exception(userinfo.Msg);
        }

        _operator.Property = userinfo.Data.UserInfo;
        _operator.Permissions = userinfo.Data.Permissions;
        _operator.Avatar = userinfo.Data.UserInfo.Avatar;

        var menuinfo = await _dataProvider.GetData<List<Base_ActionTree>>("/Base_Manage/Home/GetOperatorMenuList");
        if (!menuinfo.Success)
        {
            throw new System.Exception(menuinfo.Msg);
        }
        BuildMenu(menuinfo.Data);
    }

    private void BuildMenu(List<Base_ActionTree> base_Actions)
    {
        var nodes = base_Actions.Where(p => string.IsNullOrEmpty(p.ParentId));
        foreach (var node in nodes)
        {
            AMenuItem aMenuItem = new AMenuItem() { Glyph = node.Icon, Label = node.Text, Code = node.Url, Type = node.Type, ParentId = node.ParentId, Id = node.Id };
            MenuItems.Add(aMenuItem);
            SubBuildMenu(aMenuItem, node, aMenuItem.Id);
        }
    }

    private void SubBuildMenu(AMenuItem menuItem, Base_ActionTree parent, string parentid)
    {
        if (parent.Children != null)
        {
            foreach (var node in parent.Children)
            {
                AMenuItem aMenuItem = new AMenuItem() { Glyph = node.Icon, Label = node.Text, Code = node.Url, Type = node.Type, ParentId = node.ParentId, Id = node.Id };
                menuItem.AddChildren(aMenuItem);
                SubBuildMenu(aMenuItem, node, aMenuItem.Id);
            }
        }
    }

    运行,报错。因为需要登录后才能获取菜单,而这个在MainWindow初始化的时候就会执行。但是MainView尚未Loaded,所以我们可以在登录后Loaded的过程中实现获取菜单信息。简单的方法是在MainView.cs里面加Loaded事件,然后传替给MainViewModel进行InitMenu.

    第四步:我们借助已经实现好的类库Accelerider.Extensions进行VM内直接获得Loaded方法。

    核心代码,在实例化View和ViewModel的时候,将View的Loaded触发ViewModel的Loaded。

    public static IViewModelResolver UseDefaultConfigure(this IViewModelResolver @this) => @this
        .IfInheritsFrom<ViewModelBase>((view, viewModel) =>
        {
            viewModel.Dispatcher = view.Dispatcher;
        })
        .IfInheritsFrom<IViewLoadedAndUnloadedAware>((view, viewModel) =>
        {
            view.Loaded += (sender, e) => viewModel.OnLoaded();
            view.Unloaded += (sender, e) => viewModel.OnUnloaded();
        })
        .IfInheritsFrom(typeof(IViewLoadedAndUnloadedAware<>), (view, viewModel, interfaceInstance) =>
        {
            var viewType = view.GetType();
            if (interfaceInstance.GenericArguments.Single() != viewType)
            {
                throw new InvalidOperationException();
            }

            var onLoadedMethod = interfaceInstance.GetMethod<Action<object>>("OnLoaded", viewType);
            var onUnloadedMethod = interfaceInstance.GetMethod<Action<object>>("OnUnloaded", viewType);

            view.Loaded += (sender, args) => onLoadedMethod(sender);
            view.Unloaded += (sender, args) => onUnloadedMethod(sender);
        });

    在App.xaml.cs中将默认的ConfigureViewModelLocator替换了。

    protected override void ConfigureViewModelLocator()
    {
        //base.ConfigureViewModelLocator();
        ViewModelLocationProvider.SetDefaultViewModelFactory(new ViewModelResolver(() => Container)
            .UseDefaultConfigure()
            .ResolveViewModelForView);

        ViewModelLocationProvider.Register<MainWindow, MainWindowViewModel>();

    }

    第五步:MainViewModel实现IViewLoadedAndUnloadedAware接口,将InitMenu移动到Loaded方法内。

    class MainViewModel: BindableBase, IViewLoadedAndUnloadedAware
    {
      IContainerExtension _container;
      IRegionManager _regionManager;
      IOperator _operator;
      IDataProvider _dataProvider;

      public MainViewModel(IContainerExtension container, IRegionManager regionManager, IOperator ioperator, IDataProvider dataProvider)
      {
          _container = container;
          _regionManager = regionManager;
          _operator = ioperator;
          _dataProvider = dataProvider;
      }


      public void OnLoaded()
      {
          InitMenu();
      }

      public void OnUnloaded()
      {

      }

    万事大吉,运行。报错没有Url,原来是IDataProvider注册的方法有问题,需要用单例来实现,不然登录时候获取的Url就没有保存起来。

    containerRegistry.RegisterSingleton<IDataProvider, ApiDataProvider>();

    好了,这次真运行起来了。

    第六步:我们的Url地址写在了LoginViewModel里了,不够灵活,应该放在配置文件里,可以方便切换登录Url。在AIStudio.Wpf.Client新建App.config.并添加一个类来获取。

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <appSettings>    
        <add key="ServerIP" value="http://121.36.12.76:5000"/>
      </appSettings>  
    </configuration>
    public class LocalSetting
    {
        public static string ServerIP { get; set; } = ConfigurationManager.AppSettings["ServerIP"];
    }

    用的地方:

    private string _serverIP = LocalSetting.ServerIP;
    public string ServerIP
    {
      get { return _serverIP; }
      set
      {
          SetProperty(ref _serverIP, value);
      }
    }

    第七步:这个时候点击菜单中的框架介绍,将打不开对应界面了,需要将后台获取到的Code与我们的内部的WpfCode对应上。

    1.AMenuItem添加字段WpfCode

    public string WpfCode
    {
        get
        {
            if (Code == null)
                return null;

            var subcode = Code.Replace("/Index", "IndexView").Replace("/TreeList", "TreeView").Replace("/List", "View").Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
            if (subcode.Length == 1)
                return Code;

            subcode[subcode.Length - 1] = $"Views.{subcode[subcode.Length - 1]}";

            if (!subcode[subcode.Length - 1].EndsWith("View"))
            {
                subcode[subcode.Length - 1] = subcode[subcode.Length - 1] + "View";
            }

            return $"AIStudio.Wpf.{string.Join(".", subcode)}";
        }
    }

    将IntroduceView的注册改成FullName

    public class MainWindowModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            //regionManager.RegisterViewWithRegion("MainContentRegion", typeof(LoginView));

            regionManager.RegisterViewWithRegion("MainContentRegion", typeof(IntroduceView).FullName);
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation(typeof(IntroduceView), typeof(IntroduceView).FullName);
        }
    }

    另外需要改下页面的传参,使用WpfCode替换Code

    <!--顶部菜单-->
    <Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MahApps.Styles.MenuItem}">
        <Setter Property="MenuItem.Command" Value="{Binding Command}"/>
        <Setter Property="MenuItem.CommandParameter" Value="{Binding WpfCode}"/>
    </Style>

    第八步:目前我们只实现了一个菜单,其它菜单没有实现的时候应该给个提示,是不是友好一些。RequestNavigate添加回调方法NavigationComplete。

    _regionManager.RequestNavigate(RegionName, item.WpfCode, NavigationComplete);

    private void NavigationComplete(NavigationResult result)
    {
        if (result.Result == false)
        {
            WindowBase.ShowMessageQueue($"{result.Context.Uri.ToString()}打开失败", Identifier);
        }
    }

    好了,结束。 下一章:实现权限管理的菜单吧。

    源码地址:https://gitee.com/akwkevin/aistudio.-wpf.-client.-stepby-step

    每一章都有自己的Tag,按照链接进去直接下载就是本章内容。

    另外推荐一下我的Wpf客户端框架:https://gitee.com/akwkevin/aistudio.-wpf.-aclient

    作者:竹天笑
    互相学习,提高自己。
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    Sql server 经典常用函数
    Sql Server 时间格式化
    eval解析JSON中的注意点
    SQL Server 数据库try catch 存储过程
    SQL 添加索引
    sql中的begin catch 。。。end catch 的用法
    常用正则表达式
    css3实现背景渐变
    CacheHelper
    星座运势(等)接口控制器
  • 原文地址:https://www.cnblogs.com/akwkevin/p/15170589.html
Copyright © 2011-2022 走看看