Introduction
As a Christmas gift, I would submit an idea that I used under C++, now with C#, to support languages without the use of external files.
The support of languages was always a problem but even worse when you have lot of dialects as you have in English, German and French (and no doubt, others). In C++, you had to rewrite the Dialogs for each language / dialect which lead you to not doing it at all, not to mention the fact that the .rc often became corrupted if you played around with them (as I always did).
With C#, this was reduced to the main problem of setting the size of the Label
to fit text in. This became obvious when trying to support the local use of "State/Provence/County" (in German, it is not much shorter).
Bad enough that you had to support 2 resources for all strings in external files (also, outside the code being written). If you wanted to support the few dialect differences, you get up to 6-9 resources for all strings - most of which are the same. What a waste of time and mostly not done.
The next problem is, of course (at least under C++), you had to send an extra .exe for each language. And if not, mostly the program had to be restarted before the changes took effect.
The method offered here solves these problems, and although a bit complicated, once done, the adding of an extra language is very simple.
This article will also try to show how useful Properties can be in C#. In my mind, a very useful extension even if you could claim that the same functionality could be done in extra functions in C++.
The use of a flicker-free Panel
where the standard override of OnPaintBackground
does not work is also used in the demo for those interested. The use of an external class to fill the Panel
, StatusBar
and Menus defined in the MainForm
(Form1
or whatever) is also shown.
The main reason for doing this is to support both the normal Framework (PC) and the Compact Framework, without out any changes to the code. The resulting file (here Internationalization.cs) is simply copied to the directory of the other project with no changes.
With Compact Framework projects, I use a precompile directive (called COMPACT
) where PC/Compact specific code is used.
The source zip has inside it, 2 zip files, one for each platform.
Beware: I used a different directory for my projects (Microsoft.Net and Microsoft.Net.Compact) - the directory inside the zip have the same name!
The Panel
designing was not a major goal for this (quick and dirty, copy and paste) project. The Compact Framework version (SP1) does not support transparent Label
s. The PC version supports OnSize
logic.
You may not like my style of writing/formatting, but most people don't like the style of others anyway, so that's no problem for me. I use a lot of #region
- #endrgion
because I like the results shown in Visual Studio when I use it.
The code is highly documented (taken from a Card playing project I am working on, which I want to sell later), and an Ndoc project file and the resulting help file is included.
This effort is a thank you to the people of Code Project, and of course, microsoft.public.dotnet.framework.compact and the other microsoft.public.dotnet.framework.* newsgroups, without which I would have been lost in the last six months - please enjoy my Christmas present.
Background
When using the standard resource system, each string/control must be filled calling the resource from the .exe. So I see no real difference to what I am doing since the creation/setting of my String[]
arrays takes place when the program starts.
Just as with the resources, you are using the same Fields arrays for one language at a time. We are not creating a lot of String[]
s for all the languages at once. When a language changes, the old arrays will be freed and a new set created (sa_Menu_Language = new string[]{"Language","English","German","French"};
).
The fact that you can define an unknown amount of String
s in an Array
(sa_Menu_Language = null;
) is very useful (to say the least), and the fact that with sa_Menu_Language.Length
, you know automatically how many you have set is even better.
The use of Properties as a central point where everything is done appeals to me much more that the decentralized way (I at least) did before. Since language is not often changed, I even (mis)use this for setting constant String[]
s like DataTable
fields and the corresponding (language specific) ListView
columns/labels etc. for these fields.
As always, there is a bad side effect when using this:
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);
}
}
Don't ask me what sa_listViewCols[17]
is! But also, don't ask what it would look like if you had an extra String
for each DataTable
fieldname and ListView
column.
Setting all the Label
s and TextBox
es this way is nasty, but the OnBuildViewListe
method above takes care of all (3) ListView
s in an another program.
Here, I also use a property int
for each ListView
supported. It stores by which columns the ListView
is sorted, and does the following:
#region ip_MainFrame00SortCol
protected int ip_MainFrame00SortCol=0;
public int i_MainFrame00SortCol
{
get
{
return ip_MainFrame00SortCol;
}
set
{
progessBarMainFrame.Value = 1;
if (value != ip_MainFrame00SortCol)
ip_MainFrame00SortType = 0;
else
{
if (ip_MainFrame00SortType == 0)
ip_MainFrame00SortType = 1;
else
ip_MainFrame00SortType = 0;
}
s_MainFrame00SortType = a_SortType[ip_MainFrame00SortType];
progessBarMainFrame.Value = 2;
if ((value < 0) || (value > dtable_MainFrame00.Columns.Count))
ip_MainFrame00SortCol = 0;
else
ip_MainFrame00SortCol = value;
s_MainFrame00SortCol = sa_MainFrame00Rows[ip_MainFrame00SortCol];
progessBarMainFrame.Value = 3;
if (ip_MainFrame00SortCol == 1)
s_MainFrame00SortCol = sa_MainFrame00Rows[1]+","+sa_MainFrame00Rows[2];
progessBarMainFrame.Value = 4;
DataView dview_Table = new DataView(dtable_MainFrame00,"",
s_MainFrame00SortCol+a_SortType[ip_MainFrame00SortType],
DataViewRowState.CurrentRows);
progessBarMainFrame.Value = 5;
OnBuildViewListe(ref tabPage000, ref listView000,
ref dview_Table, ref sa_MainFrame00Cols,0,3);
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);
break;
}
}
OnSetTextDialog(ref drow_MainFrame00);
}
else
{
OnSetEmptyDialog(ref drow_MainFrame00);
b_OnMainFrame00Browse = true;
button0010.Visible = true;
}
progessBarMainFrame.Value = 10;
}
}
#endregion
3 tables are done this way, with between 10 and 27 fields. Note: when the column "Name" is sorted, the ListView
is sorted by "Name", "FirstName"!
The following method is all I do to support all three ListView
s.
#region listView_ColumnClick
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;
}
#endregion
BTW: this runs on the Compact Framework where ListView
sorting is not supported (not to mention two column sorting).
Using the code
I had not planned the above when planning this Christmas present, but now, you have my best sample of how to use properties - it is not included in the demo!
The following code is from the documentation, and the demo code is extended to support the Menu to change to the listed languages/dialects.
The Panel
not only shows the Street/Stra�e/Gasse/rue that I live in, but also shows the local changes that can take effect in a simple address program.
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
{
if ((value == 1) || (value == 1202) ||
(value == 1613) || (value == 44) ||
(value == 33) || (value == 3281) ||
(value == 4122) || (value == 1418) ||
(value == 37) || (value == 3287) ||
(value == 411) || (value == 43) ||
(value == 49))
ip_Language = value;
else
ip_Language = 1;
if ((ip_Language == 1) || (ip_Language == 1202) ||
(ip_Language == 1613) || (ip_Language == 44))
{
sa_Menu_Language = new string[]{"Language","English","German","French"};
sa_AdressTitels = new
string[]{"State","City","Street","Zip-Code"};
if ((ip_Language == 1613) || (ip_Language == 44))
sa_AdressTitels[3] = "Postal-Code";
if (ip_Language == 1613)
sa_AdressTitels[0] = "Provence";
if (ip_Language == 44)
sa_AdressTitels[0] = "County";
}
if ((ip_Language == 33) ||
(ip_Language == 3281) ||
(ip_Language == 4122) ||
(ip_Language == 1418))
{
sa_Menu_Language = new
string[]{"Langue","anglais","allemand","fran�aise"};
sa_AdressTitels = new
string[]{"Departement","ville","rue","code de poste"};
if (ip_Language == 4122)
sa_AdressTitels[0] = "Canton";
if ((ip_Language == 3281) || (ip_Language == 1418))
sa_AdressTitels[0] = "Province";
}
if ((ip_Language == 37) || (ip_Language == 43) ||
(ip_Language == 49) ||
(ip_Language == 3287) || (ip_Language == 411))
{
sa_Menu_Language = new
string[]{"Sprache","englisch","deutsch","franz�sisch"};
sa_AdressTitels = new
string[]{"Bundesland","Stadt","Stra�e","Postleitzahl"};
if (ip_Language == 37)
sa_AdressTitels[0] = "Bezirk";
if (ip_Language == 3287)
sa_AdressTitels[0] = "Provinz";
if (ip_Language == 411)
sa_AdressTitels[0] = "Kanton";
if (ip_Language == 43)
sa_AdressTitels[2] = "Gasse";
}
label_State.Text = sa_AdressTitels[0];
if (mainMenuMain != null)
{
menuItemLanguage.Text = sa_Menu_Language[0];
}
}
}
For the rest, please look at the supplied code. It compiles on the normal .NET Framework and Compact Framework with no warnings at Warning set to 0. (Exception: my messages on which platform it is being compiled on and DEBUG/RELEASE information).
About me, and others I would like to thank (I know, but it is Christmas)
I have spent the last 6 months (rather long unemployment) learning C# after being encouraged by Harald B�hr, Berlin, and I'm glad I did - despite my misgiving's about this "new .NET thing".
Also, I would like to thank Stan Persky, Vancouver, Canada, for feeding me again this summer with among other things: Banana Splits.
Also, my thanks to Allen Kempe, from the Blue Grass of Kentucky, for the meals and Italian wine, and for getting me eMbedded Visual Tools to debug on my PDA.
Otherwise, if you're looking for someone in Berlin, Germany for an Internet employment, to quote Dickings: I'm Willing!
As to any French readers, it has been a very long time since I was in France and I've never wrote it very well. As to Spanish, it's even worse, so I didn't try. Italian, well I like eating your food and drinking your wine and listening to your songs, but I speak it worse than I write French.
My apologies, and Merry Christmas to All!
Mark Johnson, Berlin Germany, mj10777@mj10777.de - 23.12.2003.
Points of Interest
I would be very interested in hearing any reaction to this article. If an E-mail does not get lost in the Junk Box, I will also reply.
History
None as of yet.
Alternate Link
This article and file can be read and downloaded here.