NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] - نسخه متنی

Matthew MacDonald

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید



























Dynamic Content


When discussing dynamic interfaces, it's useful to draw a distinction between those that generate controls dynamically (like the examples you've just seen), and those that configure their controls dynamically. Dynamic content can appear in just about any situation, but it's most common in these cases:



Applications that need to be localized or configured for different sets of users.



Data-driven applications (like product catalogs) that use interfaces designed to closely model the organization of a database.



One simple example of dynamic content is the average About box (shown in Figure 11-7). It rarely makes sense to hard-code information like a program's version directly into the user interface of a window, because it cannot be guaranteed to remain correct (and it can be extremely tedious to synchronize if you use autoincrementing version numbers). Instead, this information should be retrieved dynamically:


lblProductName.Text = Application.ProductName;
lblProductVersion.Text = "Version: " + Application.ProductVersion.ToString();
lblPath.Text = "Executing in: " + Application.ExecutablePath;


Figure 11-7: Dynamic content in the About box


Localization


The .NET platform provides considerable support for localizing Windows Forms through resource files. Using resource files, you ensure that elements that may change in different product versions (for example text labels that need to be translated into different languages) are embedded in a separate satellite assembly. You can create different localized versions of your application simply by creating a new satellite assembly.

The basic process for creating a localizable form is simple:



Set the Localizable property for the Form to true using the Properties window.



Set the Language property of the Form using the Properties window. You'll be provided with the full list of recognized languages, as defined by the Windows operating system (see Figure 11-8).


Figure 11-8: Choosing a language when designing a form



Configure the localizable properties of various controls (for example, the text of a button). Your settings will be stored in a dedicated resource file for this language.



Return to step 2 to add information for another language. As soon as you change the language, all the localizable properties of the controls on your form revert to the settings in the resource file for that language.



To get a handle on exactly what is going on, select Show All Files from the Projects menu. For every localizable form, you see multiple .resx files with different language identifiers. In fact, there will be one for each language you've configured in the design environment. Figure 11-9 shows an example with two additional languages: German (de) and French (fr).


Figure 11-9: Multiple .resx files for a form

When you compile this project, Visual Studio .NET creates a separate directory using the language identifier, and uses it to store the satellite assembly with the localization settings (see Figure 11-10).


Figure 11-10: Multiple satellite assemblies

The greatest part about this is that you won't have to delete or move files around for different versions. Because of the way probing works with .NET assemblies, you can count on the CLR to automatically inspect the right directory based on the computer's regional settings and load the correct localized text! Or, you can fall back on a code statement like the one that follows to change the program's culture for testing purposes:


Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");

The .NET SDK also ships with a utility called Winres.exe, which is extremely useful for localization. It allows another user to edit the information in an .resx resource file using a scaled down form editor. This is useful because it allows translators and other non-programming professionals to create the locale-specific resource files without allowing them the chance to see sensitive code or inadvertently alter it.

Finally, you should be aware that you can also read and write directly to resource files using the classes in the System.Resources namespace. This is useful if you have localizable strings that can't be configured at design time, like error messages that appear in dialog boxes. This task is beyond the scope of the present discussion.


Note

As a rule of thumb, localization is never as easy as it appears, because of the subtleties involved with different languages and the way they are supported by the various versions of the Windows operating system. For more help, you can refer to another Apress book, by Nick Symmonds, which is dedicated to this topic.




A Dynamic Menu Example


Here's an example that demonstrates a simple use of dynamic content. It uses a database table that maps user levels to control access permissions. Depending on the user type, some options may be disabled or hidden entirely.

The database uses three tables (see Figure 11-11). Controls lists the names of available controls in the user interface, Levels lists the supported user levels, and Controls_Levels specifies what controls are allowed for a given user level (using a special State field that indicates 0 for normal, 1 for hidden, and 2 for disabled). All controls are enabled by default, so the only records that need to be added to Controls_Levels are those that specifically hide or disable controls. In a full-blown application, there would probably also be a Users table that indicates what level each user has.


Figure 11-11: Tables mapping control access permissions

In this example, the database is configured with the information for two user levels: User and Admin. The different menu structures these users will see are shown in Figure 11-12.


Figure 11-12: Different menu structures

By pulling all the user permission logic out of the user interface and placing it in the database, it becomes very easy to write a small amount of generic code that automatically configures the user interface for the user who is currently logged on:


DataTable dtPermissions;
// Get permissions for an Admin-level user.
dtPermissions = DBPermissions.GetPermissions(DBPermissions.Level.Admin);
// Update the menu with these permissions.
SearchMenu(this.Menu, dtPermissions);

The DBPermissions class uses a static GetPermissions() function that returns a table with all the security information for the specified user level. To remove the chance of errors, it also uses an enumeration that defines the different levels of user access in the database. This is a technique you saw in Chapter 9, where a database class encapsulates all the important information about a database.


public class DBPermissions
{
public enum State
{
Normal = 0,
Disabled = 1,
Hidden = 2
}
public enum Level
{
Admin,
User
}
private static SqlConnection con = new SqlConnection("Data Source=localhost;"
+ "Integrated Security=SSPI;Initial Catalog=Apress;");
public static DataTable GetPermissions(Level userLevel)
{
con.Open();
// Permissions isn't actually a table in our data source.
// Instead, it's a view that combines the important information
// from all three tables using a Join query.
string selectPermissions = "SELECT * FROM Permissions ";
switch (userLevel)
{
case Level.Admin:
selectPermissions += "WHERE LevelName = 'Admin"';
break;
case Level.User:
selectPermissions += "WHERE LevelName = 'User"';
break;
}
SqlCommand cmd = new SqlCommand(selectPermissions, con);
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
adapter.Fill(ds, "Permissions");
con.Close();
return ds.Tables["Permissions"];
}
}

Finally, the form's SearchMenu() function recursively tunnels through the menu, hiding or disabling controls as indicated in the permissions table.



private void SearchMenu(Menu menu, DataTable dtPermissions)
{
DataRow[] rowMatch;
foreach (MenuItem mnuItem in menu.MenuItems)
{
// See if this menu item has a corresponding row.
rowMatch = dtPermissions.Select("ControlName = "' + mnuItem.Text + "');
// If it does, configure the menu item state accordingly.
if (rowMatch.GetLength(0) > 0)
{
// Retrieve the state for this menu item.
string stateString;
DBPermissions.State state;
stateString = rowMatch[0]["State"].ToString()
state = (DBPermissions.State)int.Parse(stateString);
switch (state)
{
case DBPermissions.State.Hidden:
mnuItem.Visible = false;
break;
case DBPermissions.State.Disabled:
mnuItem.Enabled = false;
break;
}
}
else
{
mnuItem.Visible = true;
mnuItem.Enabled = true;
}
// Search recursively through any submenus.
if (mnuItem.MenuItems.Count > 0)
{
SearchMenu(mnuItem, dtPermissions);
}
}
}

Best of all, if the permissions need to change or another access level needs to be added, only the database needs to be modified. An application created in this way is easy to maintain without painful recompiles and redeployment.

Our example dynamically configures menus, but there are other approaches. For example, you could disable controls in a form (at which point you would probably want to add a FormName field to the Controls table). Chapter 14 demonstrates a similar technique with dynamic help content. You could also use a similar model to create localizable content for your menus. Instead of mapping controls to user levels with a State field, you would use a Text field that would be applied to the control's Text property.


Note

You could even extend this system to make a radically configurable interface supporting user-selected themes. But beware of going too far. The more variation your application supports, the more difficult it is to create support material and solve problems in the field. This is the classic flexibility versus ease-of-use dilemma.




/ 142