.NET Framework 下支持语言的替代方法






1.13/5 (14投票s)
2003 年 12 月 23 日
7分钟阅读

57183

350
一个关于在 .NET Framework 下支持语言的替代方法的建议。
引言
作为一份圣诞礼物,我将提交一个我在 C++ 中使用过的想法,现在用 C# 来实现,以支持语言而无需使用外部文件。
语言支持一直是个问题,但当有很多方言时(比如英语、德语和法语,当然还有其他的),问题就更大了。在 C++ 中,您需要为每种语言/方言重写对话框,这会导致您根本不做,更不用说 .rc 文件经常会在您随意修改后损坏(就像我一直做的那样)。
有了 C#,这已经简化到将 Label
的大小设置为适合文本即可。当尝试支持“州/省/县”的本地化用法时(德语中的说法并不短得多),这一点变得很明显。
更糟糕的是,您必须为所有字符串在外部文件(也在您正在编写的代码之外)中支持 2 个资源。如果您想支持少数方言差异,您将为所有字符串获得多达 6-9 个资源 - 其中大部分是相同的。这真是浪费时间,而且大多数时候都没做到。
当然,下一个问题是(至少在 C++ 中),您必须为每种语言发送一个额外的 .exe。如果不是,通常必须重新启动程序才能使更改生效。
这里提供的方法解决了这些问题,尽管有点复杂,但一旦完成,添加额外的语言就非常简单了。
本文还将尝试展示 C# 中的属性有多么有用。在我看来,这是一个非常有用的扩展,即使您声称 C++ 中的额外函数也能实现相同的功能。
对于那些感兴趣的人,演示中还使用了闪烁不严重的 Panel
,其标准的 OnPaintBackground
重写不起作用。还展示了如何使用外部类来填充 MainForm
(Form1
或其他)中定义的 Panel
、StatusBar
和菜单。
这样做的主要原因是为了同时支持普通 Framework (PC) 和 Compact Framework,而无需对代码进行任何更改。结果文件(这里是 Internationalization.cs)只需复制到另一个项目的目录中,无需任何更改。
对于 Compact Framework 项目,我使用一个预编译指令(称为 COMPACT
),其中使用了 PC/Compact 特定的代码。
源 zip 文件包含两个 zip 文件,一个用于每个平台。
注意:我为我的项目使用了不同的目录(Microsoft.Net 和 Microsoft.Net.Compact)- zip 文件内的目录名称相同!
Panel
的设计不是这个(快速粗糙,复制粘贴)项目的重点。Compact Framework 版本 (SP1) 不支持透明 Label
。PC 版本支持 OnSize
逻辑。
您可能不喜欢我的写作/格式风格,但大多数人 anyway 不喜欢别人的风格,所以这对我来说不是问题。我大量使用 #region
- #endrgion
,因为我喜欢 Visual Studio 使用它时显示的结果。
代码文档非常详尽(取自我正在开发的一个纸牌游戏项目,我打算以后出售),并且包含一个 Ndoc 项目文件和生成的帮助文件。
这项工作是献给 Code Project 的人们的感谢,当然还有 microsoft.public.dotnet.framework.compact 和其他 microsoft.public.dotnet.framework.* 新闻组,如果没有它们,我将在过去六个月里迷失方向——请享受我的圣诞礼物。
背景
在使用标准资源系统时,每个字符串/控件都必须通过调用 .exe 中的资源来填充。因此,我看不出这与我正在做的事情有什么实际区别,因为我的 String[]
数组的创建/设置是在程序启动时进行的。
就像使用资源一样,您一次只为一种语言使用相同的字段数组。我们不会一次性创建大量用于所有语言的 String[]
。当语言更改时,旧数组将被释放并创建新数组(sa_Menu_Language = new string[]{"Language","English","German","French"};
)。
您可以在一个 Array
中定义任意数量的 String
(sa_Menu_Language = null;
),这至少是非常有用的,而且通过 sa_Menu_Language.Length
,您会自动知道设置了多少个,这更好。
使用属性作为一切处理的中心点,比我(至少在我之前)使用的分散方式更吸引我。由于语言不经常更改,我甚至(误)用它来设置常量 String[]
,例如 DataTable
字段以及这些字段对应的(特定语言的)ListView
列/标签等。
一如既往,使用这个有一个副作用
public void OnBuildViewListe(ref TabPage tabPageListen,
ref ListView listViewListen,
ref DataView dview_Table, ref string[] sa_listViewCols,
int i_Hide, int i_Cols)
{
....
listViewListen = new System.Windows.Forms.ListView();
listViewListen.ColumnClick += new
System.Windows.Forms.ColumnClickEventHandler(this.listView_ColumnClick);
....
for (int i=0;i < dview_Table.Table.Columns.Count;i++)
listViewListen.Columns.Add(sa_listViewCols[i],
-2,HorizontalAlignment.Center);
//-------------------------------------------------
for (int i=0;i < dview_Table.Count;i++)
{
DataRowView dset_Row = dview_Table[i];
ListViewItem item = null;
for (int j=0;j < dset_Row.DataView.Table.Columns.Count;j++)
{
if (j == 0)
item = new ListViewItem
(dset_Row[dset_Row.DataView.Table.Columns[j].ColumnName].ToString());
else
item.SubItems.Add
(dset_Row[dset_Row.DataView.Table.Columns[j].ColumnName].ToString());
}
listViewListen.Items.Add(item);
}
}
不要问我 sa_listViewCols[17]
是什么!但也不要问,如果您为每个 DataTable
字段名和 ListView
列都有一个额外的 String
,它看起来会是什么样子。
以这种方式设置所有 Label
和 TextBox
确实很麻烦,但上面的 OnBuildViewListe
方法处理了另一个程序中的所有(3)个 ListView
。
在这里,我还为每个支持的 ListView
使用了一个 int
类型的属性。它存储 ListView
是通过哪些列排序的,并执行以下操作
#region ip_MainFrame00SortCol
/// <summary>
/// Which Column is to be Sorted
/// </summary>
protected int ip_MainFrame00SortCol=0;
/// <summary>
/// Which Column is Sorted
/// </summary>
public int i_MainFrame00SortCol
{
get
{
return ip_MainFrame00SortCol;
}
set
{
progessBarMainFrame.Value = 1;
// If new Column is selected, Sort Ascending
if (value != ip_MainFrame00SortCol)
ip_MainFrame00SortType = 0;
else
{// Column was selected, Sort the other way around
if (ip_MainFrame00SortType == 0)
ip_MainFrame00SortType = 1;
else
ip_MainFrame00SortType = 0;
}
// Save the SQL Statement to Sort (used with creation of the DataView)
s_MainFrame00SortType = a_SortType[ip_MainFrame00SortType]; // "ASC","DESC"
progessBarMainFrame.Value = 2;
// Check if the chosen Colums is valid, otherwise set to first Column
if ((value < 0) || (value > dtable_MainFrame00.Columns.Count))
ip_MainFrame00SortCol = 0;
else
ip_MainFrame00SortCol = value;
// Save the SQL Fieldname Statement to Sort (Standard one Column)
// (used with creation of the DataView)
s_MainFrame00SortCol = sa_MainFrame00Rows[ip_MainFrame00SortCol];
progessBarMainFrame.Value = 3;
// Exceptions to the Standard (Sort with two Columns)
if (ip_MainFrame00SortCol == 1) // Name, Firstname
s_MainFrame00SortCol = sa_MainFrame00Rows[1]+","+sa_MainFrame00Rows[2];
progessBarMainFrame.Value = 4;
// Create the sorted DataView
DataView dview_Table = new DataView(dtable_MainFrame00,"",
s_MainFrame00SortCol+a_SortType[ip_MainFrame00SortType],
DataViewRowState.CurrentRows);
progessBarMainFrame.Value = 5;
// Create the ListView Build Function
// - on which Control is the ListView placed (positioned)
// - which ListView is to be used
// - which DataView is to be used
// - which Strings are to be used for the ListView Columns
// - Number of first Columns that should be hidden (first=0)
OnBuildViewListe(ref tabPage000, ref listView000,
ref dview_Table, ref sa_MainFrame00Cols,0,3);
// Re-Select the last selected entry (Sort)
if (dview_Table.Count > 0)
{
DataRowView dset_Row = null;
for (int i=0;i < dview_Table.Count;i++)
{
dset_Row = dview_Table[i];
if (dset_Row[sa_MainFrame00Rows[0]].ToString() == s_TableMainFrame00_ID)
{
listView000.Items[i].Selected = true;
listView000.Items[i].Focused = true;
listView000.EnsureVisible(i);
// Missing : Scroll to Selected Item
break;
}
}
OnSetTextDialog(ref drow_MainFrame00);
}
else
{
OnSetEmptyDialog(ref drow_MainFrame00);
b_OnMainFrame00Browse = true; // Deactivate the MainFrame00 Text Controls
button0010.Visible = true; // Show
}
progessBarMainFrame.Value = 10;
}
} // public int i_MainFrame00SortCol
#endregion
以这种方式完成了 3 个表,每个表包含 10 到 27 个字段。注意:当“姓氏”列排序时,ListView
按“姓氏”、“名字”排序!
以下方法是我支持所有三个 ListView
所做的全部工作。
#region listView_ColumnClick
/// <summary>
/// Find out which ListView was clicked
/// Collect the Column Number that was selected
/// and stores it, Build ListView
/// </summary>
public void listView_ColumnClick(object sender, ColumnClickEventArgs e)
{
System.Windows.Forms.ListView listView =
(System.Windows.Forms.ListView) sender;
if (listView == listView000)
i_MainFrame00SortCol = e.Column;
if (listView == listView010)
i_MainFrame01SortCol = e.Column;
if (listView == listView020)
i_MainFrame02SortCol = e.Column;
progessBarMainFrame.Value = 0;
} // private void listView000_ColumnClick(object sender,ColumnClickEventArgs e)
#endregion
顺便说一句:这在不支持 ListView
排序(更不用说双列排序)的 Compact Framework 上运行。
使用代码
我本来没有计划在计划这个圣诞礼物时包含上面的内容,但现在,您有了我如何使用属性的最佳示例——它不包含在演示中!
以下代码来自文档,演示代码已扩展以支持更改为所列语言/方言的菜单。
Panel
不仅显示我居住的街道/Straße/Gasse/rue,还显示了在一个简单的地址程序中可以生效的本地更改。
public string[] sa_Menu_Language = null;
public string[] sa_AdressTitels = null;
protected int ip_Language = 1;
public int i_Language
{
get
{
return ip_Language;
}
set
{
// Check for valid Language, set Default if an unknown value has been set !
if ((value == 1) || (value == 1202) ||
(value == 1613) || (value == 44) || // English
(value == 33) || (value == 3281) ||
(value == 4122) || (value == 1418) || // French
(value == 37) || (value == 3287) ||
(value == 411) || (value == 43) || // German
(value == 49)) // German
ip_Language = value;
else
ip_Language = 1; // English when not supported
// Set Language independent Values (like Table and Columns names)
// Set Language dependent Values (like Buttons, Lables, Messages etc.)
if ((ip_Language == 1) || (ip_Language == 1202) ||
(ip_Language == 1613) || (ip_Language == 44)) // English
{
sa_Menu_Language = new string[]{"Language","English","German","French"};
sa_AdressTitels = new
string[]{"State","City","Street","Zip-Code"}; // US-English
if ((ip_Language == 1613) || (ip_Language == 44)) // Canada and UK
sa_AdressTitels[3] = "Postal-Code";
if (ip_Language == 1613) // Canada - English
sa_AdressTitels[0] = "Provence";
if (ip_Language == 44) // United Kindom
sa_AdressTitels[0] = "County";
} // English
if ((ip_Language == 33) ||
(ip_Language == 3281) || // France, Belgien,
(ip_Language == 4122) ||
(ip_Language == 1418)) // Switzerland, Canada - Quebec
{
sa_Menu_Language = new
string[]{"Langue","anglais","allemand","française"};
sa_AdressTitels = new
string[]{"Departement","ville","rue","code de poste"}; // French
if (ip_Language == 4122) // Switzerland
sa_AdressTitels[0] = "Canton";
if ((ip_Language == 3281) || (ip_Language == 1418)) // Belgien and Canada
sa_AdressTitels[0] = "Province";
} // French
if ((ip_Language == 37) || (ip_Language == 43) ||
(ip_Language == 49) || // DDR - Austra - Germany
(ip_Language == 3287) || (ip_Language == 411)) // Belgien - Switzerland
{
sa_Menu_Language = new
string[]{"Sprache","englisch","deutsch","französisch"};
sa_AdressTitels = new
string[]{"Bundesland","Stadt","Straße","Postleitzahl"}; // German
if (ip_Language == 37) // DDR
sa_AdressTitels[0] = "Bezirk";
if (ip_Language == 3287) // Belgien
sa_AdressTitels[0] = "Provinz";
if (ip_Language == 411) // Switzerland
sa_AdressTitels[0] = "Kanton";
if (ip_Language == 43) // Austria
sa_AdressTitels[2] = "Gasse";
} // German
// Set the Controls with the Language dependent Values
// (Valid Language and setting values must be done by now)
label_State.Text = sa_AdressTitels[0];
if (mainMenuMain != null)
{ // Menu may not exist
menuItemLanguage.Text = sa_Menu_Language[0];
}
} // Set
} // i_Language
其余的,请查看提供的代码。它可以在普通的 .NET Framework 和 Compact Framework 上编译,并且警告设置为 0 时没有警告。(例外:关于正在编译的平台以及 DEBUG/RELEASE 信息的消息)。
关于我,以及我想感谢的其他人(我知道,但现在是圣诞节)
在 Harald Bähr(柏林)的鼓励下,我在过去的 6 个月里(相当长的失业期)学习了 C#,我很高兴我这么做了——尽管我对这个“新的 .NET 东西”有所顾虑。
另外,我想感谢 Stan Persky(加拿大温哥华),感谢他在今年夏天再次给我提供食物,其中包括:香蕉船。
还要感谢 Kentucky 的 Blue Grass 的 Allen Kempe,感谢他提供的餐点和意大利葡萄酒,以及他为我准备的 eMbedded Visual Tools 在我的 PDA 上进行调试。
此外,如果您在德国柏林寻找互联网工作,借用 Dickings 的话来说:我愿意!
至于任何法国读者,我很久以前就没去过法国了,而且我写得从来不好。至于西班牙语,那就更糟了,所以我没试。意大利语嘛,我喜欢吃你们的食物,喝你们的酒,听你们的歌,但我说得比我写法语还差。
我的歉意,祝大家圣诞快乐!
Mark Johnson, 德国柏林, mj10777@mj10777.de - 2003.12.23。
关注点
我非常乐意听到对本文的任何反馈。如果电子邮件没有丢失在垃圾箱中,我也会回复。
历史
目前还没有。
备用链接
本文和文件可以在 这里 阅读和下载。