|
|
10月26日
| Introduction
|
Themes are used to define visual styles for web pages. Themes
are made up of a set of elements: skins, Cascading Style Sheets (CSS), images,
and other resources. Themes can be applied at the application, page, or server
control level. We can create theme by adding App_Themes folder in our project in
solution explorer panel. The simple steps are Right click on project and select
Add ASP.NET folder – Theme. We can add files into it also. The sample code
snippets in this article have been written in C#.
|
| Applying Themes
|
Applying Theme to a Page
A Theme is an attribute of the page class that we can set in
various ways. Once the Theme is set, the page ensures that all of its controls
will render according to the visual settings defined in the Theme. Listing 1
illustrates how to define a Theme to a page.
Listing 1
<% Page Language=“C#” Theme=”myTheme” %>
Applying Theme to an Entire Application
In addition to applying an ASP.NET 2.0 theme to our ASP.NET
pages using the Theme attribute within the page directive, we can also apply it
at an application level from the Web.config file. Then the Theme will be applied
automatically to each and every page within the application. This is illustrated
in Listing 2.
Listing 2
<configuration> <system.web> <pages theme=”myTheme”/> </system.web> </configuration>
Applying Theme to all Applications in a Server
If we want to use a particular theme for all the web
applications in the server, we can do the same. We can specify the theme that we
want to use within the machine.config file. This is illustrated in Listing
3.
Listing 3
<pages buffer="true" enableSessionState="true" enableViewState="true" enableViewStateMac="true" autoEventWireup="true" validateRequest="true" enablePersonalization="false" theme=" myTheme " > </pages>
Thus adding the Theme attribute to the pages node within the
machine.config file is a great solution for the server having multiple
applications using the same Theme. Setting a Theme in the machine.config file
means that we do not need to use this theme separately for all the applications
on the server. If we specify another Theme in the application's web.config file
or in the Web page's Page directive, then settings that are set in the
web.config file override settings that are in the machine.config file and
settings that are placed in the Page directive override both settings in the
machine.config and in the web.config files.
|
| Removing Themes
|
Removing Theme from Server Control
Whether themes are set at application level or page level, we
can set a control’s own style on it.
Listing 4
<asp:Textbox ID=”txtTest” Runat=”server” BackColor=”White” ForeColor=”Black” EnableTheming=”false” />
If we apply myTheme at application or page level, the same
theme will be applied in all controls except the "txtTest" textbox control as
mentioned above. Instead, the backcolor and forecolor, as mentioned in the code
snippet, will be applied.
Removing Theme from Web Pages
If we set the Theme for an entire application in the
web.config file and then want to exclude a single ASP.NET page, we can do the
same by removing Theme setting at the page level, just like server control. This
is illustrated in Listing 5.
Listing 5
<% Page Language=“C#” EnableTheming=”false” %>
If we make EnableTheming attribute false in the page
directive, the Theme defined in web.config file will not be applied in the page.
But we can still provide Theme in respect of a specific server control in the
same page. This is illustrated in Listing 6.
Listing 6
<asp:Textbox ID=”txtTest” Runat=”server” EnableTheming=”true” Theme=” myTheme”/>
Theme vs. StylesheetTheme
The Page directive also includes the attribute
StylesheetTheme that we can use to apply themes to a page. So, the big question
is: If we have a Theme attribute and a Stylesheet attribute for the page
directive, what we should use?
Listing 7
<% Page Language=“C#” StylesheetTheme =”myTheme” %>
The StylesheetTheme attribute works the same as the Theme
attribute to apply a Theme to a page. The difference is that, when attributes
are set locally on the page within a particular server control, the attributes
are overridden by the theme when we use the Theme attribute. But if we apply
pages’s theme using the StylesheetTheme attribute, the locally set attributes of
the server control will not be overridden.
|
| Creating Theme
|
In order to create Theme for an application, first we need to
create a proper folder structure in application. Within the App_Themes folder,
we can create an additional Theme Folder for each and every Theme that we might
use in application. The reasons to use more than one Theme are because seasons
change, day/night changes, different business units, category of user, user
preferences, etc.
The elements of a Theme Folder can be as follows:
· A Single skin
file
· CSS files
· Images
Creating a Skin
A skin enables us to modify any of the properties applied to
the server controls in our ASP.NET page. Skins can work in conjunction with CSS
files or images. To create a Theme we can use a single skin file in the Theme
Folder. The skin file extension should be always .skin.
Listing 8
<asp:Label Runat=”server” ForeColor=”Red” Font-Names=”Verdana” Font-Size=”X-Small”/> <asp:Textbox Runat=”server” ForeColor=”Red” Font-Names=”Verdana” Font-Size=”X-Small” BorderStyle=”Solid” BorderWidth=”1px” BorderColor=”Yellow” Font-Bold=”True” />
As the above example suggest, we need to define a definition
for style in respect of each server control in skin file in the application. We
can create different skin files in different Theme Folder.
Listing 9
<% Page Language=“C#” Theme =”myTheme” %>
|
| Including CSS Files in Theme
|
We can provide styles only for server controls using skin
file. But ASP.NET pages are made up of HTML controls, raw HTML or raw Text and
to implement style for them we need CSS files within Theme Folder. To create a
CSS file for Theme we will click Theme Folder and then select Add New Item. In
the list of options we should choose the option Style Sheet and will provide a
name say test.CSS. When we are using both skin and CSS file, the skin file will
take precedence over CSS file for server controls.
|
| Defining Multiple Skin Options
|
In .skin file of Theme’s Folder we can create multiple
definitions in respect of same server control. To create multiple definitions of
a single element we can use the SkinID attribute to differentiate among the
definitions. The value of SkinID can be anything.
Listing 10
<asp:Textbox Runat=”server” ForeColor=”Blue” Font-Names=”Verdana” Font-Size=”X-Small” BorderStyle=”Solid” BorderWidth=”1px” BorderColor=”Red” Font-Bold=”True” /> <asp:Textbox Runat=”server” ForeColor=”Red” Font-Names=”Verdana” Font-Size=”X-Small” BorderStyle=”Dotted” BorderWidth=”5px” BorderColor=”Blue” Font-Bold=”False” SkinID=”txtDotted”/> <asp:Textbox Runat=”server” ForeColor=”Yellow” Font-Names=”Arial” Font-Size=”X-Large” BorderStyle=”Dashed” BorderWidth=”1px” BorderColor=”Red” Font-Bold=”False” SkinID=”txtDashed”/>
In the above code there is no SkinID for the 1st TextBox
server control definition that means it will be used as the default style for
TextBox Server control. Where SkinID will be used, the particular definition of
TextBox Server Control will be used.
Listing 11
<% Page Language=“C#” Theme =”myTheme” %> <html> <body> <form id=”frmThemeTest” runat=”server”> <p> <asp:TextBox ID=”txtThemeTest1” Runat=”server”>TextBox1</asp:TextBox> <p> <asp:TextBox ID=”txtThemeTest2” Runat=”server” SkinID=” txtDotted “> TextBox1</asp:TextBox> <p> <asp:TextBox ID=”txtThemeTest3” Runat=”server” SkinID=” txtDashed”> TextBox1</asp:TextBox> </body> </html>
|
| Working with Themes Programmatically
|
In the above code the Theme has been defined at design time,
but we can work with the Theme programmatically.
Assigning the Pages’s Theme Programmatically
In Listing 12 it is illustrated how we can assign the pages'
theme programmatically.
Listing 12
<script runat =”server”> Protected void Page_PreInit(object sender, System.EventArgs e) { Page.Theme=Request.QueryString[“AppliedTheme”]; } </script>
Assigning a Control’s SkinID Programmatically
There is another option to assign a specific server control’s
SkinID property programmatically, which is illustrated in Listing 13.
Listing 13
<script runat =”server”> Protected void Page_PreInit(object sender, System.EventArgs e) { txtThemeTest.SkinID=”txtDashed”; } </script>
|
| Images in Theme
|
Themes enable us to incorporate actual images into the style
definitions. A lot of controls use images to create a better visual appearance.
We should create an Images folder within the Themes folder itself.
Listing 14
<asp:image ID="Image1" runat="server" Imageurl="Images/testImage.jpg" />
When the above reaches the client, the browser will be smart
enough to request testImage.jpg from the App_Themes/ myTheme /Images directory
(when myTheme is the selected theme).
|
| References
|
|
| Conclusion
|
Themes are a powerful addition to the ASP.NET framework. By
taking advantage of themes, we can dramatically reduce the amount of content
that we need to add to individual ASP.NET pages. Themes enable us to define the
appearance of a control once and apply the appearance throughout a Web
application. Therefore, themes enable us to easily create Web sites that have a
consistent and maintainable design. This improves the maintainability of our
site and avoids unnecessary duplication of code for shared styles. | 10月17日
If you can get your recordset into a list you can use LINQ like so: HTML<asp:ListView ID="ListView1" runat="server"> <LayoutTemplate> <asp:PlaceHolder runat="server" ID="itemPlaceHolder"></asp:PlaceHolder> </LayoutTemplate> <ItemTemplate> <table> <tr> <td><%#Eval("Key")%></td> </tr> <tr> <td> <ol> <asp:ListView ID="ListView2" runat="server" DataSource='<%# Eval("emps") %>' > <LayoutTemplate> <asp:PlaceHolder runat="server" ID="itemPlaceHolder"></asp:PlaceHolder> </LayoutTemplate> <ItemTemplate> <li><%#Eval("Name")%></li> </ItemTemplate> </asp:ListView> </ol> </td> </tr> </table> <hr /> </ItemTemplate> </asp:ListView>
Code Behind Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim empsByLocation = _ From em In Employee.getEmployees() _ Group em By Key = em.Location Into Group _ Select New With {.Key = Key, .emps = Group} ListView1.DataSource = empsByLocation ListView1.DataBind() End Sub
My Employee Class Public Class Employee
Public _Location As String Public _Name As String Public _Phone As String Public _ID As Long
Public Sub New(ByVal ID As Long, ByVal Name As String, ByVal Phone As String, ByVal Location As String) Me._Location = Location Me._Name = Name Me._Phone = Phone Me._ID = ID End Sub
Public ReadOnly Property Name() As String Get Return _Name End Get End Property Public ReadOnly Property Location() As String Get Return _Location End Get End Property
Public Shared Function getEmployees() As List(Of Employee) Dim employees As New List(Of Employee) employees.Add(New Employee(1, "Bob", "09809809", "California")) employees.Add(New Employee(2, "Jack", "3453465", "California")) employees.Add(New Employee(3, "Arnold", "7899878978", "California")) employees.Add(New Employee(4, "Barry", "2134678", "Arizona")) employees.Add(New Employee(5, "Titch", "454458", "Arizona")) employees.Add(New Employee(6, "Mick", "23432424", "Arizona"))
Return employees End Function End Class
Will produce the output:
You can tidy it up but I hope this was what you were after.
Imagine that you have a database table named Players with a list of basketball players
from the NBA. The schema for this table might look as follows:
Players
|
|---|
FullName | varchar(50)
| Team | varchar(20)
|
|
Improving the Data Model...
|
The schema for the Players table is extremely simple and far from
ideal. If this were a "real" database application, we'd likely have a plethora of
additional attributes for a player. Also, we'd likely want a PlayerID
primary key field to uniquely identify each player (since a player's name is not a
guarantee unique attribute). Furthermore, we'd likely have a separate table to
store information about each NBA team, and then have the Team attribute
in the Players table be a foreign key.
|
Given this table schema, imagine that we have the following records:
- Shaq, Lakers
- Rick Fox, Lakers
- Vlade Divac, Kings
- Tim Duncan, Spurs
- Kobe Bryan, Lakers
- Terry Parker, Spurs
- Robert Horry, Lakers
- Chris Webber, Kings
- Mike Bibby, Kings
- David Robinson, Spurs
- Doug Christie, Kings
Now, imagine that you wanted to display the NBA players such that the players for each team
were grouped together. Clearly, a simple SQL query with an appropriate ORDER BY
clause will get all of the players ordered by team. That is, the following query:
SELECT FullName, Team FROM Players ORDER BY Team
|
Will return all the players playing for the Kings, followed by all the players playing for the
Lakers, followed by all the players playing for the Spurs. Specifically, the results of the
above SQL query, when run on the sample data, will be as follows:
- Vlade Divac, Kings
- Chris Webber, Kings
- Mike Bibby, Kings
- Doug Christie, Kings
- Shaq, Lakers
- Rick Fox, Lakers
- Kobe Bryan, Lakers
- Robert Horry, Lakers
- Tim Duncan, Spurs
- Terry Parker, Spurs
- David Robinson, Spurs
Therefore, we could have a DataGrid display the name of each player, followed by their team name.
However, what if we wanted the display to be a bit different. Rather than blandly list each player
and his associated team, we want to list, in big bold letters, the name of the team, followed by all
of its players. That is, we want the results to look something like:
Kings
| | Vlade Divac
| | Chris Webber
| | Mike Bibby
| | Doug Christie
| Lakers
| | Shaq
| | Rick Fox
| | Kobe Bryant
| | Robert Horry
| Spurs
| | Tim Duncan
| | Terry Parker
| | David Robinson
|
Can we get a DataGrid to render this "style" of output? Sure, if we use a TemplateColumn and a bit
of clever programming. We could also obtain such an appearance through the use of a master/detail-type DataGrid,
which is discussed in detail in An Extensive Examination of
the DataGrid Web Control: Part 14.
In using template, we can implement this sort of display through the use of a DataList and a
custom function as well. For this FAQ, let's use the DataList approach.
|
DataGrid or DataList?
|
Note that a DataList probably makes more sense here since it is designed for
templated columns. Furthermore, the DataList allows for repeated columns via its
RepeatColumns property, meaning you could display the teams n to
a table column. However, if you need functionality like pagination, then
the DataGrid would be a better choice.
|
Using a DataList to Display the Players, Grouped By Team
Imagine, for a moment, that we only want to display the players for each team, and we're not yet worried about displaying the
team name before the list of players. To accomplish this, we could use the following simple DataList:
<asp:DataList runat="server" id="dlPlayers"> <ItemTemplate> Container.DataItem("FullName") </ItemTemplate> </asp:DataList>
|
This DataList will display a table row and column for each player. Now,
to display the team name, we need to be able to
determine when the team name switches from one team to the next. That
is, when we first start by displaying the players for the Kings,
we want to emit the team name Kings and then the player's name, but
then for each of the remaining players who play for the Kings, we only
want to emit their name. When the first player for the next team (the
Lakers) is displayed, again, we want to display both the
team name and the player's name; however, for the remaining Lakers
players we only want to display the player's name.
In the FAQ, Customizing the Appearance of a DataGrid Column Value,
we saw how to use a custom function to output a custom value based on the value of a DataSource
field. We'll use
this technique in this exercise to emit the player's team name if the
team name has switched from a previous value to a new value.
This can be accomplished with the following code:
<script runat="server" language="VB"> ... Function DisplayTeamIfNeeded(team as String) as String Dim output as String = String.Empty 'Determine if this team has yet to be displayed If team <> lastUsedTeam then 'Set that the lastUsedTeam is the current team value lastUsedTeam = team 'Display the team name output = "<br /><b>" &amp; team &amp; "</b><br />" End If
Return output End Function </script>
<asp:DataList runat="server" id="dlPlayers"> <ItemTemplate> <%# DisplayTeamIfNeeded(Container.DataItem("Team")) %> <%# Container.DataItem("Player") %> </ItemTemplate> </asp:DataList>
| | VB.NET
|
<script runat="server" language="C#"> <script runat="server" language="C#"> ... string lastUsedTeam = String.Empty; string DisplayTeamIfNeeded(string team) { string output = String.Empty; // Determine if this team has yet to be displayed if (team != lastUsedTeam) { // Set that the lastUsedTeam is the current team value lastUsedTeam = team; // Display the team name output = "<br /><b>" + team + "</b><br />"; }
return output; } </script>
<asp:DataList runat="server" id="dlPlayers"> <ItemTemplate> <%# DisplayTeamIfNeeded((string) DataBinder.Eval(Container.DataItem, "Team")) %> <%# DataBinder.Eval(Container.DataItem, "Player") %> </ItemTemplate> </asp:DataList>
| | C# |
The two key templates are the LayoutTemplate and ItemTemplate. The
LayoutTemplate specifies the ListView's encasing markup, while the
ItemTemplate
specifies the markup used to generate each record bound to the
ListView. For example, to display an ordered list of items with a
ListView, the resulting
templates would look like the following:
<asp:ListView ID="..." runat="server" DataSourceID="...">
<LayoutTemplate>
<ol>
<asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
</ol>
</LayoutTemplate>
<ItemTemplate>
<li><%# Eval("columnName") %></li>
</ItemTemplate>
</asp:ListView>
|
Since the ListView's LayoutTemplate and ItemTemplate are each
defined separately, we need some way to tell the LayoutTemplate, "Hey,
for each
record you are displaying, put the rendered item markup here." This is
accomplished by adding a server-side control with the ID value
specified by the ListView's ItemPlaceholderID
property.
This property defaults to a value of "itemPlaceholder", which is why I
have named the PlaceHolder control in the LayoutTemplate as such.
I could, however, had given the PlaceHolder control an alternate ID, but then I'd need to specify this value in the ListView's
ItemPlaceholdID property.
To output a particular field value in the ItemTemplate, use the databinding syntax, <%# Eval("columnName") %>.
Imagine that the above ListView is bound to an employees database table, and that in the ItemTemplate we were rendering the FullName
column within the <li> element. What would the ListView's rendered markup look like?
Well, the ListView would start by rendering it's LayoutTemplate:
<ol>
<asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
</ol>
|
It would then render its ItemTemplate for each record bound to the ListView control. This might result in the following markup:
<li>Scott Mitchell</li>
<li>Sam Smith</li>
<li>Jisun Lee</li>
<li>Andrew Fuller</li>
<li>Edgar Johnson</li>
<li>Ellen Plank</li>
|
The ItemTemplate's rendered markup is put in place of the PlaceHolder control (since its ID matches the ListView's ItemPlaceholderID
value. The net result is the following markup:
<ol>
<li>Scott Mitchell</li>
<li>Sam Smith</li>
<li>Jisun Lee</li>
<li>Andrew Fuller</li>
<li>Edgar Johnson</li>
<li>Ellen Plank</li>
</ol>
|
An Example of Displaying Simple Data with the ListView
An ASP.NET version 3.5 website is available at the end of this article
with a demo illustrating the ListView control in action. This demo uses
the
Microsoft Access Northwind database, which is included in the demo
application's App_Data folder. The "Simple Data" demo illustrates how to use
the ListView to display records from the Northwind database's Products table. An AccessDataSource control is used to query the Products
table and bind the resulting records to the ListView.
In particular, the ListView starts by displaying
the title "Product Listing" in an <h3> element. It then lists the products within a <blockquote>
element,
which has the effect of indenting the output. Each product has its
name, category, unit price, and quantity per unit displayed. And each
product is separated from one another via a
horizontal rule element (<hr>), which is defined in the ItemSeparatorTemplate.
The ListView and AccessDataSource's declarative markup follows:
<asp:ListView ID="ProductList" runat="server" DataSourceID="ProductDataSource">
<LayoutTemplate>
<h3>Product Listing</h3>
<blockquote>
<asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
</blockquote>
</LayoutTemplate>
<ItemSeparatorTemplate>
<hr />
</ItemSeparatorTemplate>
<ItemTemplate>
<h4><%#Eval("ProductName")%> (<%# Eval("CategoryName") %>)</h4>
Available at <%#Eval("UnitPrice", "{0:c}")%>,
with <%#Eval("QuantityPerUnit")%>.
</ItemTemplate>
</asp:ListView>
<asp:AccessDataSource ID="ProductDataSource" runat="server"
DataFile="~/App_Data/Northwind.mdb"
SelectCommand="SELECT [ProductName], [QuantityPerUnit], [UnitPrice], [CategoryName] FROM [Alphabetical List of Products]">
</asp:AccessDataSource>
|
When this page is visited, the ListView renders into the following HTML:
<h3>Product Listing</h3>
<blockquote>
<h4>Chai (Beverages)</h4>
Available at $18.00,
with 10 boxes x 20 bags.
<hr />
<h4>Chang (Beverages)</h4>
Available at $19.00,
with 24 - 12 oz bottles.
<hr />
<h4>Aniseed Syrup (Condiments)</h4>
Available at $10.00,
with 12 - 550 ml bottles.
<hr />
<h4>Chef Anton's Cajun Seasoning (Condiments)</h4>
Available at $22.00,
with 48 - 6 oz jars.
<hr />
<h4>Grandma's Boysenberry Spread (Condiments)</h4>
Available at $25.00,
with 12 - 8 oz jars.
... Many products have been removed for brevity ...
</blockquote>
|
Which appears in the visitor's browser like so:
Conclusion
The ListView control, new to ASP.NET 3.5, offers the same rich data
features found in the GridView, but allows for a much more flexible
rendered
output. As we saw in this article, the ListView's rendered output is
based on the markup, databinding expressions, and Web controls added to
its
LayoutTemplate and ItemTemplate. There are a number of other templates
available, as well, and we will explore these along with features like
sorting,
paging, deleting, editing, and inserting in future installments of this
article series.
Until then... Happy Programming!
Linq over DataTable.Rows
Currently
you can't directly use Linq over and
DataSet/DataTable/DataRowCollection/etc. class. Trying something like
this won't work:
var q = from r in table.Rows select r;
Enabling Linq over the classes mentioned above is under
consideration by Microsoft and I don't doubt they will enable them. But
if you wish to do it now, here is the simple workaround. Everything you
need to do (the Linq requires) is that the class implements
IEnumerable<T>. So, I will create a generic class that wraps
around any IEnumerable and implements IEnumerable<DataRow>. Here is the code:
// simple wrapper that implements IEnumerable<T> internal class LinqList<T>: IEnumerable<T>, IEnumerable { IEnumerable items;
internal LinqList(IEnumerable items) { this.items = items; }
#region IEnumerable<DataRow> Members IEnumerator<T> IEnumerable<T>.GetEnumerator() { foreach (T item in items) yield return item; }
IEnumerator IEnumerable.GetEnumerator() { IEnumerable<T> ie = this; return ie.GetEnumerator(); } #endregion }
The code is pretty straightforward, isn't it. And here is a simple test:
// create and fill table DataTable table = new DataTable(); table.Columns.Add("Id", typeof(int)); table.Rows.Add(new object[]{1}); table.Rows.Add(new object[]{2}); table.Rows.Add(new object[]{3}); // create a wrapper around Rows LinqList<DataRow> rows = new LinqList<DataRow>(table.Rows); // do a simple select IEnumerable<DataRow> selectedRows = from r in rows where (int)r["Id"] == 2 select r; // output result foreach (DataRow row in selectedRows) Console.WriteLine(row["Id"]);
It is not pretty as it could be but it certainly works. And if you
want you can create your own strong typed table implementation that
exposes LinqList<DataRow> Rows property. Perhaps using a code
generator such as CodeSmith instead of doing boring work yourself.
Comments
#
re: Linq over DataTable.Rows
I like that "LinqList" :)
#
re: Linq over DataTable.Rows (Error FrameW 2.0)
3. februar 2006 8:38 by Dln
The non-generic type 'System.Collections.IEnumerable' cannot be used with type arguments
on word -'IEnumerable<t>' ???</t>
#
re: Linq over DataTable.Rows
Hi Din,
Did you include System.Collections.Generic namespace? Anyway, check out Sahil's solution, too.
|