详解免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)

很多的软件项目中都会使用到定时任务、定时轮询数据库同步,定时邮件通知等功能。.NET Framework具有“内置”定时器功能,通过System.Timers.Timer类。在使用Timer类需要面对的问题:计时器没有持久化机制;计时器具有不灵活的计划(仅能设置开始时间和重复间隔,没有基于日期,时间等);计时器不使用线程池(每个定时器一个线程);计时器没有真正的管理方案 - 你必须编写自己的机制,以便能够记住,组织和检索任务的名称等。

如果需要在.NET实现定时器的功能,可以尝试使用以下这款开源免费的组件Quartz.Net组件。目前Quartz.NET版本为3.0,修改了原来的一些问题:修复由于线程本地存储而不能与AdoJobStore协同工作的调度器信令;线程局部状态完全删除;quartz.serializer.type是必需的,即使非序列化RAMJobStore正在使用;JSON序列化错误地称为序列化回调。

一.Quart.NET概述: 

Quartz是一个作业调度系统,可以与任何其他软件系统集成或一起使用。作业调度程序是一个系统,负责在执行预处理程序时执行(或通知)其他软件组件 - 确定(调度)时间到达。Quartz是非常灵活的,并且包含多个使用范例,可以单独使用或一起使用,以实现您所需的行为,并使您能够以您的项目看起来最“自然”的方式编写代码。组件的使用非常轻便,并且需要非常少的设置/配置 - 如果您的需求相对基础,它实际上可以使用“开箱即用”。Quartz是容错的,并且可以在系统重新启动之间保留(记住)您的预定作业。尽管Quartz对于在给定的时间表上简单地运行某些系统进程非常有用,但当您学习如何使用Quartz来驱动应用程序的业务流程时,Quartz的全部潜能可以实现。

Quartz是作为一个小的动态链接库(.dll文件)分发的,它包含所有的核心Quartz功能。 此功能的主要接口(API)是调度程序接口。 它提供简单的操作,如调度/非调度作业,启动/停止/暂停调度程序。如果你想安排你自己的软件组件执行,他们必须实现简单的Job接口,它包含方法execute()。 如果希望在计划的触发时间到达时通知组件,则组件应实现TriggerListener或JobListener接口。主要的Quartz'进程'可以在您自己的应用程序或独立应用程序(使用远程接口)中启动和运行。

二.Quartz.NET主体类和方法解析:

1.StdSchedulerFactory类:创建QuartzScheduler实例。

  /// <summary>
    /// 返回此工厂生成的调度程序的句柄。
    /// </summary>
    /// <remarks>
    ///如果<see cref =“Initialize()”/>方法之一没有先前调用,然后是默认(no-arg)<see cref =“Initialize()”/>方法将被这个方法调用。
    /// </remarks>
    public virtual IScheduler GetScheduler()
    {
      if (cfg == null)
      {
        Initialize();
      }

      SchedulerRepository schedRep = SchedulerRepository.Instance;

      IScheduler sched = schedRep.Lookup(SchedulerName);

      if (sched != null)
      {
        if (sched.IsShutdown)
        {
          schedRep.Remove(SchedulerName);
        }
        else
        {
          return sched;
        }
      }

      sched = Instantiate();

      return sched;
    }
public interface ISchedulerFactory
  {
    /// <summary>
    /// Returns handles to all known Schedulers (made by any SchedulerFactory
    /// within this app domain.).
    /// </summary>
    ICollection<IScheduler> AllSchedulers { get; }

    /// <summary>
    /// Returns a client-usable handle to a <see cref="IScheduler" />.
    /// </summary>
    IScheduler GetScheduler();

    /// <summary>
    /// Returns a handle to the Scheduler with the given name, if it exists.
    /// </summary>
    IScheduler GetScheduler(string schedName);
  }

2.JobDetailImpl:传递给定作业实例的详细信息属性。

