Search This Blog

Showing posts with label Code Examples. Show all posts
Showing posts with label Code Examples. Show all posts

Saturday, December 29, 2012

RDLC Basics


So less than 6 hrs of time invested and I have basic RDLC reporting working for data displayed in a table / grid.  Also created several multi-column reports.

So here is my basic approach:

My data source is a MS Access MDB file but this would work for SQL server just as easy. Simply a different connection string.

Step #1 - Create a visual dataset with table adapters that can be bound to a report.



Step #2 Create basic RDLC report without using the wizard.

  1.  Add new item, select reporting template, select Report.
  2. In report data panel, select new dataset
  3. Select data source 
  4. Select table adapter
  5. Add table / grid to body of the report.
  6. Assign columns in the table / grid to fields from the tableAdapter field list
  7. Right-click in the report designer below the report body and add page header or page footer as needed.
  8. Add Report name, execution time, and page number from built-in fields
Step #3 Fetch data for report and return it as a data table
Here is  my design approach.  I only use table adapters to bind fields in the designer.  At runtime, I like to manually fetch the data and add it to report data source.  I do this so I can re-use report design and pass slightly different data sets to the same report.  This is similar to the approach I used 10 year ago with MS Access reports.  Not for everyone but it works for me.

I create a function where I can return the data I want by passing in the report name.


