WF的练习程序:Mtv点歌软件

做为学习WF的学习成果,勇哥写了这个Mtv点歌软件。

在每学一门语言或者框架时,勇哥都写过类似功能的软件。

浪费点文章的位置把其它版本的放这里给各位把玩一下吧:

------------------------------

Python版本的:

Python Tkinter 学习成果:点歌软件music

plc+单片机+c#版本:

欧姆龙cp1h常用指令学习(十四)练习篇一:红外线遥控点歌

C#版本的:

原创C#源码,抖音热门音乐播放器

vc++与Qt版:
vs2013+Qt的学习小程序:点歌软件




用FW来实现这种软件的功能,从功能上来讲是属于把简单的问题复杂化,但是做为FW的入门练习勇哥觉得还是不错的。



(一) 界面与说明

界面如下图所示。

image.png

说明一下:

(1) 宿主程序的功能需求

  功能很简单: 

        全部歌曲页的内容是读取了指定目录下的全部扩展名为*.mkv, *.mp3的音视频文件。

        选中的歌曲页是由“全部歌曲”页面添加过来的歌曲。 可以删除该页选中的歌曲。

        两个页都可以搜索歌曲,搜索框用户通过输入首拼方式查询结果,并更新列表。(首拼示例: 一壶老酒  YHLJ)

        双击歌曲名字,调用视频播放器播放。

(2) 界面上流程编辑功能分三个部分: 工具箱、工具属性面板、流程编辑区

(3) 按宿主程序功能的需求,勇哥把流程分成六个,如下:

image.png

(4) 宿主程序中执行6个流程的时机如下:

        更新歌曲:            点击image.png

        显示选中的歌曲:  点击image.png

        删除歌曲:            点击image.png

        添加到选中歌曲:  点击 image.png

        搜索歌曲:            image.png文本框内容改变时

        播放歌曲:            双击歌曲列表的项目时


(5) 流程截图如下:

image.png

    (更新歌曲)

image.png

(显示选中的歌曲)

image.png

(删除歌曲)

image.png

(添加到选中歌曲)

image.png

(搜索歌曲)

image.png

(播放歌曲)


(二)程序结构


程序结构中:

Mtv.ToolKit.Help, mtvMainform都是OSGI的插件模块。它们都是类库。

有关OSGI的模块知识,详见勇哥另一篇文章《OSGI.NET 模块化示例演示  

工程mtv是应用程序,它会载入插件mtvMainform显示窗体。

未标题-1.gif


(三) 知识点备注


(1)获取并启动主界面

OSGI的主控程序主要做两件事:

  • 启动模块运行时

  • 获取并启动主界面

有关主控程序的知识,详见勇哥另一个贴子《创建OSGI主程序


下面是主控程序的代码,在programe.cs中。

        private static void RunApplication()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

         
            using (BundleRuntime bundleRuntime = new BundleRuntime())
            {
                bundleRuntime.Start();
                // 获取Form服务,并运行。
                Form mainForm = bundleRuntime.GetFirstOrDefaultService<Form>();

                if (mainForm != null)
                {
                    Application.Run(mainForm);
                }
            }
        }

工程mtvMainform做为OSGI的一个模块启动。

它由启动器Activator.cs启动。

启动器中下面这句话注册全局服务,注册了我们要启动的窗体FormMtv。

context.AddService<Form>(MainForm);

启动器完整代码如下:

using Mtv.ToolKit.Help;
using Mtv.ToolKit.Help.model;
using System;
using System.Activities.Presentation.Toolbox;
using System.Activities.Statements;
using System.Collections.Generic;
using System.ServiceModel.Activities;
using System.Text;
using System.Windows.Forms;
using UIShell.OSGi;

namespace mtvMainform
{
    internal delegate void CloseFormDelegate();
    public class Activator : IBundleActivator
    {
        private FormMtv MainForm { get; set; }

        public static IBundleContext Context { get; set; }

        public void Start(IBundleContext context)
        {
            Context = context;
            MainForm = new FormMtv();
            context.AddService<Form>(MainForm);
        }

        public void Stop(IBundleContext context)
        {
            context.RemoveService<Form>(MainForm);
            CloseFormDelegate closeFormDel = delegate()
            {
                MainForm.Close();
            };

            if (!MainForm.IsDisposed)
            {
                MainForm.BeginInvoke(closeFormDel);
            }

        }
    }
}

(2)流程设计器、流程工具箱、流程属性是怎么安置到winform界面上的。

虽然workflow是基于wpf的,但是可以通过ElementHost控件(wpf交互功能)安置在winform上面。

主界面上放置了三个ElementHost控件。

image.png

