本文共 8072 字,大约阅读时间需要 26 分钟。
SharpDevelop浅析_2_User Interface创建易扩展且功能模块松散耦合的应用程序
http://www.cnblogs.com/michael-zhang/articles/629724.html
Demo界面及功能解释
相关概念
Demo代码分析
总结
1、Demo界面及功能解释:启动界面:
支持打开图片:
支持打开网页:
功能说明:程序有个浮动窗口显示当前磁盘文件,选择文件并双击后在中心窗口中以适当的察看器打开文件;持拖曳打开文件;程序关闭后再次启动时能够“记忆”前一次程序关闭时的窗体大小位置及打开过的文件。
2、相关概念:
主窗体部分: Workbench
磁盘文件查看窗口: Pad
文件查看窗口: ViewContent
说明:
Pad可以使用ICSharpCode.Core的Addin插件机制来扩充Pad,如Visual Studio的资源管理器、类查看器、属性窗口、消息窗口等都属于Pad
ViewContent同样支持插件扩充,根据不同的文件类型使用不同的控件来处理显示,Demo中已实现的文件类型支持:网页文件、图片、普通文本,Visual Studio中的代码窗口、窗体设计器、资源编辑器、对象查看器等都属于ViewContent
Workbench与WorkbenchLayout结合使用来控制主窗口的外观显示,如普通的MDI(PhotoShop?)或Demo使用的WeifenLuo.WinFormsUI.Docking显示方式……
3、Demo代码分析
(注:下面的分析前提是你已经了解
)
通 过上面的界面及功能说明,可以确定程序至少应包括接口声明、基本实现两个项目,而接口中又分为:Pad接口、ViewContent接口、 Workbench接口,这样的程序设计重点与难点在哪呢?思考后的结果应该是接口,从接口中能看到程序各部分是如何交互的,以及插件的扩展点,接口的设 计好坏决定着程序的易维护性与易扩展性,所以下面就重点看接口的定义(附介绍相关对象的创建等):
Pad接口 1// Core|Interface 定义部分: 2public interface IPadContent : IDisposable 3{ 4 System.Windows.Forms.Control Control { get; } 5 6 void RedrawContent(); 7} 8public class PadDescriptor : IDisposable 9{ 10 Codon codon;11 IPadContent padContent;12 bool padContentCreated;13 14 public string Title { 15 get { 16 return codon.Properties["title"];17 }18 }19 public string Icon { 20 get { 21 return codon.Properties["icon"];22 }23 }24 public string Class { 25 get { 26 return codon.Properties["class"];27 }28 }29 public IPadContent PadContent { 30 get { 31 CreatePad();32 return padContent;33 }34 }35 public void CreatePad()36 { 37 if (!padContentCreated) { 38 padContentCreated = true;39 padContent = (IPadContent)codon.AddIn.CreateObject(Class);40 }41 }42 //省略部分方法、属性43 public PadDescriptor(Codon codon)44 { 45 this.codon = codon;46 }47}48// Gui项目中的实现49class PadContentWrapper : DockContent50{ 51 PadDescriptor padDescriptor; //通过此对象来获取相关属性52 //53} 看到IPadContent接口返回一个WinForm的Control控件,此控件在 实现时被相应的窗体获取并适当地显示,Demo中的磁盘查看器Pad返回的Control是一个UserConrol,使用了TreeView和 ListView组合。注意IPadContent接口中定义返回一个Control而非Form是很聪明的技巧,可以看到实现端的 PadContentWrapper继承自WeifenLuo.WinFormsUI.DockContent,这依赖于实现端的表现方式,而接口可以不 受影响。PadDescriptor是个辅助类,用以返回Pad相关的属性信息,包括返回IPadContent的实例对象。接下来看Pad的xml声明及客户端调用: Pad声明及使用 1//取自Entry项目的SD_UI.addin 2<Path name = "/SharpDevelop/Workbench/Pads"> 3 <!-- 4 ProjectBrowser 5 ClassBrowser 6 SideBar 7 ErrorList 8 TaskList 9 CompilerMessageView10 PropertyPad11 SearchResults12 Bookmarks13 DefinitionView14 15 -->16 <Pad id = "FileScout"17 category = "Tools"18 title = "${res:MainWindow.Windows.FileScoutLabel}"19 icon = "PadIcons.FileBrowser"20 shortcut = "Control|Alt|F"21 class = "SDUserInterface.GUI.Pad.FileScout"/>22</Path>23// 取自Gui项目的DefaultWorkbench.cs24void InitializeWorkspace()25{ 26 //27 ArrayList contents = AddInTree.GetTreeNode("/SharpDevelop/Workbench/Pads").BuildChildItems(this);28 foreach (PadDescriptor content in contents)29 { 30 if (content != null)31 { 32 ShowPad(content);33 }34 }35 //36} 配置文件中的class指定了实现IPadContent的一个类型(全称限定名),使用时通过ICSharpCode.Core的AddInTree对象构建相关PadDescriptor集合……
下面来看ViewContent的定义:
ViewContent接口 1// Core|Interface 定义部分: 2public interface IViewContent : IDisposable 3{ 4 Control Control { get; set; } 5 6 IWorkbenchWindow WorkbenchWindow { get; set; } 7 8 string TitleName { get; set; } 910 string FileName { get; set; }1112 bool IsReadOnly { get; }1314 void Load(string fileName);1516 event EventHandler TitleNameChanged;17}18public class DisplayBindingDescriptor19{ 20 object binding = null;21 Codon codon;22 23 public IDisplayBinding Binding { 24 get { 25 if (binding == null) { 26 binding = codon.AddIn.CreateObject(codon.Properties["class"]);27 }28 return binding as IDisplayBinding;29 }30 }31 32 public Codon Codon { 33 get { 34 return codon;35 }36 }37 38 public DisplayBindingDescriptor(Codon codon)39 { 40 this.codon = codon;41 }42 43 public bool CanAttachToFile(string fileName)44 { 45 string fileNameRegex = codon.Properties["fileNamePattern"];46 if (fileNameRegex == null || fileNameRegex.Length == 0) // no regex specified47 return true;48 return Regex.IsMatch(fileName, fileNameRegex, RegexOptions.IgnoreCase);49 }50}51public interface IDisplayBinding52{ 53 bool CanCreateContentForFile(string fileName);54 55 IViewContent CreateContentForFile(string fileName);56}57// Gui项目中的实现:58public class SdiWorkspaceWindow : DockContent, IWorkbenchWindow59{ 60 IViewContent content;61 //62} 可以看到IViewContent同样是返回一个Conrol,供使用端(SdiWorkspaceWindow)根据需要封装组合; DisplayBindingDescriptor同样是个辅助类,除了返回ViewContent的相关属性信息外,提供了bool CanAttachToFile(string fileName)方法,用以判断当前显示插件是否可以显示相关类型的文件,这里的判断是通过配置文件中的fileNamePattern属性作正则判断 (注:文件名称符合一定规则的可能会用到此属性,一般不常用),注意到该辅助类返回了一个IDisplayBinding接口,查看该接口的方法可以看到 使用它来更进一步地判断当前文件是否是可支持类型(通过文件扩展名或试读取等方式),如果属于该插件支持类型的文件则创建并返回 IViewContenet接口。ViewContent插件的声明如下:
ViewContent插件声明 1<Path name = "/SharpDevelop/Workbench/DisplayBindings"> 2 <DisplayBinding id = "Browser" 3 supportedformats = "Web Pages" 4 class = "SDUserInterface.GUI.ViewContent.BrowserDisplayBinding"/> 5 <DisplayBinding id = "Text" 6 insertafter = "Browser" 7 supportedformats = "Text Files,Source Files" 8 class = "SDUserInterface.GUI.ViewContent.TextViewDisplayBinding" /> 9 <DisplayBinding id = "Image"10 insertbefore = "Text"11 supportedformats = "图片"12 class = "SDUserInterface.GUI.ViewContent.ImageDisplayBinding" />13</Path> 值得注意的是insertbefore, insertafter 属性,此属性指明获取所有DisplayBindingDescriptor后的先后顺序,如:一个.rtf文件可以由Office-Word和记事本打开,一般要优先选择使用Word打开。
获取ViewContent对象的过程如下:双击磁盘文件Pad中的一个文件项时,调用FileService中的OpenFile(string fileName)方法,相关代码如下: 获取/使用ViewContent 1// 取自Gui项目中的Common/FileService.cs 2public static IWorkbenchWindow OpenFile(string fileName) 3{ 4 // 5 IDisplayBinding binding = DisplayBindingService.GetBindingPerFileName(fileName); 6 7 if (binding != null) { 8 binding.CreateContentForFile(fileName); 9 WorkbenchSingleton.Workbench.ShowView(newContent);10 //11 } else { 12 throw new ApplicationException("Can't open " + fileName + ", no display codon found.");13 }14 return GetOpenFile(fileName);15}16// 取自Gui项目中的Common/DisplayBindingService.cs17static DisplayBindingDescriptor GetCodonPerFileName(string filename)18{ 19 foreach (DisplayBindingDescriptor binding in bindings) { 20 if (binding.CanAttachToFile(filename)) { 21 if (binding.Binding != null && binding.Binding.CanCreateContentForFile(filename)) { 22 return binding;23 }24 }25 }26 return null;27} 接下来看主窗体的定义:
Workbench接口 1public interface IWorkbench 2{ 3 string Title { get; set; } 4 5 List<IViewContent> ViewContentCollection { get; } 6 7 List<PadDescriptor> PadContentCollection { get; } 8 9 IWorkbenchWindow ActiveWorkbenchWindow { get; }10 11 object ActiveContent { get; }12 13 IWorkbenchLayout WorkbenchLayout { get; set; }14 15 void ShowView(IViewContent content);1617 void CloseAllViews();18 19 void CloseView(IViewContent content);20 21 void ShowPad(PadDescriptor content);22 23 PadDescriptor GetPad(Type type);2425 void RedrawAllComponents();26}27public interface IWorkbenchLayout28{ 29 bool FullScreen { get; set; }3031 IWorkbenchWindow ActiveWorkbenchwindow { get; }3233 object ActiveContent { get; }34 35 void Attach(IWorkbench workbench);36 37 void Detach();38 39 void ShowPad(PadDescriptor content);4041 void ShowPad(PadDescriptor content,bool bActivateIt);42 43 IWorkbenchWindow ShowView(IViewContent content);4445 void RedrawAllComponents();4647 void LoadConfiguration();48 void StoreConfiguration();49} IWorkbench定义主窗体,IWorkbenchLayout定义窗体布局,可以看到IWorkbench的两个重要属性是Pad和 ViewContent的对象集合(维护已打开的窗体记录,避免重复打开等作用),其实现类的ShowPad()/ShowView()方法执行的操作即 向对应的集合添加成员,然后调用IWorkbenchLayout的ShowPad()/ShowView()。IWorkbenchLayout的 LoadConfiguration()和StoreConfiguration()方法用以在窗体加载或关闭时执行加载或保存子窗口布局的操作。
至此Demo的核心已分析完了,我们可以根据定义的接口扩展程序,如增加项目管理/查看Pad, 增加.swf等文件查看的ViewContent, 或更换主界面显示为普通MDI方式……
4、总结:
此Demo更说明了可以应用到很多的方面:a, 界面组成部分Padb, 界面中不同类型的文件查看器ViewContentc, 磁盘文件查看Pad的下侧文件列表针对不同文件显示相关图标(详见SD_UI.addin文件的"/Workspace/Icons"层次下的定义)...SharpDevelop的这种界面设计方案使得应用程序很容易扩展和进一步开发,而不与已开发的模块冲突;从中得到的启发是应用程序要重点设计接口(契约)以及处理对象间的关系……说明:Demo代码基本来源于,删改了部分代码以使Demo精简和突出重点。