<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Theo Gray</title>
<link>http://www.theogray.com/blog/</link>
<description></description>
<copyright>Copyright 2012, Theo Gray</copyright>
<generator>Blog Admin v2.1</generator>
<ttl>60</ttl>
<lastBuildDate>Wed, 01 Feb 2012 14:37:38 GMT</lastBuildDate>
<pubDate>Wed, 01 Feb 2012 14:37:38 GMT</pubDate>
<language>en-us</language>
<atom:link href="http://www.theogray.com/blog/feed.xml" rel="self" type="application/rss+xml" />
<image>
  <title>Theo Gray</title>
  <url>http://www.theogray.com/i/tg150x50.gif</url>
  <link>http://www.theogray.com/blog/</link>
</image>
<item>
  <title>Error loading TS Gateway Manager</title>
  <link>http://www.theogray.com/blog/2012/02/error-loading-ts-gateway-manager</link>
  <description><![CDATA[<p><img class="inline_right noborder" title="Remote Desktop Services" src="http://www.theogray.com/blog/images/rds.jpg" alt="Remote Desktop Services" width="110" height="105" align="right" /><em>Another random problem which luckily I found a <a rel="nofollow" href="http://www.microsoft-questions.com/microsoft/Windows-Terminaldienste/32347391/problem-mit-ts-gateway.aspx" target="_blank">German article</a> about.</em></p>
<h3>Problem</h3>
<p>When opening <strong>TS Gateway Manager</strong>, you get the following unhandled exception appear:</p>
<pre class="brush:js;light:true;">Unhandled Exception in Managed Code Snap-in
FX:{4a5cf35e-7944-4869-9407-de1cbe40a268}
Object reference not set to an instance of an object.
ExceptionType:
   System.NullReferenceException
Exception Stack Trace:
   at Microsoft.TerminalServices.Proxy.SnapIn.BackEnd.WmiTerminalServicesGatewayStore.AddServer(String serverName, Boolean getData)
   at Microsoft.TerminalServices.Proxy.SnapIn.TsProxyServer.ConnectToServer(IProgressDisplay progress)
   at Microsoft.TerminalServices.Proxy.SnapIn.SnapInStartPage.SnapInStartPageNode.ConnectToServer(String serverName, Boolean silent)
   at Microsoft.TerminalServices.Proxy.SnapIn.TSProxySnapIn.OnLoadCustomData(AsyncStatus status, Byte[] persistenceData)
   at Microsoft.ManagementConsole.SnapIn.ProcessRequest(Request request)
   at Microsoft.ManagementConsole.Internal.SnapInClient.Microsoft.ManagementConsole.Internal.IMessageClient.ProcessRequest(Request request)
   at Microsoft.ManagementConsole.Internal.IMessageClient.ProcessRequest(Request request)
   at Microsoft.ManagementConsole.Executive.RequestStatus.BeginRequest(IMessageClient messageClient, RequestInfo requestInfo)
   at Microsoft.ManagementConsole.Executive.SnapInRequestOperation.ProcessRequest()
   at Microsoft.ManagementConsole.Executive.Operation.OnThreadTransfer(SimpleOperationCallback callback)</pre>
<h3>Solution</h3>
<p>Make sure you have a website called "Default Web Site" in IIS...&nbsp;It doesn't matter what you do with any other properties of the website, just make sure the name on the IIS tree is "Default Web Site"!</p>
<p>You would have thought this sort of exception should be picked up with a nice friendly error message, but there you go.</p>]]></description>
  <pubDate>Wed, 01 Feb 2012 14:27:32 GMT</pubDate>
  <category>Windows Server</category>
  <category>Remote Desktop Services</category>
  <category>Terminal Services</category>
  <category>TS Gateway</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2012/02/error-loading-ts-gateway-manager</guid>
  <comments>http://www.theogray.com/blog/2012/02/error-loading-ts-gateway-manager#readercomments</comments>
</item>
<item>
  <title>Christmas Puzzle 2011 - Snakes and ladders</title>
  <link>http://www.theogray.com/blog/2011/12/christmas-puzzle-2011-snakes-ladders</link>
  <description><![CDATA[<p><a href="http://www.christmaspuzzle.co.uk/"><img class="inline_right noborder" src="http://www.eatonbray.com/news/images/christmas-puzzle.gif" alt="Christmas Puzzle" hspace="12" width="200" align="right" /></a>This year's annual Charity <a href="http://www.christmaspuzzle.co.uk/">Christmas Puzzle</a> has now been posted, and is available to download.</p>
<p>This, the 21st annual Puzzle that Gordon Gray has set, has a theme designed to help completion and will appeal to people who enjoy doing Crossword puzzles, such as in the Daily Telegraph (though it is not a Crossword puzzle). Puzzlers have plenty of time to find the answers and Puzzles should be returned, with donation, by 22nd January 2012.</p>
<p><strong>Four winners will each receive a &pound;30 prize.<br />Everyone who enters will qualify for a &pound;30 prize draw.</strong></p>
<p>So what are you waiting for, give it a go and download the <a href="http://www.christmaspuzzle.co.uk/">Christmas Puzzle</a> now.</p>]]></description>
  <pubDate>Tue, 06 Dec 2011 00:00:00 GMT</pubDate>
  <category>Christmas Puzzle</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2011/12/christmas-puzzle-2011-snakes-ladders</guid>
  <comments>http://www.theogray.com/blog/2011/12/christmas-puzzle-2011-snakes-ladders#readercomments</comments>