public static DataTable GetReportData(string reportName, string MDBfile)
{
try
{
EnterProc(MethodBase.GetCurrentMethod());
string sql = null;
DataTable table = null;
switch (reportName)
{
case "":
break;
case "CollectionSummaryByYear":
sql = "SELECT [Year], COUNT([Year]) AS SpecimenCount FROM tblMineral_Collection WHERE (Len([Year]) > 0) GROUP BY [Year]";
break;
case "FossilLabel_SmallCabinet":
case "FossilLabel_Thumbnail":
sql = "SELECT tblFossils.ID_Text, tblFossils.CommonName, tblFossils.ProperName, tblFossils.RockFormation, tblFossils.RockAge, tblFossils.Location, tblFossils.County, tblFossils.City, lstStates.State, lstCountries.Country, Str([tblFossils].[Year]) AS strYear FROM (tblFossils LEFT JOIN lstCountries ON tblFossils.CountryID = lstCountries.ID) LEFT JOIN lstStates ON (tblFossils.StateID = lstStates.ID) AND (tblFossils.CountryID = lstStates.CountryID);";                        
break;
case "MineralLabel_Cabinet":
case "MineralLabel_SmallCabinet":
case "MineralLabel_Micromount":
case "MineralLabel_Thumbnail":
sql = "SELECT tblMineral_Collection.ID_Text, tblMineral_Collection.Variety, tblMineral_Collection.Mineral_Name, tblMineral_Collection.Mine, tblMineral_Collection.City, tblMineral_Collection.County, lstStates.State, lstCountries.Country, Str([tblMineral_Collection].[Year]) AS StrYear, tblMinerals.Formula_1, tblMinerals.Formula_2 FROM (((LabelQueue INNER JOIN tblMineral_Collection ON LabelQueue.Specimen_ID = tblMineral_Collection.Specimen_ID) LEFT JOIN lstCountries ON tblMineral_Collection.CountryID = lstCountries.ID) LEFT JOIN lstStates ON (tblMineral_Collection.CountryID = lstStates.CountryID) AND (tblMineral_Collection.StateID = lstStates.ID)) LEFT JOIN tblMinerals ON tblMineral_Collection.Mineral_Name = tblMinerals.Mineral";
break;
case "SpeciesList":
sql = "SELECT tblMineral_Collection.Mineral_Name, Count(tblMineral_Collection.Mineral_Name) AS SpecimenCount FROM  tblMineral_Collection WHERE LEN(Mineral_Name)>0 GROUP BY tblMineral_Collection.Mineral_Name";
break;
}
 
if (!string.IsNullOrEmpty(sql))
{
table = TableFromMdb(MDBfile, sql);
}
ExitProc(MethodBase.GetCurrentMethod());
return table;
 
}
catch (Exception ex)
{
ErrorLog(ex);
return null;
}

The only trick to this approach is to pass in the data using name of dataset originally used to setup report.

var rds = new ReportDataSource("DataSet1", args.ReportData);
var report = new LocalReport
{
ReportEmbeddedResource = string.Format("DRC.RDLC.{0}.rdlc", args.ReportName)
};
report.DataSources.Add(rds);



Friday, December 28, 2012

Finally Replacing CrystalReports

I've had a long hate-hate relationship with crystal reports.  Latest version of Crystal Reports via SAP is a 72 MB install!?! Why....  CrystalReport 2008 was only 17 MB.  Leave it to SAP to take a poor solution and make it worse.

So, I've been on a quest to replace crystal Reports.  In VS 2010, I've found a workable client side reporting solution in RDLC.  Now, I know its going to be an uphill battle to produce Avery style labels with RDLC but I'm going uphill 75MB lighter.

The real motivation for this reporting switch is my next version of Digital Rockhound's Companion software is going to be a download users can buy.  I'll trimmed down the files my software uses.  I'm ditching my street level arcview shapefiles dated circa 1995 for Google/Bing/OSM online maps.  Last piece is really the reporting engine.

In a little under 3 hours, I was able to add in a basic grid RDLC report into DRC 3.0 and get the report to display in the report viewer, print directly to a local printer, and render to a PDF and word file formats.

Resources that helped me with RDLC reports:

Brian Hartman's code to manually print a RDLC report.  Code sample Here.

MSDN: Walkthrough: Printing a Local Report without Preview 

Get this error attempting to print a localreport without a RDLC source.
The report definition for report 'xxx' has not been specified.

A couple of useful stackoverflow posts:
Code to convert RDLC to a PDF
Code to convert RDLC to Word.

Article from dotnetsoldier blog

My code to render a simple RDLC report to a word Doc and then launch word:


var rds = new ReportDataSource("DataSet1", args.ReportData);
                        var report = new LocalReport
                            {
                                ReportEmbeddedResource = string.Format("DRC.RDLC.{0}.rdlc", args.ReportName)
                            };

                        report.DataSources.Add(rds);
                        // ReSharper disable RedundantAssignment
                        string encoding = String.Empty;
                        string mimeType = String.Empty;
                        string extension = String.Empty;
                        // ReSharper restore RedundantAssignment

                        Warning[] warnings = null;
                        string[] streamids = null;
                        string wordDocName = args.PDFFileName.ToLower().Replace(".pdf", ".doc");
                        byte[] bytes= report.Render("WORD", null, out mimeType, out encoding, out extension, out streamids, out warnings);
                        using (var fs = new FileStream(wordDocName, FileMode.Create))
                        {
                            fs.Write(bytes, 0, bytes.Length);
                        }
                        if (File.Exists(wordDocName))
                            System.Diagnostics.Process.Start(wordDocName);



//My code to print RDLC locally:

                        var rds = new ReportDataSource("DataSet1", args.ReportData);
                        var report = new LocalReport
                            {
                                ReportEmbeddedResource = string.Format("DRC.RDLC.{0}.rdlc", args.ReportName)
                            };

                        report.DataSources.Add(rds);
                        var reportPrintDoc = new ReportPrintDocument(report)
                            {
                                PrinterSettings = {PrinterName = Properties.Settings.Default.DRC_Printer}
                            };

                        reportPrintDoc.Print();


Friday, February 24, 2012

I love stackoverflow.com

I needed a query to use Rank() row counters within a Group

Here it is:  http://stackoverflow.com/questions/1139719/sql-server-query-for-rank-rownumber-and-groupings

Here is my query and resultset


SELECT
    Rank() over (Partition by EncounterID Order by EncounterID, RefPhysID)+1 as ranks
    ,EncounterID
    , RefPhysID
FROM 
    tbEncountersCourtesyCopies
Group By
    EncounterID, RefPhysID, RefPhysOfficeID 
Order by
    ranks asc

ranks    EncounterID    RefPhysID
2        9              1022
2        12             1095
2        18             91
3        12             1279


Friday, January 20, 2012

How to re-seed identity columns

DBCC CHECKIDENT (yourtable, reseed, 34)

Get identity column seed values

Writing a routine to fetch identity column seed values for tables in a SQL 2008 database
Looking to import data into a database and I need to allow room when I do insert with identity_insert on

                using (SqlConnection conn = new SqlConnection(mMRISConnConfig.ConnectionString))
                {
                    string mrisSQL = @"SELECT IDENT_SEED(TABLE_NAME) AS Seed,
                               IDENT_INCR(TABLE_NAME) AS Increment,
                               IDENT_CURRENT(TABLE_NAME) AS Current_Identity,
                               TABLE_NAME
                               FROM INFORMATION_SCHEMA.TABLES
                               WHERE OBJECTPROPERTY(OBJECT_ID(TABLE_NAME), 'TableHasIdentity') = 1
                               AND TABLE_TYPE = 'BASE TABLE'
                               AND TABLE_NAME in ('W_PATIENT','W_REFERRING_PHYSICIAN','W_REFERRING_PHYSICIAN_LOCATION')";

                    conn.Open();
                    SqlCommand cmd = new SqlCommand(mrisSQL, conn);
                    SqlDataReader reader = cmd.ExecuteReader();
}

Wednesday, January 18, 2012

Using ShowHelp instead of HtmlHelp

My ongoing project is rewriting a database application I first wrote in MS Access 95 to VB6 and now into C#.
97% of the conversion is finally done except for the help file.

I am using HelpNDoc to author a chm help file and I was using the following VB6 code to open topics by topicID


Option Explicit

Public Declare Function HtmlHelp Lib "hhctrl.ocx" Alias "HtmlHelpA" _
        (ByVal hwndCaller As Long, ByVal pszFile As String, _
        ByVal uCommand As Long, ByVal dwData As Long) As Long

Global Const HH_DISPLAY_TOPIC = &H0
Global Const HH_SET_WIN_TYPE = &H4
Global Const HH_GET_WIN_TYPE = &H5
Global Const HH_GET_WIN_HANDLE = &H6

' Display string resource ID or text in a popupwin.
Global Const HH_DISPLAY_TEXT_POPUP = &HE
' Display mapped numeric value in dwdata
Global Const HH_HELP_CONTEXT = &HF
' Text pop-up help, similar to WinHelp's HELP_CONTEXTMENU
Global Const HH_TP_HELP_CONTEXTMENU = &H10
' Text pop-up help, similar to WinHelp's HELP_WM_HELP
Global Const HH_TP_HELP_WM_HELP = &H11

Calling HtmlHelp:

Private Sub cmdHelp_Click()
    Call HtmlHelp(0&, gHelpFile, HH_HELP_CONTEXT, HELP_BookWindow)
End Sub

So to keep my helpfile structure I needed to be able to open help topics by topicID in C#
Here is how to call ShowHelp using topicIDs.
(Needed to invoke ToString() on the int variable to get the value to pass as an object).


public static void OpenHelp(Form currentForm, Int32 helpTopicID)
{
            try           
            {
                System.Windows.Forms.Help.ShowHelp(currentForm, _helpFile, HelpNavigator.TopicId, helpTopicID.ToString());
            }
            catch(Exception ex)
            {
                DRCCommon.Common.ErrorLog(ex);
            }
}

Saturday, September 24, 2011

Open folder to location of a file with C#

Sometimes the simplest things are the hardest for me to remember.

Earlier I had wrote the same code for VB6


string myPath = @"C:\Users\admin\Desktop\fotos";
System.Diagnostics.Process prc = new System.Diagnostics.Process();
prc.StartInfo.FileName = myPath;
prc.Start();

Saturday, September 10, 2011

   C# code to add a column to an existing MS Access Table.

Revised  post to correct the horrible code formatting.  Sorry....



public static void AddDAOTableColumn(string MDBfile, string tableName, string ColumnName, TypeCode dataType, Int32? columnSize, bool AutoNumber)
{
try
{
 
EnterProc(System.Reflection.MethodBase.GetCurrentMethod());
dao.DBEngine DBE = new dao.DBEngine();
dao.Database DB = DBE.OpenDatabase(MDBfile, false, false, "");
 
LogValue(string.Format("Opened Database: {0}", DB.Name));
 
dao.TableDef table = DB.TableDefs[tableName];
 
LogValue(string.Format("Found Table: {0}", tableName));
 
bool bAddColumn = true;
 
if (table != null)
{
foreach (dao.Field fld in table.Fields)
{
if (fld.Name == ColumnName)
{
bAddColumn = false;
break;
}
}
}
else
bAddColumn = false;
 
LogValue(string.Format("Table.{0} exists?:{1}", ColumnName, !bAddColumn));
 
if (bAddColumn)
{
dao.DataTypeEnum columnType;
 
switch (dataType)
{
case TypeCode.Boolean:
columnType = dao.DataTypeEnum.dbBoolean;
break;
case TypeCode.DateTime:
columnType = dao.DataTypeEnum.dbDate;
break;
case TypeCode.Int16:
columnType = dao.DataTypeEnum.dbInteger;                            
break;
case TypeCode.Int32:
columnType = dao.DataTypeEnum.dbLong;                            
break;
case TypeCode.Object:
columnType = dao.DataTypeEnum.dbMemo;
columnSize = null;
break;
case TypeCode.String:
if (columnSize <= 255)
columnType = dao.DataTypeEnum.dbText;
else
{
columnType = dao.DataTypeEnum.dbMemo;
columnSize = null;
}
break;
default:
columnType = dao.DataTypeEnum.dbText;
if (columnSize == null)
columnSize = 50;
break;
}
 
dao.Field newfield = new dao.Field();
newfield.Name = ColumnName;
newfield.Type = (short)columnType;
 
if (newfield.Type == (short) dao.DataTypeEnum.dbText)
newfield.AllowZeroLength = true;
 
if (columnSize != null)
newfield.Size = Convert.ToInt32(columnSize);
 
if (AutoNumber)
newfield.Attributes = (int)dao.FieldAttributeEnum.dbAutoIncrField;
 
table.Fields.Append(newfield);
table.Fields.Refresh();
DB.TableDefs.Refresh();
 
LogValue(string.Format("Created Column: {0}", newfield.Name));
 
DB.Close();
 
table = null;
newfield = null;
DB = null;
DBE = null;
}
ExitProc(System.Reflection.MethodBase.GetCurrentMethod());
}
catch (Exception ex)
{
ErrorLog(ex);
}
}


Sunday, August 28, 2011

C# Code to add a registry Key

Discovered that adding Registry keys is slightly more complicated using C# vs VB6...
Needed to define a user and registry security to a key change....

public static bool AddRegKey(string keyName, string valueName) { try { string user = Environment.UserDomainName + "\\" + Environment.UserName; RegistrySecurity rs = new RegistrySecurity(); rs.AddAccessRule(new RegistryAccessRule(user, RegistryRights.ReadKey | RegistryRights.Delete | RegistryRights.WriteKey | RegistryRights.ChangePermissions, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow)); RegistryKey key = Registry.CurrentUser.OpenSubKey(sDRCRegKey,true); key.SetAccessControl(rs); key.SetValue(keyName, valueName); return true; } catch { return false; } }

Tuesday, July 5, 2011

How can I lock an application after period of user inactivity?

My post on StackOverFlow.com

I have a fat Windows application written in VB6. User must log into the application to use it. I need to log the user out after a period of inactivity. There are over 100 separate forms with one Main form that is always open after the user logs in, so I am looking for an application solution not a form level solution.

Here is the solution I decided upon. I wanted to document it properly. As this is the approach I had envisioned, it is not my code. Someone smarter than I did awhile ago.
I simply implemented the solution into my application.

The app is an multiple-document interface app.

Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal cb As Long)
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer

