After all of the success of the first round of Boston XAMLFest (12/11 and 12/12), Microsoft decided to host a second set of days this week (12/15 and 12/16). XAMLFest was a quick but detailed look into WPF and SilverLight; with minimal marketing slides and a lot of hands-on coding and walk-throughs. The time was split up between Visual Studio development (John P) for the coding aspects and Expression Blend 2 (John B) for the UI.
While I have been doing a lot WPF work for a bit, this experience was awesome. I learned several tips and tricks that should help for my current and future projects. I also learned about some great tools that are must-haves for working with SilverLight or WPF.
One tool that is a must for understanding the details of what is happening within a running WPF application is Snoop. This tool allows you to see the entire inheritance tree of all visual elements, with "actual" properties, values, and indicators so you know if the values are default, set, or inherited.
I also learned a simple tip that helped solve a minor problem I was having. I created a fancy title bar to replace the default title bar. The problem was, one of my visual elements was over the buttons, causing only half of the button to respond to click events. While there were likely many other ways to fix the problem, one simple property setting took care of the problem. By setting IsHitTestVisible to False on the UI element, it no longer blocked the mouse down event from reaching the makeshift close and minimize buttons on my title bar.
Finally, there were a couple of differences between SilverLight and WPF that made a difference to me (though the full list is much bigger!)
- In SilverLight, you have a much more limited list of set Brushes to use when styling. This was to keep the download package smaller.
- The default binding mode for SilverLight elements is OneWayToSource, while in WPF it is "default" (which varies by object type).
- There is no native support for synchronized collection binding (IsSynchronizedWithCurrentItem or "/" notation to indicate "current item in collection". This makes it more difficult to do "master-detail" binding, where you have a list view that automagically updates a detailed view.
I'm playing around with some list templates that should allow for some cool visual effects for both viewing and editing data. I'll post them up when I get them done.
Joe
An enumerator decorated with the [Flags] or [FlagAttribute] attribute (they are one in the same), is able to store a byte that represents the concatenation of all possible combinations of the enumeration. Because of this concatenation it is required that the enumeration be numbered implementing a base 2 sequence (i.e. 0, 1, 2, 4). As seen in the sample code this evaluates to the following base 2 numbers:
0 = 0000
1 = 0001
2 = 0010
4 = 0100
For the sake of simplicity we added another enum to represent when all of the flags have been selected All = 7
7 = 0111
The following code (a simple aspx page and its code-behind) will display a the uses of Flags as well as the differences from regular enums. The code uses Bitwise OR to concatenate a flag (i.e. will toggle the bit on if either or both bits in the sequence are true (1)), as follows:
0010
0001
0011
The code also uses the the Bitwise Complement (~) operator as well as the Bitwise AND (&) operator to negate (remove) a specific flag (i.e. will toggle the bit off if both bits in the sequence are true (1)), as follows:
0111
~0010
0101
ASPX Code:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UsingEnums.aspx.cs" Inherits="UsingEnums" %>
<!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 runat="server">
<title>Using Enumerators Page</title>
</head>
<body>
<form id="form1" runat="server">
<div style="text-align: center; width: 604px;">
<asp:Label ID="lblProjectRequirements" runat="server" Text="Project Requirements (Pick up to two)"></asp:Label>
<asp:CheckBox ID="chkAutoCorrect" runat="server" Text="Auto-correct" />
<asp:CheckBoxList ID="cblProjectPreference" runat="server">
</asp:CheckBoxList>
<asp:Button ID="btnSelectRequirements" runat="server" Text="Select Requirements"
OnClick="btnSelectRequirements_Click" Style="height: 26px" /><br />
<asp:Label ID="lblResults" runat="server" Height="91px" Width="552px"></asp:Label><br />
<asp:Button ID="btnCompare" runat="server" Text="Comapre Flags to Enums" OnClick="btnCompare_Click"
Style="height: 26px" /><br />
<asp:Label ID="lblFlagsDisplay" runat="server" Height="40px" Width="300px"></asp:Label>
<asp:Label ID="lblEnumDisplay" runat="server" Height="40px" Width="300px"></asp:Label>
</div>
</form>
</body>
</html>
Code-Behind:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
[Flags]
public enum ProjectPreference
{
/// <summary>
/// No project requirements are selected
/// </summary>
None = 0, // (0000)
/// <summary>
/// The project must be done as inexpensively as possible
/// </summary>
Cheap = 1, // (0001)
/// <summary>
/// The project must be done as quickly as possible
/// </summary>
Fast = 2, // (0010)
/// <summary>
/// The project must be of the highest quality
/// </summary>
Good = 4, // (0100)
/// <summary>
/// This is an illegal value, since only two can be slected at one time
/// </summary>
All = Cheap | Fast | Good // (0111)
}
// Based on Microsoft http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspx
// Define an Enum without FlagsAttribute.
public enum SingleHue : short
{
Black = 0,
Red = 1,
Yellow = 2,
Blue = 4
}
// Define an Enum with FlagsAttribute.
[FlagsAttribute]
public enum MultiHue : short
{
Black = 0,
Red = 1,
Yellow = 2,
Blue = 4
}
public partial class UsingEnums : Page
{
private const int SKIP_FOR_NONE_AND_ALL = 2;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Get a string array of all the enum names
string[] projectTypes = Enum.GetNames(typeof(ProjectPreference));
// Don't display None or All enum values
string[] displayProjectPreferences = new string[projectTypes.Length - SKIP_FOR_NONE_AND_ALL];
int i = 0;
foreach (string projectType in projectTypes)
{
if (
!projectType.Equals(ProjectPreference.None.ToString())
&&
!projectType.Equals(ProjectPreference.All.ToString())
)
{
displayProjectPreferences[i++] = projectType;
}
}
cblProjectPreference.DataSource = displayProjectPreferences;
cblProjectPreference.DataBind();
}
}
protected void btnSelectRequirements_Click(object sender, EventArgs e)
{
ProjectPreference selectedProjectPreferences = new ProjectPreference();
try
{
// Gather all of the selected flags
foreach (ListItem liProjectPreference in cblProjectPreference.Items)
{
if (liProjectPreference.Selected)
{
// Concatenate the flag using the Enum.Parse method and the Bitwise OR (|) operator
selectedProjectPreferences |=
(ProjectPreference) Enum.Parse(typeof (ProjectPreference), liProjectPreference.Value, true);
}
}
// if all have been selected then ...
if (selectedProjectPreferences == ProjectPreference.All)
{
// if auto-correct is on remove the 'Cheap' flag
if (chkAutoCorrect.Checked)
{
// Use the Bitwise Complement (~) operator as well as the Bitwise AND (&)
// operator to negate (remove) a specific flag
selectedProjectPreferences &= ~ProjectPreference.Cheap;
foreach (ListItem liProjectPreference in cblProjectPreference.Items)
{
if (liProjectPreference.Value == ProjectPreference.Cheap.ToString())
{
liProjectPreference.Selected = false;
}
}
}
// otherwise throw an error
else
{
cblProjectPreference.SelectedIndex = -1;
throw new Exception("You can select no more than two project preferences.");
}
}
lblResults.Text = selectedProjectPreferences.ToString();
}
catch (Exception ex)
{
// The flag enumeration evaluates to 'All' - no coding required
string errMessage = string.Format("The error value of the flag enumeration is: {0}<br />",
selectedProjectPreferences);
// Reset to 'None'
selectedProjectPreferences = ProjectPreference.None;
errMessage += string.Format("{0}<br />The current value of the flag enumeration has been reset to: {1}",
ex.Message, selectedProjectPreferences);
lblResults.Text = errMessage;
}
}
protected void btnCompare_Click(object sender, EventArgs e)
{
// Display all possible combinations of values.
// Also display an invalid value.
string flagDisplay = "<b>With</b> FlagsAttribute:<br />";
for (int val = 0; val <= 8; val++)
{
flagDisplay += string.Format("{0,3} - {1}<br />", val, ((MultiHue)val));
}
lblFlagsDisplay.Text = flagDisplay;
string enumDisplay = "<b>Without</b> FlagsAttribute:<br />";
for (int val = 0; val <= 8; val++)
{
enumDisplay += string.Format("{0,3} - {1}<br />", val, ((SingleHue)val));
}
lblEnumDisplay.Text = enumDisplay;
}
}
Using the [Flags] attribute allows you to let the code handle some of the display of the flags, as the ToString() method will return a comma seperated list of enums (e.g. "Cheap, Fast"), it will also resolve the ToString() to an aggregated value (in this case to 'All' if all of the flags are selected) and finally in walking through code you will be able to hover and see the concatenated list of flags instead of a somewhat hard-to-decipher number. That is, if you are walking through code after a breakpoint you can hover over a [Flags] enum and see "Cheap | Good" instead of "5", making debugging far easier.
That wraps it up for this time. Hope it helps.
In this blog post I'm going to show you the four easy steps to implementing an ASP.NET AJAX Web Service:
- 1) Set-up a web service, this sample call invalidates any name beginning with the letter ‘a' (it is already taken), throws an exception for any name beginning with the letter ‘b' and validates all other names.
using System;
using System.Globalization;
using System.Web.Services;
namespace BlogDemo.Utility
{
/// <summary>
/// The BlogDemoService provides a sample web service to demonstrate AJAX client-side web service calls
/// </summary>
[WebService(Namespace = "http://syrinx.net/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
Make sure to remove the following comment marks è // [System.Web.Script.Services.ScriptService]
[System.Web.Script.Services.ScriptService]
public class BlogDemoService : WebService
{
[WebMethod]
public bool IsNameTaken(string checkName)
{
bool retVal = false;
if (checkName.StartsWith("a", true, CultureInfo.CurrentCulture))
{
retVal = true;
}
else if (checkName.StartsWith("b", true, CultureInfo.CurrentCulture))
{
throw new Exception("Names cannot start with either the letter 'B' or 'b'");
}
return retVal;
}
}
}
N.B.: The asmx file must have the fully qualified name (including namespace) :
<%@ WebService Language="C#" CodeBehind="~/App_Code/BlogDemoService.cs" Class="BlogDemo.Utility.BlogDemoService" %>
- 2) Add the ScriptManager control to the page like so:
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Services/BlogDemoService.asmx" />
</Services>
</asp:ScriptManager>
- 3) Add javascript to call the webservice - note that it requires a call to the fully qualified name
<script type="text/javascript">
function CheckNameAvailability(info)
{
var checkName = $get("txtUserName").value;
BlogDemo.Utility.BlogDemoService.IsNameTaken(checkName, OnCheckNameSuccess, OnCheckNameFailure, info);
}
function OnCheckNameSuccess(results, userContext, methodName)
{
alert("Success using method: " + methodName);
alert("This data was passed with the call: " + userContext);
if(results)
{
alert("Name is taken");
}
else
{
alert("Name is available");
}
}
function OnCheckNameFailure(exception, userContext, methodName)
{
alert("Failure using method: " + methodName);
alert("This data was passed with the call: " + userContext);
alert(exception._message);
}
</script>
The calls to and from the web service use the complete signature to the Web Service call. The signature for the call into the Web Service is:
FullyQualifiedWebService. WebMethod(parameters, successfulCallback, failedCallback, userContext);
The successful callback signature is: function successfulCallback(result, userContext, methodName) {}
The failed callback signature is: function failedCallback(exception,userContext,methodName) {}. The exception variable passed to the failedCallback is a JSON serialized Exception object, providing the description of the error and a full stack trace. This allows the developer to handle-and-re-throw exceptions on the server and still gracefully deal with them on the client.
The following web page shows how the information can be used - results are displayed using a simple alert as well as accessing a text box from the client side.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!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>AJAX Web Service Page</title>
<script type="text/javascript">
var txtResultsID = '<% =txtResults.ClientID %>';
function CheckNameAvailability(info)
{
var checkName = $get("txtUserName").value;
BlogDemo.Utility.BlogDemoService.IsNameTaken(checkName, OnCheckNameSuccess, OnCheckNameFailure, info);
}
function OnCheckNameSuccess(results, userContext, methodName)
{
alert("Success using method: " + methodName);
alert("This data was passed with the call: " + userContext);
if(results)
{
$get(txtResultsID).value = "Name is taken";
}
else
{
$get(txtResultsID).value = "Name is available";
}
}
function OnCheckNameFailure(exception, userContext, methodName)
{
alert("Failure using method: " + methodName);
alert("This data was passed with the call: " + userContext);
$get(txtResultsID).value = exception._message;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Services/BlogDemoService.asmx" />
</Services>
</asp:ScriptManager>
<div>
<asp:TextBox ID="txtUserName" runat="server"></asp:TextBox><br />
<asp:Button ID="btnSendBasicInfo" runat="server" Text="Send Basic" OnClientClick="CheckNameAvailability('Just the basics'); return false;" />
<asp:Button ID="btnSendMoreInfo" runat="server" Text="Send More" OnClientClick="CheckNameAvailability('Very detailed information'); return false;" /><br />
<asp:TextBox ID="txtResults" runat="server" Text="" Width="350px" ReadOnly="true"></asp:TextBox>
</div>
</form>
</body>
</html>
That's it for this time - hope you found it useful.