下面的代码为三个elementHost控件赋值。

   private factory factory = new factory();
        public string Title { get; set; }
        public FormMtv()
        {
            InitializeComponent();
            factory.RegisterMetadata();
            elementHost1.Child = factory.LoadToolBox(0);      //工具箱 
            elementHost3.Child = factory.LoadDesigner("");   //流程设计器
            elementHost2.Child = factory.LoadPropertyInspector();  //属性
        }

(3)运行工作流程

如文章开头所述,程序定义了6个工作流程,与界面的控件操作进行绑定。

下面是运行工作流程的代码:

参数fname传入流程的名字。

参数designer是流程的设计器,如果指定,则会启动流程追踪功能(下面会谈到什么是流程追踪)。

参数 param是传入流程中的某些活动所需要的输出与输出参数。

 private WFDesignDebugTracking tracker = null;
        /// <summary>
        /// 运行工作流
        /// </summary>
        /// <param name="fname">传入流程名字</param>
        /// <param name="designer">designer!=null 追踪</param>
        /// <param name="param">有参数则传参数</param>
        /// <returns></returns>
        public bool RunFlow(string fname, WorkflowDesigner designer=null, IDictionary<string, object> param = null)
        {
            try
            {
                string filePath = AppDomain.CurrentDomain.BaseDirectory + "flowXmal\\" + fname;
                DynamicActivity activityXaml = ActivityXamlServices.Load(filePath) as DynamicActivity;
                WorkflowApplication instance = null;
                if (null == param)
                    instance = new WorkflowApplication(activityXaml);
                else
                    instance = new WorkflowApplication(activityXaml, param);
                tracker = null;


                if (designer != null)
                {
                    tracker = new WFDesignDebugTracking(designer);
                    instance.Extensions.Add(tracker);
                }
                instance.Extensions.Add(fname); //new StringWriter());
                instance.Completed = WorkflowCompleted;  //流程完成事件
                instance.OnUnhandledException = WorkflowUnhandledException; //流程异常处理
                instance.Run();
                NotifyG.Debug(fname + ":运行(开始)");
              
            }
            catch (Exception ex)
            {
                NotifyG.Error(ex.ToString());
                return false;
            }
            return true;
        }

(4)流程追踪

它是一种调试功能,一方面可以记录每个活动的运行信息,另一方面可以定位当前正在运行的位置。

勾选主界面上的”工作流开启跟踪“,随便运行一个流程,比如”更新歌曲“。

image.png

可以看到设计器上会以黄色框提示你当前运行到了什么位置。

这种提示可以提醒你是否流程出错退出或者走的分支不是你想要的。

如果你想下断点让它停止下来,目前勇哥用的WF4.5还没这种功能。

注意,如果你是在VS中调试流程,是可以下断点的,但是像本例中,通过载入xmal的流程,则无法下断点。

image.png

(5)流程变量、活动的输入与输出参数

活动的输入与输出参数是活动专有的参数,对应流程中的InArgument与OutArgument。可以在宿主程序那边为该类型参数赋值。

而流程变量比较特殊,在宿主程序中要通过静态变量或者静态对象的方式进行赋值。

image.png

下面是活动”读取歌曲“的完整代码。

从中我们可以看到输入输出参数:

public InArgument<string> MusicFileDicPath { get; set; }
public OutArgument<TmusicList> OutMusicDicList {get;set;}

其中MusicFileDicPath参数勇哥直接赋值了一个字符串常量。

输出参数OutMusicDicList则和输出参数outMusic绑定了起来,这个类型TmusicList还是个自定义的数据类型。

有关自定义数据类型做为参数的知识,请参见勇哥另一篇贴子《WF小练习:自定义变量和参数的数据类型

image.png

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;
using System.IO;
using System.Activities.Presentation.Metadata;
using UIShell.OSGi;

namespace Mtv.ToolKit.Help.commonFun
{
    /// <summary>
    /// 数据类型:歌曲列表
    /// </summary>
    public class TmusicList
    {
        public Dictionary<string, string> obj;
        public TmusicList()
        {
            obj = new Dictionary<string, string>();
        }

        /// <summary>
        /// 取歌曲名字列表
        /// </summary>
        /// <returns></returns>
        public List<string> getMusicNameList()
        {
            var list = new List<string>();
            foreach(var m in obj)
            {
                list.Add(m.Key);
            }
            return list;
        }

        public  string toString()
        {
            var sb1 = new StringBuilder();
            foreach(var m in obj)
            {
                sb1.Append(m.Key); sb1.Append(",");
                sb1.Append(m.Value); sb1.Append(Environment.NewLine);
            }
            return sb1.ToString();
        }
    }

    public sealed class readMusicFormDisk : CodeActivity
    {
        #region Field
        private IHelpDevice helpDevice = null;
        #endregion

        #region 参数输入

