Building Microsoft ASP.NET Applications for Mobile Devices, Second Edition [Electronic resources] نسخه متنی

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

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

Building Microsoft ASP.NET Applications for Mobile Devices, Second Edition [Electronic resources] - نسخه متنی

Andy Wigley; Peter Roxburgh

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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






Building Controls by Composition

You build a composite control by creating a class that inherits directly or indirectly from System.Web.UI.MobileControls, and you implement the composite control's user interface by instantiating one or more mobile controls as child controls within it. You define properties and events to give your control the capabilities you need. The controls you build this way are analogous to user controls. The principal difference between user controls and composite controls is that you define the former declaratively using ASP.NET tag syntax. User controls consist of a text file with an .ascx extension that contains ASP.NET server control syntax and which may also have a code-behind module. Composite controls consist entirely of code and are compiled into assemblies, consequently offering improved performance over user controls.

It's generally a good idea to inherit from the Panel control instead of directly from MobileControl. This is because the .NET Framework tries to avoid splitting child controls of a Panel control across multiple pages. Device adapters for the MobileControl class and the Panel class render child controls by default. Because you construct the interface by using existing mobile controls (or custom controls built by inheriting a standard control, as described earlier), you don't have to write any new device adapter classes to handle rendering.

Composite controls and controls that provide data binding must implement the System.Web.UI.INamingContainer interface. This interface doesn't have any methods, but it serves as a marker to the .NET Framework and guarantees any child controls a unique ID. You declare a class that inherits from Panel and implements the INamingContainer interface in C# as shown here:

public class MyCompositeControl : Panel, INamingContainer


Creating the Child Controls in a Composite Control


When you create a composite control, you must override the CreateChildControls method of the System.Web.UI.Control base class. In this method, you must create the child controls, which implement the user interface, and add them to the custom control's Controls collection. By default, the runtime calls the CreateChildControls method after OnLoad. Therefore, any initialization of the controls must occur in the CreateChildControls method. The runtime can call CreateChildControls many times during various phases of a control's life. To stop the runtime from executing CreateChildControls more than once, set the ChildControlsCreated property to True at the end of this method.

Other methods requiring that the child controls already exist can call the EnsureChildControls method, which causes the runtime to call CreateChildControls if it hasn't done so already. For example, you might have a public property that gets or sets a property or properties of a child control directly.

Listings 20-1 and 20-2 in Chapter 20, and in fact it even has the same name, CMShortDate. However, unlike the user control in Chapter 20, all the functionality of this control is defined in code. To keep things simple, this listing doesn't implement a different user interface depending on whether the client is HTML or WML, although it could easily be extended to do so. We'll build this control in three steps, increasing the functionality each time. The first step of this control, shown here, simply displays a date specified through the SelectedDate property. This listing hasn't yet implemented any logic that returns the user's selection after a date has been selected using the control.

Listing 21-4: CMShortDate.cs—step 1: the source of a composite control






using System;
using System.Web.UI;
using System.Web.UI.MobileControls;
namespace MSPress.MobWeb.CustomControls
{
/// <summary>
/// Example of a composite control
/// </summary>
public class CMShortDate : Panel, INamingContainer
{
private SelectionList _selDay;
private SelectionList _selMonth;
private SelectionList _selYear;
private Label _lblPrompt;
private DateTime _currentdate;
private DateTime _minDate;
private DateTime _maxDate;
/// <summary>
/// Gets and sets the date displayed in System.DateTime format
/// </summary>
public System.DateTime SelectedDate
{
get
{
return _currentdate;
}
set
{
if ((value < _minDate) || (value > _maxDate))
{
// Invalid date
throw(new ArgumentOutOfRangeException
("SelectedDate",
value.ToString("d-MMM-yyy"),
"Date out of supported range 01-Jan-2002 to 31-Dec-2012"
));
}
_currentdate = value;
}
}
/// <summary>
/// Gets and sets the text displayed for a prompt
/// </summary>
public String Text
{
get
{
this.EnsureChildControls();
return _lblPrompt.Text;
}
set
{
this.EnsureChildControls();
_lblPrompt.Text = value;
}
}
public CMShortDate()
{
_currentdate = DateTime.Now;
_minDate = new DateTime(2002,1,1);
_maxDate = new DateTime(2010,12,31);
}
protected override void CreateChildControls()
{
// Create child controls.
Label label;
MobileListItem item;
_lblPrompt = new Label();
_lblPrompt.Text = "Select a date:";
Controls.Add(_lblPrompt);
label = new Label();
label.Text = "Day: ";
Controls.Add(label);
_selDay = new SelectionList();
for (int intDay=1; intDay < 32; intDay++ )
{
item = new MobileListItem();
item.Text = intDay.ToString();
_selDay.Items.Add(item);
}
Controls.Add(_selDay);
label = new Label();
label.Text = "Month: ";
Controls.Add(label);
_selMonth = new SelectionList();
for (int intMonth=1; intMonth < 13; intMonth++ )
{
item = new MobileListItem();
DateTime dt = new DateTime(1,intMonth,1);
item.Text = dt.ToString("MMM");
item.Value = intMonth.ToString();
_selMonth.Items.Add(item);
}
Controls.Add(_selMonth);
label = new Label();
label.Text = "Year: ";
Controls.Add(label);
_selYear = new SelectionList();
for (int intYear=2002; intYear < 2011; intYear++ )
{
item = new MobileListItem();
item.Text = intYear.ToString();
_selYear.Items.Add(item);
}
Controls.Add(_selYear);
//Set the controls for the currentdate
_selDay.SelectedIndex = _currentdate.Day - 1;
_selMonth.SelectedIndex = _currentdate.Month - 1;
_selYear.SelectedIndex = _currentdate.Year - 2002;
ChildControlsCreated = true;
}
}
}











