做为学习WF的学习成果,勇哥写了这个Mtv点歌软件。
在每学一门语言或者框架时,勇哥都写过类似功能的软件。
浪费点文章的位置把其它版本的放这里给各位把玩一下吧:
------------------------------
Python版本的:
《Python Tkinter 学习成果:点歌软件music》
plc+单片机+c#版本:
《欧姆龙cp1h常用指令学习(十四)练习篇一:红外线遥控点歌》
C#版本的:
vc++与Qt版:
《vs2013+Qt的学习小程序:点歌软件》
用FW来实现这种软件的功能,从功能上来讲是属于把简单的问题复杂化,但是做为FW的入门练习勇哥觉得还是不错的。
(一) 界面与说明
界面如下图所示。
说明一下:
(1) 宿主程序的功能需求
功能很简单:
全部歌曲页的内容是读取了指定目录下的全部扩展名为*.mkv, *.mp3的音视频文件。
选中的歌曲页是由“全部歌曲”页面添加过来的歌曲。 可以删除该页选中的歌曲。
两个页都可以搜索歌曲,搜索框用户通过输入首拼方式查询结果,并更新列表。(首拼示例: 一壶老酒 YHLJ)
双击歌曲名字,调用视频播放器播放。
(2) 界面上流程编辑功能分三个部分: 工具箱、工具属性面板、流程编辑区
(3) 按宿主程序功能的需求,勇哥把流程分成六个,如下:
(4) 宿主程序中执行6个流程的时机如下:
更新歌曲: 点击
显示选中的歌曲: 点击
删除歌曲: 点击
添加到选中歌曲: 点击
搜索歌曲: 文本框内容改变时
播放歌曲: 双击歌曲列表的项目时
(5) 流程截图如下:
(更新歌曲)
(显示选中的歌曲)
(删除歌曲)
(添加到选中歌曲)
(搜索歌曲)
(播放歌曲)
(二)程序结构
程序结构中:
Mtv.ToolKit.Help, mtvMainform都是OSGI的插件模块。它们都是类库。
有关OSGI的模块知识,详见勇哥另一篇文章《OSGI.NET 模块化示例演示 》
工程mtv是应用程序,它会载入插件mtvMainform显示窗体。
(三) 知识点备注
(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控件。
下面的代码为三个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)流程追踪
它是一种调试功能,一方面可以记录每个活动的运行信息,另一方面可以定位当前正在运行的位置。
勾选主界面上的”工作流开启跟踪“,随便运行一个流程,比如”更新歌曲“。
可以看到设计器上会以黄色框提示你当前运行到了什么位置。
这种提示可以提醒你是否流程出错退出或者走的分支不是你想要的。
如果你想下断点让它停止下来,目前勇哥用的WF4.5还没这种功能。
注意,如果你是在VS中调试流程,是可以下断点的,但是像本例中,通过载入xmal的流程,则无法下断点。
(5)流程变量、活动的输入与输出参数
活动的输入与输出参数是活动专有的参数,对应流程中的InArgument与OutArgument。可以在宿主程序那边为该类型参数赋值。
而流程变量比较特殊,在宿主程序中要通过静态变量或者静态对象的方式进行赋值。
下面是活动”读取歌曲“的完整代码。
从中我们可以看到输入输出参数:
public InArgument<string> MusicFileDicPath { get; set; } public OutArgument<TmusicList> OutMusicDicList {get;set;}
其中MusicFileDicPath参数勇哥直接赋值了一个字符串常量。
输出参数OutMusicDicList则和输出参数outMusic绑定了起来,这个类型TmusicList还是个自定义的数据类型。
有关自定义数据类型做为参数的知识,请参见勇哥另一篇贴子《WF小练习:自定义变量和参数的数据类型》
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,正是由宿主程序传过来的静态对象的一个属性。
有关流程变量赋值的方式,详见勇哥另一篇贴子《在宿主程序中为流程变量赋值出错: 为根活动的参数提供的值不满足根活动的要求(宿主给流程变量传值的正确方式)》
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!