</item>
<item>
  <title>Slow Login to Windows Server 2008 R2 Remote Desktop</title>
  <link>http://www.theogray.com/blog/2011/08/slow-login-to-windows-server-2008-r2-remote-desktop</link>
  <description><![CDATA[<p><img class="inline_right noborder" title="Remote Desktop Services" src="http://www.theogray.com/blog/images/rds.jpg" alt="Remote Desktop Services" width="275" height="262" align="right" /><em>Having spent 5 weeks with Microsoft technicians trying to work this one out, and seeing plenty of unsolved forum posts on this topic, it seemed worth sharing as the final solution was fairly basic.</em></p>
<h3>Problem</h3>
<p>While logging into Terminal/Remote Desktop Services (TS/RDS) on Windows Server 2008 R2, both the "Securing remote connection..." and "Applying User Settings..." phases take a very long time (45 to 90 seconds in some cases) to complete.</p>
<h3>Solution</h3>
<p>Ensure that the IPv6 address assigned to the 6TO4 Tunnel (as well as the IPv4 address) of the Terminal Server is allowed on the domain controller for the following:</p>
<ul>
<li>All <em>Active Directory</em>&nbsp;rules</li>
<li>All <em>Kerberos</em>&nbsp;rules</li>
<li><em>Core Networking - IPv6 (IPv6-In)</em> for just the IPv4 address.</li>
</ul>
<p>&nbsp;</p>
<hr class="required" />
<p>During the case with Microsoft, many logs were taken from various machines, but it was the <a href="http://www.microsoft.com/download/en/details.aspx?displaylang=en&amp;id=4865" target="_blank">Netmon</a> traces that showed that multiple Kerberos packets were being sent by the TS server and not being acknowledged by the DC.</p>
<p>Checking the firewall logs for dropped packets on the domain controller showed that the Terminal Server was trying to connect to port 88 (Kerberos) using&nbsp;protocol 41 (used by the 6TO4 tunnel) from the IPv4 address and having the packets dropped. Once that had been opened, further packets from the 6TO4 tunnel IPv6 address were then being dropped for LDAP requests.</p>
<p>This was on Server 2008 R2, but there's no reason to think this wouldn't also solve similar issues on previous versions of Windows Server.</p>]]></description>
  <pubDate>Tue, 23 Aug 2011 09:15:00 GMT</pubDate>
  <category>Windows Server</category>
  <category>Remote Desktop Services</category>
  <category>Terminal Services</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2011/08/slow-login-to-windows-server-2008-r2-remote-desktop</guid>
  <comments>http://www.theogray.com/blog/2011/08/slow-login-to-windows-server-2008-r2-remote-desktop#readercomments</comments>
</item>
<item>
  <title>Christmas Puzzle 2010 - Winnie-the-Pooh</title>
  <link>http://www.theogray.com/blog/2010/12/christmas-puzzle-2010-winnie-the-pooh</link>
  <description><![CDATA[<p><a href="http://www.christmaspuzzle.co.uk/"><img class="inline_right noborder" src="http://www.eatonbray.com/news/images/christmas-puzzle.gif" alt="Christmas Puzzle" hspace="12" width="200" align="right" /></a>This year's annual Charity <a href="http://www.christmaspuzzle.co.uk/">Christmas Puzzle</a> has now been posted, and is available to download.</p>
<p>This, the 20th annual Puzzle that Gordon Gray has set, has a theme designed to help completion and will appeal to people who enjoy doing Crossword puzzles, such as in the Daily Telegraph (though it is not a Crossword puzzle). Puzzlers have plenty of time to find the answers and Puzzles should be returned, with donation, by 23rd January 2011.</p>
<p><strong>Four winners will each receive a &pound;30 prize.<br />Everyone who enters will qualify for a &pound;30 prize draw.</strong></p>
<p>So what are you waiting for, give it a go and download the <a href="http://www.christmaspuzzle.co.uk/">Christmas Puzzle</a> now.</p>]]></description>
  <pubDate>Wed, 08 Dec 2010 00:00:00 GMT</pubDate>
  <category>Christmas Puzzle</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2010/12/christmas-puzzle-2010-winnie-the-pooh</guid>
  <comments>http://www.theogray.com/blog/2010/12/christmas-puzzle-2010-winnie-the-pooh#readercomments</comments>
