|
Implementing Mirror-Aware Controls for Windows Application with Visual Studio .NET
Content
Introduction
Visual Studio .NET provides a wide support of Arabic, in application development. Yet, Windows Forms do not support mirroring directly, as they do with the RightToLeft property. Meanwhile, to enable this feature, Windows Forms do give developers the capability to customize the appearance and functionality of the controls through inheritance. This discussion only involves managed code, since you may not need the hassle of unmanaged code. This article will explain the meaning of mirroring and investigate the ability to extend existing controls to add the mirroring property.

What is Mirroring?
Mirroring is a term used to specify right-to left (RTL) layout, which identifies how text and GDI objects are laid out in a window. Therefore, it gives a perfect right-to-left (RTL) look and feel to the UI. For Windows 98 and Windows Me, this technology was only available on Arabic enabled and localized operating systems. However, on all Windows 2000 varieties and later, are already mirroring aware. Mirroring is in fact nothing else than a coordinate transformation:

The window layout applies to text but also affects the other GDI elements of the window, including bitmaps, icons, the location of the origin, buttons, cascading tree controls, and whether the horizontal coordinate increases as you go left or right. For example, after an application has set RTL layout, the origin is positioned at the right edge of the window or device, and the number representing the horizontal coordinate increases as you move left. However, not all objects are affected by the layout of a window. For example, the layout for dialog boxes, message boxes, and device contexts is not associated with a window. However, changing to a RTL layout is not supported for windows that have the style CS_OWNDC. For a DC with the GM_ADVANCED graphic mode the behavior may be inconsistent with what you need. It reverses text as you would see later.

How to Enable Mirroring of your Control?
The steps to mirror your controls:
- Create a new custom control class. Set it to inherit from the control you wish to mirror.
- Create the Mirrored property under the category Appearance.
- Override the CreateParams method of the control's class, which gives you the ability to set the desired control styles while the control is still being created. If the Mirrored property is set to yes then add the RTL layout to the control and if not keep the same styles.
- Create a test form, and add a reference to the previously created mirrored control; add the new control to the form.
Now, you have the Mirrored property available in the properties window and you can use it as needed.
Note The Windows form is considered a control since it is derived from the System.Windows.Forms.Control class.
The two window styles you would need:
- WS_EX_LAYOUTRTL (400000 hexadecimal)
This style sets the layout to RTL. Therefore, the horizontal origin is the right edge and increasing horizontal value would advance to the left.
- WS_EX_NOINHERITLAYOUT (100000 hexadecimal)
This style prevents the layout from being inherited, by the child windows because not all controls need RTL layout. Therefore, you need to enforce the controls on the forms will not get mirrored. Imagine that you mirror text and then the letters would appear in reversed order!!! This behavior is caused because the label control device context is in the GM_ADVANCED mode.
These figures show the effect of removing this flag:
| |
 |
|
 |
|
Original Form : both the WS_EX_LAYOUTRTL and the WS_EX_NOINHERITLAYOUT are set |
|
Form without setting the WS_EX_NOINHERITLAYOUT |
These two styles are not defined in the system and therefore you need to use their hexadecimal values to use them, as shown later.
The following is a list of the controls, which can now support mirroring and whether they need to allow inheritance of the layout.
| Control |
Should not allow layout inheritance |
| Listview |
No |
| Panel |
Yes |
| Statusbar |
Yes |
| Tabcontrol |
Yes |
| TabPage |
Yes |
| Toolbar |
No |
| TreeView |
No |
| Form |
Yes |
| Splitter |
Yes |
The following are two examples of the inherited custom controls.
Example 1: Mirrored Treeview control. The same code may be used with the Listview, and Toolbar controls. You should change the inheritance to the appropriate control.
| [Visual Basic] |
Imports System.ComponentModel
Public class RTLTreeView |
 |
