In addition to the portrait and landscape displays, Windows Mobile also supports devices with square displays. Adding support for these devices requires a little more flexibility in the application. In this exercise, you will modify the application to work with square displays and any other displays that the application may encounter. Adding this flexibility ensures that the application provides a reasonable viewing experience on all Windows Mobile powered device displays—even those that may not exist yet.
In this exercise, that's exactly what you're going to do—modify the application so, in the case of a square display, the application uses three forms rather than the two forms that are used for portrait and landscape displays. In this three-form layout, the property listing occupies the entire main form, and the main form now has an additional command, View Photo, that displays the form containing the property photo. The property photo is now on its own form and displays the photo corresponding to the property that is currently selected on the main form when the user selects the View Photo command. The process of viewing the property detail information is unchanged; property detail information is still on its own form and displays the details for the property that is currently selected on the main form when the user selects the Details command.
To update the application to support square-display devices
-
Open DisplayInfo.cs in Code view.
-
Locate the DisplayMode enumeration, and then add a Simple value, as shown in the following code example.
The DisplayMode.Simple enumeration identifies that neither the portrait or landscape layouts are appropriate. A DisplayMode value of Simple indicates that the application is using the new three form layout.
enum DisplayMode
{
Portrait,
Landscape,
Simple
}
-
Locate the GetDisplayMode method in the DisplayInfo class.
This method will be modified to return the DisplayMode.Simple value in addition to Portrait and Landscape. One way to implement GetDisplayMode is to return Portrait when the screenBounds.Height property is greater than the screenBounds.Width property, Landscape when the reverse is true, and Simple when they are equal. The shortcoming in this implementation is that it assumes that either the portrait or landscape layout will work for all display types other than those that are exactly square. This is not a totally safe assumption.
Although none exist today, there may be a time when devices have a nearly square display—where the display height is just a little greater than the display width or vice versa. In these cases, a square-like layout is more appropriate than portrait or landscape. To address this situation, the GetDisplayMode method identifies ranges that each layout is appropriate for. In the case of the portrait layout, the display must have a width-to-height display ratio less than 0.8. For the landscape layout, the width-to-height display ratio must be greater than 1.3. Any display with a width-to-height ratio between 0.8 and 1.3 inclusive uses the Simple layout.
Note: |
|---|
|
There is no absolute rule about the appropriate width-to-height ratio for each of the display layouts, but the width-to-height ratios used in this exercise serve as a good guideline. The display layouts used in this exercise are based on the width-to-height display ratios used by currently available Pocket PCs. Today, all portrait display Pocket PCs have a display size of 240 x 320 or 480 x 640, both of which are a width-to-height display ratio just under 0.8 (0.75). Similarly, all landscape display Pocket PCs that are available today have a display size of 320 x 240 or 640 x 480, both of which are a width-to-height display ratio are greater than 1.3 (1.3333…). Although the portrait and landscape width-to-height display ratios are based on common Pocket PC display sizes, the ratios work equally well when determining layout requirements for Smartphones.
|
-
Rather than hardcode the display ratio boundaries, declare two constants with a data type of float just before the GetDisplayMode method. Name the constants minSimpleDisplayRatio and maxSimpleDisplayRatio. Assign the constants values of 0.8F and 1.3F respectively, as shown in the following code example.
const float minSimpleDisplayRatio = 0.8F;
const float maxSimpleDisplayRatio = 1.3F;
-
Within the GetDisplayMode method just after the declaration of the screenBounds variable, calculate the width-to-height display ratio. First declare a variable named widthToHeightRatio with a data type of float. Calculate the width-to-height display ratio by dividing the screenBounds.Width property by the screenBounds.Height property, and then assign the result to the widthToHeightRatio variable. In the calculation, cast the Width and Height properties to float or the calculation will use integer division (whole numbers only, no fractional values), as shown in the following code example.
float widthToHeightRatio = (float)screenBounds.Width / (float)screenBounds.Height;
-
Modify the condition of the existing if/else statement block so the mode variable is assigned the DisplayMode.Portrait value when the widthToHeightRatio variable is less than the minSimpleDisplayRatio constant. Change the existing else to an else if statement with a condition that assigns the DisplayMode.Landscape value to the mode variable when the widthToHeightRatio is greater than the maxSimpleDisplayRatio constant. Add an else block that sets the mode variable to the DisplayMode.Simple value if the other two conditions test false.
-
Verify that the complete GetDisplayMode method looks like the following code example.
public static DisplayMode GetDisplayMode()
{
Rectangle screenBounds = Screen.PrimaryScreen.Bounds;
DisplayMode mode = DisplayMode.Simple ;
float widthToHeightRatio = (float)screenBounds.Width / (float)screenBounds.Height;
if (widthToHeightRatio < minSimpleDisplayRatio)
{
mode = DisplayMode.Portrait;
}
else if (widthToHeightRatio > maxSimpleDisplayRatio)
{
mode = DisplayMode.Landscape;
}
else
{
mode = DisplayMode.Simple;
}
return mode;
}
-
With the DisplayInfo.GetDisplayMode method complete, open ImageForm.cs in the form designer.
Notice that the form contains a PictureBox control whose Dock property is set to Fill. Other than being set to fill the form, this PictureBox control has the same properties as the PictureBox control on the main form. The ImageForm form is used only when the DisplayInfo.GetDisplayMode method returns a DisplayMode.Simple value.
-
Open ImageForm.cs in Code view.
Notice that the implementation of the ImageForm class has only one public member: the SetBindingSource method. The SetBindingSource method is used to set the BindingSource for the ImageForm class to the same BindingSource as the Form1 and PropertyDetailForm forms. The private members of the ImageForm class are primarily housekeeping methods that are used to display the property photo each time the BindingSource's current record changes.
-
Open Form1.cs in Code view, and then locate the PropertyDetailForm member variable declaration near the beginning of the class.
-
Immediately following the _propertyDetailForm class member declaration, declare a class member variable menuViewPhoto with a data type of MenuItem. Following the menuViewPhoto class member declaration, add a class member declaration for the ImageForm form named _imageForm, as shown in the following code example.
Bitmap _imageBitmap;
PropertyDetailForm _propertyDetailForm;
MenuItem menuViewPhoto;
ImageForm _imageForm;
-
Add a new method to the Form1 class named menuViewPhoto_Click. The menuViewPhoto_Click method handles the click event for a command that you will be adding soon, so it must conform to the delegate signature that is required for menu Click event handlers, and it must have a return type of void and two parameters: an object parameter and an EventArgs parameter.
-
In the menuViewPhoto_Click method, call the ShowDialog method on the _imageForm member variable, but only do so when the _imageForm member variable does not have a value of null, as shown in the following code example.
private void menuViewPhoto_Click(object sender, EventArgs e)
{
if (_imageForm != null)
_imageForm.ShowDialog();
}
-
Locate the Form1_Load method. In the Form1_Load method, add an if statement block immediately following the call to DisplayImage. In the if condition, call the DisplayInfo.GetDisplayMode method and test to see if the return value is DisplayMode.Simple, as shown in the following code example.
if (DisplayInfo.GetDisplayMode() == DisplayMode.Simple)
{
}
-
In the if block, create the _imageForm instance, and then call the _imageForm.SetBindingSource method passing in the propertyBindingSource property, as shown in the following code example. The propertyBindingSource is the same BindingSource used by Form1 and the PropertyDetailForm class. By binding to the propertyBindingSource, the property photo on the ImageForm automatically stays synchronized with the property selection on Form1.
_imageForm = new ImageForm();
_imageForm.SetBindingSource(propertyBindingSource);
In the DisplayMode.Simple layout, the property photo is displayed on the ImageForm; therefore, the property PictureBox on Form1 should not be shown.
-
In the if block, hide the PictureBox by setting the propertyPictureBox.Visible property to false, as shown in the following code example.
propertyPictureBox.Visible = false;
-
The property DataGrid can now occupy all of Form1, so set the propertyDataGrid.Dock property to DockStyle.Fill in the if block, as shown in the following code example.
propertyDataGrid.Dock = DockStyle.Fill;
When the application is using the DisplayMode.Simple layout, all of Form1 is occupied by the property listing; therefore, the user needs a command to display the ImageForm form.
-
In the if block, create a new MenuItem instance, and then assign it to the menuViewPhoto class member variable. Set the menuViewPhoto.Text property to View Photo, as shown in the following code example.
menuViewPhoto = new MenuItem();
menuViewPhoto.Text = "View Photo";
-
Associate the menuViewPhoto_Click method that you created earlier with the menuViewPhoto.Click event. Finally, add the menuViewPhoto class member to the menuPopup.MenuItems collection.
-
Verify that the complete Form1_Load method looks like the following code example.
private void Form1_Load(object sender, EventArgs e)
{
// Get values for control scaling
DisplayInfo.GetDisplayRatios(this, ref _widthRatio, ref _heightRatio);
PopulateHouseViewDataSet();
_propertyDetailForm = new PropertyDetailForm();
_propertyDetailForm.SetBindingSource(propertyBindingSource);
DisplayImage();
// In Simple mode, picture is displayed on a separate form
if (DisplayInfo.GetDisplayMode() == DisplayMode.Simple)
{
// Create the image form and hook up data binding
_imageForm = new ImageForm();
_imageForm.SetBindingSource(propertyBindingSource);
// Hide the picture box on Form1 and set the grid
// to occupy the whole form
propertyPictureBox.Visible = false;
propertyDataGrid.Dock = DockStyle.Fill;
// Add a command to display image
menuViewPhoto = new MenuItem();
menuViewPhoto.Text = "View Photo";
menuViewPhoto.Click += menuViewPhoto_Click;
menuPopup.MenuItems.Add(menuViewPhoto);
}
}
Note: |
|---|
|
For simplicity, the Form1_Load method and the rest of the lab code focuses on providing easy-to-read code that is related to managing device differences rather than focusing on code optimization at the cost of readability. The Form1_Load method is one such case where this is true. If desired, you can add a slight resource optimization to the Form1_Load method by adding an else block to the existing if statement block, so the DisplayImage method only gets called when the layout is not DisplayMode.Simple.
|
The DisplayImage method updates the PictureBox on the main form, so the image that is displayed corresponds to the currently selected property. When the application is using the DisplayMode.Simple layout, the PictureBox on the main form is hidden; therefore, the call to the DisplayImage method has no visible effect on the application, but it consumes some resources by creating a BitMap instance. Although changing the Form1_Load method to not call the DisplayImage method saves some resources, the savings is not that significant because the Form1_Load method is only called once at the beginning of the application. An even better optimization is to modify the DisplayImage method itself, so it only updates the image displayed in the PictureBox when the layout is DisplayMode.Simple.
-
Locate the AdjustFormLayout method.
Locate the last three lines in the AdjustFormLayout method. They set the size of the propertyDataGrid and propertyPictureBox controls and the location of the propertyPictureBox control. These assignments are only necessary for the DisplayMode.Portrait and DisplayMode.Landscape layouts because, in the DisplayMode.Simple layout, the propertyPictureBox is hidden and the propertyDataGrid is set to fill the form.
-
To prevent these assignments from happening when using the simple layout, place the three statements in an if statement block that checks that the dispMode variable is not set to DisplayMode.Simple.
With the new if statement block added, the last several lines of the AdjustFormLayout method look like the following code example.
ScaleFormControls(ref gridSize, ref pictureBoxLocation, ref pictureBoxSize);
if (dispMode != DisplayMode.Simple)
{
propertyDataGrid.Size = gridSize;
propertyPictureBox.Location = pictureBoxLocation;
propertyPictureBox.Size = pictureBoxSize;
}
}
-
Locate the ScaleFormControls method in the Form1 class.
You need to modify the ScaleFormControls method so the DataGrid and PictureBox are only scaled when the application is using a DisplayMode.Portrait or DisplayMode.Landscape layout.
-
In the ScaleFormControls method, add an if/else statement block immediately following the call to DisplayInfo.GetDisplayMode.
The if condition tests whether the dispMode variable has a value of DisplayMode.Simple. The else block contains all of the remaining code in the method except the call to the ScaleGridMembers method. The call to the ScaleGridMembers method is just after the end of the else block because the columns within the DataGrid need to be appropriately sized for all display layouts.
-
Add a line to the true part of the if block that divides the propertyDataGrid's width by the gridDesignWidth constant and assigns the result to the gridWidthRatio variable.
The completed ScaleFormControls method looks like the following code example.
void ScaleFormControls(ref Size gridSize, ref Point pictureBoxLocation, ref Size pictureBoxSize)
{
float gridWidthRatio = 0;
DisplayMode dispMode = DisplayInfo.GetDisplayMode();
if (dispMode == DisplayMode.Simple)
{
// In default layout, the size of the grid is not
// adjusted but
// the grid may still be a different size then at
// design-time
// because in default layout, the grid is set to fill
// the form
gridWidthRatio = propertyDataGrid.Size.Width / gridDesignWidth;
}
else
{
gridSize.Width = (int)(gridSize.Width * _widthRatio);
gridSize.Height = (int)(gridSize.Height * _heightRatio);
pictureBoxSize.Width = (int)(pictureBoxSize.Width * _widthRatio);
pictureBoxSize.Height = (int)(pictureBoxSize.Height * _heightRatio);
gridWidthRatio = gridSize.Width / gridDesignWidth;
if (dispMode == DisplayMode.Portrait)
// portrait
pictureBoxLocation.Y = (int)(pictureBoxLocation.Y * _heightRatio);
else if (dispMode == DisplayMode.Landscape)
// landscape
pictureBoxLocation.X = (int)(pictureBoxLocation.X * _widthRatio);
}
// Must always adjust grid members to correspond
// to the grid's current width
ScaleGridMembers(propertyDataGrid, gridWidthRatio);
}
To test the final application on the square-screen emulator
To test the final application on other emulators