The code implements only two properties directly. But because this custom control descends from Panel, which itself descends from MobileControl, it already possesses all the standard properties, such as UniqueID, Font, and ForeColor.

The Text property sets and gets the Text property of the child _lblPrompt Label control; before it does so, the accessor methods call EnsureChildControls to verify that the child controls exist. This example simply produces three drop-down lists with accompanying prompts, as Figure 21-3 shows.


Figure 21-3: Selecting a date in the CMShortDate user control


Processing Postback Data


The CMShortDate control must act on the data that the client posts back to the server to determine which date the user selected and update its SelectedDate property accordingly. To do so, capture the appropriate PostDataChangedEvent of the control's child controls—in this case, the SelectedIndexChanged event of the three SelectionList controls. In the CreateChildControls method, set the SelectedIndexChanged property of each SelectionList control to the CMShortDate control's OnSelectionChanged event handler method. Listing 21-5 depicts the changes you make to the control code.

Listing 21-5: CMShortDate.cs—step 2: modifications to capture change events from the child: controls






using System;
using System.Web.UI;
using System.Web.UI.MobileControls;
namespace MSPress.MobWeb.CustomControls
{
public class CMShortDate : Panel, INamingContainer
{


protected void OnSelectionChanged(object sender, EventArgs e)
{
_currentdate = new DateTime(
_selYear.SelectedIndex + 2002,
_selMonth.SelectedIndex + 1,
_selDay.SelectedIndex + 1);
}
protected override void CreateChildControls()
{


//Set the controls for the currentdate
_selDay.SelectedIndex = _currentdate.Day - 1;
_selMonth.SelectedIndex = _currentdate.Month - 1;
_selYear.SelectedIndex = _currentdate.Year - 2002;
// Capture the change events of the child controls.
_selDay.SelectedIndexChanged +=
new EventHandler(this.OnSelectionChanged);
_selMonth.SelectedIndexChanged +=
new EventHandler(this.OnSelectionChanged);
_selYear.SelectedIndexChanged +=
new EventHandler(this.OnSelectionChanged);
ChildControlsCreated = true;
}
}
}












With these changes in place, the control now provides the same functionality as the user control you developed in Chapter 20, in Listings 20-1 and 20-2. You can use the SelectedDate property to set the start date and to retrieve the user's selection after postback.


Raising Custom Events


After you've trapped the change events of the child controls in order to update the custom control's state, you can raise a change event, offering additional functionality to the page developer who is building applications using your custom control. We'll change the CMShortDate control so that if the application's user changes the date set in the control, the composite control raises the DateChanged event on the server. You implement this by making some simple additions to the CMShortDate class. First, you declare the event name in the class:

    public class CMShortDate : Panel, INamingContainer
{
public event EventHandler DateChanged;



In OnSelectionChanged, the method we wrote in the previous section to capture the change events of the three SelectionList child controls, the application raises the event if the page developer has declared a DateChanged event handler. The updated OnSelectionChanged method is listed here with the modifications in bold.

protected void OnSelectionChanged(object sender, EventArgs e)
{
_currentdate = new DateTime(
_selYear.SelectedIndex + 2002,
_selMonth.SelectedIndex + 1,
_selDay.SelectedIndex + 1);
// The'DateChanged' event property of this control is null
// until the page developer has registered an event handler
// for the event
EventHandler onDateChanged = DateChanged;
if (onDateChanged != null)
{
// Call any user-declared event handlers.
onDateChanged(this, new EventArgs());
}
}

Event handler methods always take a first parameter of the originating control and a second parameter of an EventArgs object or a class descended from EventArgs. In this example, the code sets the second parameter to an empty EventArgs instance. In other applications, you might want to define your own class with custom properties that you deliver with the event.

The page developer can now write his or her own event handler method and wire it up so that it is called when the control raises the event. This is done in exactly the same way as with standard controls, either by setting DateChanged to the developer's own event handler in code or by wiring up the event handler declaratively. The following code shows how to do the latter:

<CMcustom:CMShortDate runat="server" OnDateChanged="HasChanged" />

This particular control needs a refinement to make it work correctly. At the moment, if the user changes only one part of the date, such as the day, the runtime calls OnSelectionChanged only once. However, if the month or year changes too, the runtime calls OnSelectionChanged two or three times. Because you want the runtime to call the DateChanged event only once when any part of the date changes, you must add a private bool data member to act as a flag. The code initializes this flag to false in the class constructor (each time the code builds the control, at the beginning of processing of each request) and sets this flag the first time the runtime calls OnSelectionChanged. The runtime then uses this flag to block repeat processing in any one request. Listing 21-6 shows the full code changes needed for this event.

Listing 21-6: CMShortDate.cs—step 3: implementation of the DateChanged event






