Tuesday, December 9, 2008

Tutor.com Classroom How-To #11: Build a Silverlight ToolbarButton user control

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

One of the controls missing out-of-the gate from Silverlight is the Toolbar. There is no magic to the WPF Toolbar (it’s just a control container that auto-docks itself and auto-styles its children), but for free you get the flat button look-and-feel that users expect at the top of an application.

In Silverlight, we have to manually create this look-and-feel. Since our application’s toolbar consists solely of buttons (both normal and toggle buttons), and since the buttons in our toolbar have the “icon followed by a text description” format, we can abstract this to build the ToolbarButton user control.

Begin by creating a new user control. Define the styles for the Hover and Pressed states in your user control’s Xaml resources:

<UserControl.Resources>
<LinearGradientBrush x:Key="HoverBrush" StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="White" />
<GradientStop Color="#F8D28F" Offset=".3" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>

<LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#F8D28F"/>
<GradientStop Color="White" Offset=".3" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</UserControl.Resources>

Then, use a Grid to hold two Border objects; one contains the image and the text label, and the other is a half-opaque, white filled Border that we use when the button is disabled.

<Grid>
<Border x:Name="MainBorder" BorderThickness="1" CornerRadius="1" MouseEnter="Border_MouseEnter" MouseLeave="Border_MouseLeave" MouseLeftButtonUp="Border_MouseLeftButtonUp" Cursor="Hand" Margin="1">
<StackPanel Orientation="Horizontal" Margin="5,1,5,1">
<Image x:Name="BorderImage" Width="16" />
<TextBlock x:Name="BorderTextBlock" Style="{StaticResource blackText}" Margin="5" />
</StackPanel>
</Border>
<Border Background="#B2FFFFFF" x:Name="DisabledBackground" BorderThickness="1" CornerRadius="2" BorderBrush="#B2FFFFFF" Visibility="Collapsed" />
</Grid>

Next, wire up the IsEnabled, ToolTip, ImageSource, and Caption properties, and implement the hover and click functionality. Note that these properties are backed by dependency properties on the objects we defined in our Xaml.

public partial class ToolbarButton : UserControl
{
bool m_IsEnabled = true;

public new bool IsEnabled
{
set
{
MainBorder.Background = null;
MainBorder.BorderBrush = null;
DisabledBackground.Visibility = (value) ? Visibility.Collapsed : Visibility.Visible;
}
}

public string ToolTip
{
get { return ToolTipService.GetToolTip(MainBorder).ToString();}
set { ToolTipService.SetToolTip(MainBorder, value);}
}

public ImageSource ImageSource
{
get { return BorderImage.Source;}
set { BorderImage.Source = value;}
}

public string Caption
{
get { return BorderTextBlock.Text;}
set { BorderTextBlock.Text = value;}
}

public bool IsToggleButton { get; set; }
public bool IsToggled { get; set; }

public event MouseButtonEventHandler Clicked;

public ToolbarButton()
{
InitializeComponent();
}

private void Border_MouseEnter(object sender, MouseEventArgs e)
{
//if we're a toggle button and we're on...
if (this.IsToggleButton && this.IsToggled)
return;

Border b = sender as Border;

b.Background = Resources["HoverBrush"] as Brush;
b.BorderBrush = new SolidColorBrush(ColorHelper.FromHexString("FFCA8103"));
}

private void Border_MouseLeave(object sender, MouseEventArgs e)
{
//if we're a toggle button and we're on...
if (this.IsToggleButton && this.IsToggled)
return;

Border b = sender as Border;

b.Background = null;
b.BorderBrush = null;
}

private void Border_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!m_IsEnabled)
return;

if (this.IsToggleButton)
{
this.IsToggled = !this.IsToggled;

//set state
Border b = sender as Border;
b.Background = (this.IsToggled) ? Resources["PressedBrush"] as Brush : Resources["HoverBrush"] as Brush;
}

if (this.Clicked != null)
this.Clicked(sender, e);
}
}

Finally, in your container Xaml, use the ToolbarButton class inside a StackPanel with Orientation set to Horizontal:

<StackPanel Orientation="Horizontal">
<local:ToolbarButton ImageSource="/Resources/board.png" Caption="Add Whiteboard" Clicked="AddWhiteboard_Click" Margin="1" />
...
</StackPanel>

Friday, December 5, 2008

default DateTime parameters to an old date, never NULL

you want to use:

CREATE PROCEDURE …
@StartDate = ‘1/1/2000’

not

CREATE PROCEDURE …
@StartDate = NULL

the reason for this is that if you use NULL, you have to put WHERE (@StartDate IS NULL OR DateValue > @StartDate) in your where clause, which screws your query optimization. if you default to an old date, you can just put WHERE (DateValue > @StartDate) so the optimizer will be happy—

beware API overloads

on business object methods like Consumer.UpdateConsumer() we have several overloads that let you update only a few specific Consumer properties. for example, there’s one overload that takes a CustomerStatusId, another that takes a WhereHeard and a CustomerStatusId, etc. these overloads all call a common, private updateConsumer() function that takes care of the database update, and they pass in the object’s properties for the fields you are not updating, so that the original values are preserved.

however, this means that if you call an overload that takes multiple parameters, any value you pass in as a parameter will be used for the database update. so passing in “null” or empty string will update the database with “null” or empty string for that field, which may not be your intention--

Thursday, December 4, 2008

beware maxBufferSize, maxReceivedMessageSize, and maxStringCotentLength when adding service reference in VS2008

when you add a service reference in VS2008, you end up with a entry in your applications app.config file. this element contains binding elements for each service you add.

BEWARE: the default value for three critical attributes, maxBufferSize, maxReceivedMessageSize, and maxStringCotentLength, is set considerably low (64KB). we have hit this problem more than once now with deployed software, and unfortunately the only way to correct it is to adjust these values in app.config and re-distribute the software. so if you think there is even the slightest chance that your service will be returning more than 64KB in data, be sure to adjust these values after you add the service reference.