        [Category("01.参数输入")]
        [DisplayName("歌曲目录路径串")]
        [Description("输入路径串,string类型,C#字符串")]
        public InArgument<string> MusicFileDicPath { get; set; }
        private string musicFileDicPath=string.Empty;
        #endregion

        #region 参数输出
        [Category("02.参数输出")]
        [DisplayName("输入所有歌曲名和对应路径")]
        [Description("输出歌曲名路径, TmusicList类型,C#")]
        public OutArgument<TmusicList> OutMusicDicList {get;set;}
        private TmusicList outMusicDicList = new TmusicList();
        #endregion

        public readMusicFormDisk()
        {
            AttributeTableBuilder builder = new AttributeTableBuilder();
            builder.AddCustomAttributes(typeof(readMusicFormDisk), "DisplayName", new Attribute[1] { new BrowsableAttribute(false) });
            MetadataStore.AddAttributeTable(builder.CreateTable());
            DisplayName = "读取歌曲";
            helpDevice = BundleRuntime.Instance.GetFirstOrDefaultService<IHelpDevice>() as IHelpDevice;
        }

        // 如果活动返回值,则从 CodeActivity<TResult>
        // 派生并从 Execute 方法返回该值。
        protected override void Execute(CodeActivityContext context)
        {
            NotifyG.Debug(DisplayName + "开始");
            try
            {
                string text = context.GetValue(this.MusicFileDicPath);
                musicFileDicPath = text;
                readMusicFromDisk();
                context.SetValue(OutMusicDicList, outMusicDicList);
            }
            catch(Exception ex)
            {
                Mtv.ToolKit.Help.NotifyG.Error(ex.Message);
            }
        }

        private void readMusicFromDisk()
        {
            if (!Directory.Exists(musicFileDicPath))
            {
                throw new ArgumentException((string.Format("传入的歌曲目录[{0}]不存在!无法读取歌曲列表。", musicFileDicPath)));
            }
            Getall(new DirectoryInfo(musicFileDicPath));

        }

        private void Getall(DirectoryInfo dd)
        {
            FileInfo[] allfile = dd.GetFiles("*.*");
            Array.Sort(allfile, delegate(FileInfo x, FileInfo y)
            {
                return x.CreationTime.CompareTo(y.CreationTime);
            });
            foreach (FileInfo tt in allfile)
            {
                var fileName = tt.Name.ToLower();
                if (fileName.EndsWith(".mkv") || fileName.EndsWith(".mp3"))
                {
                    outMusicDicList.obj.Add(tt.Name, tt.ToString());
                }
            }
            DirectoryInfo[] direct = dd.GetDirectories();
            foreach (DirectoryInfo dirTemp in direct)
            {
                Getall(dirTemp);
            }
        }
    }
}

在宿主程序中,通过如下代码的方式给输入参数listbox赋值。

  private void btnUpdateMusic_Click(object sender, EventArgs e)
        {
            tabControl1.SelectedIndex = 0;

            loadFlow(flowNameEnum.更新歌曲.ToString());

            var dic = new Dictionary<string, object>();
            dic.Add("listbox", this.lbxAllMusic);

            WorkflowDesigner wd = factory.wd;
            if (!cbxTrackingEnable.Checked) wd = null;
            RunFlow(flowNameEnum.更新歌曲.ToString(),wd, dic);

            
        }

对于流程变量来说,没有直接在宿主程序为它赋值的语法。不过可以通过静态对象或者变量来为它传值进活动内部,

然后在活动内部用Assign活动来为流程变量赋值。

  private void tbxSearchMusic_TextChanged(object sender, EventArgs e)
        {
            loadFlow(flowNameEnum.搜索歌曲.ToString());
            //搜索文本
            var txt = tbxSearchMusic.Text;
            Config.Instance.allMusicListbox = lbxAllMusic;
            Config.Instance.selMusicListbox = lbxSelectMusic;
            Config.Instance.searchMusicName = txt;
            Config.Instance.searchType = this.tabControl1.SelectedIndex == 0 ? 
                searchObjEnum.全部歌曲 : searchObjEnum.选中的歌曲;
            Config.Instance.musicDicPath = "e:\\music";

            WorkflowDesigner wd = factory.wd;
            if (!cbxTrackingEnable.Checked) wd = null;
            RunFlow(flowNameEnum.搜索歌曲.ToString(),wd);
            timer1.Start();
        }

如下图所示,我们在流程内部用Assign为流程变量allMusicListbox赋值,这个赋的值为Config.Instance.allMusicListbox,正是由宿主程序传过来的静态对象的一个属性。

image.png

有关流程变量赋值的方式,详见勇哥另一篇贴子《在宿主程序中为流程变量赋值出错: 为根活动的参数提供的值不满足根活动的要求(宿主给流程变量传值的正确方式)


--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2025年4月    »
123456
78910111213
14151617181920
21222324252627
282930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864