Sunday 6 December 2009

How I fixed my daughter's iPod (5th generation)

Picture this. I traded my three year old Windows PC in for a shiny new iMac. After transferring the family's entire 300 GB music library from the old PC to the new Mac, it was time to synchronise all four of the iPods of the family, and two iPhones with the new iTunes library. All went well until my daughter plugged in her 5th generation iPod. If this kind of thing interests you, the engraving on the back identifies the model as A1236 but the software on the fron identifies it as MA978. Here's what happend. Plugging it into the iMac caused the usual icon to appear on the desktop labelled Marie's iPod. iTunes was already launched, but even after a few moments more than what I would usually consider reasonable, it still didn't show up under the DEVICES node in the tree. So I quit iTunes and re-launched it. Same result. So I restarted the Mac OS X, with the same result. So I then reset the iPod (holding down MENU and SELECT buttons for more than six seconds until the Apple logo appeared) and hurrah! it showed up in iTunes. But, alas, that was only the first hurdle. After selecting the right playlist and verifying the iPod already had the latest version of its firmware (it had) it was time to click the Sync button. And nothing happened other than the message "Syncing iPod..." appearing in iTunes and the actual iPod reverting to displaying its main menu as if someone had "ejected" it. After a long while an annoying message popped up over iTunes informing me that it was "Looking for iPod...". You know the type of old-school error message dialog box that you can't dismiss or even move out of the way. While this box is being displayed, you can't even do anything else with iTunes. All you can do is wait for the timeout which is only 120 seconds but feels like hours! Anyways, I won't bore you with all the things I tried (things that are suggested by Apple's support no less) but failed, so here's the solution. Find a Windows machine. I know what I am trying to tell you here is probably very easy to do with a Mac and a terminal window, but life's too short to find out how (I did Google it for an hour and gave up). Find a Windows machine and plug in the iPod. Wait for the AutoPlay pop-up that asks you if you want to view the files on drive X (where X is somewhere between D and Z). Make a note of the drive letter and close the pop-up. Open a DOS box or command prompt window. Type in X: (where X is your iPod drive letter noted earlier) and press enter. Now type in ATTRIB -H .* and enter. This will unhide all the sensitive iPod folders on the drive. Now type in exactly this FOR /D %A IN (*.*) DO RD "%A" /S/Q and enter. This will remove all the folders and their contents, and may take a while. This is what the Restore button in iTunes is supposed to do, but doesn't. Click on the tray icon for USB and choose to safely remove the hardware (iPod). Unplug from the Windows machine and re-plug into the iMac and all should be well from here on. It was for me. iTunes detected the new device and asked me to name it, then asked me what music I wanted to put on it, and it worked. The irony of the matter is that I no longer had a Windows machine to do this on, so I had to do it from a Windows XP installation on a virtual machine (using Parallels) on the iMac itself!

Saturday 28 November 2009

Project for 28 Nov 2009

Write an IrfanView clone for Mac OS X. Hmm should I use Cocoa in XCode for this?

Thursday 22 October 2009

How to remove SQL Server code injections in a hurry?

So you suddenly find that your web server has been compromised. How? Who knows. In my case we had an ASP.NET website which used the default settings for ViewState (i.e. sent to the client as a hidden form field, expecting the client to return it unaltered when the form is submitted). Of course, the default settings (recommended by Microsoft) leaves IIS wide open to an exploit called SQL Injection. Some crafty buggers discovered how to do this and they've been crawling the web looking for ASPX pages and throwing all sorts of nasty stuff into the ViewState fields until they hit pay-dirt...

So, what's the harm? This virus discovers the names of all the text columns in all your tables in all your database(s) to which the website has access, then it appends a string like <script src="http://www.doubleclickr.ru/index.js"></script> to all values in that column. Your website may well return some of these text fields back to the client as HTML to be displayed, and that's when the problem starts for the client. The client's browser interprets this HTML coming from your site as a legitimate request to fetch a mash-up script from DoubleClick (or whatever), and that script then tries to turn the client's PC into a zombie that obeys the bidding of the Russian Mafia (or whoever controls the virus). Will the client blame you? For sure they will, because you are supposed to ensure your server is virus-free, right! Well, with this kind of attack, your server is not itself running any virus code, and therefore no anti-virus program running on your server will pick up this 'infection' in the data. Tricky one.