Friday, November 14, 2008

Tutor.com Classroom How-To #10: manually detecting and installing the Silverlight plug-in

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

Use the isInstalled() function provided by Silverlight.js:


<script language="javascript" type="text/javascript">

Silverlight.InstallAndCreateSilverlight = function(version)
{
var retryTimeout = 1000; //the interval at which instantiation is attempted(ms)
if (Silverlight.isInstalled(version))
{
...
}
else if (!Silverlight.isInstalled(version))
{
TimeoutDelegate = function()
{
Silverlight.InstallAndCreateSilverlight(version);
}

setTimeout(TimeoutDelegate, retryTimeout);
}
}

Silverlight.InstallAndCreateSilverlight('2.0.30923.0');

</script>

Thursday, November 13, 2008

Tutor.com Classroom How-To #9: creating a Path object from a Stroke

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

Simply transfer Stroke stylus points to a PolyLineSegment on a Path:


static Path getPathFromStroke(Stroke str)
{
//make a path from this stroke
Path path = new Path();
PathGeometry pg = new PathGeometry();

//start our figure from the first stylus point in the stroke
PathFigure fg = new PathFigure();
fg.Segments = new PathSegmentCollection();
fg.StartPoint = new Point(str.StylusPoints[0].X, str.StylusPoints[0].Y);

PolyLineSegment seg = new PolyLineSegment();

//add each additional stylus point to the line segment
for (int x = 1; x < str.StylusPoints.Count; x++)
StylusPoint sp = str.StylusPoints[x];

seg.Points.Add(new Point(sp.X, sp.Y));

fg.Segments.Add(seg);

pg.Figures = new PathFigureCollection();
pg.Figures.Add(fg);

path.Data = pg;

return path;
}

Wednesday, November 12, 2008

Tutor.com Classroom How-To #8: trapping and processing mouse movement in an InkPresenter

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

In the MouseDown event, instantiate a module-level Stroke object (e.g. m_DrawingStroke) to collect points traversed by the mouse. Fire the StrokeAdded event so that listeners can prepare for the operation as necessary. Most importantly, call CaptureMouse() to notify the mouse input engine to give you high frequency mouse event notifications.


void Whiteboard_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//get our current position
Point p = e.GetPosition(this);

//create a new styluspoint collection for our new stroke
StylusPointCollection coll = new StylusPointCollection();
coll.Add(new StylusPoint() { X = p.X, Y = p.Y });

m_DrawingStroke = new Stroke(coll);

//fire notification event
if (this.StrokeAdded != null)
this.StrokeAdded(m_DrawingStroke);

//begin capturing mouse input
this.CaptureMouse();
}

In MouseMove, add the traversed point to the Stroke’s StylusPoints collection, and fire the StrokeChanging event to alert interested listeners.

void Whiteboard_MouseMove(object sender, MouseEventArgs e)
{
//get our current position
Point p = e.GetPosition(this);

//add the traversed point to our collection
StylusPoint sp = new StylusPoint() { X = p.X, Y = p.Y };
m_DrawingStroke.StylusPoints.Add(sp);

//fire notification event
if (this.StrokeChanging != null)
this.StrokeChanging(m_DrawingStroke);
}

In MouseUp, release mouse capture and fire the StrokeComplete event.

void Whiteboard_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
//release mouse capture
this.ReleaseMouseCapture();

//fire notification event
if (this.StrokeComplete != null)
this.StrokeComplete(m_DrawingStroke);
}

Tuesday, November 11, 2008

Tutor.com Classroom How-To #7: screen-scraping an application via Win32 PrintWindow() API

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

In the WPF application, we make calls to the PrintWindow() Win32 API to gather screen contents. We then splice the return bitmap into a 4x4 grid and compare hashes of each of the slices with the previous slices’ hashes. If a slice has changed, we package up its bytes and ship it across the wire.

There are two key components to making this process of screen scrape, comparison, and messaging fast enough to run each second without overburdening the CPU. The first is that the entire process runs on a thread pool thread via a timer, which frees up the GUI. The second is that our Bitmap uses the Format32bppArgb pixel format, which uses more memory but optimizes performance of PrintWindow() calls.


[DllImport("user32.dll", SetLastError = true)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);

void processCapture()
{
using (Graphics g = Graphics.FromImage(captureBitmap))
{
//get window content
IntPtr hdc = g.GetHdc();
try
{
result = PrintWindow(m_Win32HostHandle, hdc);
}
finally
{
g.ReleaseHdc(hdc);
}

//process captureBitmap
...
}
}

Monday, November 10, 2008

Tutor.com Classroom How-To #6: creating a horizontal WrapPanel that is transform-aware and exposes row and column counts

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

Begin by deriving from Panel, which is the base class for container controls, and override the MeasureOverride() and ArrangeOverride() functions. These functions are called iteratively as the layout engine determines how to distribute available visual space. Have them call a custom function that takes either the available size (from the measure pass) or the final size (from the arrange pass), and a ShouldArrange parameter to specify which pass is processing, and returns a Size.