</item>
<item>
  <title>Telling Computers and Humans Apart in web forms without visible CAPTCHAs</title>
  <link>http://www.theogray.com/blog/2010/10/telling-computers-humans-apart-in-web-forms-without-captha</link>
  <description><![CDATA[<p>Many websites use <a href="http://en.wikipedia.org/wiki/CAPTCHA">CAPTCHAs</a> of one form or another to try and tell the difference between Humans and Computers interacting with web forms. As the <a href="http://www.bbc.co.uk/blogs/bbcinternet/2010/10/captcha_and_bbc_id.html" target="_blank">BBC have recently commented</a>, these can be cumbersome to implement and also annoying to users.</p>
<p>Automated ways to distinguish humans and computer bots when submitting data via a web page are relatively easy once you figure out the ways each interact with web servers.</p>
<p>The basic thing that almost all spambots have in common is that they</p>
<ol type="a">
  <li>Don't support session cookies,</li>
  <li>Don't run Javascript code, and</li>
  <li>Try to submit hyperlinks in one form or another</li>
</ol>
<p>Unfortunately, to implement a website form that requires both session cookies and Javascript to submit correctly requires a little more code than a basic &lt;form&gt; tag, but it shouldn't be difficult to make this code generic and manipulate the browser DOM to make the developer implementation very simple. On top of that, requiring moderation for anything that includes a hyperlink means that even the human spammers don't get through.</p>
<p>After very <a href="http://www.theogray.com/blog/2007/10/woo-ajax">close to three years</a> of running such a system on various websites that I look after, it is still yet to be cracked by any automated bots and also has not had any false-positives (apart from requiring moderation for a very small number of posts where people are submitting legitimate URLs). The only human (non-automated) spammer that has made it through to be moderated is from one IP address in Russia that regularly tries to include a link to a fake finance blog, so again something that's very easy to block with the right back end in place.</p>]]></description>
  <pubDate>Wed, 06 Oct 2010 15:39:20 GMT</pubDate>
  <category>Computers</category>
  <category>Websites</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2010/10/telling-computers-humans-apart-in-web-forms-without-captha</guid>
  <comments>http://www.theogray.com/blog/2010/10/telling-computers-humans-apart-in-web-forms-without-captha#readercomments</comments>
</item>
<item>
  <title>The International Routing System scam</title>
  <link>http://www.theogray.com/blog/2010/07/international-routing-system</link>
  <description><![CDATA[<p>I've just had a call from a charming Indian gentlemen telling me he was from the <strong>International Routing System</strong> and that my computer was generating a lot of traffic which was disrupting the "International Routing Systems".</p>
<p>Apparently he had my phone number because my IP address was creating all this traffic and so he wanted to show me how to cleanup my machine. I thought it could be fun to find out how he was going to try to scam me, so let him continue...</p>
<ul>
<li>Me: "So what's my IP address?"</li>
<li>IG: "I don't have that in front of me at the moment"</li>
<li>Me: "riigggt... so what would you like me to do to stop all this traffic blocking up the International Routings Systems?"</li>
<li>IG: "Please press the flag key and R on your keyboard"</li>
<li>Me: "OK"</li>
<li>IG: "type the letters 'i', 'n', 'f' - it stands for 'infection'"</li>
<li>Me: "OK, I've done that"</li>
<li>IG: "Now click OK"</li>
<li>Me: "Uh huh"</li>
<li>IG: "What do you see?"</li>
<li>Me: "A list of all the drivers installed on my computer"</li>
<li>IG: "You're wasting my time and your time, now bugger off"... beeeeeeeeeeeep</li>
</ul>
<p>Unfortunately caller ID only showed he was calling from an "International" number so I couldn't call him back to find out more about his wonderful International Routing System or what other things he was going to get me to do to my computer.</p>
<p>The <strong>International Routing System</strong> does not exist, and if you receive a call they will try to convince you to install virus-infected software, and probably to give them your credit card details. Either put the phone down immediately, or else laugh at every statement they make as it is pure fiction.</p>
<hr class="required" />
<p><em>Anyone worried that they have already let these people do something to your computer should run <a href="http://www.f-secure.com/en_EMEA/security/tools/online-scanner/" target="_blank">F-Secure's free online scanner</a> and if you don't already have any antivirus software installed on your machine, you should get something like <a href="http://www.microsoft.com/security_essentials/" target="_blank">Microsoft Security Essentials</a> which is a free antivirus package directly from Microsoft.</em></p>]]></description>
  <pubDate>Mon, 26 Jul 2010 12:17:53 GMT</pubDate>
  <category>Telephone Scams</category>
  <category>Internet Scams</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2010/07/international-routing-system</guid>
  <comments>http://www.theogray.com/blog/2010/07/international-routing-system#readercomments</comments>
</item>
<item>
  <title>The Secret Diary of Michael Schumacher... returns</title>
  <link>http://www.theogray.com/blog/2010/01/secret-diary-of-michael-schumacher-returns</link>
  <description><![CDATA[<p><img src="http://www.theogray.com/formula-1/images/part2010-0120.jpg" width="240" height="180" alt="Michael Schumacher" align="right" class="inline_right" />With the 2010 return of <a href="http://www.theogray.com/formula-1/schumacher-secret-diary.asp">The Secret Diary of Michael Schumacher</a> on Planet-F1, I've just added another year's worth of old entries back onto the interweb for your reading pleasure.</p>
<p>Read all his entries from 2002 to 2004, plus links to the latest entries in <a href="http://www.theogray.com/formula-1/schumacher-secret-diary.asp">The Secret Diary of Michael Schumacher</a>.</p>]]></description>
  <pubDate>Thu, 21 Jan 2010 00:00:00 GMT</pubDate>
  <category>Formula 1</category>
  <category>Michael Schumacher</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2010/01/secret-diary-of-michael-schumacher-returns</guid>
  <comments>http://www.theogray.com/blog/2010/01/secret-diary-of-michael-schumacher-returns#readercomments</comments>
</item>
<item>
  <title>Manual tagging on Facebook</title>
  <link>http://www.theogray.com/blog/2010/01/manual-tagging-on-facebook</link>
  <description><![CDATA[<p>Tagging of people in the text of status updates has been available on Facebook for a while now, but it still isn't available everywhere, such as comments and photo descriptions.</p>
<p>Luckily if you really want to tag someone in text in some of these other places (I've only made it work in photo descriptions so far) then you can do so. In addition you can manually tag people within status updates giving them whatever name you want.</p>
<p>All you have to do is include the following format in your text and Facebook will automatically display tag links:</p>
<pre class="brush:vb;light:true;">
Tagging @[USERID:blah] in a comment
</pre>
<p>Where <tt>USERID</tt> is the numeric Facebook ID of the user you want to tag, and <tt>blah</tt> can be any string.</p>
<p>So, for example:<br />
<tt>Tagging @[721981740:Theo] in a comment</tt></p>
<p>is displayed in Facebook as:<br />
<tt>Tagging <a href="http://www.facebook.com/profile.php?id=721981740">Theo</a> in a comment</tt></p>]]></description>
  <pubDate>Sat, 02 Jan 2010 10:42:22 GMT</pubDate>
  <category>Facebook</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2010/01/manual-tagging-on-facebook</guid>
  <comments>http://www.theogray.com/blog/2010/01/manual-tagging-on-facebook#readercomments</comments>
</item>
<item>
  <title>The Our Domain Set</title>
  <link>http://www.theogray.com/blog/2009/12/our-domain-set</link>
  <description><![CDATA[<p>It has to be said that over the years I have purchased quite a few domain names, but there was one that I have wanted for a while to add to the set of OurCarnival.co.uk, OurChurch.co.uk and OurPanto.co.uk - that being OurVillage.co.uk.</p>
<p>And yesterday I managed to buy it off the current owner, so finally the Eaton Bray website is available via <a href="http://www.ourvillage.co.uk/">www.ourvillage.co.uk</a>.</p>
<p>And if you hadn't heard yet, the <a href="http://www.christmaspuzzle.co.uk/">Christmas Puzzle</a> is now available to download!</p>]]></description>
  <pubDate>Tue, 08 Dec 2009 11:08:27 GMT</pubDate>
  <category>Eaton Bray</category>
  <category>Domain Names</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2009/12/our-domain-set</guid>
  <comments>http://www.theogray.com/blog/2009/12/our-domain-set#readercomments</comments>
</item>
<item>
  <title>Christmas Puzzle 2009 - Time gentlemen please</title>
  <link>http://www.theogray.com/blog/2009/12/christmas-puzzle-2009-time-gentlemen-please</link>
  <description><![CDATA[<p><a href="http://www.christmaspuzzle.co.uk/"><img alt="Christmas Puzzle" hspace=12 src="http://www.eatonbray.com/news/images/christmas-puzzle.gif" width="200" align="right" class="inline_right noborder" /></a>This year's annual Charity <a href="http://www.christmaspuzzle.co.uk/">Christmas Puzzle</a> has now been posted, and is available to download. 
<p>This, the 19th annual Puzzle that Gordon Gray has set, has a theme designed to help completion and will appeal to people who enjoy doing Crossword puzzles, such as in the Daily Telegraph (though it is not a Crossword puzzle). Puzzlers have plenty of time to find the answers and Puzzles should be returned, with donation, by 24 January. 
<p align="center"><b>Four winners will each receive a &pound;25 prize.<br />Everyone who enters will qualify for a &pound;25 prize draw.</b> 
<p>So what are you waiting for, give it a go and download the <a href="http://www.christmaspuzzle.co.uk/">Christmas Puzzle</a> now.</p>]]></description>
  <pubDate>Tue, 08 Dec 2009 11:01:22 GMT</pubDate>
  <category>Hobbies</category>
  <category>Christmas</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2009/12/christmas-puzzle-2009-time-gentlemen-please</guid>
  <comments>http://www.theogray.com/blog/2009/12/christmas-puzzle-2009-time-gentlemen-please#readercomments</comments>
</item>
<item>
  <title>COMException Class not registered on 64-bit Windows</title>
  <link>http://www.theogray.com/blog/2009/10/comexception-regdbeclassnotreg-on-64-bit-windows</link>
  <description><![CDATA[<p>Having finally got myself running live on Windows 7, this morning it was time to fix the one problem remaining - <b>Visual Studio 2008 throwing the COMException "Class not registered"</b> (REGDB_E_CLASSNOTREG) when trying to call InitializeComponent on a form which has an ActiveX control on it:</p>
<pre class="brush:js;light:true;">
System.Runtime.InteropServices.COMException (0x80040154): Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))
   at System.Windows.Forms.UnsafeNativeMethods.CoCreateInstance(Guid&amp; clsid, Object punkOuter, Int32 context, Guid&amp; iid)
   at System.Windows.Forms.AxHost.CreateWithoutLicense(Guid clsid)
   at System.Windows.Forms.AxHost.CreateWithLicense(String license, Guid clsid)
   at System.Windows.Forms.AxHost.CreateInstanceCore(Guid clsid)
   at System.Windows.Forms.AxHost.CreateInstance()
   at System.Windows.Forms.AxHost.GetOcxCreate()
   at System.Windows.Forms.AxHost.TransitionUpTo(Int32 state)
   at System.Windows.Forms.AxHost.CreateHandle()
   at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
   at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
   at System.Windows.Forms.AxHost.EndInit()
   at OCXWrappers.TXTextControl.InitializeComponent() in C:\Users\...\Form1.Designer.vb:line 42
</pre>
<p>Hunting around Google turned up nothing (the OCX was definately registered correctly), but I did twig after a few minutes... The ActiveX control in question was 32-bit, whereas Visual Studio defaults to compile to "Any CPU" and I was now running 64-bit Windows.</p>
<p><b>Solution</b>: Go into the Project Properties, Compile tab and click "Advanced Compile Options...". Change "Target CPU" to x86, click OK, save and try again.</p>
<p>Presumably this is the same problem as others have had when trying to run on Windows XP / Server 2003 and above when the customer is running 64-bit and the development environment is 32-bit.</p>]]></description>
  <pubDate>Mon, 05 Oct 2009 10:53:02 GMT</pubDate>
  <category>VB</category>
  <category>Net</category>
  <category>Microsoft</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2009/10/comexception-regdbeclassnotreg-on-64-bit-windows</guid>
  <comments>http://www.theogray.com/blog/2009/10/comexception-regdbeclassnotreg-on-64-bit-windows#readercomments</comments>
</item>
<item>
  <title>Custom DataGridViewColumn &#38; IDataGridViewEditingControl classes</title>
  <link>http://www.theogray.com/blog/2009/06/custom-datagridviewcolumn-idatagridvieweditingcont</link>
  <description><![CDATA[<p>Having just spent the last few hours getting my head round how custom <tt>DataGridView</tt> column code fitted together in VB.Net I thought it only fair to share in the hope that I can speed up the process for others.</p>
<h3>The setup</h3>
<p>In my case I was looking to use a custom usercontrol in an editable grid; a custom usercontrol that extends the standard <tt>ComboBox</tt> in all sorts of ways (and is already written &amp; tested).</p>
<p>Unlike examples such as the <a href="http://msdn.microsoft.com/en-us/library/7tas5c80.aspx" target="_blank">DateTimePicker column</a> and others such as the Colour Pickers on CodeProject etc, I was looking to both display a plain text value in the cell when not in edit mode and also to store/load a completely different numeric lookup value within the bound data. <a href="#_note1">[1]</a></p>
<h3>Useful points</h3>
<p>A few points that I would have found useful to know before starting are:</p>
<ul>
  <li><b>What is <tt>FormattedValue</tt>?</b><br />
  It appears that <tt>FormattedValue</tt> is the information stored within the bound data which is also referred to as just <tt>Value</tt> in other places. <a href="#_note2">[2]</a></li>
  <li><b>At what point should I replace standard columns with custom ones?</b><br />
  Use the <tt>DataGridView.DataSourceChanged</tt> event. <a href="#_note3">[3]</a></li>
  <li><b>How many instances of each control will be created?</b><br />
    The creation of the <tt>Cell</tt> and <tt>EditingControl</tt> control instances is handled by the <tt>DataGridView</tt> control and is out of the developer's hands. This gives you:<ul>
    <li>One <tt>ComboBoxColumn</tt> instance per column.</li>
    <li>One <tt>ComboBoxCell</tt> instance per cell.</li>
    <li>One <tt>ComboBoxEditingControl</tt> instance <b>PER GRID</b>.<br /> This last one is very important to recognise as it affects decisions later.</li>
  </ul></li>
  <li><b>How does the <tt>DataGridView</tt> know which <tt>Cell</tt> and <tt>EditingControl</tt> classes to instantiate?</b><ul>
  <li>[line 46 below]: The <tt>CellTemplate</tt> property of the custom <tt>Column</tt> class defines the custom <tt>Cell</tt> class to use. This can be specified in the <tt>DataGridViewColumn</tt> constructor.</li>
  <li>[line 95 below]: The <tt>EditType</tt> property in the custom <tt>Cell</tt> class tells <tt>DataGridView</tt> which class to use as the editing control for the cell.</li>
  </ul></li>
</ul>
<h3>On to the code</h3>
<p>A note about the Extended ComboBox control: The control supports properties such as an <tt>ImageList</tt> used for displaying images in the drop down, and a <tt>SelectedIDValue</tt> which is forced to be either <tt>DBNull.Value</tt> or a <tt>Long</tt> value for easy binding to a field in a database.</p>
<p>To walk through the code, we'll start at the outer <tt>DataGridView</tt> level and work inwards.</p>
<h3>DataSourceChanged event</h3>
<p>First the <tt>DataSourceChanged</tt> event handler which we use to replace the standard <tt>DataGridViewTextColumn</tt>s with custom columns. The code is structured with a <tt>Select Case</tt> so that other custom column types (like <tt>CalendarColumn</tt> for <tt>DateTime</tt> types) are simple to add later.</p>
<pre class="brush:vb;">
Private Sub OnDataSourceChanged(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles DataGridView1.DataSourceChanged

    Dim OriginalColumn, NewColumn As DataGridViewColumn
    Dim ColumnIndex As Integer

    For Each OriginalColumn In DataGridView1.Columns
        'Reset the NewColumn variable
        NewColumn = Nothing 

        'Decide if we need to replace the current column
        Select Case GetDBType(OriginalColumn.ValueType)
            Case SqlDbType.BigInt, SqlDbType.Int
                'Replace BigInt and Int columns with the ComboBoxColumn type
                'Pass the Sql used to display the content of the drop down
                NewColumn = _
                    New ComboBoxColumn(GetLookupSql(OriginalColumn.Name))

        End Select

        'If we have a new column to replace
        If NewColumn IsNot Nothing Then
            'Get the current index of the old column
            ColumnIndex = MyBase.Columns.IndexOf(OriginalColumn)
            'Copy the basic information from the old column
            NewColumn.DataPropertyName = OriginalColumn.DataPropertyName
            NewColumn.HeaderText = OriginalColumn.HeaderText
            NewColumn.Name = OriginalColumn.Name
            'Remove the old column
            MyBase.Columns.Remove(OriginalColumn)
            'Add the new one where the old one used to be
            MyBase.Columns.Insert(ColumnIndex, NewColumn)
        End If
   Next
End Sub
</pre>
<p>See CodeProject for the implementation of <tt><a href="http://www.codeproject.com/KB/vb/ConvToSqlDbType.aspx" target="_blank">GetDbType</a></tt>.</p>
<h3>ComboBoxColumn class</h3>
<p>Next we need to create the basic <tt>ComboBoxColumn</tt> class. Line 46 is important to hooking the Column to its related CellTemplate class, and the rest of the code allows us to simply pass in the Sql to use for populating the drop down list.</p>
<pre class="brush:vb;first-line:40;">
Public Class ComboBoxColumn
    Inherits DataGridViewColumn

    Friend DropDownDataSource As DataTable

    Public Sub New(ByVal DataSource As String)
        MyBase.New(New ComboBoxCell())

        'Open a DataTable from the specified Sql
        DropDownDataSource = OpenLookupDataTable(DataSource)
    End Sub
End Class
</pre>
<p>We should override the <tt>CellTemplate</tt> property and check that the value being passed to it is of the correct type, but I leave that for the reader to add if they want (along with the simple code for things like <tt>OpenLookupDataTable</tt> [ln49] and <tt>GetLookupSql</tt> [ln17]). This article is keeping to the basics of what we need to get things working and show how the classes interact.</p>
<h3>ComboBoxCell class</h3>
<p>Next we need the <tt>ComboBoxCell</tt> class. This is where most of the extra work is done.</p>
<pre class="brush:vb;first-line:60;">
Public Class ComboBoxCell
    Inherits DataGridViewTextBoxCell

    Private DisplayValue As String = Nothing

    Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, _
        ByVal initialFormattedValue As Object, _
        ByVal dataGridViewCellStyle As DataGridViewCellStyle)
        
        MyBase.InitializeEditingControl(rowIndex, _
            initialFormattedValue, _
            dataGridViewCellStyle)

        'Cast the EditingControl to a variable we can work with
        Dim ctl As ComboBoxEditingControl = _
            DirectCast(DataGridView.EditingControl, ComboBoxEditingControl)

        'Cast the OwningColumn to a variable we can work with
        Dim col As ComboBoxColumn = DirectCast(Me.OwningColumn, ComboBoxColumn)

        'Tell the ComboBox what to display in the drop down
        ctl.DataSource = col.DropDownDataSource
        
        'Important: Tell the ComboBoxEditingControl that this is now 
        '           the owner cell for the control
        ctl.OwnerCell = Me
    End Sub

    Friend Sub SetDisplayValue(ByVal NewValue As String)
        DisplayValue = NewValue
    End Sub

    Public Overrides ReadOnly Property EditType() As Type
        Get
            ' Return the type of the editing contol that ComboBoxCell uses.
            Return GetType(ComboBoxEditingControl)
        End Get
    End Property

    Public Overrides ReadOnly Property ValueType() As Type
        Get
            ' Return the type of the value that ComboBoxCell contains.
            Return GetType(Long)
        End Get
    End Property

    Public Overrides ReadOnly Property DefaultNewRowValue() As Object
        Get
            ' Use DBNull as the default cell value.
            Return DBNull.Value
        End Get
    End Property

    Protected Overrides Sub Paint(ByVal graphics As System.Drawing.Graphics, _
        ByVal clipBounds As System.Drawing.Rectangle, _
        ByVal cellBounds As System.Drawing.Rectangle, ByVal rowIndex As Integer, _
        ByVal cellState As DataGridViewElementStates, _
        ByVal value As Object, ByVal formattedValue As Object, _
        ByVal errorText As String, ByVal cellStyle As DataGridViewCellStyle, _
        ByVal advancedBorderStyle As DataGridViewAdvancedBorderStyle, _
        ByVal paintParts As DataGridViewPaintParts)
        
        'The first time in, make sure that we get the initial DisplayValue
        If DisplayValue Is Nothing Then SetDisplayValue(LookupDisplayValue(value))
        
        'Override paint to pass DisplayValue instead of formattedValue
        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, _
            DisplayValue, errorText, cellStyle, advancedBorderStyle, paintParts)
    End Sub

End Class
</pre>
<p>The reason we have to reset <tt>ComboBox.DataSource</tt> [ln81] every time is due to the fact that the <tt>DataGridView</tt> only creates a single instance of <tt>ComboBoxEditingControl</tt> regardless of which cell the user attempts to edit.</p>
<p>We use the <tt>OwnerCell</tt> property [ln85] to ensure that the <tt>ComboBoxEditingControl</tt> knows which <tt>ComboBoxCell</tt> to communicate with when the user selected a new item in the drop down list.</p>
<p>Using the <tt>Paint</tt> method in this way allows us to keep the styling of the cells controlled by the underlying Microsoft provided code. <a href="#_note6">[6]</a></p>
<p>The <tt>LookupDisplayValue</tt> method finds the text to display using <tt>OwningColumn.DropDownDataSource</tt> so that the first time the cell is painted we know which value to display.</p>
<h3>ComboBoxEditingControl class</h3>
<p>And finally, the class that only has a single instance during the lifetime of the grid... <a href="#_note7">[7]</a></p>
<p>In a similar way to the <tt>CalendarColumn</tt> from MSDN we inherit from the underlying extended <tt>ComboBox</tt> control and then use the <tt>SelectedValueChanged</tt> event to update the currently connected <tt>ComboBoxCell</tt>.</p>
<pre class="brush:vb;first-line:150">
Public Class ComboBoxEditingControl
    Inherits ExtendedComboBox
    Implements IDataGridViewEditingControl

    Private dataGridViewControl As DataGridView
    Private valueIsChanged As Boolean = False
    Private rowIndexNum As Integer
    Private currentCell As ComboBoxCell = Nothing

    Public Property OwnerCell() As ComboBoxCell
        Get
            Return currentCell
        End Get
        Set(ByVal value As ComboBoxCell)
            'Clear currentCell so DoSelectedValueChanged doesn't cause an endless loop
            currentCell = Nothing
            'Set SelectedIDValue
            MyBase.SelectedIDValue = value.Value
            'Show that the value hasn't changed yet
            valueIsChanged = False
            'Finally remember the new Owner Cell
            currentCell = value
        End Set
    End Property

    Public Sub ApplyCellStyleToEditingControl(_
        ByVal dataGridViewCellStyle As DataGridViewCellStyle) _
        Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl
        
        Me.Font = dataGridViewCellStyle.Font
        Me.ForeColor = dataGridViewCellStyle.ForeColor
        Me.BackColor = dataGridViewCellStyle.BackColor
    End Sub

    Public Property EditingControlDataGridView() As DataGridView _
        Implements IDataGridViewEditingControl.EditingControlDataGridView
        Get
            Return dataGridViewControl
        End Get
        Set(ByVal value As DataGridView)
            dataGridViewControl = value
        End Set
    End Property

    Public Property EditingControlFormattedValue() As Object _
        Implements IDataGridViewEditingControl.EditingControlFormattedValue
        Get
            Return MyBase.SelectedIDValue
        End Get
        Set(ByVal value As Object)
            MyBase.SelectedIDValue = value
        End Set
    End Property

    Public Property EditingControlRowIndex() As Integer _
        Implements IDataGridViewEditingControl.EditingControlRowIndex
        Get
            Return rowIndexNum
        End Get
        Set(ByVal value As Integer)
            rowIndexNum = value
        End Set
    End Property

    Public Property EditingControlValueChanged() As Boolean _
        Implements IDataGridViewEditingControl.EditingControlValueChanged
        Get
            Return valueIsChanged
        End Get
        Set(ByVal value As Boolean)
            valueIsChanged = value
        End Set
    End Property

    Public Function EditingControlWantsInputKey(ByVal keyData As Keys, _
        ByVal dataGridViewWantsInputKey As Boolean) As Boolean _
        Implements IDataGridViewEditingControl.EditingControlWantsInputKey
       
        Return True
    End Function

    Public ReadOnly Property EditingPanelCursor() As Cursor _
        Implements IDataGridViewEditingControl.EditingPanelCursor
        Get
            Return MyBase.Cursor
        End Get
    End Property

    Public Function GetEditingControlFormattedValue( _
        ByVal context As DataGridViewDataErrorContexts) As Object _
        Implements IDataGridViewEditingControl.GetEditingControlFormattedValue
        
        Return MyBase.SelectedIDValue
    End Function

    Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) _
        Implements IDataGridViewEditingControl.PrepareEditingControlForEdit
    End Sub

    Public ReadOnly Property RepositionEditingControlOnValueChange() As Boolean _
        Implements IDataGridViewEditingControl.RepositionEditingControlOnValueChange
        Get
            Return False
        End Get
    End Property

    Private Sub DoSelectedValueChanged(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Me.SelectedValueChanged

        If currentCell IsNot Nothing Then
            'Remember that the value has changed
            valueIsChanged = True
            'Pass back the new ID
            currentCell.Value = MyBase.SelectedIDValue
            'Pass back the new display value
            currentCell.SetDisplayValue(MyBase.Text)
        End If
    End Sub
End Class
</pre>
<p>&nbsp;</p>

<p>I hope these four blocks of code give a simple overview of how the custom <tt>DataGridViewColumn</tt> classes connect and interact and will make the setup a little easier for at least one other person!</p>

<p>&nbsp;</p>

<hr class="required" />
<h3>Footnotes</h3>
<ol style="list-style-type:decimal;">
<li id="_note1">One option would have been to extend the <tt>DataGridViewComboBoxColumn</tt> and <tt>DataGridViewComboBoxCell</tt> classes, but this would have meant duplicating the majority of the code from the already tested control.</li>
<li id="_note2">There are many methods and properties in relation to custom columns that mention <tt>FormattedValue</tt> and to begin with I presumed the difference between this and <tt>Value</tt> was that one was the display value and the other was the value stored in the bound data. After spending a long time trying to make this work, all of the error messages suggested that <tt>FormattedValue</tt> and <tt>Value</tt> should in fact be left containing same data.</li>
<li id="_note3">My first thought was that the best place to switch from the default <tt>Grid.DataGridTextColumn</tt> to the custom column would be in the <tt>Grid.ColumnAdded</tt> event as I presumed this would be fired before any rows were added and so save the grid doing things twice. <a href="#_note4">[4]</a><ul>
  <li>No! If the columns are replaced in <tt>Grid.ColumnAdded</tt> then they will later be re-added outside of the developer's control <a href="#_note5">[5]</a></li>
  <li>The next idea was to do it during <tt>Grid.DataBindingComplete</tt> at which point I found that this event is called twice after setting <tt>Grid.DataSource</tt> <a href="#_note5">[5]</a> and that the only safe way to switch columns was to do it on the second call to <tt>Grid.DataBindingComplete</tt> (this event is fired for many different reasons so using it seemed rather hacky).</li>
  <li>Finally I found the answer lay in the <tt>Grid.DataSourceChanged</tt> event which fires only once after setting <tt>Grid.DataSource</tt> and appears to work as expected.</li>
  </ul></li>
<li id="_note4">This was the VB6 developer in me that had previously spent years using <a href="http://www.vbaccelerator.com/home/VB/Code/Controls/S_Grid_2/" target="_blank">SGrid2</a> and its predecessor and so presumed that the data would be fully parsed while being assigned to the grid costing cycles.</li>
<li id="_note5">This took a while to figure out! It appears that if the column is replaced in either the <tt>ColumnAdded</tt> event or the first firing of the  <tt>DataBindingComplete</tt> event, then on the second round of binding (no one seems to understand why this has to happen twice) the original version of the custom column that had custom properties assigned to it is dumped and replaced with a fresh one that only has standard <tt>DataGridViewColumn</tt> properties assigned such as <tt>DataPropertyName</tt>.</li>
<li id="_note6">It was the structure of the <tt>Paint</tt> method in taking <tt>value</tt> and <tt>formattedValue</tt> parameters, and only displaying the contents of <tt>formattedValue</tt> that led me to believe that <tt>FormattedValue</tt> in the <tt>EditingControl</tt> class could be different from <tt>Cell.Value</tt>. <a href="#_note2">[2]</a></li>
<li id="_note7">Although only one instance of the <tt>EditingControl</tt> is created by default, I would imagine there are methods within the custom <tt>Cell</tt> class that can be overridden to provide the developer with more flexibility.</li>
</ol>
<p>&nbsp;</p>
<p><em>Disclaimer: All sample code is provided for illustrative purposes only. These examples have not been thoroughly tested under all conditions. There is no guarantee or implied reliability, serviceability, or function of these programs.</em></p>]]></description>
  <pubDate>Tue, 02 Jun 2009 07:33:20 GMT</pubDate>
  <category>VB</category>
  <category>Net</category>
  <category>Microsoft</category>
  <guid isPermaLink="true">http://www.theogray.com/blog/2009/06/custom-datagridviewcolumn-idatagridvieweditingcont</guid>
  <comments>http://www.theogray.com/blog/2009/06/custom-datagridviewcolumn-idatagridvieweditingcont#readercomments</comments>
</item>
</channel>
</rss>

