Wednesday 29 July 2009

User Control Snap Lines in Visual Studio (Part 2)

The earlier code does indeed only work when the contained label is at position (0,0). Here is a fix that offsets the position of the snap lines based on the label position.
public override IList SnapLines
{
get
{
if (labelDesigner == null)
{
return base.SnapLines;
}
return labelDesigner.SnapLines.Cast<SnapLine>().Select(sn => BuildSnapLine(sn)).ToList();
}
}

private SnapLine BuildSnapLine(SnapLine source)
{
return new SnapLine(source.SnapLineType, BuildOffset(source.Offset, source.IsHorizontal),
source.Filter, source.Priority);
}

private int BuildOffset(int offset, bool isHorizontal)
{
return offset + (isHorizontal ? labelDesigner.Control.Top : labelDesigner.Control.Left);
}

User Control Snap Lines in Visual Studio

When creating a user control in .Net it is often hard to line your new control up with the other controls on the form. This is because the new control with use the default control designer to tell Visual Studio about its layout properties. Where I work we have a number of user controls in which are placed Labels or LinkLabels. It would be very handy if we could override the designer behaviour so that all alignment was done based on the properties of the label contained within the control.

To this end I created a new ControlDesigner that creates a local LabelDesigner and passes its snap lines out to Visual Studio. My experiment works if the label contained in the user control is at position (0,0). Some work changes might be necessary to make this work if the label has a different position.
using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace WrappedLabel
{
public interface ICustomSnapLineControl
{
Label GetSnapLineLabel();
}

public class CustomSnapLineControlDesigner : ControlDesigner
{
private ControlDesigner labelDesigner;

public override void Initialize(IComponent component)
{
base.Initialize(component);
if (component is ICustomSnapLineControl)
{
var designAssembly = Assembly.Load("System.Design");
var type = designAssembly.GetType("System.Windows.Forms.Design.LabelDesigner");
labelDesigner = (ControlDesigner)Activator.CreateInstance(type);
labelDesigner.Initialize(((ICustomSnapLineControl)component).GetSnapLineLabel());
}
}

public override IList SnapLines
{
get
{
return labelDesigner != null ? labelDesigner.SnapLines : base.SnapLines;
}
}

}
}
To implement this feature add the Designer attribute to your user control and implement the ICustomSnapLineControl interface. Here is a simple example.
using System.ComponentModel;
using System.Windows.Forms;

namespace WrappedLabel
{
[Designer(typeof(CustomSnapLineControlDesigner))]
public partial class NewLabel : UserControl, ICustomSnapLineControl
{
public NewLabel()
{
InitializeComponent();
}

public override string Text
{
get
{
return label1.Text;
}
set
{
label1.Text = value;
}
}

public Label GetSnapLineLabel()
{
return label1;
}
}
}

Tuesday 21 July 2009

Excel Interop Bug