/// <summary>
    /// 获取或设置与<see cref =“IJob”/>相关联的<see cref =“JobDataMap”/>。
    /// </summary>
    public virtual JobDataMap JobDataMap
    {
      get
      {
        if (jobDataMap == null)
        {
          jobDataMap = new JobDataMap();
        }
        return jobDataMap;
      }

      set { jobDataMap = value; }
    }

3.JobKey:键由名称和组组成,名称必须是唯一的,在组内。 如果只指定一个组,则默认组将使用名称。

  [Serializable]
  public sealed class JobKey : Key<JobKey>
  {
    public JobKey(string name) : base(name, null)
    {
    }

    public JobKey(string name, string group) : base(name, group)
    {
    }

    public static JobKey Create(string name)
    {
      return new JobKey(name, null);
    }

    public static JobKey Create(string name, string group)
    {
      return new JobKey(name, group);
    }
  }

4.StdSchedulerFactory.Initialize():

/// <summary>
    /// 使用初始化<see cref =“ISchedulerFactory”/>
     ///给定键值集合对象的内容。
    /// </summary>
    public virtual void Initialize(NameValueCollection props)
    {
      cfg = new PropertiesParser(props);
      ValidateConfiguration();
    }

    protected virtual void ValidateConfiguration()
    {
      if (!cfg.GetBooleanProperty(PropertyCheckConfiguration, true))
      {
        // should not validate
        return;
      }

      // determine currently supported configuration keys via reflection
      List<string> supportedKeys = new List<string>();
      List<FieldInfo> fields = new List<FieldInfo>(GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy));
      // choose constant string fields
      fields = fields.FindAll(field => field.FieldType == typeof (string));

      // read value from each field
      foreach (FieldInfo field in fields)
      {
        string value = (string) field.GetValue(null);
        if (value != null && value.StartsWith(ConfigurationKeyPrefix) && value != ConfigurationKeyPrefix)
        {
          supportedKeys.Add(value);
        }
      }

      // now check against allowed
      foreach (string configurationKey in cfg.UnderlyingProperties.AllKeys)
      {
        if (!configurationKey.StartsWith(ConfigurationKeyPrefix) || configurationKey.StartsWith(ConfigurationKeyPrefixServer))
        {
          // don't bother if truly unknown property
          continue;
        }

        bool isMatch = false;
        foreach (string supportedKey in supportedKeys)
        {
          if (configurationKey.StartsWith(supportedKey, StringComparison.InvariantCulture))
          {
            isMatch = true;
            break;
          }
        }
        if (!isMatch)
        {
          throw new SchedulerConfigException("Unknown configuration property '" + configurationKey + "'");
        }
      }

    }

三.Quartz.NET的基本应用:

下面提供一些较为通用的任务处理代码:

1.任务处理帮助类:

 /// <summary>
  /// 任务处理帮助类
  /// </summary>
  public class QuartzHelper
  {
    public QuartzHelper() { }

    public QuartzHelper(string quartzServer, string quartzPort)
    {
      Server = quartzServer;
      Port = quartzPort;
    }

    /// <summary>
    /// 锁对象
    /// </summary>
    private static readonly object Obj = new object();

    /// <summary>
    /// 方案
    /// </summary>
    private const string Scheme = "tcp";

    /// <summary>
    /// 服务器的地址
    /// </summary>
    public static string Server { get; set; }

    /// <summary>
    /// 服务器的端口
    /// </summary>
    public static string Port { get; set; }

    /// <summary>
    /// 缓存任务所在程序集信息
    /// </summary>
    private static readonly Dictionary<string, Assembly> AssemblyDict = new Dictionary<string, Assembly>();

    /// <summary>
    /// 程序调度
    /// </summary>
    private static IScheduler _scheduler;

    /// <summary>
    /// 初始化任务调度对象
    /// </summary>
    public static void InitScheduler()
    {
      try
      {
        lock (Obj)
        {
          if (_scheduler != null) return;
          //配置文件的方式,配置quartz实例
          ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
          _scheduler = schedulerFactory.GetScheduler();
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 启用任务调度
    /// 启动调度时会把任务表中状态为“执行中”的任务加入到任务调度队列中
    /// </summary>
    public static void StartScheduler()
    {
      try
      {
        if (_scheduler.IsStarted) return;
        //添加全局监听
        _scheduler.ListenerManager.AddTriggerListener(new CustomTriggerListener(), GroupMatcher<TriggerKey>.AnyGroup());
        _scheduler.Start();

        //获取所有执行中的任务
        List<TaskModel> listTask = TaskHelper.GetAllTaskList().ToList();

        if (listTask.Count > 0)
        {
          foreach (TaskModel taskUtil in listTask)
          {
            try
            {
              ScheduleJob(taskUtil);
            }
            catch (Exception e)
            {
             throw new Exception(taskUtil.TaskName,e);
            }
          }
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 启用任务
    /// <param name="task">任务信息</param>
    /// <param name="isDeleteOldTask">是否删除原有任务</param>
    /// <returns>返回任务trigger</returns>
    /// </summary>
    public static void ScheduleJob(TaskModel task, bool isDeleteOldTask = false)
    {
      if (isDeleteOldTask)
      {
        //先删除现有已存在任务
        DeleteJob(task.TaskID.ToString());
      }
      //验证是否正确的Cron表达式
      if (ValidExpression(task.CronExpressionString))
      {
        IJobDetail job = new JobDetailImpl(task.TaskID.ToString(), GetClassInfo(task.AssemblyName, task.ClassName));
        //添加任务执行参数
        job.JobDataMap.Add("TaskParam", task.TaskParam);

        CronTriggerImpl trigger = new CronTriggerImpl
        {
          CronExpressionString = task.CronExpressionString,
          Name = task.TaskID.ToString(),
          Description = task.TaskName
        };
        _scheduler.ScheduleJob(job, trigger);
        if (task.Status == TaskStatus.STOP)
        {
          JobKey jk = new JobKey(task.TaskID.ToString());
          _scheduler.PauseJob(jk);
        }
        else
        {
          List<DateTime> list = GetNextFireTime(task.CronExpressionString, 5);
          foreach (var time in list)
          {
            LogHelper.WriteLog(time.ToString(CultureInfo.InvariantCulture));
          }
        }
      }
      else
      {
        throw new Exception(task.CronExpressionString + "不是正确的Cron表达式,无法启动该任务!");
      }
    }

    /// <summary>
    /// 初始化 远程Quartz服务器中的,各个Scheduler实例。
    /// 提供给远程管理端的后台,用户获取Scheduler实例的信息。
    /// </summary>
    public static void InitRemoteScheduler()
    {
      try
      {
        NameValueCollection properties = new NameValueCollection
        {
          ["quartz.scheduler.instanceName"] = "ExampleQuartzScheduler",
          ["quartz.scheduler.proxy"] = "true",
          ["quartz.scheduler.proxy.address"] =string.Format("{0}://{1}:{2}/QuartzScheduler", Scheme, Server, Port)
        };

        ISchedulerFactory sf = new StdSchedulerFactory(properties);

        _scheduler = sf.GetScheduler();
      }
      catch (Exception ex)
      {
        throw new Exception(ex.StackTrace);
      }
    }

    /// <summary>
    /// 删除现有任务
    /// </summary>
    /// <param name="jobKey"></param>
    public static void DeleteJob(string jobKey)
    {
      try
      {
        JobKey jk = new JobKey(jobKey);
        if (_scheduler.CheckExists(jk))
        {
          //任务已经存在则删除
          _scheduler.DeleteJob(jk);

        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 暂停任务
    /// </summary>
    /// <param name="jobKey"></param>
    public static void PauseJob(string jobKey)
    {
      try
      {
        JobKey jk = new JobKey(jobKey);
        if (_scheduler.CheckExists(jk))
        {
          //任务已经存在则暂停任务
          _scheduler.PauseJob(jk);
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 恢复运行暂停的任务
    /// </summary>
    /// <param name="jobKey">任务key</param>
    public static void ResumeJob(string jobKey)
    {
      try
      {
        JobKey jk = new JobKey(jobKey);
        if (_scheduler.CheckExists(jk))
        {
          //任务已经存在则暂停任务
          _scheduler.ResumeJob(jk);
        }
      }
      catch (Exception ex)
      {
       throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 获取类的属性、方法
    /// </summary>
    /// <param name="assemblyName">程序集</param>
    /// <param name="className">类名</param>
    private static Type GetClassInfo(string assemblyName, string className)
    {
      try
      {
        assemblyName = FileHelper.GetAbsolutePath(assemblyName + ".dll");
        Assembly assembly = null;
        if (!AssemblyDict.TryGetValue(assemblyName, out assembly))
        {
          assembly = Assembly.LoadFrom(assemblyName);
          AssemblyDict[assemblyName] = assembly;
        }
        Type type = assembly.GetType(className, true, true);
        return type;
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 停止任务调度
    /// </summary>
    public static void StopSchedule()
    {
      try
      {
        //判断调度是否已经关闭
        if (!_scheduler.IsShutdown)
        {
          //等待任务运行完成
          _scheduler.Shutdown(true);
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

    /// <summary>
    /// 校验字符串是否为正确的Cron表达式
    /// </summary>
    /// <param name="cronExpression">带校验表达式</param>
    /// <returns></returns>
    public static bool ValidExpression(string cronExpression)
    {
      return CronExpression.IsValidExpression(cronExpression);
    }

    /// <summary>
    /// 获取任务在未来周期内哪些时间会运行
    /// </summary>
    /// <param name="CronExpressionString">Cron表达式</param>
    /// <param name="numTimes">运行次数</param>
    /// <returns>运行时间段</returns>
    public static List<DateTime> GetNextFireTime(string CronExpressionString, int numTimes)
    {
      if (numTimes < 0)
      {
        throw new Exception("参数numTimes值大于等于0");
      }
      //时间表达式
      ITrigger trigger = TriggerBuilder.Create().WithCronSchedule(CronExpressionString).Build();
      IList<DateTimeOffset> dates = TriggerUtils.ComputeFireTimes(trigger as IOperableTrigger, null, numTimes);
      List<DateTime> list = new List<DateTime>();
      foreach (DateTimeOffset dtf in dates)
      {
        list.Add(TimeZoneInfo.ConvertTimeFromUtc(dtf.DateTime, TimeZoneInfo.Local));
      }
      return list;
    }

    public static object CurrentTaskList()
    {
      throw new NotImplementedException();
    }

    /// <summary>
    /// 获取当前执行的Task 对象
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public static TaskModel GetTaskDetail(IJobExecutionContext context)
    {
      TaskModel task = new TaskModel();

      if (context != null)
      {

        task.TaskID = Guid.Parse(context.Trigger.Key.Name);
        task.TaskName = context.Trigger.Description;
        task.RecentRunTime = DateTime.Now;
        task.TaskParam = context.JobDetail.JobDataMap.Get("TaskParam") != null ? context.JobDetail.JobDataMap.Get("TaskParam").ToString() : "";
      }
      return task;
    }
  }

2.设置执行中的任务:

public class TaskBll
  {
    private readonly TaskDAL _dal = new TaskDAL();

    /// <summary>
    /// 获取任务列表
    /// </summary>
    /// <param name="pageIndex"></param>
    /// <param name="pageSize"></param>
    /// <returns></returns>
    public PageOf<TaskModel> GetTaskList(int pageIndex, int pageSize)
    {
      return _dal.GetTaskList(pageIndex, pageSize);
    }

    /// <summary>
    /// 读取数据库中全部的任务
    /// </summary>
    /// <returns></returns>
    public List<TaskModel> GetAllTaskList()
    {
      return _dal.GetAllTaskList();
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="taskId"></param>
    /// <returns></returns>
    public TaskModel GetById(string taskId)
    {
      throw new NotImplementedException();
    }

    /// <summary>
    /// 删除任务
    /// </summary>
    /// <param name="taskId"></param>
    /// <returns></returns>
    public bool DeleteById(string taskId)
    {
      return _dal.UpdateTaskStatus(taskId, -1);
    }

    /// <summary>
    /// 修改任务
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="status"></param>
    /// <returns></returns>
    public bool UpdateTaskStatus(string taskId, int status)
    {
      return _dal.UpdateTaskStatus(taskId, status);
    }

    /// <summary>
    /// 修改任务的下次启动时间
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="nextFireTime"></param>
    /// <returns></returns>
    public bool UpdateNextFireTime(string taskId, DateTime nextFireTime)
    {
      return _dal.UpdateNextFireTime(taskId, nextFireTime);
    }

    /// <summary>
    /// 根据任务Id 修改 上次运行时间
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="recentRunTime"></param>
    /// <returns></returns>
    public bool UpdateRecentRunTime(string taskId, DateTime recentRunTime)
    {
      return _dal.UpdateRecentRunTime(taskId, recentRunTime);
    }

    /// <summary>
    /// 根据任务Id 获取任务
    /// </summary>
    /// <param name="taskId"></param>
    /// <returns></returns>
    public TaskModel GetTaskById(string taskId)
    {
      return _dal.GetTaskById(taskId);
    }

    /// <summary>
    /// 添加任务
    /// </summary>
    /// <param name="task"></param>
    /// <returns></returns>
    public bool Add(TaskModel task)
    {
      return _dal.Add(task);
    }

    /// <summary>
    /// 修改任务
    /// </summary>
    /// <param name="task"></param>
    /// <returns></returns>
    public bool Edit(TaskModel task)
    {
      return _dal.Edit(task);
    }
  }

3.任务实体:

/// <summary>
  /// 任务实体
  /// </summary>
  public class TaskModel
  {
    /// <summary>
    /// 任务ID
    /// </summary>
    public Guid TaskID { get; set; }

    /// <summary>
    /// 任务名称
    /// </summary>
    public string TaskName { get; set; }

    /// <summary>
    /// 任务执行参数
    /// </summary>
    public string TaskParam { get; set; }

    /// <summary>
    /// 运行频率设置
    /// </summary>
    public string CronExpressionString { get; set; }

    /// <summary>
    /// 任务运频率中文说明
    /// </summary>
    public string CronRemark { get; set; }

    /// <summary>
    /// 任务所在DLL对应的程序集名称
    /// </summary>
    public string AssemblyName { get; set; }

    /// <summary>
    /// 任务所在类
    /// </summary>
    public string ClassName { get; set; }

    public TaskStatus Status { get; set; }

    /// <summary>
    /// 任务创建时间
    /// </summary>
    public DateTime? CreatedTime { get; set; }

    /// <summary>
    /// 任务修改时间
    /// </summary>
    public DateTime? ModifyTime { get; set; }

    /// <summary>
    /// 任务最近运行时间
    /// </summary>
    public DateTime? RecentRunTime { get; set; }

    /// <summary>
    /// 任务下次运行时间
    /// </summary>
    public DateTime? NextFireTime { get; set; }

    /// <summary>
    /// 任务备注
    /// </summary>
    public string Remark { get; set; }

    /// <summary>
    /// 是否删除
    /// </summary>
    public int IsDelete { get; set; }
  }

4.配置文件:

 # You can configure your scheduler in either <quartz> configuration section
# or in quartz properties file
# Configuration section has precedence

quartz.scheduler.instanceName = ExampleQuartzScheduler

# configure thread pool info
quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
quartz.threadPool.threadCount = 10
quartz.threadPool.threadPriority = Normal

# job initialization plugin handles our xml reading, without it defaults are used
# quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz
# quartz.plugin.xml.fileNames = ~/quartz_jobs.xml

# export this server to remoting context
quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
quartz.scheduler.exporter.port = 555
quartz.scheduler.exporter.bindName = QuartzScheduler
quartz.scheduler.exporter.channelType = tcp
quartz.scheduler.exporter.channelName = httpQuartz

四.总结:

在项目中比较多的使用到定时任务的功能,今天的介绍的组件可以很好的完成一些定时任务的要求。这篇文章主要是作为引子,简单的介绍了组件的背景和组件的使用方式,如果项目中需要使用,可以进行更加深入的了解。

(0)

相关推荐

  • Quartz.Net调度框架配置解析

    在平时的工作中,估计大多数都做过轮询调度的任务,比如定时轮询数据库同步,定时邮件通知等等.大家通过windows计划任务,windows服务等都实现过此类任务,甚至实现过自己的配置定制化的框架.那今天就来介绍个开源的调度框架Quartz.Net(主要介绍配置的实现,因为有朋友问过此类问题).调度的实现代码很简单,在源码中有大量Demo,这里就略过了. Quartz.Net当前最新版本Quartz.NET 2.0 beta 1 Released 一.基于文件配置 先看一下简单的实现代码 using

  • .net下Quartz.Net的使用方法

    Quartz.net是作业调度框架,具体内容如下 1. 项目中添加quartz.net的引用(这里使用nuget管理) 新建一个类TimingJob,该类主要用于实现任务逻辑 using Quartz; using System; namespace QuartzNetDemo { /// <summary> /// 定时任务类 /// </summary> public class TimingJob : IJob { public void Execute(IJobExecut

  • 详解免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)

    很多的软件项目中都会使用到定时任务.定时轮询数据库同步,定时邮件通知等功能..NET Framework具有"内置"定时器功能,通过System.Timers.Timer类.在使用Timer类需要面对的问题:计时器没有持久化机制:计时器具有不灵活的计划(仅能设置开始时间和重复间隔,没有基于日期,时间等):计时器不使用线程池(每个定时器一个线程):计时器没有真正的管理方案 - 你必须编写自己的机制,以便能够记住,组织和检索任务的名称等. 如果需要在.NET实现定时器的功能,可以尝试使用以

  • 详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)

    在生活中有一种东西几乎已经快要成为我们的另一个电子"身份证",那就是二维码.无论是在软件开发的过程中,还是在普通用户的日常中,几乎都离不开二维码.二维码 (dimensional barcode) ,又称二维条码,是在一维条码的基础上扩展出的一种具有可读性的条码.设备扫描二维条码,通过识别条码的长度和宽度中所记载的二进制数据,可获取其中所包含的信息.相比一维条码,二维码记载更复杂的数据,比如图片.网络链接等. 今天介绍一种免费开源的二维码操作组件,ThoughtWorks.QRCode

  • 详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)

    前面介绍了六种.NET组件,其中有一种组件是写文件的压缩和解压,现在介绍另一种文件的解压缩组件SharpZipLib.在这个组件介绍系列中,只为简单的介绍组件的背景和简单的应用,读者在阅读时可以结合官网的相关介绍和在本地实际操作. 相关的组件功能非常强大,在笔者的介绍中只是提及到简单的应用,需要了解更多的操作和特性,可以根据官网介绍,或者查看DLL文件的相关类和方法,以此来扩展相关的业务需要. SharpZipLib是一个完全在C#中为.NET平台编写的Zip,GZip,Tar和BZip2库.

  • 详解如何在vue+element-ui的项目中封装dialog组件

    1.问题起源 由于 Vue 基于组件化的设计,得益于这个思想,我们在 Vue 的项目中可以通过封装组件提高代码的复用性.根据我目前的使用心得,知道 Vue 拆分组件至少有两个优点: 1.代码复用. 2.代码拆分 在基于 element-ui 开发的项目中,可能我们要写出一个类似的调度弹窗功能,很容易编写出以下代码: <template> <div> <el-dialog :visible.sync="cnMapVisible">我是中国地图的弹窗&l

  • 详解DevEco Studio项目构建讲解、编写页面、布局介绍、页面跳转

    首先要知道鸿蒙的APP是怎么构成的?   HarmonyOS的应用软件包以APP Pack(Application Package)形式发布,它是由一个或多个HAP(HarmonyOS Ability Package)以及描述每个HAP属性的pack.info组成.HAP是Ability的部署包,HarmonyOS应用代码围绕Ability组件展开. 一个HAP是由代码.资源.第三方库及应用配置文件组成的模块包,可分为entry和feature两种模块类型,如下图所示. 一.项目目录 首先来看一

  • 详解免费高效实用的.NET操作Excel组件NPOI(.NET组件介绍之六)

    很多的软件项目几乎都包含着对文档的操作,前面已经介绍过两款操作文档的组件,现在介绍一款文档操作的组件NPOI. NPOI可以生成没有安装在您的服务器上的Microsoft Office套件的Excel报表,并且在后台调用Microsoft Excel ActiveX更有效率;从Office文档中提取文本,以帮助您实现全文索引功能(大多数时候,此功能用于创建搜索引擎): 从Office文档提取图像: 生成包含公式的Excel工作表.  一.NPOI组件概述: NPOI是完全免费使用: 涵盖Exce

  • 详解windows下vue-cli及webpack 构建网站(三)使用组件

    1.本文章是建立在<windows下vue-cli及webpack 构建网站(一)环境安装>和<windows下vue-cli及webpack 构建网站(一)导入bootstrap样式>两篇文章之上的. 2.在src\components文件夹下面新建两个组件,分别为 header.vue.footer.vue,打开header.vue文件粘贴下面的代码作为网站的头部. <template> <!-- Fixed navbar --> <nav cla

  • 详解如何在项目中使用jest测试react native组件

    目前Javascript的测试工具很多,但是针对React的测试策略,Facebook推出的ReactJs标配测试工具是Jest.Jest的官网地址:https://facebook.github.io/jest/.我们可以看到Jest官网宣称的是:Painless JavaScript Testing.是Facebook用于测试服务和React应用程序的JavaScript单元测试框架. 所谓单元测试也就是对每个单元进行测试,通俗的将一般针对的是函数,类或单个组件,不涉及系统和集成.单元测试是

  • 详解基于Vue的支持数据双向绑定的select组件

    今天用Vue做一个小项目时需要用到多个select筛选功能,但是原生的太丑,如果直接写在当前页多个select处理起来又太繁琐,第三方ui又太大,所以就自己写了一个,并上传了GitHub仓库,仓库地址:https://github.com/tuohuang/vue-select 使用方法: 引入组件: import VueSelect from '../components/VueSelect' 注册组件 export default { components: { VueSelect } }

  • 详解IOS点击空白处隐藏键盘的几种方法介绍

    IOS7 点击空白处隐藏键盘的几种方法,具体如下: iOS开发中经常要用到输入框,默认情况下点击输入框就会弹出键盘,但是必须要实现输入框return的委托方法才能取消键盘的显示,对于用户体验来说很不友好,我们可以实现点击键盘以外的空白区域来将键盘隐藏,以下我总结出了几种隐藏键盘的方法: 首先说明两种可以让键盘隐藏的Method: 1.[view endEditing:YES]  这个方法可以让整个view取消第一响应者,从而让所有控件的键盘隐藏. 2.[textFiled resignFirst

随机推荐