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.