private Size measureAndOptionallyArrangeItems(Size size, bool ShouldArrange)
{
Point point = new Point(0, 0);
Size s = new Size(size.Width, 0);

//consider scale transform that might be affecting us
double xfactor = 1;
double yfactor = 1;
if (this.RenderTransform != null)
{
if (this.RenderTransform is ScaleTransform)
{
ScaleTransform st = (ScaleTransform)this.RenderTransform;

xfactor = st.ScaleX;
yfactor = st.ScaleY;
}
}

double largestHeight = 0.0;

this.Rows = 0;
this.Cols = 0;

foreach (UIElement child in Children)
{
if (child.DesiredSize.Height > largestHeight)
largestHeight = child.DesiredSize.Height;

//first row?
if (this.Rows == 0)
this.Rows = 1;

double desiredWidth = child.DesiredSize.Width;

//does this child cause us to wrap?
if (point.X > 0 && point.X + desiredWidth > size.Width * (1 / xfactor))
{
this.Rows++;

s.Height += largestHeight * yfactor;

//goto the next line
point.X = 0;
point.Y += largestHeight;
largestHeight = child.DesiredSize.Height;

if (ShouldArrange)
child.Arrange(new Rect(point, new Point(point.X + desiredWidth, point.Y + child.DesiredSize.Height)));

//set our current location in this new line
point.X = desiredWidth;
}
else
{
if (ShouldArrange)
child.Arrange(new Rect(point, new Point(point.X + desiredWidth, point.Y + child.DesiredSize.Height)));

point.X = point.X + desiredWidth;

//if we're doing first row, set columns
if (this.Rows == 1)
this.Cols++;
}
}

s.Height += largestHeight * yfactor;

return s;
}

Friday, November 7, 2008

Tutor.com Classroom How-To #5: providing automatic reader-to-writer lock upgrade

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

Create a class that contains a module-level System.Threading.ReaderWriterLock (e.g. m_Lock) and wrap the AcquireReaderLock() function. Then create an AcquireWriterLock() function that returns a LockCookie wrapper (SafeLockCookie), checks to see if the read lock is held, and upgrades if necessary, and a ReleaseWriterLock()function that processes the SafeLockCookie and releases the lock or downgrades back to read:


public SafeLockCookie AcquireWriterLock()
{
//if we have a read lock, upgrade
if (m_Lock.IsReaderLockHeld)
{
return new SafeLockCookie(m_Lock.UpgradeToWriterLock(TIMEOUT_MS));
}
else
{
m_Lock.AcquireWriterLock(TIMEOUT_MS);
return null;
}
}

public void ReleaseWriterLock(SafeLockCookie SafeLockCookie)
{
//do we need to downgrade?
if (SafeLockCookie != null)
{
m_Lock.DowngradeFromWriterLock(ref SafeLockCookie.LockCookie);
}
else
{
m_Lock.ReleaseWriterLock();
}
}

Then have calling functions request write locks by storing a SafeLockCookie when requesting the lock and returning it when releasing:

SafeLockCookie lc = m_Lock.AcquireWriterLock();
try
{
...
}
finally
{
m_Lock.ReleaseWriterLock(lc);
}

Thursday, November 6, 2008

Tutor.com Classroom How-To #4: enforcing an MVC-style architecture

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

The benefits of MVC-style architecture are numerous: testability, modifiability, simplicity of design, etc., and WPF/Silverlight Xaml “binding” provide an ideal structure to effectively detach the front-end from middle-tier business objects.

One simple way to enforce MVC-style architecture is to put classes with distinct responsibilities in separate assemblies, and add the minimal set of references only as required by each assembly. This enforces the principle of separation of concerns, since you’ve actually made it impossible for code in one assembly to be concerned with the business of another if it shouldn’t!

Wednesday, November 5, 2008

Tutor.com Classroom How-To #3: specifying "End-Of-Message" to a TCP/IP listener

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

Since a TCP/IP Listener is constantly in a listening state, the Listener needs to know when to act on the bytes received. To accomplish this, listen first for 8 ASCII bytes that represent two 32-bit integers specifying the number of bytes the sender is sending, and then continue listening until receiving this number of bytes. This is an easier solution to implement than using a delimiter to specify end-of-message, since you don’t have to worry about delimiter escaping, and since you can specify the exact number of bytes to listen for via the third parameter to SocketAsyncEventArgs.SetBuffer().

Tuesday, November 4, 2008

Tutor.com Classroom How-To #2: connecting to a TCP/IP Listener

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

Using the Socket class, we just need to create a SocketAsyncEventArgs and call ConnectAsync(). Notice an example of the “#if WPF” directive for subtle Silverlight/WPF differences (as described in the "Code Sharing" post) when we set the SocketAsyncEventArgs RemoteEndPoint.


public void Connect(string host, int port)
{
//instantiate socket
m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//set up endpoint
#if WPF
m_SocketSendArgs.RemoteEndPoint = new IPEndPoint(Dns.GetHostAddresses(host)[0], port);
#else
m_SocketSendArgs.RemoteEndPoint = new DnsEndPoint(host, port);
#endif

//set up args
SocketAsyncEventArgs args = new SocketAsyncEventArgs();

args.UserToken = m_Socket;
args.RemoteEndPoint = m_SocketSendArgs.RemoteEndPoint;
args.Completed += new EventHandler(OnConnect);

m_Socket.ConnectAsync(args);
}

Monday, November 3, 2008

Tutor.com Classroom How-To #1: sharing code between Silverliht and WPF applications

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

To actually share the code, use compile-time code sharing by choosing “Add As Link” when adding project files, rather than using our source code management’s “Share”. Using the “Add As Link” technique ensures that as changes are made to shared code, successfully building the solution guarantees that the changes are valid across all three applications. Source code management “Share” requires developers to check in their changes, do a “Get Latest Version” on the shared files, rebuild the solution, make any necessary changes, repeat, etc. In the meantime, the build is potentially broken.

The Tutor.com Classroom: How-To tips from the field

These How-To tips are taken from The Tutor.com Classroom: Architecture and techniques using Silverlight, WPF, and the Microsoft .NET Framework.

You can check out the Silverlight Tutor.com Classroom in "practice" mode, although the real experience is with a live tutor on the other side!

Tutor.com’s online learning system, the Tutor.com Classroom, utilizes many of the advanced capabilities of the Microsoft .NET 3.5 platform, including TCP/IP sockets and WPF, in addition to making full use of the Silverlight browser plug-in. The next set of blog postings show solutions to some of the more interesting problems and how the potential of .NET was employed.

