I have been giving much thought to ASP.NET and Silverlight menus of late. While doing research on existing sites and how they are handling menus, I came across the concept of a "mega menu".
A mega menu is basically a drop down menu that contains many, many options. It provides a user with a quick way to navigate to a particular location on a site. Here is one example:
Hovering over the Technology tab displays a large set of menu options [highlight in blue is mine].
This looks like a very interesting and user-friendly way to provide the user with a large number of choices. And it is DEFINITELY better than lots of fly-out menus. Seems like a good design for eCommerce types of sites where you want your potential customer to quickly find your products.
Check out a set of good mega menu examples here.
This is not to say that every menu should be a mega menu. In fact, I find that most line of business applications (non-eCommerce) require a different approach. Primarily because line of business application often need to perform a set of related tasks and not just find products.
Enjoy!
The ASP.NET ListBox does not display a horizontal scroll bar by default, which can be a problem if any of your list items are too long to fit. One solution to this problem is to use a tooltip. As the user moves the mouse over the ListBox entries, the full text appears in a tooltip.
The code to accomplish this is as follows:
In C#:
private void LB_PreRender(object sender, System.EventArgs e)
{
foreach (ListItem item in LB.Items) {
item.Attributes.Add("title", item.Text);
}
In C#, you also need to set up the event handler. In this example, the event handler is set up in the Page_Load event, but you could put it where it makes sense for your application.
LB.PreRender += LB_PreRender;
In VB:
Private Sub LB_PreRender(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles LB.PreRender
For Each item As ListItem In LB.Items
item.Attributes.Add("title", item.Text)
Next
End Sub
The ListBox, named LB in this example, has a PreRender event. In the PreRender event the code loops through the ListBox items and sets the title attribute to the text of the item.
Use this technique any time you want to display a tooltip over your ListBox items.
Enjoy!
A common requirement with an XML file is to find a particular node. If you are targeting the .NET framework 3.5 or higher, you can find the node using Linq to XML or by using Lambda expressions.
As with many of my prior XML examples, the XML string is as follows:
<States>
<State name="Wisconsin">
<Regions>
<Region name="Milwaukee">
<Area name="Mukwanago"/>
<Area name="Germantown"/>
</Region>
<Region name="Fox Valley">
<Area name="Oshkosh" />
<Area name="Appleton" />
</Region>
</Regions>
</State>
</States>
The code to find the node for the Milwaukee region is as follows:
In C#:
// Be sure to set a reference to System.Core and System.Xml.Linq
XElement states = XElement.Load("testXML.xml");
// Using LINQ
XElement foundNode;
var query = from XElement r in states.Descendants("Region")
where r.Attribute("name").Value == "Milwaukee"
select r;
foundNode = query.FirstOrDefault();
// Using Lambda expressions
foundNode = states.Descendants("Region").
Where(r => r.Attribute("name").Value ==
"Milwaukee").FirstOrDefault();
In VB:
' Be sure to set a reference to System.Core and System.Xml.Linq
Dim states As XElement = XElement.Load("testXML.xml")
' Using LINQ
Dim foundNode As XElement
Dim query = From r As XElement In states...<Region> _
Where r.@<name> = "Milwaukee"
foundNode = query.FirstOrDefault()
' Using Lambda expression
foundNode = states...<Region>.Where(Function(r) r.@<name> = _
"Milwaukee").FirstOrDefault
This code first loads the XML file containing the XML. The next set of code can be done using LINQ or using Lambda expressions. Use either one, but not both. :-)
The C# code uses the XElement properties and methods. The VB code uses XML literals.
NOTE: The XElement properties and methods work in VB as well.
Enjoy!
NOTE: This post was created based on a prior post that included both finding a node and adding new nodes. This post separates the first step to provide a more straightforward example.
You can document your classes, properties, methods, and so on using XML tags. I'm sure all developers know this at this point, but did you know that you can modify the set of valid tags?
If you are not familiar with XML Documentation Comments (or just XML Comments), you create them differently depending on the language you are using.
In C#:
On the line above the class, property, method, or whatever you are documenting, type three forward slashes.
///
public List<Customer> Retrieve(int Id)
Visual Studio automatically inserts the appropriate XML tags:
/// <summary>
///
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
public List<Customer> Retrieve(int Id)
In VB:
On the line above the class, property, method, or whatever you are documenting, type three apostrophes.
'''
Public Function Retrive(ByVal id As Integer) As List(Of Customer)
Visual Studio automatically inserts the appropriate XML tags:
''' <summary>
'''
''' </summary>
''' <param name="id"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function Retrive(ByVal id As Integer) As List(Of Customer)
You can then build technical documentation of your API from the XML comments.
In C#:
1. Double-click on Properties for the project in the Solution Explorer.
2. Click on the Build tab.
3. Check the "XML document file" checkbox and ensure that a file name is specified.
4. Build your project.
All of the XML comments are generated into one XML file.
In VB:
Generating the XML documentation is on by default. To check it:
1. Double-click on My Project for the project in the Solution Explorer.
2. Click on the Compile tab.
3. Check the "Generate XML documentation file" checkbox.
The XML file is generated in the bin\Debug folder for the project.
You can use a product such as SandCastle to generate your technical documentation from these comments.
But what if you want more or different tags than those that are provided? IF YOU ARE USING VB.NET you can do just that.
NOTE: As far as I have seen, this technique is not available in C#.
In VB:
Locate your XML Comment tag file. Mine was here:
C:\Documents and Settings\Deborah\Application Data\Microsoft\VisualStudio\9.0\VBXMLDoc.xml
If you don't have this file, you can create it following the instructions defined here.
This file contains the definition of the valid XML tags used in XML Comments. You can then add to the contents of this file or edit it as you desire.
For one of my projects, we wanted to add an edit history to each class that included the date, the developer's name, and a description. So I added the following to the Class code element:
<CodeElement type="Class">
<Template>
<summary/>
<remarks/>
<editHistory date="" developer=""/>
</Template>
<CompletionList>
<include file="" path=""/>
<permission cref=""/>
<remarks/>
<summary/>
<editHistory date="" developer=""/>
</CompletionList>
</CodeElement>
We could then create XML comments as follows:
''' <summary>
''' Manages a customer class
''' </summary>
''' <remarks></remarks>
''' <editHistory date="9/14/09" developer="DJK">
''' Added customer type.
''' </editHistory>
''' <editHistory date="10/17/09" developer="DJK">
''' Added an Email address.
''' </editHistory>
Public Class Customer
Use this technique any time you want to enhance your XML comment documentation in VB.NET.
Enjoy!
This one is in the category of obvious once you know how to do it. But having done Silverlight of late, I could not recall how to turn on the scrollbar in a ASP.NET ListBox. There is no scrollbar property of any kind.
A bit of "guess and check" with Bing provided lots of custom control solutions, which were more than I wanted to do for one little list box.
So I finally just tried Intellisense to see what properties and methods were available. And there was a Rows property and it worked!
To turn on the scrollbar in a ListBox, just set the Rows property to the number of items to display. If there are more than the defined number of items in the list, the scrollbar will automatically appear.
In HTML:
<asp:ListBox id="LB" runat="server"
Rows="6">
<asp:ListItem>Test1</asp:ListItem>
<asp:ListItem>Test2</asp:ListItem>
<asp:ListItem>Test3</asp:ListItem>
<asp:ListItem>Test4</asp:ListItem>
<asp:ListItem>Test5</asp:ListItem>
<asp:ListItem>Test6</asp:ListItem>
<asp:ListItem>Test7</asp:ListItem>
<asp:ListItem>Test8</asp:ListItem>
</asp:ListBox>
The result appears like this:
The Listbox displays the first 6 items as per the Rows property. Since there are 8 items, it automatically displays the scrollbar.
Enjoy!
Once I had my ASP.NET GridView in place (see this prior post), the next thing I wanted to do was select a row and go to a review/edit page. But I didn't want to add the "Select" or "Edit" buttons. It seemed more natural for the users to simply click on the row.
I used Bing and followed my always helpful "guess and check" method. I found quite a few links to solutions for clicking on a row in the GridView control. Some didn't work at all. Some worked if you turned off enableEventValidation. Some worked only if you did not try to page the results.
Here is a simple solution that works with any GridView and supports paging. It goes into the code behind file for the page containing the GridView. In this example, the GridView is called "CustomerGridView".
In C#:
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
foreach (GridViewRow row in CustomerGridView.Rows) {
if (row.RowType == DataControlRowType.DataRow) {
row.Attributes["onmouseover"] =
"this.style.cursor='hand';this.style.textDecoration='underline';";
row.Attributes["onmouseout"] =
"this.style.textDecoration='none';";
// Set the last parameter to True
// to register for event validation.
row.Attributes["onclick"] =
ClientScript.GetPostBackClientHyperlink(CustomerGridView,
"Select$" + row.DataItemIndex, true);
}
}
base.Render(writer);
}
In VB:
Protected Overrides Sub Render(ByVal writer As _
System.Web.UI.HtmlTextWriter)
For Each row As GridViewRow In CustomerGridView.Rows
If row.RowType = DataControlRowType.DataRow Then
row.Attributes("onmouseover") = _
"this.style.cursor='hand';this.style.textDecoration='underline';"
row.Attributes("onmouseout") = _
"this.style.textDecoration='none';"
' Set the last parameter to True
' to register for event validation.
row.Attributes("onclick") = _
ClientScript.GetPostBackClientHyperlink(CustomerGridView, _
"Select$" & row.DataItemIndex, True)
End If
Next
MyBase.Render(writer)
End Sub
This code overrides the Render method for the page. It loops through each of the rows in the GridView. It sets the onmouseover and onmouseout attributes so that the user sees that the row is clickable while moving the mouse over the grid rows.
The key attribute, however, is the onclick. Setting this attribute to GetPostBackClientHyperlink allows you to get a server-side click event on the row.
The first parameter to this method is the name of the GridView control. For this example, it is CustomerGridView.
The second parameter defines the name of the command, a "$" separator, and the command argument.
NOTE: In many examples I found, the command argument is set to row.RowIndex instead of row.DataItemIndex. This does not work if your GridView is paged because RowIndex is reset to 0 for the first item on each page.
Set the last parameter of the GetPostBackClientHyperlink method to true to register the event for validation. By setting this, you don't have to turn off enableEventValidation.
You can then catch this event using the RowCommand.
In C#:
private void CustomerGridView_RowCommand(object sender, System.Web.UI.WebControls.GridViewCommandEventArgs e)
{
if (e.CommandName == "Select") {
// Get the list of customers from the session
List<Customer> customerList =
Session["Customers"] as List<Customer>;
Debug.WriteLine(customerList[Convert.ToInt32(e.CommandArgument)].LastName);
}
}
In C#, you also need to set up the event handler. In this example, the event handler is set up in the Page_Load event, but you could put it where it makes sense for your application.
CustomerGridView.RowCommand += CustomerGridView_RowCommand;
In VB:
Private Sub CustomerGridView_RowCommand(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) _
Handles CustomerGridView.RowCommand
If e.CommandName = "Select" Then
' Get the list of customers from the session
Dim customerList As List(Of Customer)
customerList = TryCast(Session("Customers"), _
List(Of Customer))
Debug.WriteLine(customerList(CType(e.CommandArgument, Integer)).LastName)
End If
End Sub
This code first gets the customer list from the session. You can get the GridView information from wherever you have it defined, such as the ViewState. A Debug.WriteLine statement demonstrates how to access the CommandArgument. In a real application, you would use the CommandArgument to display the Review/Edit page for the selected customer.
Use this technique any time you want to handle a click event on an ASP.NET GridView row.
Enjoy!
In your ASP.NET application, you may want to allow a user to view some additional information without leaving the page that they are on. For example, you may want to display the current window and show some help text in a new window. Or you may want to display the concert ticket order page and allow the user to view the seating chart for the arena in a new window.
Though you can display a new window with HTML, doing it with JavaScript gives you more control over the window. To create a window with JavaScript, use the following code:
In JavaScript :
<script type="text/javascript">
<!--
var winNew
function OpenWindow(sURL,sName)
{
winNew = window.open(sURL,sName);
}
-->
</script>
With JavaScript, you have control over the location and size of the window and whether to include the toolbar, location, scrollbars, and so on. Add the desired attributes to the open command to achieve your desired look:
In JavaScript :
winNew = window.open(sURL, sName,
"toolbar=no, location=no, scrollbars=yes, width=600, height=300,
top=400, left=300");
Notice that all of window attributes are in one string parameter.
To link to a new window, call the OpenWindow function in the a (anchor) element of your HTML as follows:
In HTML:
<a href="BLOCKED SCRIPTOpenWindow('CourseList.htm','CourseList');">
View a list of our courses
</a>
To be a good citizen, close the new window when you no longer need it. Here is a function that closes the window:
In JavaScript :
function CloseWindow()
{
if (winNew && !winNew.closed)
{
winNew.close();
}
}
This code only closes the window if a new window exists and it is not already closed. This type of coding prevents errors in your JavaScript.
You may want to call this function from a button that the user can select, or from the unload event for the page so the new window is closed when the user leaves the page:
In HTML:
<body onunload="CloseWindow()">
Use this technique any time you want to open a second window from your HTML page.
(Based on an except from "Doing Web Development: Client-Side Techniques".)
Enjoy!
Most ASP.NET GridView control examples demonstrate using the GridView with a SQLDataSource. But in some cases, you may want to use your own business objects instead.
One way to achieve this goal is to use the ObjectDataSource as shown in MSDN here.
Another option is to simply bind the GridView directly to your business objects without using a DataSource control.
The example presented in this post uses business objects you build yourself. These "home made" business objects are often referred to as POCO, or "plain old CLR objects". Use the techniques presented in this post any time you want to use your business objects in a GridView without using a DataSource control.
Prerequisites
First, we need some business objects. This example uses a Customer class that defines a single customer, and a Customers (plural) class that returns a generic list of customers.
In C#:
public class Customer
{
public int CustomerId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string EmailAddress { get; set; }
}
public class Customers
{
public static List<Customer> Retrieve()
{
List<Customer> custList = new List<Customer>
{new Customer()
{ CustomerId = 1,
FirstName="Bilbo",
LastName = "Baggins",
EmailAddress = "bb@hob.me"},
new Customer()
{ CustomerId = 2,
FirstName="Frodo",
LastName = "Baggins",
EmailAddress = "fb@hob.me"},
new Customer()
{ CustomerId = 3,
FirstName="Samwise",
LastName = "Gamgee",
EmailAddress = "sg@hob.me"},
new Customer()
{ CustomerId = 4,
FirstName="Rosie",
LastName = "Cotton",
EmailAddress = "rc@hob.me"}};
return custList;
}
}
In VB:
Public Class Customer
Private _CustomerId As Integer
Public Property CustomerId() As Integer
Get
Return _CustomerId
End Get
Set(ByVal value As Integer)
_CustomerId = value
End Set
End Property
Private _FirstName As String
Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
_LastName = value
End Set
End Property
Private _EmailAddress As String
Public Property EmailAddress () As String
Get
Return _EmailAddress
End Get
Set(ByVal value As String)
_EmailAddress = value
End Set
End Property
End Class
Public Class Customers
Public Shared Function Retrieve() As List(Of Customer)
Dim custList As New List(Of Customer)
custList.Add(New Customer With {.CustomerId = 1, _
.LastName = "Baggins", _
.FirstName = "Bilbo", _
.EmailAddress = "bb@hob.me"})
custList.Add(New Customer With {.CustomerId = 2, _
.LastName = "Baggins", _
.FirstName = "Frodo", _
.EmailAddress = "fb@hob.me"})
custList.Add(New Customer With {.CustomerId = 3, _
.LastName = "Gamgee", _
.FirstName = "Samwise", _
.EmailAddress = "sg@hob.me"})
custList.Add(New Customer With {.CustomerId = 4, _
.LastName = "Cotton", _
.FirstName = "Rosie", _
.EmailAddress = "rc@hob.me"})
Return custList
End Function
End Class
The C# code here uses auto-implemented properties to shorten the property syntax. The VB code uses the full property syntax.
In a real application, the Retrieve method would collect the data from the database. This example uses hard-coded values to make it easier for you to try this code without having to set up data access.
NOTE: Since this example includes sorting and paging, you may want to add more test data to the lists to better see the sorting and paging in operation.
Define the GridView
The next step is to define the ASP.NET GridView control. This example creates the control using HTML, but you could create the control using code if desired.
In HTML:
<asp:GridView ID="CustomerGridView" runat="server"
AllowPaging="true" PageSize="3"
AllowSorting="true"
AutoGenerateColumns="false">
<Columns>
<asp:BoundField HeaderText="Last Name"
DataField="LastName" SortExpression="LastName" />
<asp:BoundField HeaderText="First Name"
DataField="FirstName" SortExpression="FirstName" />
<asp:BoundField HeaderText="Email"
DataField="EmailAddress" SortExpression="EmailAddress" />
</Columns>
</asp:GridView>
AllowPaging is set to true to demonstrate the paging feature. The PageSize is only set to 3 since this example includes such a small set of data. You can increase this number based on your user interface design.
AllowSorting is set to true to demonstrate the grid sorting feature. In addition, SortExpression was set for each BoundField.
AutoGenerateColumns is off so that the code can manually define the desired columns in the desired order.
Write the Code
Since there is no DataSource control in this example, you need to write code to perform the binding, sorting, and paging.
In this example, the Page_Load event calls the business object to obtain the data and performs the binding to that data.
ASP.NET generates the PageIndexChanging event when the grid is paging. So the code to handle the paging is in this event.
The Sorting event contains the code to handle the sorting. By default, the Sorting event will always request an ascending sort, so if you want your grid to sort both ascending and descending, you will need to handle that in the code. You can write the code so that when the user clicks on a column, the sort is ascending. If the user clicks on the same column again, the sort is descending. If the user clicks on a different column, the new column is sorted in ascending order.
In C#:
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;
using SampleBoCSharp;
namespace SampleWebCSharp
{
public partial class _Default : System.Web.UI.Page
{
public string LastSortKey
{
get { return ViewState["LastSortKey"].ToString(); }
set { ViewState["LastSortKey"] = value; }
}
public string LastSortDirection
{
get { return ViewState["LastSortDirection"].ToString(); }
set { ViewState["LastSortDirection"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Get the list of customers
List<Customer> customerList = Customers.Retrieve();
// Do the binding
CustomerGridView.DataSource = customerList;
CustomerGridView.DataBind();
// Store in a session variable
Session.Add("Customers", customerList);
// Set the sort info
LastSortDirection = string.Empty;
LastSortKey = string.Empty;
}
// Set up the events
CustomerGridView.PageIndexChanging +=
CustomerGridView_PageIndexChanging;
CustomerGridView.Sorting += CustomerGridView_Sorting;
}
private void CustomerGridView_PageIndexChanging(object sender,
GridViewPageEventArgs e)
{
// Get the list of customers from the session
List<Customer> customerList = default(List<Customer>);
customerList = Session["Customers"] as List<Customer>;
// Set the index
CustomerGridView.PageIndex = e.NewPageIndex;
// Rebind
CustomerGridView.DataSource = customerList;
CustomerGridView.DataBind();
}
private void CustomerGridView_Sorting(object sender,
GridViewSortEventArgs e)
{
// Get the list of customers from the session
List<Customer> customerList = default(List<Customer>);
customerList = Session["Customers"] as List<Customer>;
// Sort key is different, clear the last sort direction
if (LastSortKey != e.SortExpression) {
LastSortDirection = string.Empty;
}
// Perform the sort using Linq
switch (e.SortExpression) {
case "LastName":
customerList = Sort(customerList,
cust=> cust.LastName);
break;
case "FirstName":
customerList = Sort(customerList,
cust=> cust.FirstName);
break;
case "EmailAddress":
customerList = Sort(customerList,
cust => cust.EmailAddress);
break;
}
LastSortKey = e.SortExpression;
// Rebind
CustomerGridView.DataSource = customerList;
CustomerGridView.DataBind();
// Store in a session variable
Session.Add("Customers", customerList);
}
private List<Customer> Sort(List<Customer> list,
Func<Customer, string> sortKey)
{
if (LastSortDirection == "ASC") {
list = list.OrderByDescending(sortKey).ToList();
LastSortDirection = "DESC";
}
else {
list = list.OrderBy(sortKey).ToList();
LastSortDirection = "ASC";
}
return list;
}
}
}
In VB:
Partial Public Class _Default
Inherits System.Web.UI.Page
Public Property LastSortKey() As String
Get
Return ViewState("LastSortKey").ToString
End Get
Set(ByVal value As String)
ViewState("LastSortKey") = value
End Set
End Property
Public Property LastSortDirection() As String
Get
Return ViewState("LastSortDirection").ToString
End Get
Set(ByVal value As String)
ViewState("LastSortDirection") = value
End Set
End Property
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
' Get the list of customers
Dim customerList As List(Of Customer)
customerList = Customers.Retrieve()
' Do the binding
CustomerGridView.DataSource = customerList
CustomerGridView.DataBind()
' Store in a session variable
Session.Add("Customers", customerList)
' Set the sort info
LastSortDirection = String.Empty
LastSortKey = String.Empty
End If
End Sub
Private Sub CustomerGridView_PageIndexChanging(ByVal sender As Object, ByVal e As GridViewPageEventArgs) Handles CustomerGridView.PageIndexChanging
' Get the list of customers from the session
Dim customerList As List(Of Customer)
customerList = TryCast(Session("Customers"),
List(Of Customer))
' Set the index
CustomerGridView.PageIndex = e.NewPageIndex
' Rebind
CustomerGridView.DataSource = customerList
CustomerGridView.DataBind()
End Sub
Private Sub CustomerGridView_Sorting(ByVal sender As Object,
ByVal e As GridViewSortEventArgs)
Handles CustomerGridView.Sorting
' Get the list of customers from the session
Dim customerList As List(Of Customer)
customerList = TryCast(Session("Customers"),
List(Of Customer))
' If the sort key is different, clear the last sort direction
If LastSortKey <> e.SortExpression Then
LastSortDirection = String.Empty
End If
' Perform the sort using Linq
Select Case e.SortExpression
Case "LastName"
customerList = Sort(customerList,
Function(cust) cust.LastName)
Case "FirstName"
customerList = Sort(customerList,
Function(cust) cust.FirstName)
Case "EmailAddress"
customerList = Sort(customerList,
Function(cust) cust.EmailAddress)
End Select
LastSortKey = e.SortExpression
' Rebind
CustomerGridView.DataSource = customerList
CustomerGridView.DataBind()
' Store in a session variable
Session.Add("Customers", customerList)
End Sub
Private Function Sort(ByVal list As List(Of Customer), _
ByVal sortKey As Func(Of Customer, String)) As List(Of Customer)
If LastSortDirection = "ASC" Then
list = list.OrderByDescending(sortKey).ToList()
LastSortDirection = "DESC"
Else
list = list.OrderBy(sortKey).ToList()
LastSortDirection = "ASC"
End If
Return list
End Function
End Class
The LastSortKey and LastSortDirection properties retain the sort criteria so that you can sort ascending or descending. The values are stored in the ViewState so they can be retained with the other page data.
The Page_Load event calls the Retrieve method on the Customers object to retrieve the list of customers. It then sets the DataSource property of the GridView and binds it.
NOTE: For the paging to work correctly, the GridView must be bound to a List.
The code then stores the customer list in a session variable. This is not necessary if you want to ensure that the data is fresh each time that it is sorted or paged.
Finally, it sets a default value into the LastSortKey and LastSortDirection properties so they are not null.
The PageIndexChanged event retrieves the list of customers from the session variable, sets the GridView PageIndex, and rebinds to the list.
NOTE: If you re-retrieve the customer list instead of storing/retrieving it from the session, you will need to resort it before rebinding because it will have lost any sorting.
The Sorting event retrieves the list of customers from the session variable. It then checks the last sort key and clears the LastSortDirection if the user clicked on a different column. It then performs the sort using a lambda expression.
Finally, it rebinds the GridView to the list and stores the sorted list back to the session variable.
The Sort method defined in this example takes the list and a lambda expression in as parameters, performs the sort, and returns the sorted list.
The result looks like this:
You can then style the grid to match your user interface design. And adding an image to show whether the column was sorted ascending or descending would also be nice.
Enjoy!
Most ASP.NET best practice guides recommend laying out pages using Div tags instead of Tables for better performance and control. With that in mind, how do you align multiple text strings in a single row?
For example, a title bar on a section of an ASP.NET page may look like this:
The centered title defines the content of this section of the page. The right-aligned title is a hyperlink that pops open a dialog and allows the user to set preferences for this section.
This layout would be easy enough to achieve with an HTML table. (Or a Silverlight StackPanel, but this example is for ASP.NET.) However, the goal here is to do it with Div tags.
As with most of ASP.NET, the primary way to achieve this desired result is using the "Guess and Check" technique I described in a prior post. If you missed it, here is a quick review:
- Guess a key word or two describing what you are trying to accomplish
- Goggle/Bing
- Use the results to make a guess on some code (or if you look at a result and say "it can't take 2 pages of code to accomplish this!" then repeat the prior step)
- Run (F5) to check
- Repeat until something miraculously works
(And if you have to support multiple browsers, you may have to repeat the last two steps for each browser.)
Using this technique, I found that the following Div tag provides the title bar shown above:
In HTML:
<div >
<!-- Empty label for blank left spacing (to center the text) -->
<asp:Label ID="EmptyLabel" runat="server"
Text=" " style="float:left;width:33%;text-align:left;background-color:#BD2E26"/>
<asp:Label ID="Label1" runat="server"
Text="Customer Management" style="float:left;width:34%;text-align:center;background-color:#BD2E26;color:White;font-family:Calibri"/>
<asp:HyperLink ID="PreferencesLink" runat="server"
style="float:left;width:33%;text-align:right;background-color:#BD2E26;color:White;font-family:Calibri"
Text="Preferences " />
</div>
The key to this technique is to divide the title bar into three segments. Set each segment using a float:left style. This stacks the segments together starting at the left.
Set the widths to 33%, 34%, and 33% to take up the full space within its container. Adjust as needed for the design effect you are trying to achieve.
Finally, set the text-align to the correct alignment for each segment. For the first segment, it does not matter because no text is displayed. For the middle segment use text-align:center and for the right segment use text-align:right.
Notice that you must provide some text in the empty label for this to work correctly. This codes just uses to put in a non-breaking space.
Use this technique any time you need to stack text within single line and you don't want to use a Table.
Enjoy!
When Windows Presentation Framework (WPF) and Silverlight first came out, the announcements happily proclaimed that it was even easier to have your visual design team build really nice user interfaces that we, the developers, could then enhance with code. But what about the significant number of us that don't have a visual design team or even a single visual designer?
Even though we may not have had an art class since 6th grade, developers can create really nice looking Silverlight applications. And Silverlight Themes makes it easy.
NOTE: Silverlight themes are part of the Silverlight Toolkit that you can download from here.
Take this simple Silverlight page:
It looks OK. Kind of looks like a WinForms application. But it can get the job done. The user can click on Edit to edit the customer information. Or add, edit, or delete contacts for the customer. The user can also view an invoice summary or click the Open button to view the details of any invoice.
Now let's apply the Bureau Black Theme:
That looks more like a Silverlight application! Even though the functionality is exactly the same, applying a theme can make it look much nicer.
NOTE: In addition to applying the theme, some additional design features were applied to the dialogs shown in this post. For example, color and rounded corners on the page sections.
Don't like black. How about the Shiny Red Theme:
Want it more white, try the Whistler Blue Theme:
There are many more themes to choose from such as Expression Light and Rainier Purple.
Applying a theme to your page is easy. First, ensure that you have installed the Silverlight Toolkit as mentioned above. Then, if you are using Blend or Visual Studio, just drag and drop the desired theme from the toolbar to your page. This adds the appropriate references and namespaces to your code.
To theme the entire page, set the theme element around the entire page contents.
In XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:bureauBlack="clr-namespace:System.Windows.Controls.Theming;assembly=System.Windows.Controls.Theming.BureauBlack"
mc:Ignorable="d"
x:Class="SampleSimpleSL.CustomerManagementUC">
<bureauBlack:BureauBlackTheme>
<Grid x:Name="LayoutRoot">
. . . [Your code here]
</Grid>
</bureauBlack:BureauBlackTheme>
</UserControl>
NOTE: When you drag and drop the theme, the designer may add a bunch of attributes to the theme tag, such as a Content attribute. Remove these or the theme may not work properly.
Though you can't see it in the above screen shots, the themes also provide interactivity effects. For example, as you move the mouse over list box items, the items are highlighted. If you move the mouse over a button, the button color changes or highlights, and so on.
For example, here I had the mouse over the Edit button when I captured the screen:
Microsoft provided an interactive page to try out the themes. You can see the interactivity effects for each of the themes here. Select Theming | Theme Browser from the panel on the left. Then click on the desired theme across the top:
Pick a theme for your application and see what a difference it can make in your visual design.
Enjoy!
Depending on how you code the XAML for a RadioButton in Silverlight, you may not be able to get it to center. If you have simple RadioButton text, this issue is not noticeable. But if you include controls like a NumericUpDown as part of the RadioButton, it is very noticeable. I would assume that this is a bug in the RadioButton control.
Here is an example:
In this example, the second bullet point is shown twice. In the first case, the radio button is not centered with the other text. In the second case it is.
In XAML:
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock
HorizontalAlignment="Left" VerticalAlignment="Center"
Text="When is this process allowed?"/>
<RadioButton Margin="20,0,0,0"
Content="Always" IsChecked="True"/>
<RadioButton Margin="20,0,0,0" VerticalAlignment="Center">
<RadioButton.Content>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="Only during the first"/>
<inputToolkit:NumericUpDown
Width="40" HorizontalAlignment="Left"
VerticalAlignment="Center" Margin="4,2,4,2"
Value="15" />
<TextBlock VerticalAlignment="Center"
Text="days"/>
</StackPanel>
</RadioButton.Content>
</RadioButton>
<StackPanel Orientation="Horizontal" Margin="20,0,0,0" >
<RadioButton VerticalAlignment="Center"
Content="Only during the first"/>
<inputToolkit:NumericUpDown
Width="40" HorizontalAlignment="Left"
VerticalAlignment="Center" Margin="4,2,4,2"
Value="15" />
<TextBlock VerticalAlignment="Center"
Text="days"/>
</StackPanel>
</StackPanel>
In the first case, the code uses RadioButton.Content to place a StackPanel with TextBlock, NumericUpDown, and TextBlock controls within the RadioButton. This way of coding the RadioButton prevents it from being centered.
In the second case, the code uses a StackPanel with a RadioButton, NumericUpDown, and TextBlock within it. In this case, the RadioButton is correctly centered.
Keep this in mind when using RadioButtons with more than simple text.
Enjoy!
If you are starting a new Silverlight project, you may want to take a look at SketchFlow. SketchFlow allows you to easily build an interactive prototype.
Building a Silverlight prototype with SketchFlow has several advantages:
- SketchFlow allows you to quickly put together a sample user interface for your Silverlight application. This saves you time and money as you start to flesh out the application user interface.
- Since it is so easy to build pages with SketchFlow, you can provide multiple choices and alternate design ideas.
- The style of the controls makes it clear that it is a prototype. (The fonts and controls look like they would if you drew them on your whiteboard.) This helps the users focus on functionality, not on the visual design.
- SketchFlow provides an easy way to hook pages together. For example, in the SketchFlow designer, you can right-click on a Log In button in a prototype login page to define the page that should be displayed after a successful log in.
- SketchFlow makes it easy to dummy up data to display in your UI.
- The SketchFlow player provides an easy way to navigate through your pages without building any code.
- Reviewers can add comments about the prototype directly from the SketchFlow player, making it easy to collect feedback.
- You can learn how to use SketchFlow in about 20 minutes.
Here is an example screen shot shown within the SketchFlow player. Notice the Map in the lower left corner. It allows you to navigate to any page of the prototype.
SketchFlow is part of Microsoft Expression Blend 3.0. However, it is not immediately obvious how to use it. Luckily, there is a set of videos that provide a quick start. Each video is around 5 minutes in length, getting you going quickly. You can find the videos here.
Enjoy!
Don't know what I was thinking to vacation in New York in January! Burrrr!
Had a great time though.
Stayed at the Marriott Marquis in Times Square and could see the New Year's Eve ball from our window:
Skated at Rockefeller Center:
(That's me in the back with the black coat.)
Saw the dinosaurs:
And Wicked:
And the ball from the World Trade Center:
The Statue of Liberty:
And some Van Gogh's:
Did I mention it was cold?
Hope you had a great holiday!
I just wanted to set focus to a TextBox on a modal dialog created using the ModalPopupExtender. How hard can it be?
As a Physics and Math major, I spent my student years using logic to attack problems and solve puzzles. So I was a little concerned when my daughters learned the "guess and check" method in Math class. How scientific or logical is that? I later came to appreciate its usefulness in some circumstances. For example, on the SAT (college entrance) tests it may be faster to "guess" each of the multiple choice items as correct and "check" them than to figure out the answer.
What does "guess and check" have to do with ASP.NET? Often it seems that there is no obvious solution to accomplish a specific design requirement. There is no obvious control property, method call or programming logic that achieves the desired result. The "guess and check" method is the only way to go:
- Guess a key word or two describing what you are trying to accomplish
- Goggle/Bing
- Use the results to make a guess on some code
- Run (F5) to check
- Repeat for several hours until something miraculously works
This was definitely the case for what seemed like such a simple task: set the focus to a control in a modal dialog. It's just control.focus(), right? Yes, but where would you put this code?
The problem here is that the modal dialog is initially hidden. You can't set focus to the control when the page is loaded. Rather, you need to set the focus when the modal dialog is shown. There are no onShown or onVisible attributes for a panel/div/span. So on to "guess and check".
Because the vast majority of the sample code I found via Bing that was supposed to provide a solution for this did not work (at least not for me), here is a solution that did work.
1) Follow the steps here to define a modal dialog on one of your Web pages.
2) Add the BehaviorId attribute to the ModalPopupExtender in the ASP.NET page. The BehaviorId provides a mechanism to access the extender from script code.
In HTML:
<!-- Define the modal dialog -->
<asp:ModalPopupExtender ID="SecurityQuestionModalPopupExtender" runat="server"
BehaviorID="SecurityQuestionModalBehavior"
TargetControlID="SecurityQuestionLink"
PopupControlID="SecurityQuestionModalPopup"
CancelControlID="CancelButton"
OnCancelScript="OnSecurityQuestionCancel();"/>
3) Add this script code to your ASP.NET page:
In java script:
<script language="javascript" type="text/javascript">
/* On page load */
function pageLoad() {
var modalPopup = $find("SecurityQuestionModalBehavior");
modalPopup.add_shown(OnPopupShow);
}
/* Set the focus to the correct control */
function OnPopupShow() {
var tb = $get("QuestionTextBox");
tb.focus();
}
</script>
The first function executes when the page is loaded. Since the ModalPopupExtender has a BehaviorId, you can use the $find method to find it. You can then use the add_shown method and pass it a function to call when the modal popup is shown.
The OnPopupShow function then sets the focus when the modal popup is shown by finding the desired control and setting focus to it.
The result:
Yea, it seems like this add_shown method was pulled out of thin air. Without Bing, there is no way I would have found this method. It is not in any documentation I have read nor does it appear in intellisense.
The only place I was able to find any documentation (if you would call it that) is in the ModalPoupBehavior.js file provided with the Ajax toolkit source code. Is it just me or is it TOTALLY LAME to have to read the Ajax source code to get something this basic to work?
NOTE: Most of the examples I found during my "guess and check" used add_showing instead of add_shown. This generated the following error during my "check" phase: "Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus."
If you need to write this code in VB/C# code behind instead of javascript in the ASP.NET page, see this link.
Use this technique any time you need to set focus to one of the controls on a modal dialog. And use the "guess and check" method every time you need to do anything beyond the basics with ASP.NET.
Enjoy!
In my last post, I demonstrated how to build a modal dialog using the AJAX control in ASP.NET. As soon as you add validation to your modal dialog you will find the same issue that I did: Cancelling the dialog does not clear the content or validation messages.
Take this user scenario for example.
1) User clicks Security Question to open the security question dialog.
2) User enters the question, but no answer and clicks OK.
3) User clicks Cancel.
4) Sometime later on this same page, the user clicks Security Question again.
Notice that the entered text and validation message are still there.
Yes, this may be a far fetched scenario, but as you work with the ModalPopupExtender, you will see this issue come up again and again. In some cases, leaving the prior entered value may be OK. But in other cases, you want to open this modal dialog in a reset state.
There are several ways to attack this problem and this post covers two of them:
- Use server-side code in the Cancel button to clear the dialog.
- Use client-side script for the Cancel button to clear the dialog.
Using Server-Side Code
Using server-side script requires the following steps:
1) Remove the CancelControlID from the ModalPopupExtender control in the ASP.NET code. This allows the Cancel button to be processed by the server-side code.
2) Add a Click event handler for the Cancel button. Follow a technique similar to the OK button in my last post to set up the event handler.
NOTE: For C# ONLY, be sure to add the onclick attribute:
<asp:Button ID="CancelButton" runat="server"
Text="Cancel" ToolTip="Click to cancel any changes"
CausesValidation="false"
onclick="CancelButton_Click"/>
3) Add the desired code to the Cancel button Click event handler in the code behind file.
In C#:
Be sure to set a reference to the System.Web.UI namespace.
protected void CancelButton_Click(object sender, EventArgs e)
{
// Clear the validators
foreach (IValidator ctrl in Validators)
{
ctrl.IsValid=true;
}
// Clear the control contents
QuestionTextBox.Text = String.Empty;
AnswerTextBox.Text = String.Empty;
//Close the popup
SecurityQuestionModalPopupExtender.Hide();
}
In VB:
Private Sub CancelButton_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles CancelButton.Click
' Clear the validators
For Each ctrl As IValidator In Validators
ctrl.IsValid = True
Next
' Clear the control contents
QuestionTextBox.Text = String.Empty
AnswerTextBox.Text = String.Empty
' Close the popup
SecurityQuestionModalPopupExtender.Hide()
End Sub
This code first clears any validation controls. It then clears the text from both of the TextBoxes. Finally, it closes the modal dialog.
The down side of this approach is that it is server side. This means that it requires hitting the server to clear the dialog. A more performant approach is to use client-side scripting.
Using Client-Side Code
Using client-side script requires the following steps:
1) Ensure the ModalPopupExtender has both the CancelControlID and OnCancelScript attributes set:
<asp:ModalPopupExtender ID="SecurityQuestionModalPopupExtender"
runat="server"
TargetControlID="SecurityQuestionLink"
PopupControlID="SecurityQuestionModalPopup"
CancelControlID="CancelButton"
OnCancelScript="OnSecurityQuestionCancel();"/>
The OnCancelScript attribute must be set to the name of the javascript function defined in the next step.
2) Write the javascript code:
<script language="javascript" type="text/javascript">
/* On cancel of the Signin dialog, clear the fields */
function OnSecurityQuestionCancel() {
$get("QuestionTextBox").value = "";
$get("AnswerTextBox").value = "";
$get("QuestionValidator").innerHTML = "";
$get("AnswerValidator").innerHTML = "";
}
</script>
For this example, I added this javascript code to the head tag of the ASP.NET page.
This code uses the Ajax $get shortcut for the getElementById to find both TextBoxes and both validators. It then clears them. Because this is javascript, it has to work with the controls that are rendered. For the TextBox controls, these are HTML input controls that have a value attribute. For the validation controls, these are the HTML span controls. To clear those, you need to clear the innerHTML attribute.
Use one of these techniques any time you want to clear a modal dialog.
Enjoy!
There are often times when your Web design includes a modal dialog. For example: a login form, a dialog for entering basic information, a search form, or a data entry form. This post covers how to build a modal dialog in ASP.NET.
There are several advantages to using a modal dialog instead of another Web page:
- The user does not have to navigate away from the current page. This allows the user to focus on the main information, yet enter criteria or other parameters in a modal dialog.
- The code has better control over the dialog operations. That is to say that the user must provide some response to the modal dialog. However, the user can still navigate to another page or close the browser.
I mentioned in a recent post that after being away from ASP.NET for a while, I was surprise at how little had changed. But there is one area where ASP.NET has changed significantly: AJAX. If you have not downloaded the ASP.NET AJAX toolkit, check it out here.
This post focuses on the ModalPopupExtender control that is part of the AJAX toolkit. In this example, the user sets a security question and answer in a modal dialog. If the user clicks on the Security Question link in the upper right corner, the Security Question modal dialog appears for entry of the question and answer.
As with most modal dialogs, this one implements validation. If the user clicks on OK without entering the required data, validation messages appear:
I wrote this example in both VB and C#. But since the only line of code that is different is the Page tag that defines the language, I am only showing the ASP.NET code one time.
In HTML:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SampleWebCSharp._Default" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="asp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Sample Web Page</title>
</head>
<body style="background-image: url(Images/Gradient2.jpg);background-repeat: repeat-x;">
<form id="form1" runat="server">
<asp:ScriptManager ID="sm" runat="server"/>
<!-- Right side Link bar -->
<div style="text-align:right">
<!-- Signin Area -->
<asp:HyperLink ID="SigninLink" runat="server"
style="color:White;cursor:pointer"
ToolTip="Click to sign into the application"
Text="Sign in" />
<label style="margin-left:8px;
margin-right:8px;color:White">|</label>
<asp:HyperLink ID="SecurityQuestionLink"
runat="server"
style="color:White;cursor:pointer"
ToolTip="Click to set the security question"
Text="Security Question" />
</div>
<!-- Security Question Popup -->
<asp:Panel ID="SecurityQuestionModalPopup"
runat="server"
Style="display:none;width:300px;
background-color:White;
border-width:3px;border-style:solid;
border-color:#B92217;color:#7C1810;
padding:3px;">
<asp:Label ID="Label1" runat="server"
Text="Security Question" width="300px"/><br />
<br />
<table style="width: 100%;">
<tr>
<td align="right">
<asp:Label ID="Label2"
runat="server"
Text="Question:"/>
</td>
<td>
<asp:TextBox ID="QuestionTextBox"
runat="server" Width="150"/>
</td>
</tr>
<tr>
<td align="right">
<asp:Label ID="Label3"
runat="server"
Text="Answer:"/>
</td>
<td>
<asp:TextBox ID="AnswerTextBox"
runat="server" Width="150"/>
</td>
</tr>
<tr>
<td colspan="2">
<asp:RequiredFieldValidator
ID="QuestionValidator"
runat="server"
ControlToValidate="QuestionTextBox"
Display="Dynamic"
ErrorMessage="Please enter your security question.<br/>"/>
<asp:RequiredFieldValidator
ID="AnswerValidator"
runat="server"
ControlToValidate="AnswerTextBox"
Display="Dynamic"
ErrorMessage="Please enter the answer to your security question.<br/>"/>
</td>
</tr>
</table>
<br />
<!-- Bottom Buttons -->
<div style="text-align:right">
<asp:Button ID="OKButton" runat="server"
Text="OK"
ToolTip="Click to save the entered question/answer"/>
<asp:Button ID="CancelButton" runat="server"
Text="Cancel"
CausesValidation="false"
ToolTip="Click to cancel any changes"/>
</div>
</asp:Panel> <!-- Define the modal dialog -->
<asp:ModalPopupExtender
ID="SecurityQuestionModalPopupExtender" runat="server"
TargetControlID="SecurityQuestionLink"
PopupControlID="SecurityQuestionModalPopup"
CancelControlID="CancelButton" />
</form>
</body>
</html>
Notice at the very top of the code the Register tag registers the Ajax Control Toolkit. This line is added to your code automatically if you drag an Ajax control onto your WebForm.
The body tag uses a gradient image as shown in this prior post.
The ScriptManager control manages all of the Ajax features and is required on any page that uses Ajax controls.
The first div element contains the code to display the links in the upper right corner. No special code is required on these elements. The HyperLink control does not have an attribute to display the modal dialog. Rather, the ModalPopupExtender identifies the control that will cause it to popup. (More on this later in this post.)
The Panel control contains all of the controls that appear in the modal dialog. It also defines the style of the popup including its background color, foreground color, and border. Notice that the display style is set to none. This ensures that the popup does not display when the page is first rendered.
In this example, the modal dialog contains a title at the top, a table to layout the Labels and TextBoxes in the middle, and OK and Cancel buttons on the bottom. Notice that the Cancel button has its CausesValidation attribute set to false. This ensures that pressing the Cancel button won't perform the validation and display the validation messages.
An additional row in the table provides an area to display validation messages. The validation controls have their Display attribute set to Dynamic so that space is not allocated to the message text unless there is a validation error.
Finally, the key part of this code: the ModalPopupExtender. The key attributes of this control in this example are:
- TargetControlID: Id of the control that causes display of the modal dialog. In this case, it is the Security Question Hyperlink control at the top right of the page.
- PopupControlID: Id of the control that is the modal dialog. In this case, it is the Panel control.
- CancelControlID: Id of the button on the modal dialog that causes a cancel operation. By default, this closes the modal dialog.
Notice that the OKControlID attribute is not set for this control. By default, setting the OKControlID also closes the modal dialog, so validation messages won't appear.
If you don't set the OKControlID attribute, you can instead write code for the OK button in the code behind. This allows for the display of the validation messages. You can also perform processing, such as additional server-side validation or saving the entered values.
In C#:
using System;
namespace SampleWebCSharp
{
public partial class _Default : System.Web.UI.Page
{
protected void OKButton_Click(object sender, EventArgs e)
{
// Do any additional processing, such as saving the values
//Close the popup
SecurityQuestionModalPopupExtender.Hide();
}
}
}
In VB:
Partial Public Class _Default
Inherits System.Web.UI.Page
Private Sub OKButton_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles OKButton.Click
' Do any additional processing, such as saving the values ' Close the popup
SecurityQuestionModalPopupExtender.Hide()
End Sub
End Class
NOTE: The technique you use to set up the events is very different in VB than in C#.
In C#:
- Double-click on the control.
In this example, this is not an easy task because the panel containing the button is hidden. So to use this method, you would need to temporarily make the panel not hidden, double-click on the button, and make the panel hidden again. - OR, manually type in the event signature in the code behind file.
With the first technique, Visual Studio automatically changes the Button attributes in your ASP.NET code as follows:
<asp:Button ID="OKButton" runat="server"
Text="OK" ToolTip="Click to save the entered question/answer"
onclick="OKButton_Click"/>
With the second technique, you need to add the onclick attribute yourself.
In VB:
- Double-click on the control.
Again, not so easy because the control is hidden. - OR, open the code behind file and use the two ComboBoxes at the top of the code editor. Select the control from the first ComboBox on the left. Select the event from the second ComboBox on the right. This is the best technique to use in this example.
NOTE: VB does NOT require a change in the ASP.NET code.
Use this technique any time you want to display a modal dialog from your Web page.
Enjoy!
Creating nice looking buttons for ASP.NET is still in the category of "I can't believe it is STILL this hard".
As more and more normal people use great looking Web sites, Silverlight apps and even their IPhones, they experience some really nice user interfaces. I'd like to do those things in my Web application as well. I don't want my Web pages looking like VB3 gray looking Windows forms.
So I got to the point where I needed a Login button on my form. Hmmm. How do I build a nice looking button in ASP.NET?
After extensive research, work with Bing, and some trial and error, I came to these choices:
As you could tell, I am going with a Red/White color scheme for the Web application, you could pick other colors.
The ASP.NET source code I used to display this list of buttons is as follows:
In HTML:
<!-- Normal buttons-->
<asp:Button ID="Button1" runat="server"
Text="Button1" /><asp:Label runat="server" Text=" <- Standard Button" /><br /><br />
<asp:Button ID="Button10" runat="server"
Text="Button1" forecolor="#7C1810"/><asp:Label runat="server" Text=" <- Standard Button with Colored Text" /><br /><br />
<asp:Button ID="Button11" runat="server"
Text="Button1" forecolor="#7C1810" BorderColor="#7C1810"/><asp:Label runat="server" Text=" <- Standard Button with Border" /><br /><br />
The first set of buttons are standard ASP.NET buttons. Notice the following:
- The simple standard button looks a standard WinForms button and is not very exciting. It does, however, have a built in roll-over effect that you can see if you put one in your code.
- Adding a font color adds a little something while still retaining the built in roll-over effect.
- As soon as you add a border to the button, the button becomes more square and flat, and you lose the roll-over effect.
In HTML:
<asp:Button ID="Button2" runat="server"
Text="Button1" BackColor="#B92217" ForeColor="White"/><asp:Label runat="server" Text=" <- Standard Button with Backcolor" /><br /><br />
<asp:Button ID="Button3" runat="server"
Text="Button1" BackColor="#B92217" ForeColor="White"
BorderStyle="Groove" /> <asp:Label runat="server" Text=" <- Standard Button with Backcolor and Groove" /><br /><br />
<asp:Button ID="Button5" runat="server"
Text="Button1" BackColor="#B92217" ForeColor="White"
BorderStyle="Inset" /> <asp:Label runat="server" Text=" <- Standard Button with Backcolor and Inset" /><br /><br />
<asp:Button ID="Button6" runat="server"
Text="Button1" BackColor="#B92217" ForeColor="White"
BorderStyle="Outset" /> <asp:Label runat="server" Text=" <- Standard Button with Backcolor and Outset" /><br /><br />
The above set of buttons are standard ASP.NET buttons with a background color set. Notice the following:
- They each have different border style properties.
- In all of these cases, the buttons are more square and you lose the roll-over effect.
In HTML:
<!-- Normal buttons with Border color -->
<asp:Button ID="Button7" runat="server"
Text="Button1" BackColor="#B92217" ForeColor="White"
BorderColor="#7C1810" BorderStyle="Groove" />
<asp:Label runat="server"
Text=" <- Standard Button with Backcolor, Border and Groove" /><br /><br />
<asp:Button ID="Button8" runat="server"
Text="Button1" BackColor="#B92217" ForeColor="White"
BorderColor="#7C1810" BorderStyle="Inset" /> <asp:Label runat="server" Text=" <- Standard Button with Backcolor, Border and Inset" /><br /><br />
<asp:Button ID="Button9" runat="server"
Text="Button1" BackColor="#B92217" ForeColor="White"
BorderColor="#7C1810" BorderStyle="Outset" /> <asp:Label runat="server" Text=" <- Standard Button with Backcolor, Border and Outset" /><br /><br />
The above set of buttons are standard ASP.NET buttons with a border. Notice the following:
- They each have different border style properties.
- In all of these cases, the buttons are more square and you lose the roll-over effect.
- With the border, the button also becomes more flat looking.
In HTML:
<!-- Image Buttons -->
<asp:ImageButton ID="ImageButton1" runat="server"
ImageUrl="Images/RedButton.jpg" width="65" height="20" BorderColor="Black"/><asp:Label runat="server" Text=" <- Image Button (with Silverlight image)" /><br /><br />
<asp:ImageButton ID="ImageButton2" runat="server"
ImageUrl="Images/Gradient2.jpg" width="65" height="20"
style="background-repeat: repeat-x" BorderColor="Black"/><asp:Label runat="server" Text=" <- Image Button with Gradient" /><br /><br />
The above set of buttons are ASP.NET image buttons. Notice the following:
- The first button looks nice, but because it is an image, it has no effects.
- I built this button in Silverlight, took a screen shot of it using SnagIt, and saved it as an image file.
- To give it a roll-over effect I would need to do a second screen shot with the roll-over effect on and then replace the original image with this image as the user hovers over it.
- If you want to use images in this manner, you would have to build one for every set of text.
- If you are localizing the application, that means a new button image file for every set of text for every language.
- OR, you could do an image without text and use CSS styles to write text over the image.
- The second button does not quite look like a button.
- It uses a gradient image and the background-repeat set to repeat it through the button.
- You would need to use CSS styles to write text over the image.
Another option along the lines of using images, is to use separate images for the pieces of the button: One for the upper left corner, one for the top, one for the upper right corner, and so on. That way you can have rounded corner images that can expand to the size of your text. To manage the set of image pieces and get the text on the image, create a custom control. There are instructions for creating a custom control for this purpose in the article: "Dynamically Expandable Rounded Cornered Button".
In HTML:
<!-- Rounded Panel -->
<asp:Panel ID="Panel1" runat="server" BackColor="#B92217"
Width="68" height="15">
<asp:Button ID="InsideButton" runat="server"
BackColor="#B92217" Text="Button1"
BorderStyle="None" ForeColor="White"/>
</asp:Panel>
<asp:Label runat="server" Text=" ^- Standard Button inside Rounded Panel" /><br />
<asp:RoundedCornersExtender ID="RoundedCornersExtender3" runat="server"
BorderColor="#7C1810" Corners="All" Radius="6"
TargetControlID="Panel1">
</asp:RoundedCornersExtender>
<asp:Panel ID="Panel2" runat="server"
BackImageUrl="Images/Gradient2.jpg"
Width="68" height="15">
<asp:Button ID="Button4" runat="server"
BackColor="#B92217" Text="Button1"
BorderStyle="None" ForeColor="White"/>
</asp:Panel>
<asp:Label runat="server" Text="^- Standard Button inside Rounded Panel with Background Image" /><br />
<asp:RoundedCornersExtender ID="RoundedCornersExtender4" runat="server"
Corners="All" Radius="6"
TargetControlID="Panel2">
</asp:RoundedCornersExtender>
This last set of buttons are in rounded panels. Notice the following:
- The panels define the background color.
- The RoundedCornersExtender control (part of the Ajax Toolkit) rounds the corners of the panel.
NOTE: You cannot use the RoundedCornersExtender to simply round the corners of the button. But that would be nice, won't it? - The buttons in the panel have the same background color and no border so it appears as if they are rounded instead of being within a rounded container.
- If the user does click on an edge of the button outside of the actual button itself, the click event won't occur.
- The second button has an image background. Notice that the corners are then no longer rounded. Also, unless the image in the button exactly matches the image in the panel, you can see the button within the panel.
- This basically means that you won't be able to have a rounded corner button and a image (like a gradient image) background using this technique.
So no good options here. :-(
The good news is … I am not alone.
Look at the buttons that Twitter uses. It looks like the standard HTML button to me:
Amazon has nice buttons. No! Wait! Look more closely and you'll see they are not buttons at all. They are graphics with href links on them! Notice the underline under Cart (I had my mouse over it). Is that cheating? :-)
And this one from Amazon is just an image with no roll-over or click effect.
Facebook? It just has square buttons:
Blizzard, maker of World of Warcraft (WOW), on the other hand has really nice buttons. Anyone know how they did it? (Looking at the View Source, it appears to be done by CSS.)
If you know of other choices, I'd love to hear about them.
Enjoy!
My first Xaml/Silverlight post was on gradients, it seems only right that my first ASP.NET post should cover the same topic.
I started Web development in the 90's using VB 5/6 and classic ASP and learned all about HTML, CSS, Javascript, DOM, XML, XSL, and all of the other related technologies. This lead to writing a book: "Doing Web Development: Client-Side Techniques" back in 2002 that provides an introduction to those technologies including HTML, CSS, JavaScript, and XML.
When .NET came out, I jumped over to ASP.NET, enjoying its power but struggling with its quirks.
I then got busy with WinForms and Silverlight development and ignored much of ASP.NET for the past few years. I just got back to it this week and was excited to see what has changed. Amazingly, not that much. I am surprised how many quirky things are still there.
Which brings us back to the topic at hand. 10+ years ago we achieved gradient backgrounds on our Web pages using a pencil thin gradient graphic repeated across the screen. Today, we achieve gradient backgrounds on our Web pages using a pencil thin gradient graphic repeated across the screen.
Funny, many of the forum posts on this topic these days say "use Silverlight instead", like that is something we can just decide to do. Well, maybe you can, but as a consultant I certainly can't. And if you work in a corporate environment, I bet you can't either.
So, here is the tried and true way to get a gradient background for your pages.
1) Create/buy/borrow an image file (such as a .jpg file) that contains a very thin sliver of the desired gradient.
My image file looks like this:
Yes, that is a little hard to see. But it is basically a very thin gradient line going from Red to White.
I found an online tool to help create this image file: SecretGeek's Gradien-Maker. It looks a little loud when you first access the page, but it allows you to easily create a gradient. You can get the gradient just like you want it, then Save Background As to generate the image file with a pencil thin piece of the gradient. It worked great.
NOTE: This does not allow for setting specific gradient stops or for anything but a linear gradient, but it works for simple vertical or horizontal gradients.
2) Define the style
Define the style in a cascading stylesheet (CSS) like this:
In CSS:
.GradientBackground
{
background-image: url(Images/Gradient2.jpg);
background-repeat: repeat-x;
}
[If you are not familiar with HTML or CSS, see the above referenced book.]
This sets the URL of the background image to my gradient image file. It then sets it to repeat across the x-axis. This causes the sliver to be repeating horizontally across the page.
3) Set the new style on the desired HTML element in the ASP.NET page.
I added it to the Body element as follows:
In HTML:
<body class="GradientBackground">
OR
You can forget about step #2 and put the style directly in the ASP.NET page:
In HTML:
<body style="background-image: url(Images/Gradient2.jpg);background-repeat: repeat-x;">
In either case, the result is something like this:
You can make the gradient longer or shorter by creating a new gradient image file with a different size. Or you can use a similar technique to create a horizontal gradient repeated vertically across the page.
Until we are all doing Silverlight or ASP.NET comes out with gradient features on its controls, this is the best technique I've found to put gradients on Web pages.
Enjoy!
Yes, it is that time of the year when we put together our holiday wish list and hope Santa has us on his "nice" list. Here are some of the accessories that I really wish I would get for my Silverlight.
NOTE: These are based on the Silverlight 3.0 release. I have not checked whether Silverlight 4.0 beta already has these items hanging in my stocking.
1) Curved Text
I was quite amazed that this was not a feature I already had. I was so convinced that I had this feature, I hunted around for a while. But several minutes of Bing later I found that it was just not there.
So why not add a Path property to one of the text controls?
Or cooler yet, why not give a text property to some of the less fortunate graphic controls such as lines, rectangles, and ellipses? Wouldn't it be cool for an ellipse to have a Text property? And then a TextAlign property where you can define inside or outside of the ellipse border?
2) ComboBox ValueMemberPath
Yea, I have already covered this one. If you have not seen it, it starts here.
3) Text Outline/Shadow
This was another one I was surprised about. I wanted to display color names in their appropriate color. But some colors, such as yellow, just don't show up very well. So I wanted to add an outline or drop shadow. No go.
4) Easier Editing of Chart Tooltips
When building charts, much of the data is numeric: 50 of type with ID of 1, 60 of type with ID of 2, 42 of type with ID of 3 and so on. But having a tooltip of: 1 50 does not tell the user anything. Something like "Corporate Customers: 50" is much more useful. But try to update the tooltip to something like that. After turning my graph orange (which, according to the forums, is more common than you would think), I gave up.
5) Allowing Different Colors in Column Charts
Surprisingly, the scenarios used when building the Column Chart features did not include one to show different colors, like a Pie Chart does. Rather, you only have one color for all of the columns. There are ways around this, none of which are easy and I was not successful within the time frame I had. So I built a separate series for each of the columns. Looked OK for the prototype, but won't cut it for the real application.
6) Better Image Error Messages
Yea, I covered this one to. If it does not work with bmp's it should either not allow you to pick them or it should give you an error message if you try to use them. I have more information on this one here.
7) Commenting XAML Blocks
This is not so much a Silverlight feature, but a XAML feature. I would like to see a new commenting character, like "//", that allows blocking out a set of XAML without messing with the internal <!-- --> comments. Right now, you have to remove all internal comments in a block of XAML before you can comment out that block. With the amount of trial and error required to accomplish anything when you are first learning Silverlight, this would be really useful.
8) Expanded WCF RIA Services
I know Santa's workshop has been busy with this one.
9) Interaction with the Desktop
This one I almost didn't put on the list because I peeked and know I am getting these in Silverlight 4.0: Clipboard support, Printing support, COM support, File accessing and so on.
10) Peace on Earth and Good Will to All Persons
Can Silverlight do that?
Enjoy and Happy Holiday!
When last we saw our Silverlight ComboBox in this prior post, it was correctly populating, but as we paged through the records in our DataForm, the SelectedItem was not set correctly.

In XAML:
<ComboBox ItemsSource=
"{Binding Data, Source={StaticResource CustomerTypeSource}}"
DisplayMemberPath="CodeText"
SelectedItem="{Binding CustomerTypeId, Mode=TwoWay}"/>
Regardless of the customer type for a Customer, in this example the SelectedItem is always set to the first item on the list.
The basic problem is that the data bound to the ComboBox has both a display member (the CodeText) and a value member (the CodeId). We want to bind the Customer object's CustomerTypeId property to the Code object's Code Id.
The ComboBox has a DisplayMemberPath property so the ComboBox does display the CodeText properly. It also has a SelectedItem property, which is expecting a Code object. But the Binding statement does not allow us to specify a Code object, only a property name (CustomerTypeId in this case).
Because the ComboBox does not have a ValueMemberPath property, there is no easy way to tell the control that it should map the CustomerTypeId to the CodeId. But there is a hard way using value converters.
A value converter is basically what it sounds like: it converts one value to another value. Use a value converter any time that you want to reformat or change a value in any way.
For the ComboBox SelectedItem to work correctly, we need to "convert" the CustomerTypeId to an appropriate Code object. Basically we need to use the CustomerTypeId to find and return the Code object with a matching CodeId.
Building a converter is not very hard once you know the basics. Just build a class that implements IValueConverter. Then write the code in the associated Convert and ConvertBack methods. I added this class directly to my Silverlight project.
NOTE: Be sure to import the System.Windows.Data namespace.
In C#:
using System.Windows.Controls;
using System.Windows.Data;
namespace SLCSharp
{
public class CustomerTypeIdConverter : IValueConverter
{
public DomainDataSource ItemsSource { get; set; }
public object Convert(object value,
System.Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
// Set a default return value
int custTypeId = (int)value;
BoCSharp.Code returnValue = null;
// Look for the value in the list of items
foreach (var item in ItemsSource.Data)
{
BoCSharp.Code codeObject = (BoCSharp.Code)item;
if (codeObject.CodeId == custTypeId)
{
returnValue = codeObject;
break;
}
}
return returnValue;
}
public object ConvertBack(object value,
System.Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
// Set a default return value
BoCSharp.Code codeObject = (BoCSharp.Code)value;
int returnValue = 0;
if (codeObject != null)
{
returnValue = codeObject.CodeId;
}
return returnValue;
}
}
}
In VB:
Imports System.Windows.Data
Public Class CustomerTypeIdConverter
Implements IValueConverter
Private _ItemsSource As DomainDataSource
Public Property ItemsSource() As DomainDataSource
Get
Return _ItemsSource
End Get
Set(ByVal value As DomainDataSource)
_ItemsSource = value
End Set
End Property
Public Function Convert(ByVal value As Object, _
ByVal targetType As System.Type, _
ByVal parameter As Object, _
ByVal culture As System.Globalization.CultureInfo) _
As Object _
Implements System.Windows.Data.IValueConverter.Convert
' Set a default return value
Dim custTypeId As Integer = CType(value, Integer)
Dim returnValue As BoVB.Code = Nothing
' Look for the value in the list of items
For Each item In ItemsSource.Data
Dim codeObject As BoVB.Code = DirectCast(item, BoVB.Code)
If codeObject.CodeId = custTypeId Then
returnValue = codeObject
Exit For
End If
Next
Return returnValue
End Function
Public Function ConvertBack(ByVal value As Object, _
ByVal targetType As System.Type, _
ByVal parameter As Object, _
ByVal culture As System.Globalization.CultureInfo) _
As Object _
Implements System.Windows.Data.IValueConverter.ConvertBack
' Set a default return value
Dim codeObject As BoVB.Code = DirectCast(value, BoVB.Code)
Dim returnValue As Integer = 0
If codeObject IsNot Nothing Then
returnValue = codeObject.CodeId
End If
Return returnValue
End Function
End Class
This code defines a property for the DomainDataSource. This allows you to pass in the Codes data source so you can find the appropriate Code object based on the CustomerTypeId.
The Convert function "converts" the CustomerTypeId to the associated Code object. When the ComboBox SelectedItem is set to a particular CustomerTypeId, the CustomerTypeId is passed to the Convert function. The code in the Convert function first converts the passed in value to an integer. It then loops through the ItemsSource to find the Code object with a CodeId that matches the passed in CustomerTypeId. It then returns the found Code object.
NOTE: Instead of the loop, you could use LINQ instead:
In C#:
var returnValue = ItemsSource.Data.Cast<BoCSharp.Code>().Where(
item => item.CodeId == custTypeId).FirstOrDefault();
In VB:
Dim returnValue = ItemsSource.Data.Cast(Of BoVB.Code). _
Where(Function(item) item.CodeId = custTypeId).FirstOrDefault
The ConvertBack function converts back from a Code object to a CustomerTypeId. This one is easy. As long as a valid Code object is based in, the CustomerTypeId is just the Code object's Code Id.
There is one more required step: you need to modify the XAML to define the converter as a resource and associate it with the ComboBox.
In the UserControl.Resources section, add the following to define the value converter:
In XAML:
<UserControl.Resources>
<local:CustomerTypeIdConverter x:Key="CustomerTypeIdConverter"
ItemsSource="{StaticResource CustomerTypeSource}"/>
</UserControl.Resources>
Notice how this sets the ItemsSource property to the CustomerTypeSource, which contains our customer type codes.
Then replace the ComboBox item in the DataForm with this:
In XAML:
<ComboBox ItemsSource=
"{Binding Data, Source={StaticResource CustomerTypeSource}}"
DisplayMemberPath="CodeText"
SelectedItem="{Binding CustomerTypeId, Mode=TwoWay,
Converter={StaticResource CustomerTypeIdConverter}}"/>
This references the converter from the UserControl resources.
Voila! Paging through the DataForm now shows the correct values!
Dang it! It works until you hit the back button.
Then all of the values are off by one (the value from the prior Customer).
| This MUST be a bug in the DataForm. BUMMER! |
Now what?
You have several choices:
1) Remove the pager control.
In a "real" application, you don't want your users to page through 500 customers. Rather you will have a selection box or search feature for the user to find the customer to edit. Without the pager control, this technique works great!
2) Subclass the ComboBox control and add your own SelectedValue.
Rocky shows how to do this on his blog here.
3) Buy a suite of Silverlight controls that includes a ComboBox from a third party vendor.
In my application, I went with Option #1. I have a DataGrid that displays data for the customers and allows searching and sorting. Double-clicking on in customer in the grid row then displays this DataForm (WITHOUT the pager control). All is well.
Enjoy!
More Posts
Next page »