So here is a little SQL script to create a procedure that will remove such a string from every text field in every table of the current database. This script employs much the same tricks to discover all the text fields, as I'm sure the virus must be employing..
   1:  Alter procedure RemoveSqlInjections
   2:  (
   3:      @remove nVarChar(4000) = '<script src=http://www.doubleclickr.ru/index.js></script>'
   4:  )
   5:  as
   6:   
   7:  /*
   8:   
   9:  Inject one test...
  10:  -------------------
  11:   
  12:  Update    users 
  13:  set        messageText = messageText + @remove 
  14:  where    userid = 
  15:  (
  16:      Select top 1    userId 
  17:      from            users 
  18:      where            messageText is not null
  19:  )
  20:   
  21:  -------------------
  22:  */
  23:   
  24:  Declare @sql nVarChar(max), 
  25:          @table nVarChar(64), 
  26:          @column nVarChar(64), 
  27:          @value nVarChar(128),
  28:          @count int
  29:   
  30:  Set noCount on
  31:   
  32:  Declare injectionRemover cursor for
  33:  Select    [table], 
  34:          [column], 
  35:          case when [convert] is not null
  36:              then 'convert(' + [convert] + ', ' + [column] + ')'
  37:              else [column]
  38:          end as [value]
  39:  from
  40:  (
  41:      Select         '[' + a.name + ']' as 'table', 
  42:                  '[' + b.name + ']' as 'column', 
  43:                  case 
  44:                      when b.xType in (98,99) then 'nVarChar(4000)'
  45:                      when b.xType = 35 then 'varChar(8000)'
  46:                  end as 'convert'
  47:      from        sysobjects a
  48:      inner join    syscolumns b
  49:          on        a.id = b.id
  50:      where        a.xtype='U' 
  51:          and        b.xtype in (1,6,7,35,98,99,167,175,231,239)
  52:  ) c
  53:  order by 1
  54:   
  55:  Open injectionRemover
  56:   
  57:  While 1=1 begin
  58:      Fetch next from injectionRemover into @table, @column, @value
  59:      If @@fetch_status <> 0 break
  60:   
  61:      Set @sql =    'Update ' + @table + 
  62:                  ' set ' +  @column + 
  63:                  ' = replace(' + @value + ', ''' + @remove + ''', '''') where ' +
  64:                  @value + ' like ''%<script%'''
  65:   
  66:      Exec(@sql)
  67:      Set @count = @@rowCount
  68:      If @count > 0 begin
  69:          Set @sql = 'Replaced ' + convert(nVarChar(10), @count) + ' occurence(s) in ' + @table + '.' + @column
  70:          Print @sql
  71:      end
  72:  end
  73:   
  74:  Close injectionRemover
  75:  Deallocate injectionRemover