Friday, October 17, 2008

WPF gotcha: MessageBox.Show() inside Dispatcher.Invoke

in WPF, if you’re calling MessageBox.Show() inside a Dispatcher.Invoke call (when you’re moving back to the GUI thread from a worker thread), make sure you pass a Window object for the optional first parameter when calling MessageBox.Show (e.g. MessageBox.Show(this, "Message Here", "Title Here");). if you don’t, the message box can show up non-modal alongside your app, which is almost never your intent

 

 

Thursday, October 16, 2008

use a specialized Xml writer when creating xml strings

avoid creating Xml streams by string manipulation (e.g. string s = "<Name First='" + firstName + "' Last='" + lastName + "'/>), since you get no reserved character escaping and will throw an error when certain input characters are processed.

in .NET 3.5, there are two good choices: XDocument (in System.Xml.LINQ) and XmlWriter (in System.Xml). XDocument is particularly powerful, since both it and XElement have param of objects constructors, which means you can specify all elements and each element’s attributes in one statement. it ends up looking very declarative:

XDocument xDoc = new XDocument(
new XElement("Name",
new XAttribute("First", firstName),
new XAttribute("Last", lastName)
));

string encodedXml = xDoc.ToString();

you can use XmlWriter to write directly to a Stream or to a StringBuilder:

StringBuilder sb = new StringBuilder();

using (XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true } ))
{
writer.WriteStartElement("Name");
writer.WriteAttributeString("First", firstName);
writer.WriteAttributeString("Id", lastName);
writer.WriteEndElement();
}

string encodedXml = sb.ToString();


Tuesday, September 30, 2008

make sure you manually stop storyboards on hidden objects

storyboards continue to run even if the object you’re animating is invisible, so you’re generally burning CPU cycles for nothing. in silverlight, you can stop any storyboard just by calling Stop:

myStoryboard.Begin();
myStoryboard.Stop();

in WPF, you need to specifically let the runtime know that you want Stop functionality when you call Begin via the isControllable paramater:

myStoryboard.Begin(this, true);
myStoryboard.Stop();

Saturday, September 13, 2008

properties vs. functions

although there’s little difference after compilation, treat properties and functions very different from a logical perspective. properties should almost always be deterministic, i.e. calling the same property twice should return the same value (so object.Property == object.Property must be true). a property can have a lazy get constructor, so it actually does some work the first time it is called, but it must remember the result of that work and return it on subsequent calls. so in general a property returns something stateful or something static.

in all other cases, use a function, and give it a meaningful name based on what it’s doing (i.e. GetRandomNumber(), CreateUser(), etc.).

Thursday, August 7, 2008

Action<> and Func<> delegates

these new generic delegates were added in .NET 3.5 for LINQ, but they’re also very useful in your own classes. Action<> allows you to specify types for up to 4 parameters and returns void, and Func<> does the same except you can also specify a return type. if you look at the LINQ extension methods, you’ll see these delegates extensively.

so for example, if you needed a delegate that returned void and took no parameters, you’d have to write something like:

public delegate void VoidDelegate();

public event VoidDelegate MyEvent;

so what you end up with is repetitive declarations of such a VoidDelegate delegate in lots of classes. now, you can just use the Action<> delegate to do this (Action<> works in any case where you return void and take up to 4 parameters):

public event Action MyEvent;

same goes for Func<>. before, you’d have to create a delegate for each specific set of input variable and return types:

public delegate string TakeTwoStringsAndReturnStringDelegate(string String1, string String2);

and then call it like:


return (String)Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
(TakeTwoStringsAndReturnStringDelegate)delegate
{
return TakeTwoStringsAndReturnString("1", "2");
}
);

now you can use Func<> instead (Func<> also works for up to 4 parameters):

return (String)Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
(Func<String, String, String>)delegate
{
return TakeTwoStringsAndReturnString("1", "2");
}
);

there are probably still times when you’ll want to create your own delegate, but these two generics are very useful for filling in the gaps—

Monday, July 28, 2008

catching and ignoring an exception

if you want to catch an exception and ignore it, do this to avoid the compiler warning:

catch
{
//do nothing
}

instead of:

catch (Exception ex)
{
//do nothing
}

you can similarly catch exceptions of a certain type without assigning a variable to them, for example:

catch (ArgumentException)
{
//do nothing
}

instead of:

catch (ArgumentException ex)
{
//do nothing
}

Friday, July 25, 2008

if you ever get locked out of your own database...

if you set your default database, and then you kill that database, you might end up locked out of sql server entirely. if so, do this:

- open admin command prompt

- run this: sqlcmd –E -S YOURCOMPUTERNAME –d master

o alter login [DOMAIN\USERNAME] with default_database = master
o go

Tuesday, July 22, 2008

copy and paste files when remote desktop'ing

something i’ve wanted for a long time, not sure if this is new with the Vista version of RDP (6.0) but it’s very easy to setup:

- click ‘Options >>’ button after you run Remote Desktop

- go to the ‘Local Resources’ tab and click ‘More…’ button

- check one of your drives

two things will happen: you’ll see the drive you picked listed as a drive on the remote computer’s list of drives, and you’ll be able to copy files to the clipboard on the remote computer and paste them locally--

Friday, July 18, 2008

when logging exceptions, use Exception.ToString()

this will get you error type, message, and stacktrace, not only for the Exception but also for its InnerException if there is one--

Friday, July 11, 2008

josh smith's wpf binding article in this month's msdn magazine

http://msdn.microsoft.com/en-us/magazine/cc700358.aspx

a couple of things new to me:

- the way he uses IsSynchronizedWithCurrentItem and nested, hierarchical bindings like {Binding Path=CurrentItem.Orders.CurrentItem.OrderDetails}

- the capabilities of the .NET 3.5 validators (implementing validation logic in code using System.ComponentModel.IDataErrorInfo)

