65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.13/5 (14投票s)

2003 年 12 月 23 日

7分钟阅读

viewsIcon

57183

downloadIcon

350

一个关于在 .NET Framework 下支持语言的替代方法的建议。

Sample Image - Internationalization.gif

引言

作为一份圣诞礼物,我将提交一个我在 C++ 中使用过的想法,现在用 C# 来实现,以支持语言而无需使用外部文件。

语言支持一直是个问题,但当有很多方言时(比如英语、德语和法语,当然还有其他的),问题就更大了。在 C++ 中,您需要为每种语言/方言重写对话框,这会导致您根本不做,更不用说 .rc 文件经常会在您随意修改后损坏(就像我一直做的那样)。

有了 C#,这已经简化到将 Label 的大小设置为适合文本即可。当尝试支持“州/省/县”的本地化用法时(德语中的说法并不短得多),这一点变得很明显。

更糟糕的是,您必须为所有字符串在外部文件(也在您正在编写的代码之外)中支持 2 个资源。如果您想支持少数方言差异,您将为所有字符串获得多达 6-9 个资源 - 其中大部分是相同的。这真是浪费时间,而且大多数时候都没做到。

当然,下一个问题是(至少在 C++ 中),您必须为每种语言发送一个额外的 .exe。如果不是,通常必须重新启动程序才能使更改生效。

这里提供的方法解决了这些问题,尽管有点复杂,但一旦完成,添加额外的语言就非常简单了。

本文还将尝试展示 C# 中的属性有多么有用。在我看来,这是一个非常有用的扩展,即使您声称 C++ 中的额外函数也能实现相同的功能。

对于那些感兴趣的人,演示中还使用了闪烁不严重的 Panel,其标准的 OnPaintBackground 重写不起作用。还展示了如何使用外部类来填充 MainFormForm1 或其他)中定义的 PanelStatusBar 和菜单。

这样做的主要原因是为了同时支持普通 Framework (PC) 和 Compact Framework,而无需对代码进行任何更改。结果文件(这里是 Internationalization.cs)只需复制到另一个项目的目录中,无需任何更改。

对于 Compact Framework 项目,我使用一个预编译指令(称为 COMPACT),其中使用了 PC/Compact 特定的代码。

源 zip 文件包含两个 zip 文件,一个用于每个平台。

注意:我为我的项目使用了不同的目录(Microsoft.NetMicrosoft.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 中定义任意数量的 Stringsa_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,它看起来会是什么样子。

以这种方式设置所有 LabelTextBox 确实很麻烦,但上面的 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。

关注点

我非常乐意听到对本文的任何反馈。如果电子邮件没有丢失在垃圾箱中,我也会回复。

历史

目前还没有。

备用链接

本文和文件可以在 这里 阅读和下载。

© . All rights reserved.