After creating the procedure just run it. With no parameters it will remove the the same nasty that infected my server, but you can supply a single parameter like so:
Exec RemoveSqlInjections '<script src="some other nasty thing"></script>'
It is not case sensitive (by default, but if your server's collation is binary or some other case-sensitive variant, then watch out for case variations).

Tuesday 13 October 2009

Enumerate all columns reachable from a given seed table

Have you often wanted to enumerate all the tables (and their columns) that can be reached via joins (or chained joins) from a given seed table? I have. Here's my solution entirely in Transact-SQL (SQL 2005 or 2008 only) using recursive common table expressions.
   1:  Create procedure EnumAllColumns 
   2:  (
   3:      @seedTable nVarChar(100) = 'Projects'
   4:  )
   5:  as
   6:   
   7:  With cte(PrimaryEntity,PrimaryTable,RelatedEntity,RelatedTable,Recurse,[Path]) as
   8:  (
   9:      -- Select the root level
  10:      Select      distinct 
  11:                  r.Entity    as [PrimaryEntity], 
  12:                  r.Name        as [PrimaryTable],
  13:                  f.Entity    as [RelatedEntity], 
  14:                  f.Name        as [RelatedTable],
  15:                  1            as [Recurse],
  16:                  convert(nVarChar(800), r.Entity + '.' + f.Entity) as [Path]
  17:      from        sysForeignKeys a
  18:   
  19:      inner join -- table containing foreign key
  20:      (
  21:          Select    id, [name], 
  22:                  case when [name] like '%ies' then 
  23:                      left([name], len([name]) - 3) + 'y' 
  24:                  when [name] like '%s' then 
  25:                      left([name],len([name]) - 1) 
  26:                  else
  27:                      [name]
  28:                  end as [Entity]
  29:          from    sysObjects 
  30:      ) as f on    f.id = a.fKeyId
  31:   
  32:      inner join -- table containing foreign key
  33:      (
  34:          Select    id, [name],
  35:                  case when [name] like '%ies' then 
  36:                      left([name], len([name]) - 3) + 'y' 
  37:                  when [name] like '%s' then 
  38:                      left([name],len([name]) - 1) 
  39:                  else
  40:                      [name]
  41:                  end as [Entity]
  42:          from    sysObjects 
  43:      ) as r on    r.id = a.rKeyId
  44:   
  45:                  -- ignore subsequent columns of multi-column relations
  46:      where        a.keyNo = 1 
  47:          and        r.Name = @seedTable
  48:      -- end of root level
  49:   
  50:      union all
  51:   
  52:      -- select the child levels
  53:      Select      r.Entity as [PrimaryEntity],
  54:                  r.Name as [PrimaryTable],
  55:                  f.Entity as [RelatedEntity],
  56:                  f.Name as [RelatedTable],
  57:                  cte.recurse + 1,
  58:                  convert(nVarChar(800), cte.Path + '.' + f.Entity)
  59:      from        sysForeignKeys a
  60:   
  61:      inner join -- table containing foreign key
  62:      (
  63:          Select    id, [name], 
  64:                  case when [name] like '%ies' then 
  65:                      left([name], len([name]) - 3) + 'y' 
  66:                  when [name] like '%s' then 
  67:                      left([name],len([name])-1) 
  68:                  else
  69:                      [name]
  70:                  end as [Entity]
  71:          from    sysObjects 
  72:      ) as f on    f.id = a.fKeyId
  73:   
  74:      inner join -- table containing foreign key
  75:      (
  76:          Select    id, [name], 
  77:                  case when [name] like '%ies' then 
  78:                      left([name], len([name]) - 3) + 'y' 
  79:                  when [name] like '%s' then 
  80:                      left([name],len([name])-1) 
  81:                  else
  82:                      [name]
  83:                  end as [Entity]
  84:          from    sysObjects 
  85:      ) as r on    r.id = a.rKeyId
  86:   
  87:      inner join    cte 
  88:          on        cte.relatedTable = r.[name]
  89:   
  90:                  -- ignore subsequent columns of multi-column relations
  91:      where        a.keyNo = 1 
  92:                  -- restrict recursion to a level that SQL will be happy with
  93:          and        cte.recurse < 100 
  94:                  -- ignore same-table references (could cause max recursion level to be hit)
  95:          and        r.[name] <> f.[name] 
  96:   
  97:      -- end of child level
  98:  )
  99:  Select  [Path] + '.' + col
 100:   
 101:  from  -- Select the tables
 102:  (
 103:      Select      *
 104:      from        cte
 105:   
 106:      union
 107:   
 108:      Select        null, null, @seedTable, @seedTable, 0, @seedTable
 109:  ) as cte
 110:   
 111:  inner join -- join to the columns
 112:  (
 113:      Select        col.[name] as col, tbl.[name] as tbl
 114:      from        sysColumns col
 115:      inner join    sysObjects tbl
 116:          on        tbl.id = col.id
 117:  ) as cols on    cte.relatedTable = tbl
 118:   
 119:  order by        recurse, 1
The above query produces output similar to this:-
  • Project.pActive
  • Project.pCheckedOutByUserID
  • Project.pDate
  • Project.pFinish
  • Project.pID
  • Project.pLastAccessed
  • Project.pOwnerID
  • Project.pProjectName
  • Project.pProjectNotes
  • Project.pSecurity
  • Project.pStart
  • Project.ActionLog.alActionMsg
  • Project.ActionLog.alActionSource
  • Project.ActionLog.alActionType
  • Project.ActionLog.alID
  • Project.ActionLog.alProjectID
  • Project.ActionLog.alTimeStamp
  • Project.ActionLog.alUserID
  • Project.Appraisal.asAcqVAT
  • Project.Appraisal.asAddress
  • Project.Appraisal.asAnalysedOnCosts
  • Project.Appraisal.asAppraisalBy
  • Project.Appraisal.asAppraisalName
  • Project.Appraisal.asAppraisalPath
  • Project.Appraisal.asBidNo
  • Project.Appraisal.asCapInterest
  • Project.Appraisal.asCashflowID
  • Project.Appraisal.asCompliesSDS
  • Project.Appraisal.asComRentedUnits
  • Project.Appraisal.asComSales
  • Project.Appraisal.asComSalesUnits
  • Project.Appraisal.asDate
  • Project.Appraisal.asDateofAppraisal
  • Project.Appraisal.asFileRef
  • Project.Appraisal.asFirstEvent
  • Project.Appraisal.asFirstSalesDate
  • Project.Appraisal.asFirstWorksPayment
  • Project.Appraisal.asFundingYear
  • Project.Appraisal.asHCSHG
  • Project.Appraisal.asID
  • Project.Appraisal.asImportDate
  • Project.Appraisal.asImportFlags
  • Project.Appraisal.asLA
  • Project.Appraisal.asLASHG
  • Project.Appraisal.asLastAcqPayment
  • Project.Appraisal.asLastEvent
  • Project.Appraisal.asLastSalesDate
  • Project.Appraisal.asLastWorksPayment
  • Project.Appraisal.asLCHOUnits
  • Project.Appraisal.asNPV
  • Project.Appraisal.asNPVDiscountPeriod
  • Project.Appraisal.asNPVDiscountRate
  • Project.Appraisal.asNPVLabel
  • Project.Appraisal.asNPVNettRent
  • Project.Appraisal.asOpeningLoan
  • Project.Appraisal.asOtherCostsLine1
  • Project.Appraisal.asOtherCostsLine2
  • Project.Appraisal.asOtherDescription
  • Project.Appraisal.asOtherDescriptionLabel
  • Project.Appraisal.asOtherGrantLine1
  • Project.Appraisal.asOtherGrantLine2
  • Project.Appraisal.asPFSubsidyLine1
  • Project.Appraisal.asPFSubsidyLine2
  • Project.Appraisal.asPFSubsidyLine3
  • Project.Appraisal.asProjectID
  • Project.Appraisal.asPublicSubsidy
  • Project.Appraisal.asRCGF
  • Project.Appraisal.asRentedUnits
  • Project.Appraisal.asRentPeriods
  • Project.Appraisal.asRentToHomebuyUnits
  • Project.Appraisal.asResSales
  • Project.Appraisal.asResSalesUnits
  • Project.Appraisal.asSchemeCostIndex
  • Project.Appraisal.asSchemeType
  • Project.Appraisal.asTotalAcq
  • Project.Appraisal.asTotalFees
  • Project.Appraisal.asTotalWorks
  • Project.Appraisal.asTSC
  • Project.Appraisal.asVersion
  • Project.Appraisal.asWorksVAT
  • Project.Appraisal.asYield
  • Project.Note.DueDate
  • Project.Note.IsComplete
  • Project.Note.NoteID
  • Project.Note.NoteText
  • Project.Note.OriginatorID
  • Project.Note.OriginatorKey
  • Project.Order.orCashflowID
  • Project.Order.orDate
  • Project.Order.orDatePaid
  • Project.Order.orID
  • Project.Order.orOrgID
  • Project.Order.orProjectCostCodeID
  • Project.Order.orProjectID
  • Project.Order.orRef
  • Project.Order.orScope
  • Project.Order.orSupplyEnd
  • Project.Order.orSupplyStart
  • Project.Order.orValue
  • Project.ProjectCashflow.pcfCashflowName
  • Project.ProjectCashflow.pcfDateIntoMgmt
  • Project.ProjectCashflow.pcfID
  • Project.ProjectCashflow.pcfInterestRate
  • Project.ProjectCashflow.pcfLastEdited
  • Project.ProjectCashflow.pcfLastEditedBy
  • Project.ProjectCashflow.pcfPeriodEnd
  • Project.ProjectCashflow.pcfPeriodEndDate
  • Project.ProjectCashflow.pcfProjectID
  • Project.ProjectCashflow.pcfSchemeCode
  • Project.ProjectCashflow.pcfTransFile
  • Project.ProjectInfoLink.pilID
  • Project.ProjectInfoLink.pilProjectID
  • Project.ProjectInfoLink.pilProjectInfoID
  • Project.ProjectInfoLink.pilValue
  • Project.ProjectOrganisation.potID
  • Project.ProjectOrganisation.potOrgCatLinkID
  • Project.ProjectOrganisation.potProjectID
  • Project.ProjectWorkflow.pwfID
  • Project.ProjectWorkflow.pwfOrder
  • Project.ProjectWorkflow.pwfProjectID
  • Project.ProjectWorkflow.pwfWorkflowName
  • Project.Property.prAppraisalUnitID
  • Project.Property.prCaption
  • Project.Property.prID
  • Project.Property.prIsParent
  • Project.Property.prOrder
  • Project.Property.prParentPropertyID
  • Project.Property.prProjectID
  • Project.Timesheet.tsCategoryID
  • Project.Timesheet.tsDate
  • Project.Timesheet.tsHours
  • Project.Timesheet.tsID
  • Project.Timesheet.tsNotes
  • Project.Timesheet.tsProjectID
  • Project.Timesheet.tsUserID
  • Project.Appraisal.AppraisalDescription.adAppraisalID
  • Project.Appraisal.AppraisalDescription.adDescription
  • Project.Appraisal.AppraisalDescription.adID
  • Project.Appraisal.AppraisalUnitDetail.audAppraisalID
  • Project.Appraisal.AppraisalUnitDetail.audBeds
  • Project.Appraisal.AppraisalUnitDetail.audCatalystDevType
  • Project.Appraisal.AppraisalUnitDetail.audComRent
  • Project.Appraisal.AppraisalUnitDetail.audDwelling
  • Project.Appraisal.AppraisalUnitDetail.audEquityShare
  • Project.Appraisal.AppraisalUnitDetail.audExtended
  • Project.Appraisal.AppraisalUnitDetail.audFlat
  • Project.Appraisal.AppraisalUnitDetail.audFloorArea
  • Project.Appraisal.AppraisalUnitDetail.audGroundRent
  • Project.Appraisal.AppraisalUnitDetail.audHabRooms
  • Project.Appraisal.AppraisalUnitDetail.audHCProductType
  • Project.Appraisal.AppraisalUnitDetail.audHCTargetRent
  • Project.Appraisal.AppraisalUnitDetail.audID
  • Project.Appraisal.AppraisalUnitDetail.audJan99
  • Project.Appraisal.AppraisalUnitDetail.audNewBuild
  • Project.Appraisal.AppraisalUnitDetail.audOpenMarketValue
  • Project.Appraisal.AppraisalUnitDetail.audOtherCharges
  • Project.Appraisal.AppraisalUnitDetail.audOtherDescLabel
  • Project.Appraisal.AppraisalUnitDetail.audOtherDescValue
  • Project.Appraisal.AppraisalUnitDetail.audPersonalCharges
  • Project.Appraisal.AppraisalUnitDetail.audPersons
  • Project.Appraisal.AppraisalUnitDetail.audResRent
  • Project.Appraisal.AppraisalUnitDetail.audServiceCharges
  • Project.Appraisal.AppraisalUnitDetail.audShared
  • Project.Appraisal.AppraisalUnitDetail.audSheltered
  • Project.Appraisal.AppraisalUnitDetail.audStoreys
  • Project.Appraisal.AppraisalUnitDetail.audUnits
  • Project.Appraisal.AppraisalUnitDetail.audUnitType
  • Project.Appraisal.Note.DueDate
  • Project.Appraisal.Note.IsComplete
  • Project.Appraisal.Note.NoteID
  • Project.Appraisal.Note.NoteText
  • Project.Appraisal.Note.OriginatorID
  • Project.Appraisal.Note.OriginatorKey
  • Project.ProjectCashflow.Allocation.alActualAllocDate
  • Project.ProjectCashflow.Allocation.alActualApprovalDate
  • Project.ProjectCashflow.Allocation.alActualBidDate
  • Project.ProjectCashflow.Allocation.alAllocationName
  • Project.ProjectCashflow.Allocation.alCashflowID
  • Project.ProjectCashflow.Allocation.alForecastAllocDate
  • Project.ProjectCashflow.Allocation.alForecastApprovalDate
  • Project.ProjectCashflow.Allocation.alForecastBidDate
  • Project.ProjectCashflow.Allocation.alID
  • Project.ProjectCashflow.Allocation.alRegion
  • Project.ProjectCashflow.Allocation.alTenure
  • Project.ProjectCashflow.Appraisal.asAcqVAT
  • Project.ProjectCashflow.Appraisal.asAddress
  • Project.ProjectCashflow.Appraisal.asAnalysedOnCosts
  • Project.ProjectCashflow.Appraisal.asAppraisalBy
  • Project.ProjectCashflow.Appraisal.asAppraisalName
  • Project.ProjectCashflow.Appraisal.asAppraisalPath
  • Project.ProjectCashflow.Appraisal.asBidNo
  • Project.ProjectCashflow.Appraisal.asCapInterest
  • Project.ProjectCashflow.Appraisal.asCashflowID
  • Project.ProjectCashflow.Appraisal.asCompliesSDS
  • Project.ProjectCashflow.Appraisal.asComRentedUnits
  • Project.ProjectCashflow.Appraisal.asComSales
  • Project.ProjectCashflow.Appraisal.asComSalesUnits
  • Project.ProjectCashflow.Appraisal.asDate
  • Project.ProjectCashflow.Appraisal.asDateofAppraisal
  • Project.ProjectCashflow.Appraisal.asFileRef
  • Project.ProjectCashflow.Appraisal.asFirstEvent
  • Project.ProjectCashflow.Appraisal.asFirstSalesDate
  • Project.ProjectCashflow.Appraisal.asFirstWorksPayment
  • Project.ProjectCashflow.Appraisal.asFundingYear
  • Project.ProjectCashflow.Appraisal.asHCSHG
  • Project.ProjectCashflow.Appraisal.asID
  • Project.ProjectCashflow.Appraisal.asImportDate
  • Project.ProjectCashflow.Appraisal.asImportFlags
  • Project.ProjectCashflow.Appraisal.asLA
  • Project.ProjectCashflow.Appraisal.asLASHG
  • Project.ProjectCashflow.Appraisal.asLastAcqPayment
  • Project.ProjectCashflow.Appraisal.asLastEvent
  • Project.ProjectCashflow.Appraisal.asLastSalesDate
  • Project.ProjectCashflow.Appraisal.asLastWorksPayment
  • Project.ProjectCashflow.Appraisal.asLCHOUnits
  • Project.ProjectCashflow.Appraisal.asNPV
  • Project.ProjectCashflow.Appraisal.asNPVDiscountPeriod
  • Project.ProjectCashflow.Appraisal.asNPVDiscountRate
  • Project.ProjectCashflow.Appraisal.asNPVLabel
  • Project.ProjectCashflow.Appraisal.asNPVNettRent
  • Project.ProjectCashflow.Appraisal.asOpeningLoan
  • Project.ProjectCashflow.Appraisal.asOtherCostsLine1
  • Project.ProjectCashflow.Appraisal.asOtherCostsLine2
  • Project.ProjectCashflow.Appraisal.asOtherDescription
  • Project.ProjectCashflow.Appraisal.asOtherDescriptionLabel
  • Project.ProjectCashflow.Appraisal.asOtherGrantLine1
  • Project.ProjectCashflow.Appraisal.asOtherGrantLine2
  • Project.ProjectCashflow.Appraisal.asPFSubsidyLine1
  • Project.ProjectCashflow.Appraisal.asPFSubsidyLine2
  • Project.ProjectCashflow.Appraisal.asPFSubsidyLine3
  • Project.ProjectCashflow.Appraisal.asProjectID
  • Project.ProjectCashflow.Appraisal.asPublicSubsidy
  • Project.ProjectCashflow.Appraisal.asRCGF
  • Project.ProjectCashflow.Appraisal.asRentedUnits
  • Project.ProjectCashflow.Appraisal.asRentPeriods
  • Project.ProjectCashflow.Appraisal.asRentToHomebuyUnits
  • Project.ProjectCashflow.Appraisal.asResSales
  • Project.ProjectCashflow.Appraisal.asResSalesUnits
  • Project.ProjectCashflow.Appraisal.asSchemeCostIndex
  • Project.ProjectCashflow.Appraisal.asSchemeType
  • Project.ProjectCashflow.Appraisal.asTotalAcq
  • Project.ProjectCashflow.Appraisal.asTotalFees
  • Project.ProjectCashflow.Appraisal.asTotalWorks
  • Project.ProjectCashflow.Appraisal.asTSC
  • Project.ProjectCashflow.Appraisal.asVersion
  • Project.ProjectCashflow.Appraisal.asWorksVAT
  • Project.ProjectCashflow.Appraisal.asYield
  • Project.ProjectCashflow.BudgetLog.blBalance
  • Project.ProjectCashflow.BudgetLog.blBudgetExp
  • Project.ProjectCashflow.BudgetLog.blBudgetInc
  • Project.ProjectCashflow.BudgetLog.blBudgetST
  • Project.ProjectCashflow.BudgetLog.blCashflowID
  • Project.ProjectCashflow.BudgetLog.blFCExp
  • Project.ProjectCashflow.BudgetLog.blFCInc
  • Project.ProjectCashflow.BudgetLog.blFCST
  • Project.ProjectCashflow.BudgetLog.blID
  • Project.ProjectCashflow.BudgetLog.blInterest
  • Project.ProjectCashflow.BudgetLog.blLEExp
  • Project.ProjectCashflow.BudgetLog.blLEInc
  • Project.ProjectCashflow.BudgetLog.blLEST
  • Project.ProjectCashflow.BudgetLog.blNett
  • Project.ProjectCashflow.BudgetLog.blTimeStamp
  • Project.ProjectCashflow.BudgetLog.blTransExp
  • Project.ProjectCashflow.BudgetLog.blTransInc
  • Project.ProjectCashflow.BudgetLog.blTransST
  • Project.ProjectCashflow.BudgetLog.blUserID
  • Project.ProjectCashflow.CalculatedBalances_.cbpBalance
  • Project.ProjectCashflow.CalculatedBalances_.cbpCashflowID
  • Project.ProjectCashflow.CalculatedBalances_.cbpDate
  • Project.ProjectCashflow.CalculatedBalances_.cbpID
  • Project.ProjectCashflow.CalculatedBalances_.cbpInterest
  • Project.ProjectCashflow.CalculatedBalances_.cbpNett
  • Project.ProjectCashflow.CalculatedBalances_.cbtBalance
  • Project.ProjectCashflow.CalculatedBalances_.cbtCashflowID
  • Project.ProjectCashflow.CalculatedBalances_.cbtDate
  • Project.ProjectCashflow.CalculatedBalances_.cbtID
  • Project.ProjectCashflow.CalculatedBalances_.cbtInterest
  • Project.ProjectCashflow.CalculatedBalances_.cbtNett
  • Project.ProjectCashflow.CashflowAttributeValue.cavAttributeID
  • Project.ProjectCashflow.CashflowAttributeValue.cavCashflowID
  • Project.ProjectCashflow.CashflowAttributeValue.cavID
  • Project.ProjectCashflow.CashflowAttributeValue.cavValue
  • Project.ProjectCashflow.Note.DueDate
  • Project.ProjectCashflow.Note.IsComplete
  • Project.ProjectCashflow.Note.NoteID
  • Project.ProjectCashflow.Note.NoteText
  • Project.ProjectCashflow.Note.OriginatorID
  • Project.ProjectCashflow.Note.OriginatorKey
  • Project.ProjectCashflow.Order.orCashflowID
  • Project.ProjectCashflow.Order.orDate
  • Project.ProjectCashflow.Order.orDatePaid
  • Project.ProjectCashflow.Order.orID
  • Project.ProjectCashflow.Order.orOrgID
  • Project.ProjectCashflow.Order.orProjectCostCodeID
  • Project.ProjectCashflow.Order.orProjectID
  • Project.ProjectCashflow.Order.orRef
  • Project.ProjectCashflow.Order.orScope
  • Project.ProjectCashflow.Order.orSupplyEnd
  • Project.ProjectCashflow.Order.orSupplyStart
  • Project.ProjectCashflow.Order.orValue
  • Project.ProjectCashflow.ProjectCostCode.pccAccountCodeID
  • Project.ProjectCashflow.ProjectCostCode.pccCashflowID
  • Project.ProjectCashflow.ProjectCostCode.pccID
  • Project.ProjectCashflow.SpreadProfile.spCashflowID
  • Project.ProjectCashflow.SpreadProfile.spDelayStart
  • Project.ProjectCashflow.SpreadProfile.spExtendFinish
  • Project.ProjectCashflow.SpreadProfile.spFinishDate
  • Project.ProjectCashflow.SpreadProfile.spFunds
  • Project.ProjectCashflow.SpreadProfile.spID
  • Project.ProjectCashflow.SpreadProfile.spOverwrite
  • Project.ProjectCashflow.SpreadProfile.spProfile
  • Project.ProjectCashflow.SpreadProfile.spProfileName
  • Project.ProjectCashflow.SpreadProfile.spStartDate
  • Project.ProjectCashflow.SpreadProfile.spUseExisting
  • Project.ProjectCashflow.Transaction.tAccountCodeID
  • Project.ProjectCashflow.Transaction.tAmount
  • Project.ProjectCashflow.Transaction.tCashflowID
  • Project.ProjectCashflow.Transaction.tDC
  • Project.ProjectCashflow.Transaction.tDescription
  • Project.ProjectCashflow.Transaction.tID
  • Project.ProjectCashflow.Transaction.tPostedDate
  • Project.ProjectCashflow.Transaction.tReference
  • Project.ProjectCashflow.Transaction.tSchemeCode
  • Project.ProjectCashflow.Transaction.tSupplierCode
  • Project.ProjectCashflow.Transaction.tTransDate
  • Project.ProjectCashflow.Transaction.tTransPeriod
  • Project.ProjectOrganisation.Note.DueDate
  • Project.ProjectOrganisation.Note.IsComplete
  • Project.ProjectOrganisation.Note.NoteID
  • Project.ProjectOrganisation.Note.NoteText
  • Project.ProjectOrganisation.Note.OriginatorID
  • Project.ProjectOrganisation.Note.OriginatorKey
  • Project.ProjectOrganisation.OrganisationAdvance.oaID
  • Project.ProjectOrganisation.OrganisationAdvance.oaOrgID
  • Project.ProjectOrganisation.OrganisationAdvance.oaTopicID
  • Project.ProjectOrganisation.OrganisationAdvance.oaValue
  • Project.ProjectWorkflow.Task.tActualFinishDate
  • Project.ProjectWorkflow.Task.tActualStartDate
  • Project.ProjectWorkflow.Task.tCategoryID
  • Project.ProjectWorkflow.Task.tComment
  • Project.ProjectWorkflow.Task.tCurrentStatus
  • Project.ProjectWorkflow.Task.tFinishAbstractID
  • Project.ProjectWorkflow.Task.tFinishDate
  • Project.ProjectWorkflow.Task.tForceComplete
  • Project.ProjectWorkflow.Task.tID
  • Project.ProjectWorkflow.Task.tOrder
  • Project.ProjectWorkflow.Task.tOriginatorID
  • Project.ProjectWorkflow.Task.tScope
  • Project.ProjectWorkflow.Task.tSecurityCompleteID
  • Project.ProjectWorkflow.Task.tSecurityLevelID
  • Project.ProjectWorkflow.Task.tStartAbstractID
  • Project.ProjectWorkflow.Task.tStartDate
  • Project.ProjectWorkflow.Task.tTaskName
  • Project.ProjectWorkflow.Task.tWorkflowID
  • Project.Property.Note.DueDate
  • Project.Property.Note.IsComplete
  • Project.Property.Note.NoteID
  • Project.Property.Note.NoteText
  • Project.Property.Note.OriginatorID
  • Project.Property.Note.OriginatorKey
  • Project.Property.PropertyDetail.prdAttributeID
  • Project.Property.PropertyDetail.prdID
  • Project.Property.PropertyDetail.prdPropertyID
  • Project.Property.PropertyDetail.prdValue
  • Project.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocAccountCodeID
  • Project.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocAmount
  • Project.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocAppraisalDescriptionID
  • Project.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocDate
  • Project.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocID
  • Project.Appraisal.AppraisalUnitDetail.Property.prAppraisalUnitID
  • Project.Appraisal.AppraisalUnitDetail.Property.prCaption
  • Project.Appraisal.AppraisalUnitDetail.Property.prID
  • Project.Appraisal.AppraisalUnitDetail.Property.prIsParent
  • Project.Appraisal.AppraisalUnitDetail.Property.prOrder
  • Project.Appraisal.AppraisalUnitDetail.Property.prParentPropertyID
  • Project.Appraisal.AppraisalUnitDetail.Property.prProjectID
  • Project.ProjectCashflow.Allocation.AllocationDetail.adActual
  • Project.ProjectCashflow.Allocation.AllocationDetail.adActualAmount
  • Project.ProjectCashflow.Allocation.AllocationDetail.adActualUnits
  • Project.ProjectCashflow.Allocation.AllocationDetail.adAllocationID
  • Project.ProjectCashflow.Allocation.AllocationDetail.adForecast
  • Project.ProjectCashflow.Allocation.AllocationDetail.adForecastAmount
  • Project.ProjectCashflow.Allocation.AllocationDetail.adForecastUnits
  • Project.ProjectCashflow.Allocation.AllocationDetail.adHcAmount
  • Project.ProjectCashflow.Allocation.AllocationDetail.adHcTarget
  • Project.ProjectCashflow.Allocation.AllocationDetail.adHcUnits
  • Project.ProjectCashflow.Allocation.AllocationDetail.adID
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.adAppraisalID
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.adDescription
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.adID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audAppraisalID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audBeds
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audCatalystDevType
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audComRent
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audDwelling
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audEquityShare
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audExtended
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audFlat
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audFloorArea
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audGroundRent
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audHabRooms
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audHCProductType
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audHCTargetRent
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audJan99
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audNewBuild
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audOpenMarketValue
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audOtherCharges
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audOtherDescLabel
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audOtherDescValue
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audPersonalCharges
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audPersons
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audResRent
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audServiceCharges
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audShared
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audSheltered
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audStoreys
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audUnits
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.audUnitType
  • Project.ProjectCashflow.Appraisal.Note.DueDate
  • Project.ProjectCashflow.Appraisal.Note.IsComplete
  • Project.ProjectCashflow.Appraisal.Note.NoteID
  • Project.ProjectCashflow.Appraisal.Note.NoteText
  • Project.ProjectCashflow.Appraisal.Note.OriginatorID
  • Project.ProjectCashflow.Appraisal.Note.OriginatorKey
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclAccCodeID
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclBudgetChanged
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclID
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclLatestChanged
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclNewValue
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclOldValue
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclReason
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclTimeStamp
  • Project.ProjectCashflow.ProjectCostCode.AccountCodeLog.aclUserID
  • Project.ProjectCashflow.ProjectCostCode.Budget.bBudget
  • Project.ProjectCashflow.ProjectCostCode.Budget.bCommitted
  • Project.ProjectCashflow.ProjectCostCode.Budget.bCostCodeID
  • Project.ProjectCashflow.ProjectCostCode.Budget.bID
  • Project.ProjectCashflow.ProjectCostCode.Budget.bLatestEstimate
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoCostCodeID
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoCurveEndOffset
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoCurveStartOffset
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoEndDate
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoEveryNMonths
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoFirstRetDate
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoFirstRetPercentage
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoFundsOption
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoID
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoSecondRetDate
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoSecondRetPercentage
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoSpreadOption
  • Project.ProjectCashflow.ProjectCostCode.CodeForecastOption.cfoStartDate
  • Project.ProjectCashflow.ProjectCostCode.CodesToTask.cttCodeID
  • Project.ProjectCashflow.ProjectCostCode.CodesToTask.cttCodePercentage
  • Project.ProjectCashflow.ProjectCostCode.CodesToTask.cttID
  • Project.ProjectCashflow.ProjectCostCode.CodesToTask.cttTaskID
  • Project.ProjectCashflow.ProjectCostCode.CodesToTask.cttTaskPercentage
  • Project.ProjectCashflow.ProjectCostCode.Forecast.fCostCodeID
  • Project.ProjectCashflow.ProjectCostCode.Forecast.fDate
  • Project.ProjectCashflow.ProjectCostCode.Forecast.fForecast
  • Project.ProjectCashflow.ProjectCostCode.Forecast.fID
  • Project.ProjectCashflow.ProjectCostCode.Order.orCashflowID
  • Project.ProjectCashflow.ProjectCostCode.Order.orDate
  • Project.ProjectCashflow.ProjectCostCode.Order.orDatePaid
  • Project.ProjectCashflow.ProjectCostCode.Order.orID
  • Project.ProjectCashflow.ProjectCostCode.Order.orOrgID
  • Project.ProjectCashflow.ProjectCostCode.Order.orProjectCostCodeID
  • Project.ProjectCashflow.ProjectCostCode.Order.orProjectID
  • Project.ProjectCashflow.ProjectCostCode.Order.orRef
  • Project.ProjectCashflow.ProjectCostCode.Order.orScope
  • Project.ProjectCashflow.ProjectCostCode.Order.orSupplyEnd
  • Project.ProjectCashflow.ProjectCostCode.Order.orSupplyStart
  • Project.ProjectCashflow.ProjectCostCode.Order.orValue
  • Project.ProjectWorkflow.Task.CodesToTask.cttCodeID
  • Project.ProjectWorkflow.Task.CodesToTask.cttCodePercentage
  • Project.ProjectWorkflow.Task.CodesToTask.cttID
  • Project.ProjectWorkflow.Task.CodesToTask.cttTaskID
  • Project.ProjectWorkflow.Task.CodesToTask.cttTaskPercentage
  • Project.ProjectWorkflow.Task.Note.DueDate
  • Project.ProjectWorkflow.Task.Note.IsComplete
  • Project.ProjectWorkflow.Task.Note.NoteID
  • Project.ProjectWorkflow.Task.Note.NoteText
  • Project.ProjectWorkflow.Task.Note.OriginatorID
  • Project.ProjectWorkflow.Task.Note.OriginatorKey
  • Project.ProjectWorkflow.Task.ProjectDependency.pdConnectionType
  • Project.ProjectWorkflow.Task.ProjectDependency.pdDependentTaskID
  • Project.ProjectWorkflow.Task.ProjectDependency.pdDependsOnTaskID
  • Project.ProjectWorkflow.Task.ProjectDependency.pdDurationType
  • Project.ProjectWorkflow.Task.ProjectDependency.pdID
  • Project.ProjectWorkflow.Task.ProjectDependency.pdOffset
  • Project.ProjectWorkflow.Task.ProjectTaskLink.ptlID
  • Project.ProjectWorkflow.Task.ProjectTaskLink.ptlLinkID
  • Project.ProjectWorkflow.Task.ProjectTaskLink.ptlTaskID
  • Project.ProjectWorkflow.Task.TaskCompletion.tcDate
  • Project.ProjectWorkflow.Task.TaskCompletion.tcID
  • Project.ProjectWorkflow.Task.TaskCompletion.tcNotes
  • Project.ProjectWorkflow.Task.TaskCompletion.tcStatus
  • Project.ProjectWorkflow.Task.TaskCompletion.tcTaskID
  • Project.ProjectWorkflow.Task.TaskCompletion.tcUserID
  • Project.Appraisal.AppraisalUnitDetail.Property.Note.DueDate
  • Project.Appraisal.AppraisalUnitDetail.Property.Note.IsComplete
  • Project.Appraisal.AppraisalUnitDetail.Property.Note.NoteID
  • Project.Appraisal.AppraisalUnitDetail.Property.Note.NoteText
  • Project.Appraisal.AppraisalUnitDetail.Property.Note.OriginatorID
  • Project.Appraisal.AppraisalUnitDetail.Property.Note.OriginatorKey
  • Project.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdAttributeID
  • Project.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdID
  • Project.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdPropertyID
  • Project.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdValue
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocAccountCodeID
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocAmount
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocAppraisalDescriptionID
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocDate
  • Project.ProjectCashflow.Appraisal.AppraisalDescription.AppraisalDetailedOnCost.adocID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.prAppraisalUnitID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.prCaption
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.prID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.prIsParent
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.prOrder
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.prParentPropertyID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.prProjectID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.Note.DueDate
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.Note.IsComplete
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.Note.NoteID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.Note.NoteText
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.Note.OriginatorID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.Note.OriginatorKey
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdAttributeID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdPropertyID
  • Project.ProjectCashflow.Appraisal.AppraisalUnitDetail.Property.PropertyDetail.prdValue
Let me know if this is of use to you.

Wednesday 12 August 2009

Thread safe collection binding in WPF

The Microsoft Dot Net framework version 3.5 supports powerful binding between business objects and user interface elements. Before we get into the thorny cross-threading issues on collections as promised in the title, let's quickly lay some groundwork to establish a common understanding...
Let's say you have a business object like this:
   1:  public class MyObject
   2:  {
   3:      public string MyProperty { get; set; }
   4:  }
You now want to bind it to a TextEdit control on a WPF form. A trivial example would be to create a new WPF Form then dump the following inside the default control:-
Change the constructor in the code-behind to look like this:-
   1:  public Window1()
   2:  {
   3:      InitializeComponent();
   4:   
   5:      var obj = new MyObject
   6:      {
   7:          MyProperty = "Hello World"
   8:      };
   9:   
  10:      textBox1.SetBinding(
  11:          TextBox.TextProperty,
  12:          new Binding
  13:      {
  14:          Path = new PropertyPath("MyProperty"),
  15:          Source = obj,
  16:          Mode = BindingMode.TwoWay
  17:      });
  18:   
  19:  }
In this example we are creating an instance of MyObject, and at the same time initialising the MyProperty value on it. We then bind that property to the Text property on the TextEdit control. As you can see, I don't much care for writing bindings into the XAML. It may be less typing to do so, but I very seldom bind to static objects that exist at start-up. Most of my real-world programs tend to create bindings at run-time. Maybe I'll blog about that later. For now, just accept my word for it that this is the only "proper" way to bind in WPF.
The example we are building so far will work. But if you later change the MyProperty value of the object instance obj, the user-interface will not be updated. This is because the default setter of MyProperty does not tell anyone that the property is about to, or has changed. So let's fix that quickly...
   1:  public class MyObject : INotifyPropertyChanged
   2:  {
   3:      public MyObject() {    }
   4:   
   5:      string _myProperty;
   6:      public string MyProperty
   7:      {
   8:          get { return _myProperty; }
   9:          set
  10:          {
  11:              if (_myProperty != value)
  12:              {
  13:                  _myProperty = value;
  14:                  OnChange("MyProperty");
  15:              }
  16:          }
  17:      }
  18:   
  19:      public event PropertyChangedEventHandler PropertyChanged;
  20:      // helper method
  21:      protected void OnChange(string propName)
  22:      {
  23:          // is anyone listening?
  24:          if (PropertyChanged != null)    
  25:          {
  26:              var e = new PropertyChangedEventArgs(propName);
  27:              PropertyChanged(this, e);
  28:          }
  29:      }
  30:  }
These source objects usually need to implement either INotifyPropertyChanged or INotifyCollectionChanged, which is really simple and easy to do. Each time a public property (that can act as a binding source) is changed, you need to raise a PropertyChanged event, which means there's usually only a few changes to be made to a class to enable it to be bound.