There is a bug in the Excel Interop libraries (at least in version 2003, I'm not sure about other versions) that means that when an Excel Application object is no longer referenced it's process is not ended. The Excel process will remain running on the host PC even after the application that created the COM object has ended too.

This can be a particular issue in a web application where a page might process data in an uploaded Excel file or dynamically produce an Excel file for download. Every time that page is requested a new process is started and the old processes quickly bog down the PC.

To kill a process is easy enough, just use the Process.Kill() method. However in order to kill the correct process you have to be able to find it. Enter the GetWindowThreadProcessId(IntPtr, int) Windows function.

To demonstrate its use here is a quick sample application
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace ConsoleApplication4
{
class Program
{
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

static void Main(string[] args)
{
var app = new Microsoft.Office.Interop.Excel.Application();
if (app.Workbooks.Count == 0)
{
app.Workbooks.Add(Type.Missing);
}
var book = (Workbook)app.Workbooks[1];
var sheet = (Worksheet)book.Worksheets[1];
var cells = (Range)sheet.Cells[1, 1];
cells.Value2 = "Hello Dave";
book.SaveAs("C:\\Users\\David Brunger\\Desktop\\Test.xlsx", Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, XlSaveAsAccessMode.xlNoChange, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
cells = null;
book.Close(false, Type.Missing, Type.Missing);
book = null;
var appid = app.Hwnd;
app = null;
int excelProcessId;
GetWindowThreadProcessId(new IntPtr(appid), out excelProcessId);
var excelProcess = Process.GetProcessById(excelProcessId);
excelProcess.Kill();
}
}
}
It might be adding a few checks around the GetWindowThreadProcessId function call, so that you don't try and kill the wrong or a non-existent process.

Friday 10 July 2009

Window Mobile Accelerometer Apps

My wife has a HTC Diamond mobile phone and I recently stumbled across this article, http://blog.enterprisemobile.com/2008/07/using-htc-diamonds-sensor-sdk-from-managed-code/, so I thought I'd give it a go myself.

The first thing I want to try, after checking it works, is the ubiquitous spirit level application, then who knows what, an egg timer, one of those things that makes a silly cow noise when you turn it over. The sky's the limit.

Thursday 9 July 2009

Get the Current Revision Number from Subversion over HTTP

Using code similar to that in the last article I found I could get the current revision number just change the code to match the following two lines:
request.Method = "PROPFIND";
and
var requestbody = "<D:propfind xmlns:D=\"DAV:\"><D:allprop/></D:propfind>";
The current revision number is buried somewhere in the response.

Getting more information about each revision is pretty simple too.
var requestbody = "<S:log-report xmlns:S=\"svn:\"><S:start-revision>2</S:start-revision>" +
"<S:end-revision>2</S:end-revision><S:discover-changed-paths/></S:log-report>";

Wednesday 8 July 2009

Subversion Log Queries

I want to show off how good Subversion is as a source control system, and one of it's strengths is the vast amount of information you can get about the code that is in your repository. At a previous employer we had a very cool set of old style ASP pages that tied our source repository to our timesheet system to make some extremely useful reports. To make a start at trying to recreate these reports in subversion, I needed to be able to query the subversion logs.

Rather than relying on having the svn command line client installed, or using SharpSVN which I found a bit unsatisfactory for querying logs I thought I'd have a bash at writing a standalone HTTP client in .Net. After a bit of searching for some documentation, I came up with the following fairly simple code:
static void Main(string[] args)
{
var sandpiperUri = new Uri(@"https://sandpiper.svn.sourceforge.net/svnroot/sandpiper");
var request = WebRequest.Create(sandpiperUri + "/!svn/vcc/default");
request.Method = "REPORT";
request.ContentType = "text/xml";
var requestbody = "<S:log-report xmlns:S=\"svn:\"><S:start-revision>2</S:start-revision>" +
"<S:end-revision>2</S:end-revision></S:log-report>";
request.ContentLength = requestbody.Length;
using (var requestStream = request.GetRequestStream())
{
using (var writer = new StreamWriter(requestStream))
{
writer.Write(requestbody);
}
}
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
Console.WriteLine(reader.ReadToEnd());
}
}
}
Console.ReadLine();
}
This code queries the log of my rarely updated sourceforge project and produces the following results:
<?xml version="1.0" encoding="utf-8"?>
<S:log-report xmlns:S="svn:" xmlns:D="DAV:">
<S:log-item>
<D:version-name>2</D:version-name>
<D:comment>Initial Commit of Pre-Alpha Code</D:comment>
<D:creator-displayname>davebrunger</D:creator-displayname>
<S:date>2008-02-02T10:08:17.918376Z</S:date>
</S:log-item>
</S:log-report>
Now all I have to do is parse the ouput and create a nice interface to call the code. Oh yes, and implement some more of the subversion report features.

For information on the subversion http interface see this file: http://svn.collab.net/repos/svn/trunk/notes/webdav-protocol.

Thursday 2 July 2009

More Basic C++ Resources

I've also found this good introduction to C++ with exercises from the nice people at Imperial College, London. http://www.doc.ic.ac.uk/~wjk/C++Intro/CourseStructure.html