    public class CMShortDate : Panel, INamingContainer
{
private bool _DateChangeProcessed; // Flag to stop repeat events
public event EventHandler DateChanged; // Event declaration
// Other property declarations not shown


public CMShortDate()
{
_currentdate = DateTime.Now;
_minDate = new DateTime(2002,1,1);
_maxDate = new DateTime(2010,12,31);
_DateChangeProcessed = false;
}
protected void OnSelectionChanged(object sender, EventArgs e)
{
if (!_DateChangeProcessed)
{
_currentdate = new DateTime(
_selYear.SelectedIndex + 2002,
_selMonth.SelectedIndex + 1,
_selDay.SelectedIndex + 1);
EventHandler onDateChanged = DateChanged;
if (onDateChanged != null)
{
onDateChanged(this, new EventArgs());
}
_DateChangeProcessed = true;
}
}


}












The CMShortDate control now possesses a lot of useful functionality. Listing 21-7 shows how you can use this control in a mobile Web Forms page. This example also demonstrates how properties such as Font-Bold and ForeColor inherit from the containing Panel control.

Listing 21-7: Source file Default.aspx, which uses the CMShortDate custom control






<%@ Register TagPrefix="mobile" 
Namespace="System.Web.UI.MobileControls"
Assembly="System.Web.Mobile" %>
<%@ Page language="c#"
Inherits="System.Web.UI.MobileControls.MobilePage" %>
<%@ Register TagPrefix="CMcustom" Namespace="MSPress.MobWeb.CustomControls"
Assembly="CustomMobileControlLibrary" %>
<head>
<script runat="server" language="C#">
public void DateHasChanged(Object sender, EventArgs e)
{
Label1.Text = "You selected: "
+ CMShortDate1.SelectedDate.ToLongDateString();
this.ActiveForm = Form2;
}
</script>
</head>
<body>
<mobile:Form id="Form1" runat="server">
<CMcustom:CMShortDate id="CMShortDate1" runat="server"
OnDateChanged="DateHasChanged" SelectedDate="01-Jan-2002"
Font-Bold="true" ForeColor="Red" >
</CMcustom:CMShortDate>
<mobile:Command id="Command1" Text="Next" Runat="server"/>
</mobile:Form>
<mobile:Form id="Form2" runat="server">
<mobile:Label id="Label1" runat="server"></mobile:Label>
</mobile:Form>
</body>











Figure 21-4 shows the code output when accessed from the Nokia simulator.


Figure 21-4: Output from Listing 21-7 on the Nokia simulator


Managing ViewState


Currently the CMShortDate control has a problem that will surface if your application displays it at a later stage of execution. For example, try modifying the sample application shown in Listing 21-7 by placing a Command control on Form2. When the user clicks the button shown in the client browser, this causes a postback to the server, where the Command control's click is raised. The event handler for the Click event that we now add to this application is called Return, which sets the ActiveForm to Form1 again. Listing 21-8 shows the code and highlights the changes.

Listing 21-8: Source file Default.aspx—demonstrating loss of control state






<%@ Register TagPrefix="mobile" 
Namespace="System.Web.UI.MobileControls"
Assembly="System.Web.Mobile" %>
<%@ Page language="c#"
Inherits="System.Web.UI.MobileControls.MobilePage" %>
<%@ Register TagPrefix="CMcustom" Namespace="MSPress.MobWeb.CustomControls"
Assembly="CustomMobileControlLibrary" %>
<head>
<script runat="server" language="C#">


public void Return(Object sender, EventArgs e)
{
this.ActiveForm = Form1;
}
</script>
</head>
<body>


<mobile:Form id="Form2" runat="server">
<mobile:Label id="Label1" runat="server"></mobile:Label>
<mobile:Command id="Command2" Text="Return" Runat="server"
OnClick="Return"/>
</mobile:Form>
</body>












When Form1 is displayed again as a result of the user clicking the Command button, the CMShortDate control displays the date it has initialized within the server control syntax ("1-Jan-2002"), rather than the date the user set. Figure 21-5 illustrates this problem.


Figure 21-5: The CMShortDate control doesn't retain its settings across subsequent requests and doesn't implement ViewState correctly.

On each postback, the mobile page loads and the runtime creates all the controls. The application sets the _currentdate property of the CMShortDate control in the class constructor to DateTime.Now—in other words, the default value is today's date. Then the ASP.NET Runtime initializes properties of all controls according to the values declared in the .aspx file's server control syntax. In this instance, the code sets the CMShortDate control's SelectedDate property to "1-Jan-2002". Soon after, the runtime calls the CreateChildControls method. This method creates the SelectionList controls that implement CMShortDate's user interface and sets them to display the control's current date.

If you revisit the stages of a control's life shown in Table 21-1, you'll see that after the control initializes, its LoadViewState method executes. This updates properties from settings stored in the control's ViewState at the end of the previous request. Then the LoadPostData method executes, causing the control to analyze the data posted back from the client. If the user entered a value or selected an item that changes the control's state, the postback translates the user's instructions into action. (In this case, the code must update the SelectedDate property.) Just before rendering the output that's sent back to the client, the runtime calls the SaveViewState method to persist any properties or other settings that must be restored at the beginning of the next request.

Clearly, you want the CMShortDate control to retain its date setting across requests, which means storing the _currentdate property in ViewState. Saving the _currentdate value in the control's ViewState collection rather than as a private data member easily achieves this. Because the ViewState collection is dictionary structured, you can store and retrieve objects by specifying a string key value, as shown here:

// Save a value.
ViewState["currentdate"] = value;


// Restore a value.
DateTime datefromViewState = (DateTime)ViewState["currentdate"];


Once you store values in the control's ViewState collection, the LoadViewState and SaveViewState methods of the control base class persist that ViewState across requests. You can store most simple types and arrays in the ViewState collection. However, if you have a complex object that you can't save by default in the persisted ViewState collection, you must override SaveViewState and LoadViewState to handle the serialization and deserialization of this object.

Listing 21-9 shows the changes the CMShortDate class requires.

Listing 21-9: CMShortDate.cs—final version: storing properties to save across requests in the ViewState object rather than storing them as class member variables






using System;
using System.Web.UI;
using System.Web.UI.MobileControls;
namespace MSPress.MobWeb.CustomControls
{
/// <summary>
/// Example of a composite control
/// </summary>
public class CMShortDate : Panel, INamingContainer
{
private bool _DateChangeProcessed; // Flag to stop repeat events
public event EventHandler DateChanged; // Event declaration
private SelectionList _selDay;
private SelectionList _selMonth;
private SelectionList _selYear;
private Label _lblPrompt;
// private DateTime _currentdate; - no longer required
private DateTime _minDate;
private DateTime _maxDate;
private bool _DateChangeProcessed;
/// <summary>
/// Gets and sets the date displayed in System.DateTime format
/// </summary>
public DateTime SelectedDate
{
get
{
return (DateTime) ViewState["currentdate"] ;
}
set
{
if ((value < _minDate) || (value > _maxDate))
{
// Invalid date
throw(new ArgumentOutOfRangeException
("SelectedDate",
value.ToString("d-MMM-yyy"),
"Date out of supported range 01-Jan-2002 to 31-Dec-2012"
));
}
ViewState["currentdate"] = value;
}
}


public CMShortDate()
{
ViewState["currentdate"] = DateTime.Now;
_minDate = new DateTime(2002,1,1);
_maxDate = new DateTime(2010,12,31);
_DateChangeProcessed = false;
}
protected void OnSelectionChanged(object sender, EventArgs e)
{
if (!_DateChangeProcessed)
{
ViewState["currentdate"] = new DateTime(
_selYear.SelectedIndex + 2002,
_selMonth.SelectedIndex + 1,
_selDay.SelectedIndex + 1);
EventHandler onDateChanged = DateChanged;
if (onDateChanged != null)
{
onDateChanged(this, new EventArgs());
}
_DateChangeProcessed = true;
}
}
protected override void CreateChildControls()
{


Controls.Add(_selYear);
// Set the controls for the current date.
DateTime currentdate = (DateTime) ViewState["currentdate"];
_selDay.SelectedIndex = currentdate.Day - 1;
_selMonth.SelectedIndex = currentdate.Month - 1;
_selYear.SelectedIndex = currentdate.Year - 2002;
// Capture the change events of the child controls.
_selDay.SelectedIndexChanged +=
new EventHandler(this.OnSelectionChanged);


}
}
}












/ 145