also check out how smoothly he binds an object hierarchy to a treeview, good stuff--

Thursday, July 10, 2008

error adding service reference to silverlight 2 app

if you run into problems adding a service reference to a sl2b2 app, it’s likely due to a sl2b2 install bug that manifests itself if you installed with certain pre-conditions. to fix it:

-----
(from http://silverlight.net/forums/t/17500.aspx)

First check your Silverlight Tools for VS 2008 install size on Add/Remove Programs window. If it is 1.14 MB, then you need to do the following:

1) Uninstall your Silverlight Tools for VS 2008,

2) Remove (delete) the following dll C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\Microsoft.VisualStudio.ServicesProxy.dll

3) Re-Install Silverlight Tools for VS 2008.

-----

Sunday, June 8, 2008

silverlight 2 beta 2

installer is here: http://www.microsoft.com/downloads/details.aspx?FamilyId=50A9EC01-267B-4521-B7D7-C0DBA8866434&displaylang=en

there is no problem installing this on your normal dev machine, but note that i had to manually uninstall KB949325 (in Vista it's listed under Program and Features > Installed Updates) before i could get beta 2 installed.

a good summary of changes is here: http://adoguy.com/2008/06/06/What_s_Changed_in_Silverlight_2_Beta_2.aspx

and fuller lists of breaking changes (there are lots of them!) are here: http://msdn.microsoft.com/en-us/library/cc645049(VS.95).aspx and http://blogs.msdn.com/silverlight_sdk/attachment/8574227.ashx.

the release version is expected “end of summer”, so assuming beta 2 is as solid as beta 1 was, this is the version that the classroom will first go live with--

Thursday, May 22, 2008

code snippet for INotifyPropertyChanged

import this snippet and use “propnp” to implement a property in an object that implements INotifyPropertyChanged--

http://www.russellgreenspan.com/software/code/propnp.zip

Tuesday, May 20, 2008

wpf data binding one step at a time

good overview and step-by-step introduction to wpf-style data binding:

http://www.codeproject.com/KB/WPF/MovingTowardWpfBinding.aspx

more good wpf binding info

differences between Source, RelativeSource, and ElementName (the latter 2 are WPF-only) mark-up extensions:

http://www.wpfwiki.com/WPF%20Q5.3.ashx

Thursday, May 15, 2008

wpf: key concepts

databinding and itemtemplate

databinding is key to writing less code for populating fields. you can databind to anything that implements INotifyPropertyChanged (for objects) or INotifyCollectionChanged (such as ObservableCollection, for collections). the syntax uses “{}” brackets and the keyword “Binding” and looks like this:

<TextBox Text={Binding Path=Name} />

elements in your xaml inherit the DataContext (the object or collection that you’re binding to) from their parent object(s). you can set the DataContext in code or via markup with the Source attribute of a Binding markup that generally points to a static resource:

<TextBox Text={Binding Path=Name, Source={StaticResource MyCollection}} />

the syntax definitely feels weird at first, but it gets easier as you start using it, and it lets you have nice MVC-style UI/business logic separation.

ItemTemplate works like a repeater or grid template in asp.net. you define exactly how you want the items in your ItemsContainer (e.g. a listbox) to be displayed:

<ListBox ItemsSource="{Binding Conferences, Source={StaticResource ConferenceManagerDS}}" SelectionChanged="ConferencesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Id}"></TextBlock>
<ListBox ItemsSource="{Binding Members}"></ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

see here for a good overview: http://www.vsj.co.uk/dotnet/display.asp?id=500

dependency properties

the idea here is that the property owner doesn’t necessarily know the value of his own property, since it “depends” on external factors, like whether or not an animation is occurring. so instead of creating a normal {get; set;} property, you “register” your property with the global, static “PropertyManager”, and then you access the value of your property via the instance method DependencyObject.GetValue(). so if you have a Button, you access it’s FontStyle property like this:

b.GetValue(Button.FontStyleProperty)

almost always the property author will implement a normal C#-style property accessor as well, which just calls the GetValue() and SetValue() dependency methods for you, so you can still say b.FontStyle = new Font() or in this example canvas.InkThickness = 4:

public int InkThickness
{
get { return (int)GetValue(InkThicknessProperty); }
set { SetValue(InkThicknessProperty, value); }
}

use the snippet "propdp" and most of the magic will be taken care of for you.

attached properties

attached properties are a special type of dependency property that allow a child element to set a property value that its parent reads.

as an example of why this is useful, think of how in WinForms every UI component derives from “Control”, and “Control” has properties like “Left” and “Top”. when a container goes to layout its children, it uses Control.Left. this means that the container has to have hard-coded logic that only something that derives from “Control” can be one of its children. this works out ok for core properties like Left and Top, but if you want to add another property that some UI components need, you either need to derive from Control and then have your custom controls derive from that derivation, or you need to edit the source for Control (if you have it) and add the property there. the problem is that the new property may or may not be relevant to all derived classes, and it may even interfere with a property that the derived class has added to itself.

attached properties let the container specify “i have these properties that are impt to me but may or may not be of interest to you”, and then the child can optionally “attach” a value to them. for example, to specify that a textbox in a canvas should have a left of 40, you write:

<TextBox Canvas.Left=”40” />

and to specify the row an element should be in inside a grid, you write:

<TextBox Grid.Row=”1” />

this allows a container to contain any type of child, not just those that derive from an expected type.

use the snippet “propa” and most of the magic will be taken care of for you.

routed events

this is just an elegant way of having Preview and non-Preview events. Preview events go top-down (they “tunnel”), and allow a parent to handle the event before it gets to a child. non-Preview events (they “bubble”) go from the child up. you register your events with the EventManager in a similar way that you do with dependency properties and the PropertyManager.

commands

these are WPF-only (i.e. they don’t exist in Silverlight 2 Beta 1). commands enable a MVC-type of design pattern, where the view (xaml) just asks the Controller (who implements ICommand) to perform an action or if an action is performable. josh smith has a good article on using commands here: http://www.codeproject.com/KB/WPF/MVCtoUnitTestinWPF.aspx.

triggers

another WPF-only feature, triggers do what you’d imagine: they let you specify a condition that causes an action. these are used extensively in styles when you want to have the style change on some event, for example:

<Style x:Key="MyContainer" TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
</Style.Triggers>
</Style>

Monday, April 28, 2008

row concatenation in sql2005

just reading about a new “FOR XML PATH” operator in sql2005 that apparently gives us a better way to do row concatenation than what we’ve been doing. instead of something like:

DECLARE @categories varchar(200)
SET @categories = NULL

SELECT @categories = COALESCE(@categories + ',','') + Name
FROM Production.ProductCategory
ORDER BY LEN(Name)

SELECT @categories

you write this (you’ll need to strip out leading ‘,’):

SELECT ',' + Name
FROM Production.ProductCategory
ORDER BY LEN(Name)
FOR XML PATH('')

one author saw up to a 10x speed improvement, but this will depend on the specifics of your query, so double-check the execution times if you use it. articles are here:

http://www.sqlmag.com/Article/ArticleID/96784/sql_server_96784.html

http://msmvps.com/blogs/robfarley/archive/2007/04/08/coalesce-is-not-the-answer-to-string-concatentation-in-t-sql.aspx

Sunday, March 30, 2008

msft wpf bootcamp

http://www.visitmix.com/university/wpf/bc08/

IE-only for some reason, but these video recordings of the presentations at Microsoft’s WPF Bootcamp look like a great starting point if WPF/Silverlight is new to you. WPF and Silverlight (v2) are going to be a big part of the work we do this summer and going forward, so start reading, learning, and playing with these technologies whenever you have some time--

Wednesday, March 26, 2008

silverlight 2 and IIS

if you want to access your silverlight 2 app from IIS, make sure you add a mime type in IIS for “.xap”:

Extension: .xap

MIME Type: application/x-msdownload


took me forever to figure out why it was working fine when running via the development server but not via IIS…

Wednesday, March 19, 2008

getting VS to launch in admin mode when double-clicking .sln file

this has been bugging me forever, finally got it worked out: the trick is that .sln files are associated with the ‘Visual Studio Version Selector’, not with Visual Studio itself. so you have to set the version selector, not visual studio, to run in administrator mode by default. first you have to find it:

C:\Program Files\Common Files\microsoft shared\MSEnv\VSLauncher.exe

then you just go to the .exe’s Properties window -> Compatibility tab -> Privilege Level frame and check 'Run this program as an administrator'.

Friday, March 7, 2008

Mix08 days 2 and 3

Mix08 finished up today, lots more good stuff. especially want to call out the "Story of the Office Ribbon" presentation, the speaker (jensen harris) was awesome as he showed various Ribbon prototypes, and it was super-interesting to see the evolution and all the thinking that went into it. you can watch it here:

http://msstudios.vo.llnwd.net/o21/mix08/08_WMVs/UX09.wmv


the ASP.NET MVC talk was also very interesting. MVC will live side-by-side with WebForms, and it gives a really clean (java) way to build test-driven web sites. it definitely has a future with us (it's in somewhat early beta now). you can watch or see slidedecks of it and others here:

http://sessions.visitmix.com/


-----


What's New in .NET 3.5, Rob Reylea, MSFT
- hardware-accelerated bitmap effects
- performance improvements
o cold-startup speed improvements of 15-40%
- easier install process

AOL Silverlight Mail Client Lessons Learner, AOL
- poorly attended, bad speakers, AOL irrelevant to this crowd even with ~50 million users

Silverlight 2.0, Karen Corby, MSFT
- design-ability improvements from 1.0
o user controls (encapsulation, re-use)
- derive from UserControl
o events at element level, as opposed to application level
o tabbing works automatically (like it does in html)
- custom controls - derive from Control
o use generic.xaml to specify style and template
- Parts Model for template support (override visual tree) - easy way for template designer t o say "these are the parts i'm expecting"
o ElementParts
§ Named elements in template
o StatesParts
§ Storyboard that controls the parts
o specify [TemplatePart()] attributes for each parts element
- Blend 2.5 has built-in support to build UserControls from selected set of controls


Steve Ballmer Keynote
- he's crazy and he loves developers
- silverlight is part of msft's long-term vision of web/desktop fusion
o 1.5 million silverlight downloads (did he say per day? possible?)
- in response to msft's diversity of businesses: you either broaden or you become irrelevant
- heathfall -> msft investment in healthcare

Social Networks, panel with Garret Camp (msft live) Marc Canter, Allen Hurff (MySpace), David Morin (business sn), someone from Facebook
- openId - relies on URL, not consumer friendly
o major players (facebook, msft live) are watching, but not adopting
- facebook charges $.50/app install
- privacy is the biggest issue, bigger than security and pedophiles
- lack of data portability is major issue, users need to preserve anonymity at times and share data across the web at other times
- 4/10,000 click through an ad

ASP.NET MVC, Scott Hanselman, MSFT
- implemented using HttpHandlers and HttpModules
- release expected later this year/early next
- side-by-side with WebForms
o more control over <html> output
o makes possible unit testing and test-driven development
o SEO and REST friendly url structures
o ideal separation of tiers
- request engine
o "route" is determined (routes specified in Application_Start) from specified URL (by default "/Controller/Action")
§ routing engine handles friendly url parsing and is decoupled from MVC
o controller method is invoked
o controller optionally talks to model
o controller passes off something to view
o view knows what its getting and how to render it
§ view can spit out html (WebForm, xslt, etc.), rss, json, etc.
§ view engine can be replaced (e.g. NHaml)
- to test controller, use empty view engine that implement IViewEngine

The Office Ribbon, Jensen Harris, MSFT (http://blogs.msdn.com/jensenh)
- easily the best presentation of the conference
- lots of prototyping; can we buy time in the UI lab?
- small buttons close to cursor or large buttons far away
- side vs. top
o people felt more comfortable on top in testing
o fear that sidebar would devolve into scrolling list
- evaluate
o longitudinal visibility - usage over time (i.e. 2 months, not 2 hours)
o feature affinity - how people use things together
o command loops - eliminate tab switches
o get fresh eyes!
o eye tracking - heat map, gaze tracking
- ask users where they think items should go
o some features like header/footer had 50/50% split
- iterate!
o account for iteration in dev schedule
o 5 major revisions, 4 later in the cycle
- when is a ribbon appropriate?
o real estate tradeoff, so make sure to take other things out
o lots of commands
o galleries, visual representations of features

Next Gen User Interfaces with WPF, Electric Rain
- workflow, design, build
o workflow = goals, foundation
o research, prototype, functional not pretty
- avoid monolithic specs in favor of rapid prototyping
o developers ask for product design specification when needed

Thursday, March 6, 2008

Mix08 day 1 notes

Key Note Addresses


- ray ozzie spends 30 minutes describing something we've all heard before and unfortunately have to hear more about once again, aka "cloud computing"

- msft still without a legitimate strategy to transform desktop monopoly to a web one

o connected pcs/devices

o web presence

o business apps

o virtualized servers/horizontal apps is key

- scott guthrie recounts his blog postings

- dean hachamovitch (ie8 general manager) reads too much from his script

o ie8 key focus on standard compliance and interopability (as opposed to ie7's focus on security)

§ w3c spec itself is vague and open to interpretation with regard to standards

§ css 2.1 support

§ poked fun at ie7's differences from firefox and safari

o looks more or less like ie7

o ie7 "compatability mode" controlled by developers

o html5

§ back button can work as expected with ajax

§ smooth experience when connectivity is lost

o integrated developer tools similar to firebug or webdevelopmenthelper (script debugging, css inspection)

o "activities" allow in-place right-click actions on selected text (i.e. select address, right-click, view map); very cool

o subscribe to "webslices" (slices of web pages); richer content/simpler implementation than rss

o ie8 beta 1 released

- scott guthrie returns to discuss silverlight 2.0 beta 1 release

- media and advertising

o adaptive streaming

o windows media services 2008 – scalable media hosting and delivery

o bandwidth savings: iis 7 bitrate throttling allows you to specify distinct bitrates for different downloads, so you deliver initial burst, then slower follow up

o jon harris discussed rich advertising: tracking with atlas admanager, iis7 playlists to force ads before video, ad overlays with xaml

o ari paparo from doubleclick discussed doubleclick/silverlight integration vs template

o perkins miller from nbc described silverlight for 2008 Olympics (2200 live hours plus broadcast coverage)

- wpf features

o almost full wpf control library

o sockets support

o protected isolated storage

- controls shipped with OPEN source and test code; you can modify and resell

- roy ben-yoseph and eric hoffman from aol described aol’s next mail client in silverlight 2.0


Lou Carbone, UX Guru – Experience Management

- effect vs. function: manage how users feel as opposed to what you are doing for them (fedex, airlines, rotorooter, etc.)

- think about the details! users remember how they felt more than they remember what they did



Nathan Buggia, MSFT, Live Search – SEO

- 30% of all traffic to web sites is from search engines

- html tips

o <title> and <h1> tags are the 2 most heavily weighted; <h1> is actually SUPER important on live.com since it’s used to parse organization of content

o semantic html is impt (i.e. <h1>title</h1> more easily read by robots <span class=’header’>title</span>);

o make text in href tags meaningful (i.e. not ‘click here’)

o <meta>: keywords is unused, description is used for snippet

o fqdn is important (i.e. x.y.com is higher than y.com/x) since rank is aggregated at fqdn, not at folder level (although google does consider folders)

o use RIA for spice within html pages, or at the very least have distinct urls for desired search rankings

- google is only search engine of the big 3 (google/yahoo/live) that will follow javascript redirects; need to use 301 redirect if you care

- yahoo site explorer (http://siteexplorer.search.yahoo.com) : see inbound links to your page

- prefer hyphen to underscores, since search engines are likely to interpret “_” as part of word, not as word delineators

- casing matters (since 50% of since run apache). links and links distributed to other sites need to always use the same case.

- link to the root, not to a specific page (i.e. http://www.blogger.com/exchweb/bin/redir.asp?URL=http://y.com/items not http://y.com/items/default.aspx).

- http codes

o 304 – conditional get (useful for letting engines know your page hasn’t changed to save you bandwidth)

o 503 – down for maintenance

o 302 – temporary redirect (BAD! since “temporary” is ambiguous; always use 301)

- links and tools

o http://seomoz.org/

o blogs.msdn.com/webmaster

o upcoming adcenter – msft competitor to google analytics



Silverlight 2.0, Mike Harsh, Program Manager, MSFT

- support for almost everything in WPF, plus some new controls

o MutliScaleImage – tool recursively splices a large image into 256x256 tiles, and the MutliScaleImage control automatically renders necessary tiles based on where on image the user zooms

- UserControl support!

- Local storage

o up to 1MB; if you want more, user must approve via prompt

o cross-browser “cookies on steroids”

- HTML DOM Interop

o make calls into the DOM and call javascript functions like alert()

o add items to browser’s history list

- OpenFileDialog support


Thursday, February 28, 2008

set @variable then select @variable = no rows

here’s a strange one that came up the other day. if you have the following sql statement:

DECLARE @AppId INT

SET @AppId = 1

SELECT @AppId = 2

WHERE 0 = 1 --i.e. a condition that forces an empty recordset to be returned

SELECT @AppId

since @AppId was selected from a table with no rows (WHERE 0 = 1), i was expecting it to be NULL after this executes… but it turns out that the variable maintains its value from the SET call , so it’s 1. just an unexpected side effect to be aware of--

Tuesday, February 26, 2008

web service response caching: CacheDuration property doesn't work

if you want to cache the results from a web service call on the server, you’d think you just set the CacheDuration property the way you can with aspx pages (i.e. [WebMethod(CacheDuration=5)] and everything would be happy. turns out... no.
  • web service output is NEVER cached for http-post web service requests, so you have to use http-get, which is disabled by default for web services and enabling it is not a recommended practice. great.
  • browsers can stipulate “no-cache” in their request header, and the server will ignore its own cache for these requests. so even once you get http-get caching working, Firefox requests force the server to ignore its cache.

why this isn’t configurable on the server, i have no idea, but the short of it is: if you want real control over web service output caching, don’t use CacheDuration (i.e. write it yourself using Context.Cache, Application[] variables, or static variables). Context.Cache is a very cool class; you can specify an absolute expiration date, or dependencies on the cached content, so that the item is cleared from the cache when a dependency changes, or a sliding expiration date, like “5 seconds from last access”—

Thursday, February 21, 2008

beware of flash

sorry for the rant, but a couple of things came up today that wasted hours of time for me and are worth filing away in your flash development mental compartment.

adobe releases new flash builds frequently:

you can see what version of flash you have installed here: http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_15507

so what, you say? well, i mention this because i encountered a nasty bug with specific versions of flash 9 and ExternalCallback today. ExternalCallback is the interface that makes it easy to call from javascript into a flash movie. the bug is this: in IE, when a flash movie is inside a <form> tag, a cached flash movie is not properly re-loaded when it is pulled from the cache, and ExternalCallback just stops working. the bug was present or introduced at some point up to 9.0.44.0, but fixed by 9.0.115.0 (so i didn’t see it at first).

the fix (hack, really), is to append a unique querystring value to the .swf filename, so that IE never pulls the movie from the cache. this was acceptable in my case (an 8kb flash movie), but obviously wouldn’t have worked if the movie was larger.

another bug that was present in earlier versions of flash 9 caused IE to crash when a flash object was unloaded as the browser quit. the fix for that is just as nasty; get a load of this:



function cleanUpCounterObjects()
{
var objects = document.getElementsByTagName("object");

for (var i=0; i < objects.length; i++)
{
for (var x in objects[i])
{
if (typeof objects[i][x] == 'function')
objects[i][x] = null;
}
}
}

beforeUnloadCounterObjects = function()
{
//fix for memory bug with flash 9 and IE
__flash_unloadHandler = function(){};
__flash_savedUnloadHandler = function(){};

window.attachEvent( "onunload", cleanUpCounterObjects );
}

if (window.attachEvent)
window.attachEvent( "onbeforeunload", beforeUnloadCounterObjects);

i get it that cross-browser, cross-platform is hard to do, but doing flash development right is almost impossible with adobe’s versioning strategy. seems to me that side-by-side installations would solve all of these problems; developers could say “flash 9.0.45.0 only”, and when users update to 9.0.115.0, they actually just install it side-by-side with 9.0.45.0, so apps released for 9.0.45.0 continue to run in 9.0.45.0! or am i missing something?

again, just make sure you do as much testing as possible in different configurations. you can download older flash versions from adobe here:

http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_14266&sliceId=1

and the flash uninstaller is here:

http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_14157

good luck…

remember IE6

kind of goes along with the 1024x768 note: IE6 still accounts for 40% of our IE visits, and IE as a whole accounts for 80% of site visits. this means about 1 in 3 of all site visitors see what we do using IE6. this # is declining, but it’s still very much in play for now, so remember to check your work in IE6 in addition to IE7/Firefox. unfortunately, there are subtle differences in the IE rendering engines, and some things that work fine in IE7 just don’t in IE6.

when Firefox 3 comes out (fairly soon), we’ll have yet another browser to add to the make-sure-you-test-on list; ahhh, web development J

Wednesday, February 20, 2008

when doing GUI work, run your second monitor at 1024x768

remember that most of the world sees things very differently than we do, so make sure you frequently check your GUI work on a 1024x768 display; it’s still half of users. recent stats from our site:

1

1024x768

47.56%

2

1280x800

16.57%

3

1280x1024

10.05%

4

800x600

8.81%

5

1440x900

5.05%

Thursday, February 14, 2008

beware a Deleted flag on tables

some of our tables have a 'Deleted' flag, and a row cannot be considered to really be in a table unless the Deleted flag = 0. this means your joins will always have something like "t1.Id = t2.Id AND t1.Deleted = 0".

the design goal here was to keep some historical record of changes without the hassle of a history table, but it was really a bad idea since it’s way too easy to forget about when writing queries, and all indexes are irrelevant unless they start with the Deleted bit...

Thursday, February 7, 2008

vista shadow copies just saved me

vista keeps shadow copies of files and folders when it creates its daily restore points. so if you delete something accidentally, right-click on the folder and go to the “Previous Versions” tab; you can browse through the contents of the folder as it existed at the last restore point. there’s no fancy gui and you only get the last 2 restore points, but it got the job done--

Monday, February 4, 2008

chaining the C# ?? Operator

?? operator was news to me, Rick Stahl's blog is great: http://www.west-wind.com/weblog/posts/236298.aspx. snippet:

string value1 = null;
string value2 = "Test1";
string result = value1 != null ? value1 : value2;

which causes result containing Test1 or the second value.

In C# you can shortcut this special null comparison case with the new ??:

string result = value1 ?? value2;

Friday, January 25, 2008

debug .net library code

msft released the code to a whole bunch of .net libraries last week, Shawn Burke details the steps to take to see it:

http://blogs.msdn.com/sburke/archive/2008/01/16/configuring-visual-studio-to-debug-net-framework-source-code.aspx