如何在 Linux 上使用 C# 编写:实现 PanelApplet 到 Gnome 桌面。






4.86/5 (46投票s)
本文介绍了如何利用 .NET 框架的编程技能来实现平台无关的代码

目录
引言
本文介绍了如何利用 .NET 框架的编程技能为 Gnome 实现面板小程序。整个开发过程在 Ubuntu 操作系统中使用 MonoDevelop IDE 进行,因此您也可以找到关于该操作系统和 IDE 的一些信息。
文章的第一部分收集自各种来源(如书籍和网站)的信息。第二部分描述了创建显示选定地点当前天气的 Applet。
本文的主要思想是激发开发人员对平台无关代码的兴趣。所有参考文献都可以在文章末尾找到。
第一部分 – 理论
.NET 的平台无关性
我认为许多具有 Microsoft Foundation Classes、Active Template Library、Component Object Model 等先前开发工具和技术经验的 .NET 开发人员可能会对 .NET Framework 是平台无关的事实感到惊讶。我们可以不仅在 Microsoft Windows 操作系统下编译和执行 .NET 程序集。使用 Mono、Portable.NET 等开源 .NET 实现使其成为可能。
借助这些 .NET 实现编写的跨平台应用程序不一定只是简单的控制台应用程序。我们可以利用 ADO.NET、ASP.NET、XML Web 服务以及核心命名空间和语言特性的许多其他优势。
.NET 的跨平台性质的实现方式与 Sun Microsystems 处理 Java 编程平台的方法不同。与 Java 不同,Microsoft 本身不提供 .NET 在 Mac、Linux 等平台上的安装程序。相反,Microsoft 发布了一系列标准化规范,其他实体可以以此作为路线图,为自己选择的平台构建 .NET 发行版。
- ECMA-334,它定义了 C# 编程语言的语法和语义
- ECMA-335,它定义了 .NET 平台的许多细节
CLI 发行版
CLI | 网站 | 描述 |
Mono | http://www.mono-project.com | Mono 是由 Novell 公司赞助的开源且受商业支持的 .NET 发行版。 |
Portable .NET | http://www.dotgnu.org | Portable .NET 在 GNU 通用公共许可证下分发; |
每个 CLI 实现都提供了一个功能齐全的 C# 编译器、大量的命令行开发工具、全局程序集缓存实现、示例代码、有用的文档以及构成基类库的数十个程序集。
除了实现 ECMA-335 第四部分定义的核心库之外,Mono 和 Portable .NET 还提供了与 Microsoft 兼容的 mscorlib.dll、System.Data.dll、System.Web.dll、System.Drawing.dll 和 System.Windows.Forms.dll(以及许多其他)的实现。此外,Mono 和 Portable .NET 发行版还附带了一些专门针对 Unix/Linux 和 Mac OS X 操作系统的程序集。
在我的示例应用程序中,我使用了 Mono 的 CLI 实现,因此我们将研究它是什么。
什么是 Mono?
正如前面提到的,Mono 是一个软件平台,旨在让开发人员轻松创建跨平台应用程序。它是 Microsoft .Net Framework 的开源实现,基于 ECMA 标准的 C# 和通用语言运行时。
Components
Mono 由几个组件构成- C# 编译器 - C# 编译器功能齐全,可编译 C# 1.0 和 2.0 (ECMA),并包含许多 C# 3.0 功能。
- Mono Runtime - Runtime 实现 ECMA 通用语言基础结构 (CLI)。Runtime 提供即时 (JIT) 编译器、预编译 (AOT) 编译器、库加载器、垃圾收集器、线程系统和互操作性功能。
- 基类库 - Mono 平台提供了一套全面的类,为构建应用程序提供了坚实的基础。这些类与 Microsoft 的 .Net Framework 类兼容。
- Mono 类库 - Mono 还提供了许多超越 Microsoft 提供的基类库的类。这些类提供了额外的功能,特别是在构建 Linux 应用程序时非常有用。例如,有用于 Gtk+、Zip 文件、LDAP、OpenGL、Cairo、POSIX 等的类。
兼容性
Mono 的当前发布版本是 2.6。(发布于 2009 年 12 月)描述 Mono 当前支持的最简单方法是:.NET 3.5 中的所有内容,除了 WPF 和 WF,WCF 有限。
如果您已经有一个用 .Net 编写的应用程序,您可以使用 Mono Migration Analyzer (MoMA) 扫描您的应用程序,以确定您的应用程序是否使用了 Mono 不支持的任何内容。
即将发布的 Mono 版本目前仅可通过 AnonSVN 供用户使用,尚未发布,但还包含
- C# 4.0
- LINQ 4.0
- ASP.NET 4.0(部分实现)
开发
可用的 IDE
我们可以使用以下 IDE 在 Mono 上开发应用程序
- Microsoft Visual Studio 配合插件 (http://go-mono.com/monotools/)
- SharpDevelop (http://www.icsharpcode.net/OpenSource/SD/)
- MonoDevelop (http://monodevelop.com/)
本文我将介绍使用 MonoDevelop
MonoDevelop
MonoDevelop 是一个主要为 C# 和其他 .NET 语言设计的 IDE。MonoDevelop 使开发人员能够快速在 Linux、Windows 和 Mac OSX 上编写桌面和 ASP.NET Web 应用程序。MonoDevelop 使开发人员能够轻松地将使用 Visual Studio 创建的 .NET 应用程序移植到 Linux,并为所有平台维护单一代码库。

您也可以使用 Microsoft Visual Studio 打开 MonoDevelop 项目。带有 csproj 扩展名的 MonoDevelop 项目文件是有效的 MSBuild 文件。
MonoDevelop 的当前发布版本是 2.2.1
Ubuntu
在我们国家(我住在白俄罗斯),在使用正版软件的文化方面存在一些问题。因此,我主要的家庭操作系统是 Ubuntu 9.10 Karmic。Ubuntu 是一个基于 Debian GNU/Linux 发行版的计算机操作系统。它由全球专家开发团队构建,包含您需要的所有应用程序:网页浏览器、办公套件、媒体应用程序、即时通讯等等。Ubuntu 是 Windows 和 Office 的开源替代品。
Ubuntu 的理念
- Ubuntu 及其常规的企业发行版和安全更新将始终免费
- Ubuntu 提供 Canonical 和全球数百家公司的全面商业支持
- Ubuntu 提供免费软件社区提供的最佳翻译和辅助功能
- Ubuntu 核心应用程序全部免费且开源。
GNOME 是 Ubuntu 中的默认用户桌面。这是我的桌面截图

Gnome
GNOME 项目提供两样东西:GNOME 桌面环境,一个直观且吸引人的用户桌面;以及 GNOME 开发平台,一个用于构建与桌面其余部分集成应用程序的广泛框架。
作为开发人员,我们对 GTK+ 库感兴趣。GTK+ 是用于在 GNOME 应用程序中构建用户界面的主要库。让我们考虑该库的可移植版本。
Gtk#
Gtk# 是 Mono 和 .Net 的图形用户界面工具包。该项目绑定了 gtk+ 工具包和各种 GNOME 库,使得使用 Mono 和 .Net 开发框架进行完全本地化的 Gnome 应用程序图形开发成为可能。更多信息可以在这里找到。
摘要
在本部分文章中,我们考虑了使用 Mono 进行应用程序开发的一些理论方面。我还简要介绍了 Ubuntu、Gnome 和 Gtk#。
第二部分 – 实践
在本部分,我将介绍 Gnome 面板小程序,它将显示选定地点的当前天气。
什么是 Applet?
Applet 是一个小应用程序,设计用于放置在 Gnome 面板中,提供对控件的快速简便访问,例如音量控件或天气仪表。从技术上讲,Applet 是嵌入在 Gnome 面板中的 Bonobo 控件。这意味着与独立 Gnome 程序有一些细微的差别。
Bonobo 配置文件
每个 Applet 都需要一个“服务器”文件,其中包含 Bonobo 功能的描述。
让我们看一下我们的 .server 文件<oaf_info>
<oaf_server iid="OAFIID:appletWeather_Factory" type="exe"
location="/home/aidan/Projects/appletGDK/appletSource1/bin/Debug/weatherApplet.exe">
<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:Bonobo/GenericFactory:1.0"/>
<item value="IDL:Bonobo/Unknown:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="Weather Applet Factory"/>
<oaf_attribute name="description" type="string" value="Factory to create the weather applet"/>
</oaf_server>
<oaf_server iid="OAFIID:appletWeather" type="factory"
location="OAFIID:appletWeather_Factory">
<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0"/>
<item value="IDL:Bonobo/Control:1.0"/>
<item value="IDL:Bonobo/Unknown:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="appletWeather_Factory"/>
<oaf_attribute name="description" type="string" value="Demo weather applet on Mono"/>
<oaf_attribute name="panel:category" type="string" value="Amusements"/>
<oaf_attribute name="panel:icon" type="string" value="myicon.png"/>
</oaf_server>
</oaf_info>
注意。 oaf_server 标签定义了我们的可执行文件的位置,其中 type 为“exe”。在此示例中,我们的可执行文件名为 weatherApplet.exe,位于“/home/aidan/Projects/appletGDK/appletSource1/bin/Debug/”。我们还定义了我们的 Applet 的“工厂”名称,weatherApplet-Factory。这是 .server 文件的名称,通常位于 /usr/lib/bonobo/servers/。
GTK# 中的 PanelApplet 类
PanelApplet 类用于使用 Mono 创建 Applet。关于该类的官方文档可在此处找到,完整的示例可在此处看到。
为了创建 Applet,我们必须创建自己的类,它将继承 PanelApplet 类并实现以下方法和属性
void Creation()
– 当 Applet 被创建时调用此方法string IID
– 此属性返回 OAFIID 字符串,用于与 bonobo 服务器文件绑定string FactoryIID
- 此属性返回 OAFIID 字符串,用于定义 bonobo 工厂
让我们看看我们的 Creation()
实现。
首先,我们定义名为 Label 的 GTK# 用户元素控件,并将其添加到画布上
testLabel = new Label ("Update..");
this.Add (testLabel);
然后我们定义一个上下文菜单。我们添加两个菜单项,给它们起名字,当右键单击时会出现在菜单中
string menuItemTemplate = "<menuitem name=\"{0}\" verb=\"{1}\" _label=\"_{2}\" pixtype=\"stock\" pixname=\"gtk-properties\"/>";
StringBuilder menuBuilder = new StringBuilder();
menuBuilder.Append("<popup name=\"button3\">");
menuBuilder.AppendFormat(menuItemTemplate,"Properties","LabelChange","Change label");
menuBuilder.AppendFormat(menuItemTemplate,"Properties","Preferences","Preferences");
menuBuilder.Append("</popup>");
最后,我们将此菜单描述添加到 PanelApplet
this.SetupMenu (menuBuilder.ToString(), new BonoboUIVerb []
{
new BonoboUIVerb ("LabelChange", new ContextMenuItemCallback (LabelChangeCB)),
new BonoboUIVerb ("Preferences", new ContextMenuItemCallback (PreferenceMenuClicked))
}
);
属性的实现很简单:我们只返回字符串,这些字符串是在服务器文件中预先定义的
public override string IID {
get {
return "OAFIID:appletWeather";
}
}
public override string FactoryIID {
get {
return "OAFIID:appletWeather_Factory";
}
}
我们还将添加鼠标单击时的附加功能。为此,我们可以使用 OnButtonPressEvent
protected override bool OnButtonPressEvent (EventButton evnt)
{
//see http://www.go-mono.com/docs/index.aspx?link=P%3AGdk.EventButton.Time
//1 - left
//2 - middle
//3 - right
if(evnt.Button == 1)
{
if(w!=null)
w.Destroy();
w = new MyWindow();
}
return base.OnButtonPressEvent (evnt);
}
在此方法中,我们在按下鼠标左键时创建一个 MyWindow
对象。
在应用程序中注册 Applet
为了注册我们的 Applet,我们必须在 Main
方法中添加以下代码
PanelAppletFactory.Register(typeof (PanelAppletClass));
使用天气 Web 服务
我使用 http://www.webserviceX.NET 的 Web 服务。以获取天气信息。它提供了以下方法
GetCitiesByCountry
- 根据国家名称(全称/简称)获取所有主要城市GetWeather
- 获取全球所有主要城市的天气报告。
WSDL 架构在此网站上可用。
WSDL
Mono 提供了一个专门的工具,允许我们从 wsdl 文件生成相应的 C# 源文件。此文件包含可以调用 Web 服务方法的类。要生成此文件,请在命令行中运行 wsdl 工具

该工具将生成 GlobalWeather 类,其中包含 GetCitiesByCountry
和 GetWeather
方法。GetWeather 方法返回数据的示例
<currentweather>
<location>Minsk, Belarus (UMMS) 53-56N 027-38E 231M</location>
<time>Mar 04, 2010 - 08:00 AM EST / 2010.03.04 1300 UTC</time>
<wind> from the SW (230 degrees) at 9 MPH (8 KT) gusting to 16 MPH (14 KT) (direction variable):0</wind>
<visibility> greater than 7 mile(s):0</visibility>
<skyconditions> mostly cloudy</skyconditions>
<temperature> 30 F (-1 C)</temperature>
<wind>Windchill: 21 F (-6 C):1</wind>
<dewpoint> 17 F (-8 C)</dewpoint>
<relativehumidity> 58%</relativehumidity>
<pressure> 29.80 in. Hg (1009 hPa)</pressure>
<status>Success</status>
</currentweather>
使用代码
从示例中可以看出,数据以 xml 格式返回。让我们创建一个类来表示任何城市的当前天气public class CurrentWeather
{
public string Location {get;set;}
public string Time {get;set;}
public string Wind {get;set;}
public string Visibility {get;set;}
public string SkyConditions {get;set;}
public string Temperature {get;set;}
public string DewPoint {get;set;}
public string RelativeHumidity {get;set;}
public string Pressure {get;set;}
}
为了创建该类的 xml 对象,我们添加了一个工厂方法 Create。在此方法中,我们使用 System.Xml.Serialization 和 System.IO 命名空间提供的 XmlSerializer 和 MemoryStream 类public static CurrentWeather Create(string xml)
{
if (String.IsNullOrEmpty(xml))
return new CurrentWeather();
try
{
XmlSerializer sr = new XmlSerializer(typeof(CurrentWeather));
MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
return (CurrentWeather)sr.Deserialize(memoryStream);
}
catch
{
return new CurrentWeather();
}
}
为了隐藏这些实现细节,我创建了一个 WeatherService 类。此类提供了相同的两个方法- GetWeather – 调用 Web 服务,解析响应并返回 CurrentWeather 对象,该对象表示相应国家和城市的天气
public static CurrentWeather GetWeather(string country, string city) { string xml = service.GetWeather(city, country); XElement root = XElement.Parse(xml); return CurrentWeather.Create(root.ToString()); }
- GetCitiesByCountry – 返回城市列表
private static GlobalWeather service = new GlobalWeather(); private static Dictionary<string, IEnumerable<string>> citiesOfcountry = new Dictionary<string, IEnumerable<string>>(); public static IEnumerable<string> GetCitiesByCountry(string country) { if(!citiesOfcountry.ContainsKey(country)) { string xml = service.GetCitiesByCountry(country); XElement root = XElement.Parse(xml); IList<string> cities = new List<string>(); citiesOfcountry.Add(country, (from city in root.Elements() orderby city.Element("City").Value select city.Element("City").Value)); } return citiesOfcountry[country]; }
我在这里还使用了 Mono 中可用的 Linq 功能。
用户界面
Window
为了显示任何地点的当前天气,我创建了一个特殊的窗口public class MyWindow:Window
{
private Label lblLocation = new Label();
private Label lblTime = new Label();
private Label lblWind = new Label();
private Label lblVisibility = new Label();
private Label lblSkyConditions = new Label();
private Label lblTemperature = new Label();
private Label lblDewPoint = new Label();
private Label lblRelativeHumidity = new Label();
private Label lblPressure = new Label();
public MyWindow():base("Center")
{
CreateControls();
ShowAll();
}
...
}
在这里,我声明了 MyWindow 类,它继承自内置的 Gtk.Window 类。在此类中,我还添加了一个 Label,它将显示所选地点当前天气的相关信息。在构造函数中,我调用 CreateControls() 方法,在其中将控件添加到窗口中 private void CreateControls()
{
SetDefaultSize(400, 200);
Fixed fix = new Fixed();
fix.Put(new Label("Location"), 10, 10 );
fix.Put(lblLocation, 180, 10 );
fix.Put(new Label("Pressure"), 10, 170 );
fix.Put(lblPressure, 180, 170 );
...
this.Add(fix);
}
国家树
Gtk 库提供了 TreeView 控件。我在我的 Applet 中使用它来显示国家/地区及其城市的树

- 创建滚动窗口
ScrolledWindow sw = new Gtk.ScrolledWindow ();
- 创建我们的 TreeView 和显示当前选择的标签
Gtk.TreeView tree = new Gtk.TreeView (); filterEntry = new Gtk.Label ();
- 创建一个框来容纳 Entry 和 Tree
Gtk.VBox box = new Gtk.VBox ();
- 将小部件添加到框中
box.PackStart(filterBox, false, false, 5); sw.Add (tree); box.PackStart(sw, true, true, 5);
- 创建列
Gtk.TreeViewColumn countriesColumn = new Gtk.TreeViewColumn (); countriesColumn.Title = "Countries"; Gtk.CellRendererText countriesNameCell = new Gtk.CellRendererText (); countriesColumn.PackStart (countriesNameCell, true); tree.AppendColumn (countriesColumn); countriesColumn.AddAttribute(countriesNameCell, "text", 0);
- 添加选择处理程序
tree.Selection.Changed += OnSelectionChanged;
- 创建国家列表
tree.Model = BuildTree();
private TreeStore BuildTree()
{
Gtk.TreeStore countryListStore = new Gtk.TreeStore(typeof (string));
Gtk.TreeIter europe = countryListStore.AppendValues ("Europe");
SetCountryCities("Belarus", europe, ref countryListStore);
SetCountryCities("Russia", europe, ref countryListStore);
return countryListStore;
}
SetCountryCities 方法将城市附加到单个节点private void SetCountryCities(string country,TreeIter region, ref TreeStore store)
{
TreeIter countryIter = store.AppendNode(region);
store.SetValue(countryIter,0, country);
foreach(var city in WeatherService.GetCitiesByCountry(country))
store.AppendValues (countryIter, city);
}
让我们看一下树选择处理程序void OnSelectionChanged (object o, EventArgs args)
{
TreeIter iter;
TreeModel model;
if (((TreeSelection)o).GetSelected (out model, out iter))
{
if(model.IterHasChild(iter)) return;
TreeIter parent;
model.IterParent(out parent, iter);
string state = (string) model.GetValue (parent, 0);
string city = (string) model.GetValue (iter, 0);
filterEntry.Text = String.Format("{0}-{1} was selected", state, city);
SettingsStore.Set("state", state);
SettingsStore.Set("city", city);
}
}
在这里,我们获取用户的选择,更改标签并使用 SettingStore 保存它。我们可以在哪里保存 Applet 的设置?为此,在 gnome 环境中,我们可以使用 GConf 配置系统。使用 Gconf 存储设置
GConf 是一个用于存储应用程序首选项的系统。它用于用户首选项;而不是配置 Apache 之类的内容,或任意数据。这正是我们需要的using GConf;
public static class SettingsStore
{
//path in gconf-editor
private const string APPLICATION_KEY = "/apps/appletweather/";
//gconf object
private static Client client = new Client();
//set value
public static void Set(string key, object val)
{
client.Set(APPLICATION_KEY+key, val);
}
//get value
public static object Get(string key)
{
return client.Get(APPLICATION_KEY+key);
}
}
实际上,我认为它与 Windows 中的注册表工具非常相似

安装 Applet
为了在系统中注册 Applet,我们必须执行以下步骤- 在服务器文件中设置可执行文件的位置
- 将服务器文件添加到 /usr/lib/bonobo/servers 目录
- 将服务器文件引用的图标文件添加到 /usr/share/pixmaps 目录。图标文件是显示在“添加到面板...”对话框中的图像
- 右键单击工具栏并选择“添加到面板...”选项
从列表中选择 Applet
使用 Applet
该 Applet 仅用于演示目的。尽管如此,它知道如何显示所选地点的信息。要做到这一点,我们必须右键单击 Applet,选择“首选项”选项,然后在 TreeView 中选择地点

要获得当前天气的详细视图,您应该单击鼠标左键

摘要
在本部分文章中,我们介绍了 Gnome 桌面环境面板 Applet 的实现。我们使用了 .NET Framework 的标准类,这些类以前可以在 Windows 下的实现中使用。此外,我们还使用了特定的 Gtk 库。结论
我并不是很久以前才对开源技术产生兴趣的。因此,本文可能包含错误和不准确之处。但我对这项技术很感兴趣,并希望参与其发展。资源
- Mono: A Developer's Notebook 作者:Niel M. Bornstein, Edd Dumbill, O’Reilly, 2004, ISBN 0-596-00792-2
- Pro C# 2008 and the .NET 3.5 Platform 作者:Andrew Troelsen,第四版,Apress, 2007, ISBN-10: 1-59059-884-9
- Mono 官方网站
- 标准 ECMA-334 “C# 语言规范”
- 标准 ECMA-335 “通用语言基础结构 (CLI)”
- Ubuntu 官方网站
- GNOME 官方网站
- 创建 Bonobo 控件的艺术 作者:Dirk-Jan C. Binnema
历史
- 2010 年 3 月 5 日 - 文章初稿