CODEGATOR

.NET info worth sinking your teeth into!
Welcome to CODEGATOR Sign in | Join | Help
in Search

Martin Cook

Yet another C# developer with a blog.
  • WPF / Visual Studio 2008 Unit Test Workaround

    In my day job I have been closing down my last project and ramping up for a new one. Project transitions don't often leave me with much time to have fun on CODEGATOR. Sad Oh well, I have to pay the bills somehow, right?

    One thing I did want to blog about today was a workaround I discovered for a bug that's been perplexing me for awhile. I have been re-creating some of my Windows code for WPF, including all of my customized common dialogs. The dialogs all work great in an application but they fail during automated unit testing in Visual Studio 2008. Specifically, a unit test that calls a method to create and display a Window object fails with the following message:

    "An unhandled exception of type 'System.Runtime.InteropServices.InvalidComObjectException' occurred in PresentationCore.dll Additional information: COM object that has been separated from its underlying RCW cannot be used."

     

    Here is a quick sample of code that causes this problem: 

    class AboutBoxDialog

    {

        public bool ShowDialog()

        {

            System.Windows.Window w = new System.Windows.Window();

            w.ShowDialog();

        }

    }

    Nothing fancy, just some code to display a WPF window. Now, here is the code for my unit test:

    [TestClass()]

    public class AboutBoxDialogTest

    {

        [TestCleanup]

        public void TestCleanup()

        {

            // Required due to a bug in the current release of WPF/VS 2008.

            System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown();

        }

     

        [TestMethod()]

        public void Test1()

        {

            AboutBoxDialog target = new AboutBoxDialog();

            target.ShowDialog();

        }

    }

    Now, without the code in the TestCleanup method, this unit test fails after the window is closed. Why? I don't know, but the problem drove me nuts for days before I ran across a solution on Microsoft's website. Here is a link to the original source. I don't know exactly which versions of Visual Studio / WPF are affected by this problem.

     

    See ya!

  • JobAPI - A First Draft

    I've been working on an API for my CG.Job library. Here is what I have so far:

     

    /// <summary>

    /// Represents the public API for the CODEGATOR job library.

    /// </summary>

    public static class JobApi

    {

        /// <summary>

        /// Creates a job entry in the system and schedules the job

        /// for execution.

        /// </summary>

        /// <typeparam name="J">The job type.</typeparam>

        /// <param name="job">The job to be created.</param>

        public static void Create<J>(J job)

            where J : JobBase, new();

     

        // ******************************************************************

     

        /// <summary>

        /// Removes a job from the system.

        /// </summary>

        /// <param name="jobId">The job to be deleted from the system.</param>

        public static void Delete(Guid jobId);

     

        // ******************************************************************

     

        /// <summary>

        /// Saves changes to a job's properties.

        /// </summary>

        /// <typeparam name="J">The job type.</typeparam>

        /// <param name="job">The job to be saved.</param>

        public static void Update<J>(J job)

            where J : JobBase, new();

     

        // ******************************************************************

     

        /// <summary>

        /// Returns a specific job from the system.

        /// </summary>

        /// <typeparam name="J">The job type</typeparam>

        /// <param name="jobId">The identifier for the job.</param>

        /// <returns>A <see cref="JobBase"/> instance.</returns>

        public static J Read<J>(Guid jobId)

            where J : JobBase, new();

     

        // ******************************************************************

     

        /// <summary>

        /// Returns a list of all the jobs in the system.

        /// </summary>

        /// <returns>A list of <see cref="JobBase"/> instances.</returns>

        public static List<JobBase> ReadAll();

     

        // ******************************************************************

     

        /// <summary>

        /// Returns a list of jobs that match a specified type.

        /// </summary>

        /// <typeparam name="J">The job type to search for.</typeparam>

        /// <returns>A list of <see cref="JobBase"/> instances.</returns>

        public static List<J> ReadAll<J>()

            where J : JobBase, new();

    }

     

    I'll probably modify the way I'm searching for jobs in the future, but for now, I'm keeping things simple. I'll have a better idea of what I'll need when I eventually create a UI for the library.

    For now, you can use the JobApi class to manage jobs in the CG.Job library. I intend the JobApi class to be only class that developers will need to deal with in order to use my library.

     

    See ya!

  • Job Library & Server (An Idea)

    Background 

    Sometimes my day job as a consultant occasionally involves scheduling small units of work to run periodically without any user-interaction. I know that the Microsoft O/S includes a scheduler but that has never really met my needs (go figure). In the past I have written schedulers for various customers in C++ and C#, so I decided to write another one for CODEGATOR and target my efforts specifically at .NET developers.

    What I have so far is split into two projects: CG.Job & CG.Job.Server. The first project is a library that can be used to schedule and manage jobs. The second project is a Windows service that periodically checks for jobs in need of scheduling, and runs those jobs automatically. The details of my architecture are a little sketchy, since I'm in the process of porting old code and writing new code besides. Things will likely change, but for now, this is how I envision things working:

     

    Overview 

    I am currently using an abstract class named JobBase as a base for custom jobs. Developers who want to run their jobs can simply derive from my base class and code their own jobs. Something like this: 

    [Serializable]

    public sealed class ExampleJob : JobBase

    {

        public override void Run()

        {

            System.Threading.Thread.Sleep(1000);

        }

    }

    Eventually, if I want to be able to run remote jobs (code on other machines), I'll have to do things a little differently. But for now, I've decided to start simple and get something up and running. So, to design custom jobs just derive from JobBase and override the Run method. Pretty easy, right? The only caveat will be that the CG.Job.Server will need to be able to load your custom assembly at runtime, so you'll probably need to put your assembly in the GAC. Maybe not, I'll think about that some more down the road...

    I am currently using my CG.Storage library to handle the storage and retrieval of job objects. That way I can decide later whether I want to store them in a file, in a database, or whatever.

    I am exposing a single class named JobAPI from the CG.Job library. I envision everyone using that API to schedule and manage jobs. Here is how I see that working:

    ExampleJob job = new ExampleJob();

    job.RunAt = DateTime.Now.AddSeconds(15);

    JobApi.Create<ExampleJob>(job); 

    Of course, there will be other methods for updating jobs, deleting jobs, etc.

     

    Conclusion

    The idea is, you can use the CG.Job library from your code to schedule your custom job objects, which will be serialize and stored into some central storage location known only to the CG.Storage library, where the CG.Job.Server Windows service will, at some later time, run that job on your behalf. I also figure I'll use my CG.Task library to actually run the jobs in separate threads.

    I've put what I have so far in the downloads section of CODEGATOR. The code that's there actually runs, but you'll have to install the service manually using the 'InstallUtil' utility. You'll also have to create a database for the CG.Storage library, or re-configure the app.config to use the disk based storage provider. If you decide to use the databasde provider then the .SQL file is located in the CG.Storage project under the 'Data\Provider' folder.

    One of these days I'll write a nifty .MSI installer for all of this code - after I get it written of course... LOL!

     

    Sound cool? I think so. Now I just have to make it work....  Um, I'll see ya later; I've got some work to do. Big Smile

  • Back to Yesterday.

    I have been contacted recently by several people regarding some old projects that I had removed from the downloads section. The projects in question are outdated and rely on other outdated libraries that I no longer care to mess with. I cleaned things up to avoid confusion and free up space on my webserver.

    In response to the comments & emails I have added the following projects back to the downloads section, where they will stay for the immediate future. I will not support these projects or make bug fixes to them. Some of them have references to other projects that I wont include on the website again. The projects may not compile but the code is there for viewing. 

    The projects are:

    • CG.Security
    • CG.Security.Sql
    • CG.Wizard

    Please don't email me asking for help with these libraries. Please don't email me to say that they wont compile, or that they wont work on your platform/language/project.

    See ya.

  • CG.Storage - Some Examples

    Introduction

    If you are like me, then you have written code to store files/blobs/data/objects/whatever into databases at least a thousand times. I have written that code so often in my career that I finally decided to put the functionality into a library that I could reuse. That's the reasoning behind my CG.Storage library. CG.Storage is a small reusable library that can be used to store and retrieve things. It's like storing a BLOB into a database except that my code adds a layer of abstraction that allows for storing into other devices (like a file on a disk drive) by changing a single line of text in a configuration file.

     

    StorageManager

    The library contains a single "manager" class (see my CG.Manager library for an explaination of what a manager is) called StorageManager that provides the following methods:

    /// <summary>

    /// Stores the contents of the specified stream.

    /// </summary>

    /// <param name="stream">A stream containing data to be copied to storage.</param>

    /// <returns>An identifier that represents the newly created storage

    /// record.</returns>

    public virtual Guid Create(

        Stream stream

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Stores the contents of the specified file.

    /// </summary>

    /// <param name="filePath">The path to a file whose contents will be

    /// copied to storage.</param>

    /// <returns>An identifier that represents the newly created storage

    /// record.</returns>

    public virtual Guid Create(

        string filePath

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Stores a byte array.

    /// </summary>

    /// <param name="buffer">An array of data that will be copied to

    /// storage.</param>

    /// <returns>An identifier that represents the newly created storage

    /// record.</returns>

    public virtual Guid Create(

        byte[] buffer

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Stores an object reference.

    /// </summary>

    /// <param name="obj">An object whose state will be copied to

    /// storage.</param>

    /// <returns>An identifier that represents the newly created storage

    /// record.</returns>

    public virtual Guid Create<T>(

        T obj

        )

        where T : class, new();

     

    // ******************************************************************

     

    /// <summary>

    /// Updates a storage record with the contents of the specified file.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="stream">A stream containing data to be copied to

    /// the storage record.</param>

    public virtual void Update(

        Guid id,

        Stream stream

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Updates a storage record with the contents of the specified file.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="filePath">The path to a file that will be read from.</param>

    public virtual void Update(

        Guid id,

        string filePath

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Updates a storage record with the contents of an array.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="buffer">An array of data that will be copied to

    /// storage.</param>

    public virtual void Update(

        Guid id,

        byte[] buffer

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Updates a storage record with the contents of an array.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="obj">An object whose state will be copied to storage.</param>

    public virtual void Update(

        Guid id,

        object obj

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Deletes the specified storage record.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    public virtual void Delete(

        Guid id

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Reads the contents of the specified storage record.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="stream">A stream to contain the data for the storage

    /// record.</param>

    public virtual void Read(

        Guid id,

        out Stream stream

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Reads the contents of the specified storage record.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="filePath">The path to a file that will be created

    /// to contain a copy of the data for the storage record. Note that

    /// this file will be overwritten!</param>

    public virtual void Read(

        Guid id,

        string filePath

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Reads the contents of the specified storage record.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="buffer">A buffer containing the data for the

    /// specified storage record.</param>

    public virtual void Read(

        Guid id,

        out byte[] buffer

        );

     

    // ******************************************************************

     

    /// <summary>

    /// Reads the contents of the specified storage record.

    /// </summary>

    /// <param name="id">The identifier for the storage record.</param>

    /// <param name="obj">A deserialized instance of the specified

    /// storage record.</param>

    public virtual void Read<T>(

        Guid id,

        out T obj

        )

        where T : class, new();

     

    // ******************************************************************

     

    /// <summary>

    /// Returns a list of identifiers to represent all the storage records.

    /// </summary>

    /// <returns>A list of identifiers.</returns>

    public virtual List<Guid> FindAll();

    So, looking at these method signatures it should be clear that my StorageManager class handles creating, reading, updating, and deleting files, streams, byte arrays, and serializable objects. The idea behind StorageManager is that you give it something to store and it gives you back a GUID that you can use later to retrieve what you just stored. StorageManager doesn't remember any metadata related to what you store, it just stores things and retrieves them. The lack of associated metadata keeps the code simple and increases the likelihood that the CG.Storage library could be incorporated into existing projects (both mine and yours). Of course, the lack of metadata also means that you can't use StorageManager to search for files using metadata like keywords, etc. For that, you'll either have to write your own code or use my CG.Archive library. (Yep, CG.Archive is in the downloads section of CODEGATOR)

    Library Configuration

    Let's start by adding some configuration XML that CG.Storage requires at runtime:

    <?xml version="1.0" encoding="utf-8" ?>

    <configuration>

      <configSections>

        <section name="CG.Storage.StorageManager" type="CG.Manager.ManagerConfigurationSection, CG.Manager, Culture=neutral, PublicKeyToken=a9f0e63f80ee0325"/>

      </configSections>

      <connectionStrings>

        <clear />

        <add name="LocalHostSqlServer" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=cg_storage;Integrated Security=True"/>

      </connectionStrings>

      <CG.Storage.StorageManager defaultProvider="DiskStorageProvider">

        <providers>

          <add name="DbStorageProvider" type="CG.Storage.Data.Provider.DbStorageProvider, CG.Storage, Culture=neutral, PublicKeyToken=a9f0e63f80ee0325" connectionString="LocalHostSqlServer" chunkSize="8040" />

          <add name="DiskStorageProvider" type="CG.Storage.Data.Provider.DiskStorageProvider, CG.Storage, Culture=neutral, PublicKeyToken=a9f0e63f80ee0325" rootFolder="c:\RootFolder" chunkSize="8040" />

        </providers>

      </CG.Storage.StorageManager>

    </configuration>

    (Sorry, I don't know of any way to pretty up XML - see why I almost never include XML in my blog?) This configuration XML is stored in a file called App.config, and should be incorporated into your application. It configures the StorageManager class to load a provider at runtime. That provider performs the actual storage on behalf of the StorageManager class. As shown, StorageManager is configured to use the DiskStorageProvider, and will store things into a folder on the hard drive at the 'C:\RootFolder' folder on your hard drive. The SQL-Server provider can also be used by simply changing the defaultProvider attribute to 'DbStorageProvider' and changing the connectionString to point to your database. Both the disk and database providers are included in my CG.Storage library. You can also easily create your own storage provider be extending my base provider class. That's a subject for another blog entry though...

    Sample Code

    OK, so we have configured things, now, how do we use the StorageManager? Here is a quick example of storing a file:

    StorageManager target = Singleton<StorageManager>.Default;

    string filePath = Path.GetTempFileName();

    File.AppendAllText(filePath, "hello world");

    Guid id = target.Create(filePath);

    Pretty easy stuff. You give StorageManager a file path and it stores the contents of the file internally and gives you back a GUID.

    Here is an example of storing an object. Let's start by defining a class for our example:

    [Serializable]

    public class TestObject

    {

        private string name;

     

        public string Name

        {

            get { return name; }

            set { name = value; }

        }

     

        public TestObject()

        {

            name = string.Empty;

        }

    }

     Now here is the code to create an instance of TestObject and store it:

    StorageManager target = Singleton<StorageManager>.Default;

    TestObject objA = new TestObject();

    objA.Name = "hi there!";

    Guid id = target.Create<TestObject>(objA);

    Once again, easy stuff. Retrieving things is just as easy. Here is an example of that:

    StorageManager target = Singleton<StorageManager>.Default;

    TestObject objB = null;

    Guid id = // set id to a valid GUID

    target.Read<TestObject>(id, out objB);

     

    Conclusion

    CG.Storage is a good "drop-in" tool for whenever you need to store things in your project and you don't feel like writing your own custom SQL code. It also partially answers an age-old question related to storing BLOB's, which is, should they be stored in a file or in the database. If you are using my StorageManager class then you can decide for yourself and setup your configuration accordingly. If you change your mind later you can simply tweak the XML instead of rewriting your application.

    The library code was recently factored out of another library and is still under development. I have performed basic testing of what I've deployed to CODEGATOR and everything seems to be working. Look for further changes to CG.Storage down the road.

     

    Finally, I didn't bother to explain the internals of the CG.Storage library today but I may do that in the near future when the codebase settles down a bit more. 'Till then, download the CG.Storage library and stop writing the same old "store a BLOB in a database" code over and over again! Sheesh! Don't we have better things to do with our time???

     

  • Smaller CODEGATOR Libraries

    I refactored my libraries recently. The new projects are more focused, and each contain fewer classes. I hope the change will make them easier to maintain and ultimately more useful - for myself as well as everyone else. The projects mostly contains the same code, they're just arranged a little differently.

     

    The new libraries are:

    CG.Core - This library contains common foundational code that most other CODEGATOR libraries are built on. If you are using another CODEGATOR library then chances are you'll need this library as well. 

    CG.Storage - A "no frills" file storage library. The library contains a provider that stores the files in a SQL-Server database and another that stores the files in a folder on the file-system. This library abstracts the storing of files for other CODEGATOR libraries.

    CG.Archive - A "no-frills" content management library that provides for basic storage and retrieval of files. This library builds on the CG.Storage library, while adding the ability to associate meta-data with files and perform searches using keywords. This library is also capable of managing multiple versions of files.

    CG.Manager - This library contains the classes that implement the Manager pattern used in other CODEGATOR libraries. If you are using a manager from a CODEGATOR library then chances are you'll need this library as well.

    CG.Manager.EnterpriseLibrary - This lbrary adapts the CG.Manager library for use with the Microsoft's Enterprise Library. If you want to use Enterprise Library with a manager then you'll need this library.

    CG.Data - This library contains code that is specifically related to data storage and retrieval.

    CG.Task - This library contains the TaskDirector component and all it's support classes. TaskDirector is a BackgroundWorker like component that is capable of directing multiple background operations simultaneously

    CG.Event - This library contains a fully functional event broker. It can be used to loosly couple software in almost any environment, including ASP.NET, Windows, WPF, WCF, etc.

    As always, these libraries can be downloaded by visiting the DOWNLOADS section of this website.

    See ya!

  • My virtual ListView class.

    While digging around in my old code folder I recently stumbled across a bunch of Windows controls that I wrote back in 2005. I thought I would share some of them on CODEGATOR, starting with my virtual ListView. Unlike other virtual ListView implementations I've seen on the Internet, this one caches list items correctly. Of course, I had to "tweak" my code a bit before I published it here.  (What is it about old source code that always makes one want to start rewriting for no apparent reason?). The code ran fine when I was building the sample code. 

    I included a couple of nice bug fixes that I noticed on *** Grano Salis' website this week. I'm including his bug fixes as part of my code listing in the interest of completeness. A big toothy CODEGATOR grin goes out to him for the bug fixes.

     

    Here is a listing for the code:

    /// <summary>

    /// A virtual <see cref="ListView"/> class.

    /// </summary>

    public partial class VirtualListView : ListView

    {

     

        // ******************************************************************

        // Events.

        // ******************************************************************

     

        #region Events

     

        /// <summary>

        /// Fired whenever the list needs to create a new cache item.

        /// </summary>

        public event EventHandler<CreateVirtualListItemEventArgs> CreateVirtualListItem;

     

        /// <summary>

        /// Fired whenever the list has cleared the internal cache.

        /// </summary>

        public event EventHandler ClearVirtualCache;

     

        #endregion

     

        // ******************************************************************

        // Fields.

        // ******************************************************************

     

        #region Fields

     

        private Dictionary<int, ListViewItem> lviCache = new Dictionary<int, ListViewItem>();

     

        #endregion

     

        // ******************************************************************

        // Properties.

        // ******************************************************************

     

        #region Properties

     

        /// <summary>

        /// Gets or sets the virtual mode for the list.

        /// </summary>

        [Browsable(false)]

        public new bool VirtualMode

        {

            get { return base.VirtualMode; }

            set { base.VirtualMode = true; }

        }

     

        // ******************************************************************

     

        /// <summary>

        /// Gets and sets the size of the virtual list.

        /// </summary>

        public new int VirtualListSize

        {

            get { return base.VirtualListSize; }

            set

            {

                // Clear the cache.

                OnClearVirtualCache();

     

                // Save the new list size (which will rebuild the cache).

                base.VirtualListSize = value;

            }

        }

     

        #endregion

     

        // ******************************************************************

        // Constructors.

        // ******************************************************************

     

        #region Constructors

     

        /// <summary>

        /// Creates a new instzance of the <see cref="VirtualListView"/> class.

        /// </summary>

        public VirtualListView()

        {

            InitializeComponent();

        }

     

        #endregion

     

        // ******************************************************************

        // Public methods.

        // ******************************************************************

     

        #region Public methods

     

        /// <summary>

        /// Clears the internal cache of items.

        /// </summary>

        public void ClearItemCache()

        {

            OnClearVirtualCache();

        }

     

        #endregion

     

        // ******************************************************************

        // Overrides.

        // ******************************************************************

     

        #region Overrides

     

        /// <summary>

        /// Overridden in order to setup the virtual list properties.

        /// </summary>

        protected override void OnCreateControl()

        {

            // Give the base class a chance.

            base.OnCreateControl();

     

            // Setup the virtual list properties.

            base.VirtualMode = true;

        }

     

        // ******************************************************************

     

        /// <summary>

        /// Overridden in order to fetch virtual items from the cache

        /// </summary>

        /// <param name="e">The event arguments.</param>

        protected override void OnRetrieveVirtualItem(

            RetrieveVirtualItemEventArgs e

            )

        {

            // Give the base class a chance.

            base.OnRetrieveVirtualItem(e);

     

            ListViewItem lvi;

     

            // Check the cache first.

            if (lviCache.TryGetValue(e.ItemIndex, out lvi))

            {

                e.Item = lvi;

                return;

            }

     

            // Create an item and store it in the cache.

            e.Item = OnCreateVirtualListItem(e.ItemIndex);

            lviCache[e.ItemIndex] = e.Item;

        }

     

        #endregion

     

        // ******************************************************************

        // Protected methods.

        // ******************************************************************

     

        #region Protected methods

     

        /// <summary>

        /// Fires the <see cref="CreateVirtualListItem"/> event.

        /// <see cref="ListVireItem"/> for the internal cache.

        /// </summary>

        /// <param name="index">The index of the item.</param>

        /// <returns>A <see cref="ListViewItem"/> instance.</returns>

        protected virtual ListViewItem OnCreateVirtualListItem(

            int index

            )

        {

            // Should we fire the event?

            if (CreateVirtualListItem != null)

            {

                // Create the arguments.

                CreateVirtualListItemEventArgs e =

                    new CreateVirtualListItemEventArgs(index);

     

                // Fire the event.

                CreateVirtualListItem(this, e);

     

                // Should we validate the item?

                if (e.Item != null)

                {

                    // Loop and check each item in the list.

                    foreach (ListViewItem.ListViewSubItem subItem in e.Item.SubItems)

                    {

                        // If an items text is 260 characters long, add a space so it does

                        // not crash the program. Thanks to *** Grano Salis for the

                        // fix.

                        // https://blogs.msdn.com/cumgranosalis/archive/2006/03/18/ListViewVirtualModeBugs.aspx

                        if (subItem.Text.Length == 260)

                            subItem.Text = subItem.Text + " ";

                    }

                }

                // Return the new item.

                return e.Item;

            }

     

            // Return something by default.

            return new ListViewItem();

        }

     

        // ******************************************************************

     

        /// <summary>

        /// Fires the <see cref="ClearVirtualCache"/> event.

        /// </summary>

        protected virtual void OnClearVirtualCache()

        {

            // Should we fire the event?

            if (ClearVirtualCache != null)

                ClearVirtualCache(this, EventArgs.Empty);

     

            // Clear the internal cache.

            lviCache.Clear();

        }

     

        #endregion

    }

    I handle the caching of items by using a Dictionary object, which is much easier than using an array of ListViewItem instances. I cache the items after I create them inside the OnRetriveVirtualItem method. Managing the ListView items in a single method is easier than trying to deal with the OnCacheVirtualItems method, which I think is completely useless since I have to cache the items as I create then inside my OnRetrieveVirtualItem override anyway.

    The VirtualListView class exposes two public events:

    • CreateVirtualListItem - fired when the ListView needs a ListViewItem.
    • ClearVirtualCach - fired when the ListView clears it's internal cache.

     

    Here is a listing for the CreateVirtualListItem event arguments class:

    /// <summary>

    /// The arguments for the CreateVirtualListItem event on the

    /// <see cref="VirtualListView"/> class.

    /// </summary>

    public class CreateVirtualListItemEventArgs : EventArgs

    {

     

        // ******************************************************************

        // Fields.

        // ******************************************************************

     

        #region Fields

     

        private int index;

        private ListViewItem lvi;

     

        #endregion

     

        // ******************************************************************

        // Properties.

        // ******************************************************************

     

        #region Properties

     

        /// <summary>

        /// Gets the index of the virtual list item.

        /// </summary>

        public int Index

        {

            get { return index; }

        }

     

        // ******************************************************************

     

        /// <summary>

        /// Gets and sets the list view item.

        /// </summary>

        public ListViewItem Item

        {

            get { return lvi; }

            set { lvi = value; }

        }

     

        #endregion

     

        // ******************************************************************

        // Constructors.

        // ******************************************************************

     

        #region Constructors

     

        /// <summary>

        /// Creates a new instance of the <see cref="CreateVirtualListItemEventArgs"/>

        /// class.

        /// </summary>

        /// <param name="index">The index of the item.</param>

        public CreateVirtualListItemEventArgs(int index)

        {

            this.index = index;

        }

     

        #endregion

     

    }

     

    Using this virtual ListView control is pretty easy, here is a quick example: Start by creating a form in Visual Studio, then drop my VirtualListView control on the form in the designer. It should look like this:

    After that, wire up a handler for the CreateVirtualListItem event and set the size of the list in the form's OnLoad handler. Your code should look like this:

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

     

        private void Form1_Load(object sender, EventArgs e)

        {

            listView1.VirtualListSize = 10000;

        }

     

        private void listView1_CreateVirtualListItem(

            object sender,

            CreateVirtualListItemEventArgs e

            )

        {

            e.Item = new ListViewItem("virtual item " + e.Index);

        }

    }

    Then run your code. The UI should look like this:

     

    Pretty cool huh? (And easy). Of course, you can always intercept the ListView's virtual events and do the caching yourself, but this class makes that effort unnecessary. 

    Have fun!

  • Is this method overridden?

    I recently needed to know if a virtual method had been overridden in one of my base classes. Making that determination wasn't hard and only required a bit of reflection. I thought the solution might make a good blog entry since I'm looking for something to do on my lunch hour today.

    Here is the code, with a description afterwards: 

    class A

    {

        public virtual string Foo()

        {

            if (IsFooOverridden())

                return "overridden A";

            else

                return "A";

        }

     

        private bool IsFooOverridden()

        {

            Type classType = typeof(A);

            Type type = base.GetType();

     

            if (type != classType)

            {

                MethodInfo mi = type.GetMethod(

                    "Foo",

                    BindingFlags.Public | BindingFlags.Instance,

                    null,

                    new Type[0],

                    null

                    );

     

                return mi.DeclaringType != classType;

            }

     

            return false;

        }

    }

     

    // ***********

     

    class B : A

    {

        public override string Foo()

        {

            return base.Foo() + " plus B";

        }

    }

     

    // ***********

     

    class Class1

    {

     

        public static void Test()

        {

            A a = new A();

            A b = new B();

     

            string fooA = a.Foo();

            string fooB = b.Foo();

        }

    }

    Class "A" is a base class with a single virtual method. Class "B" derives from "A" and overrides that method. The "IsOverridden" method in the "A" class is used to determine whether the "Foo" method has been overridden at runtime. They way it does that is by first comparing the type for the base class with a type known at compile time (in this case the "A" type). If the two types don't match the code assumes that the method is being called from a derived type and goes on to check the type associated with the method. If the "Foo" method has been associated with a type other than the "A" class then we know that the method is being called from an override.

    The test method in Class1 creates an instance of "A" and other of "B". It then calls the "Foo" method and saves the result in a variable. If you set a breakpoint just after the calls to "Foo" and run the code you'll see that the "fooA" variable contains "A" and the "fooB" variable contains "overridden A plus B". These results demonstrate how the code in the base class can change depending upon whether the method has been overridden at runtime.

    Nothing complicated but I thought someone might be interested.

    See ya!

    Posted Dec 20 2007, 11:05 AM by Martin with no comments
    Filed under: ,