Private m_hDllKbdHook As Long

Private Type POINTAPI
x As Long
y As Long
End Type
Private Declare Function GetCursorPos Lib "user32.dll" (ByRef lpPoint As POINTAPI) As Long


Global variables to hold DateTime last user activity and if mouse and keyboard activity has occurred

Public KeysHaveBeenPressed As Boolean
Public HasMouseMoved As Boolean
Public gLastUserActivity As Date

Code to detect keyboard activity

Public Function HookKeyboard() As Long
On Error GoTo ErrorHookKeyboard
m_hDllKbdHook = SetWindowsHookEx(WH_KEYBOARD_LL, AddressOf LowLevelKeyboardProc, App.hInstance, 0&)
HookKeyboard = m_hDllKbdHook
Exit Function
ErrorHookKeyboard:
MsgBox Err & ":Error in call to HookKeyboard()." _
& vbCrLf & vbCrLf & "Error Description: " & Err.Description, vbCritical, "Warning"
Exit Function
End Function
Public Sub UnHookKeyboard()
On Error GoTo ErrorUnHookKeyboard
UnhookWindowsHookEx (m_hDllKbdHook)
Exit Sub
ErrorUnHookKeyboard:
MsgBox Err & ":Error in call to UnHookKeyboard()." _
& vbCrLf & vbCrLf & "Error Description: " & Err.Description, vbCritical, "Warning"
Exit Sub
End Sub
Public Function LowLevelKeyboardProc(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Static kbdllhs As KBDLLHOOKSTRUCT
If nCode = HC_ACTION Then
'keys have been pressed
KeysHaveBeenPressed = True
End If
LowLevelKeyboardProc = CallNextHookEx(m_hDllKbdHook, nCode, wParam, lParam)
End Function

Code to detect mouse movement:

Public Sub CheckMouse()
On Error GoTo ErrCheckMouse
Dim p As POINTAPI
GetCursorPos p
If p.x <> LastMouse.x Or p.y <> LastMouse.y Then
HasMouseMoved = True
LastMouse.x = p.x
LastMouse.y = p.y
End If
Exit Sub
ErrCheckMouse:
MsgBox Err.Number & ": Error in CheckMouse(). Error Description: " & Err.Description, vbCritical, "Error"
Exit Sub
End Sub



On the Main parent Form:
Added a timer:

Private Sub muTimer_Timer()
CheckMouse
'Debug.Print "MU Timer Fire"
'Debug.Print "Keyboard:" & KeysHaveBeenPressed & " - " & "Mouse:" & HasMouseMoved
If HasMouseMoved = False And KeysHaveBeenPressed = False Then
If DateDiff("m", gLastUserActivity, Now) > gnMUTimeOut Then
muTimer.Interval = 0

Else
'Debug.Print " dT "; DateDiff("s", gLastUserActivity, Now)
End If
Else
HasMouseMoved = False
KeysHaveBeenPressed = False
gLastUserActivity = Now
End If
'Debug.Print " dT "; DateDiff("s", gLastUserActivity, Now)
End Sub

Also on the MainForm load event:

Private Sub MDIForm_Load()
HookKeyboard
end sub

Private Sub MDIForm_QueryUnload(Cancel As Integer, UnloadMode As Integer)
UnHookKeyboard
end sub

Monday, May 16, 2011

How to Implement a wait() in VB6

Here is how I created a Wait() for VB6.
Needed for a project to allow form code to pause while still allowing a 3rd party control to upload a file to a server.

Public Sub Wait(milliseconds As Integer)
Dim dTimer As Double
dTimer = Timer
Do While Timer dTimer + CDbl(milliseconds / 1000)
DoEvents
Loop
End Sub

Monday, April 11, 2011

Determine Windows version via WIN32 API in VB6

'Get Windows Version
Public Declare Function GetVersionExA Lib "kernel32" _
(lpVersionInformation As OSVERSIONINFO) As Integer

Public Type OSVERSIONINFO
dwOSVersionInfoSize As Long
dwMajorVersion As Long
dwMinorVersion As Long
dwBuildNumber As Long
dwPlatformId As Long
szCSDVersion As String * 128
End Type

Public Function IsVistaOrHigher() As Boolean
Dim osinfo As OSVERSIONINFO
Dim retvalue As Integer
Dim bVista As Boolean

bVista = False

osinfo.dwOSVersionInfoSize = 148
osinfo.szCSDVersion = Space$(128)
retvalue = GetVersionExA(osinfo)

If osinfo.dwPlatformId = 2 Then
If osinfo.dwMajorVersion = 6 Then
bVista = True
End If
End If
IsVistaOrHigher = bVista
End Function

Monday, November 22, 2010

Read Windows Registry settings for VB and VBA Projects

My continuing exercise in comparative languages VB6 vs C#

Read Windows Registry settings for VB and VBA Projects

VB6
GetSetting("DRC", "Config", "DRC_CD")

C#

using Microsoft.Win32;

RegistryKey DRCKEY = Registry.CurrentUser.OpenSubKey("Software\\VB and VBA Program Settings\\DRC\\Config");
object CDPath = DRCKEY.GetValue("DRC_CD");

Thursday, July 22, 2010

A quick way to copy a table row

From http://www.devx.com/tips/Tip/32233

Because I keep looking this up. 

A Quick Way to Copy DataRow
Instead of copying DataRow column by column, the following code copies data in one line from the source to the destination row:


DataTable dtDest = new DataTable();
dtDest = dsActivity.Tables[0].Clone();
foreach(DataRow dr in dsSrc.Tables[0].Rows)
{
DataRow newRow = dtDest .NewRow();
newRow.ItemArray = dr.ItemArray;
dtDest.Rows.Add(newRow);
}

Note: The ImportRow method does the same thing, except that the RowState of source is preserved in the destination, whereas NewRow sets RowState to Added.

Saturday, January 23, 2010

Using PropertyGrid in C#

Stupidly easy.

I am going to stop creating generic UI to display data and use the PropertyGrid as my new default to display object properties

PropertyGrid1.SelectedObject = myObject;

Arrays in C#

Switching between C# and VB6 totally messing up my thinking about arrays.....

Examples of C# Arrays

int[] numbers = new int[5] {1, 2, 3, 4, 5};
string[] names = new string[3] {"Matt", "Joanne", "Robert"};

Saturday, January 2, 2010

SharpMap patch

Something novel, fixed my first bug in open source software: SharpMap
Adding this control as part of my DRC rewrite in C#


Index: Trunk/SharpMap/Data/Providers/DbaseReader.cs
===================================================================
--- Trunk/SharpMap/Data/Providers/DbaseReader.cs    (revision 61030)
+++ Trunk/SharpMap/Data/Providers/DbaseReader.cs    (working copy)
@@ -417,7 +417,16 @@
         {
             baseTable = new FeatureDataTable();
             foreach (DbaseField dbf in DbaseColumns)
-                baseTable.Columns.Add(dbf.ColumnName, dbf.DataType);
+            {
+                try
+                {
+                    baseTable.Columns.Add(dbf.ColumnName, dbf.DataType);
+                }
+                catch(DuplicateNameException)
+                {
+                    baseTable.Columns.Add(string.Format("{0}_1",dbf.ColumnName), dbf.DataType);
+                }
+            }
         }

         internal FeatureDataTable NewTable

Thursday, December 24, 2009

Changing Table Layout panel control dynamically at runtime

Change Column span and Row Span for a control in a tablelayoutpanel at runtime...
TableLayoutPanel1.SetRowSpan(TextBox1, 2)
TableLayoutPanel1.SetColumnSpan(TextBox1, 2)
Change Column and row position of a control in a tablelayoutpanel at runtime...
TableLayoutPanel1.SetRow(TextBox1, 2)
TableLayoutPanel1.SetColumn(TextBox1, 2)

Thursday, October 1, 2009

Numeric formats using string.format()

"
"



























































Numeric Format Specifiers
Specifier Description Example C#
c Currency; specify the number of decimal places  $12,345.00 string.Format("Currency: {0:c}", iNbr)
d Whole numbers; specifies the minimum number of digits - zeroes will be used to pad the result  12345 string.Format("Whole: {0:d}", iNbr)
e Scientific notation; specifies the number of decimal places  1.2345e+004 string.Format("Exponential: {0:e}", iNbr)
f Fixed-point; specifies the number of decimal places  12345.00 string.Format("Fixed: {0:f3}", iNbr)
n Fixed-point with comma separators; specifies the number of decimal places  12,345.00 string.Format("Fixed formatted: {0:n3}", iNbr)
p percentage; specifies the number of decimal places  1,234,500.00% string.Format("Percentage: {0:p2}", iNbr)
x Hexadecimal  3039 string.Format("Hexadecimal: {0:x}", iNbr)

Wednesday, September 2, 2009

Fun with Binary Serialization

Articles to read...
How to serialize an object which is NOT marked as 'Serializable' using a surrogate.
C# Tutorial - Serialize Objects to a File
Serialization in C#
Object Serialization using C#

Sample code - will serialize class with child classes that are not explicitly serializable. (Work in progress though).

SurrogateSelector ss = new SurrogateSelector();
PicasaAlbumAccessorSurrogate aass = new PicasaAlbumAccessorSurrogate();
PicasaEntrySurrogate pess = new PicasaEntrySurrogate();
PicasaFeedSurrogate pfss = new PicasaFeedSurrogate();

ss.AddSurrogate(typeof(AlbumAccessor),
new StreamingContext(StreamingContextStates.All), aass);
ss.AddSurrogate(typeof(PicasaEntry),
new StreamingContext(StreamingContextStates.All), pess);
ss.AddSurrogate(typeof(PicasaFeed),
new StreamingContext(StreamingContextStates.All), pfss);

BinaryFormatter formatter = new BinaryFormatter();
string filename = string.Format("{0}\\album_{1}_{2}_{3}.dat",
System.Environment.CurrentDirectory, _accessor.AlbumAuthor,
_accessor.Name, _accessor.Id);
FileStream fs = new FileStream(filename, FileMode.Create);
formatter.SurrogateSelector = ss;
formatter.Serialize(fs, this);
fs.Close();