Inherits System.Windows.Forms.TreeView 'Inherits from the standard TreeView control
'Define your style
Const WS_EX_LAYOUTRTL = &H400000
Private _RTL As Boolean = False
'Mirrored property to test the RTL order
<Description ("Change to the right-to-left layout."), _
DefaultValue(False),Localizable (True), _
Category("Appearance")> _
Public Property Mirrored() As Boolean
Get
Return _RTL
End Get
Set(ByVal Value As Boolean)
'Set the new value if it changed
If _RTL <> Value Then
_RTL = Value
MyBase.OnRightToLeftChanged(EventArgs.Empty)
End If
End Set
End Property
Protected Overrides ReadOnly Property CreateParams()As System.Windows.Forms.CreateParams
Get
'Retrieve the CreateParams class to change the style
Dim CP As System.Windows.Forms.CreateParams
CP = New System.Windows.Forms.CreateParams()
CP = MyBase.CreateParams
'If the control needs RTL add these styles
If Mirrored Then
CP.ExStyle = CP.ExStyle Or WS_EX_LAYOUTRTL
End If
Return CP
End Get
End Property
End Class
[C#]
public class RTLTreeView : System.Windows.Forms.TreeView
{
const int WS_EX_LAYOUTRTL = 0x400000;
private bool _RTL = false;
[Description("Change to the right-to-left layout."),DefaultValue(false),
Localizable(true),Category("Appearance"),Browsable(true)]
public bool Mirrored
{
get
{
return _RTL;
}
set
{
if (_RTL != value)
_RTL = value;
base.OnRightToLeftChanged(EventArgs.Empty);
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams CP;
CP = base.CreateParams;
if (this.Mirrored)
CP.ExStyle = CP.ExStyle | WS_EX_LAYOUTRTL;
return CP;
}
}
//Rest of Tree View Code
|
Example 2: Mirrored Windows Form, the following example differs slightly from the previous one in that the style is set to WS_EX_NOINHERITLAYOUT.
[Visual Basic]
Public Class RTLForm
InheritsSystem.Windows.Forms.Form
'Define your styles
Const WS_EX_LAYOUTRTL = &H400000
Const WS_EX_ NOINHERITLAYOUT = &H100000
.
.
.
If bMirror Then
cp.ExStyle = cp.ExStyle Or WS_EX_LAYOUTRTL Or WS_EX_NOINHERITLAYOUT
End If
.
.
.
End Class
[C#]
Public Class RTLForm
Inherits System.Windows.Forms.Form
{
Const WS_EX_LAYOUTRTL = &H400000
Const WS_EX_NOINHERITLAYOUT = &H100000
.
.
.
If (bMirror)
CP.ExStyle = CP.ExStyle | WS_EX_LAYOUTRTL | WS_EX_NOINHERITLAYOUT;
.
.
.
}
|
However, this solution is not applicable to all controls that may require mirroring. Although, the ProgressBar control needs mirroring, yet the class is not inheritable and so we can't implement this method. Another class that is not inheritable is the Imagelist control class.

How to Mirror your Images?
You can mirror your images very easily by flipping the image over its horizontal axis using one line of code:
[Visual Basic]
Img.RotateFlip(RotateFlipType.RotateNoneFlipX)
[C#]
Img.RotateFlip(RotateFlipType.RotateNoneFlipX);
|
The Imagelist control is used with a number of controls mainly the Treeview, Listview and ToolBar. When you set the RTL layout of these controls the images are also mirrored as shown in this figure.
 This is a toolbar with an Imagelist control that appear flipped.
If you are sure that all your application has RTL layout, then you have two alternatives. First method, flip your images horizontally at rendering time. Therefore as the control is mirrored it would display the images properly. Second method, you may add the code for the image flipping. You may place this code in the form load method.
[Visual Basic]
'Check that you have a valid ImageList
If Treeview1.ImageList() Is Nothing Then
Debug.WriteLine ("You don't have an Imagelist")
Else
Dim i As Integer
Dim TempImg As Image
'Go through all the elements
For i = 0 To i = RtlTreeView1.ImageList.Images.Count
'Get the item
TempImg = RtlTreeView1.ImageList.Images.Item(i)
'Flip the item horizontally
TempImg.RotateFlip(RotateFlipType.RotateNoneFlipX)
'Set the item again
RtlTreeView1.ImageList.Images.Item(i) = TempImg
Next
End if
[C#]
int i;
Image tempImg;
//Go through all the elements
for(i = 0; i < rtlTreeView1.Nodes.Count;i++)
{
//Get the item
tempImg = rtlTreeView1.ImageList.Images[i];
//Flip the item horizontally
tempImg.RotateFlip(RotateFlipType.RotateNoneFlipX);
//Set the item again
rtlTreeView1.ImageList.Images[i] = tempImg;
}
|
Yet, this solution is not suitable for international application. As mentioned before, the Imagelist control class is not inheritable and therefore you do not have control over the flipping of class elements, within the class. Another disadvantage of using this method is that the Imagelist control may be used with a number of controls, not only one, therefore you have to know the effect of flipping images on the other controls. An appropriate solution for international applications is to create your images which are indifferent to mirroring. That means it can be displayed from right-to-left or from left-to-right without changing its look. Remember, you should not use text in images for international applications.
However, the image flipping problem doesn't apply to the PictureBox control because it is created as a separate control. So feel free to add pictures on your forms and they will be displayed correctly as long as you set the no inherit layout style for its parent forms.

Conclusion
This article provided an overview of how to enrich your application with a better feel of the RTL language requirements. You may use the provided assembly to enhance your program with minimal programming involved. All you need to do is to include this assembly in your project and inherit from the controls. The Form, Panel, ListView, SatusBar, TabControl, TabPage, ToolBar and Treeview controls are now mirror aware.

Downloads
Each solution consists of two projects, the main application and an assembly. The application uses the controls with the Mirrored property. The assembly contains the inherited controls. You may include this assembly in your application and use it directly.

System Requirements
Platforms: Arabic Windows 98 (enabled and localized), Arabic Windows Millennium Edition (enabled and localized), Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows .NET Server 2003 family.
Environment: Visual Studio .NET, the .NET framework.

|