Recently in Windows Forms Category

Part one of a series of tutorials explaining how to use the Crystal Image Toolkit.  In this example, I will show you how to quickly add an image thumbnail control to your Windows Form application.

First of all, download the latest Crystal Image Toolkit from my downloads page.  Compile the code using the solution that I provided. 

Designer toolbox

Create a new Windows Forms application in Visual Studio. In the Visual Studio toolbox, add a new tab called Crystal Image Toolkit (to separate it from Crystal Reports) and add the controls found in CrystalToolkit.dll.

step 1 - drop CrystalImageGridView on Form

Select the control called “CrystalImageGridView”.  This is the control that displays the thumbnail images.  Drag this control onto your new Form.

step 2 - set properties

Now go to the Properties window, with “crystalImageGridView1” selected.  Change the Dock property to Fill.  Change the Orientation property to Vertical.

Control after set properties

After you set the properties, the image grid control should look like the above picture.  The blue borders simply give you an idea of what the image items will look like in the selected state.  The broken image bitmap is just a placeholder.  At runtime, your images will be placed inside this blue rectangle.  Yes, you can change many other properties, including the border color, background color, and so on.  But for now, let’s accept the defaults.

Let’s write a little bit of code.  Go to the events tab and double click on the Load event of the Form.  Now that you are in Form1.cs, let’s add this field:

   1: /// <summary>

   2: /// CrystalCollector object, assists in retrieving image files in a specified folder.

   3: /// </summary>

   4: private CrystalCollector _theCollector = null;


The CrystalCollector object is the controller in this framework. 

The collector works with the CrystalImageGridView and the CrystalImageGridModel.  It finds image files set in the ImageLocation property, creates objects to mirror those files as CrystalImageItem objects, and places those within the model.  The collector spawns a background thread to start thumbnailing the image items; this thread sends events to the view to tell it when an image thumbnail is available.  That is what happens behind the scenes, but here in this simple example, you only need to add this method for your Form:

   1: private void InitCollector()

   2: {

   3:     _theCollector =

   4:         CrystalCollectorFactory.DefaultFactory.CreateCollector(CrystalCollectorType.CrystalFileCollector);

   5:  

   6:     // Add the CrystalImageGridView object to the collector.

   7:     // The collector will work with the view to draw the images.

   8:     _theCollector.SetupView(crystalImageGridView1);

   9:  

  10:     // Optional:

  11:     // Set an initial folder to collect images.

  12:     // If no folder is set, collector starts at MyPictures in WinXP or Pictures folder in Vista

  13:     //CrystalCollectorFactory.DefaultFactory.InitCollectorSource("c:\\myImages", _theCollector);

  14:  

  15:     // Tell CrystalFileCollector to collect the images in the ImageLocation folder.

  16:     _theCollector.CollectImages();

  17: }

  18:  

  19: private void Form1_Load(object sender, EventArgs e)

  20: {

  21:     InitCollector();

  22: }

  23:  

  24: protected override void OnFormClosing(FormClosingEventArgs e)

  25: {

  26:     if (_theCollector != null)

  27:     {

  28:         _theCollector.StopCollection();

  29:     }

  30:     base.OnFormClosing(e);

  31: }


InitCollector creates the collector object using a factory.  The type it chooses is a file-based collector.  You could just create a new CrystalFileCollector yourself, but it is better to use the Factory.  This can be used to track down objects getting created/destroyed later.

Once the collector has been created, we call SetupView, passing in the CrystalImageGridView object that we dropped onto the Form.  This must be done before we gather any images.  The collector creates a CrystalImageGridModel object and links up the view to it.  The model must know about certain view properties, which affects how the image items will be displayed.

CollectImages is called at the end, which tells the collector to look at the ImageLocation and start gathering data about the images found there.  In this case, we did not set the ImageLocation—the default is set to your Pictures folder (on Vista) or MyPictures folder (in WinXP).  If you want to initialize another ImageLocation, use the code I commented out above the call to CollectImages.

Form1_Load calls InitCollector.  But there’s also the override to OnFormClosing, which calls the StopCollector method on the collector object.  This call will stop any background threads going on to thumbnail the images.

Simple form with thumbnail image control

When you press F5 and run, you should see the thumbnails for whatever images are in your Pictures folder.  Hopefully, they will be images of friends, family, and pets instead of comic book images!  Play around with this form—you should see that the control responds to resizing events, making the number of image items per row grow or shrink the form.  You can do ctrl-click and shift-click on image items for multiple selection as well.

Thumbnail image location

Where are these thumbnail images stored?  The CrystalThumbnailer object, which does the work of creating them, has a property called ThumbnailLocationRoot.  By default, it is your AppData folder, under the name of your Company and Product name.  The collector creates a sub folder here, based on the hash number of the original location.  It stores the thumbnails here—the largest thumbnail size it needs to display.  In these simple demos, the thumbnails will exist permanently.  I’ve decided the behavior to retain or erase them is application dependent and left the choice up to you.

This is obviously a very simple example, but I have included many sample Form applications in the toolkit.

SimpleImageGridDemo shows a slightly more complex version of this application, by allowing you to open any folder and view the thumbnail images.

PictureShowDemo

PictureShowControllerDemo shows you how to create a more realistic picture viewing application.  A split container is used to hold the CrystalImageGridView in Horizontal orientation on the bottom pane.  The top pane contains the CrystalPictureShow control, which is used to display images, magnify them, and present slideshows.  There’s also a panning window that appears whenever the image is displayed in a non-fit mode.

I haven't posted any articles here for quite a while.  You might think I've given up on the Crystal Toolkit, but not yet!  I've been making various improvements, bug fixes, and enhancements.  It's slowly getting better.  Probably by the time I am done, Windows Forms will be dead!

Here's a big feature, coming very soon in Crystal Toolkit 0.80, that I've wanted for my own image viewing programs: zooming the thumbnail images that are contained within CrystalImageGridView.  Previously I was only able to allow users to set the CellSize property and that was it.  If you had a thumbnail image that was 300 x 300, you were stuck with that until you changed that property and had the internal grid recalculate itself.  It might have worked, but the images would all have to be re-thumbnailed at that new size, and a lot of time would be wasted.

   1: /// <summary>
   2: /// Sets up the Matrix object used for displaying the image.
   3: /// </summary>
   4: /// <param name="gfx">Graphics object used for drawing.</param>
   5: /// <param name="zoomScale">Scale used for matrix.</param>
   6: protected virtual void SetupMainImageMatrix(Graphics gfx, float zoomScale)
   7: {
   8:     gfx.ResetTransform();
   9:  
  10:     // Set up the transformation to handle zooming and panning.
  11:     Matrix mx = new Matrix();
  12:  
  13:     // Account for the scroll position.
  14:     mx.Translate(AutoScrollPosition.X, AutoScrollPosition.Y);
  15:  
  16:     // Account for the zoom factor.
  17:     mx.Scale(zoomScale, zoomScale);
  18:  
  19:     // Set the transform into the global transform for the specified Graphics context.
  20:     gfx.Transform = mx;
  21:  
  22:     // Set the interpolation mode.
  23:     gfx.InterpolationMode = _interpolationMode;
  24: }

To make thumbnail scaling work in the new release, I've taken some code and patterns found in CrystalPictureBox.  In that class, I used a Matrix object and set the scale according to a property I called ZoomFactor.  This enabled me to present an image in CrystalPictureBox at any scale I desired, like from 30% of full size to 300% of full size.

CrystalImageGridView now has a property called ZoomFactor.  Using this, the entire grid can be displayed at a percentage of the full sized grid.  To implement an image grid that goes from small images to medium images to large images, you would just set the ZoomFactor in the image grid, and the object takes care of the rest.

How it works:

Setting up CrystalImageGridView for large thumbnails

First, setup a Form and drop the CrystalImageGridView onto the form.  Set the CellSize property to the largest possible size that you wish to display.  In this case, I chose a cell size of 375 x 375.  If you set it up this way, the thumbnailer object (internal to the grid) will make thumbnail images at this size, providing users with a crisp view.  It's easier to downscale a larger image than it is to upscale a smaller image.  Now, an alternative way to do this could have been a different pattern--where we send out a signal that we are changing the size and thumbnail the viewable images on the spot.  Windows Explorer and several other programs do this.  Perhaps in the future I will provide an alternative method like this.  The advantage of this method is that the image scaling is pretty fast.  The disadvantage is that the thumbnailed image requires more disk space at the largest possible size.

   1: private float[] zoomFactor = { .30f, .45f, .60f, .75f, .90f, 1.0f };

The ZoomFactor property is set by a trackbar in my little example program (which will be in Crystal Toolkit 0.80).  The zoom factors are predefined in array.  At the lower end of the scale (represented by setting the trackbar all the way to the left) we have 30% of the full size 375x375, which is 125x125.  At the upper end, we have 100% of the thumbnail size.  There is a trackbar on the form with Minimum set to 0 and Maximum set to 5, to match the settings in the array.

CrystalImageGridView with ZoomFactor at 30% 

When the program is launched, the default ZoomFactor is set to 0.30 (125x125).  I've got about 24 thumbnails displaying here.  Notice how the text for the image is still visible at this 30% setting, this was actually the trickiest part of the whole deal (and there may be bugs left to find in that regard).  OK, I've got the Hulk selected in the middle of the screen, keep an eye on him.

CrystalImageGridView with ZoomFactor at 50%

Here the ZoomFactor is set close to 50%.  The grid has repositioned the images as the ZoomFactor increased in size.  The Hulk remains selected, in the center of the grid so that the user can view his beautiful face.  Is that a booger in his hose?  Let's zoom it up further.

CrystalImageGridView with ZoomFactor at 100%

Finally, here's what the CrystalImageGridView looks like with the ZoomFactor at 100% (375x375).  We've got two images side by side, the Hulk is still selected and viewable.  And it's not a booger, it's this wild Marvel character called Ant-Man, making a quick exit from the Hulk's gamma irradiated body.

With good luck, I should be posting Crystal Toolkit 0.80 with this feature this Memorial Day.  Nuff said.

Programming, to me, is like one big mashup, thanks to Google and all the much smarter programmers than me who are out there sharing their code. I've learned a lot of cool GDI+ tricks from various programmers and "borrowed" their open source code where I needed it for the Crystal Toolkit. But I think I've learned and lifted from one man in particular: Bob Powell and his GDI tips and tricks website. In trying to create the classes for a cool image viewer, I stumbled into the limitations of the PictureBox control, which Bob has railed against for a while. To prove that there was an alternative, he wrote the ZoomPicBox example, which shows you how to live without it.

CrystalPictureBox ImageSizeMode set to RatioStretch
CrystalPictureBox is a "crystalization" of ZoomPicBox. You can see it here in action in the CrystalImageGridDemo application. CrystalPictureShow (a derivative of CrystalPictureBox) is in the top splitter pane, while CrystalImageGridView is displaying the thumbnails from an image folder on the bottom. If you examine the painting code in CrystalPictureBox, you'll see that I retained Powell's technique of setting up a Matrix with a zoom factor, setting the Matrix in the Graphics object, and using DrawImage. One thing I added to the mix was the ability to center the image within the client rectangle. I did this by adjusting the starting location, based on the zoom factor. To use this mode, all you have to do is make sure the ImageSizeMode is set to RatioStretch in the CrystalPictureBox object.

CrystalPictureBox ImageSizeMode set to Scrollable
When the user starts dragging the trackbar on the lower toolbar, the zoom factor changes. Inside the code, I set ImageSizeMode to Scrollable and also set the zoom factor represented by the trackbar (or combo box). The result is a pretty nice and clean scaling of the image as the user drags the mouse. Give a salute to Captain America in all his glory for his unfortunate "death" (what a joke as I think he will be revived) was revealed this week. And let's hope that Silver Surfer looks as nice in the Fantastic Four movie as he does in this close-up.

CrystalPictureBox simply allows you to display images, zoom to a certain percentage of the original size, ratio stretch within the available rectangle, and gives you a cool gradient background. The concept for Scrollable and RatioStretch size modes came from Gil Klod's Viewer user control on CodeProject. I got very used to switching between these two modes and decided to retain them.

CrystalPictureShow, derived from CrystalPictureBox, is more tightly integrated with CrystalImageGridModel to allow you to navigate through lists of CrystalImageItem objects. In the regular display mode, you can view the image in a window just like the above example. If you press the Slideshow button in the demo, you'll see the sample application take over your whole screen (making the form border, menu, toolbars, etc. disappear) and call CrystalPictureShow's StartSlideShow method.

There are four slideshow effects in this first release of CrystalPictureShow: Slide (image moves in from top, bottom, left or right), Fade (image slowly ghosts into the next one), Spin (second image spins and zooms from the current image), and Iris (second image zooms from the current image in an ellipitical form). There was no way that I could have done these without Bob Powell's help. He used to have a subscription based newsletter called Well Formed, and in a number of these he showed people how to do image transitions using GDI+. Unfortunately, Well Formed is not being published at the moment and there's no way to get them on his website. However, this is where being relentless and using Google searches pays off: I found this article on Google Groups where Mr. Powell graciously published a VB example program of the image transition effects. Which I gratefully gobbled up and converted to C#.

Check out CrystalImageGridDemo for an example of all of these things in action (get the latest version of CrystalToolkit on the Downloads page). I feel like I owe Bob Powell several drinks and a fine dinner.

CrystalImageGridView Demo
The latest Crystal Toolkit version 0.70 has an early alpha version of classes to help you build image viewers using thumbnails. You may have seen the Microsoft Office Picture Viewer, which has a nice filmstrip view of thumbnails in a splitter like view. I wanted to build an image grid view control that has that nice look and feel, but I also wanted to build it in a classic model-view-controller pattern. And I also wanted to have my control use other tools to automatically thumbnail image folders that I need to browse.

Image Grid Vertical
CrystalImageGridView does all of this, working with CrystalImageGridModel, CrystalFileCollector, and the CrystalThumbnailer to provide all of these features. You can see from the screenshots (this demo is included in the zip file on the Downloads page) that you can view the thumbnails docked in the split container on the bottom. Click on the left hand button on the upper toolbar and the split container expands the thumbnail view to fill the client area.

I'll have more bug fixes, little features, and documentation to write later. If you want to play around with this control, do the following:

1. Build Crystal Toolkit and add a reference to your Form project.
2. Drag CrystalImageGridView from the toolbox to your Form, preferrably to a split container.
3. Hook up the Load event to your Form and add this code to the method:

CrystalFileCollector _collector = 

    new CrystalFileCollector();

_collector = new CrystalFileCollector();

_collector.AddView(crystalImageGridView1);

_collector.CollectImages();


It's that easy. Run this simple form you just made, and the image grid starts previewing images from your MyPictures folder. The CrystalFileCollector spins off a background thread and pops the images into the grid as they are thumbnailed. If you don't like that location, you can modify the ImageLocation property.

More later, much more, I hope. If you have any comments, bug fixes, or whatever, contact me at richard {at} attilan [dot] com.

BUG: Invisible CrystalTrackBars
A little over a week ago, a user named MG reported a problem that I couldn't duplicate until now. When he ran the demo program CrystalTrackBarDemo.exe, all the CrystalTrackBar objects were invisible. No trackbar, no ticks, no thumb, only the gradient background remained on the non-transparent objects. After a second user reported this bug on the CodeProject article, I suddenly remembered about my usage of TrackBarRenderer.IsSupported in CrystalTrackBar OnPaint:

protected override void OnPaint(PaintEventArgs e)
{
    if (!TrackBarRenderer.IsSupported)
    {
        this.Parent.Text = 
            "CrystalTrackBar Disabled";
        return;
    }

    DrawTrackBar(e.Graphics);
    DrawTicks(e.Graphics);
    DrawThumb(e.Graphics);
}



On the MSDN Forum, Mick Doherty pointed out that TrackBarRenderer.IsSupported == VisualStylesRenderer.IsSupported. According to the documentation, one of the things that can make the VisualStylesRenderer is the VisualStyleInformation.IsEnabledByUser property. Users can enable visual styles in the Appearance tab of the Display option in Control Panel.

Windows Classic Style
I was able to reenact the bug by setting my Appearance setting for Windows and Buttons to Windows Classic Style (on XP: Control Panel->Display->Appearance tab). As some people are addicted to the old Windows NT look and feel, it makes sense this how they ran into the bug. When I ran my application again, TrackBarRenderer.IsSupported returned false and all I got was the rectangles, no trackbar or ticks.

I'll need to draw the TrackBar and Ticks myself if TrackBarRenderer isn't supported. I'm already drawing the Thumb, so this should be an easy bug to fix. I hope to have it posted in a few days.

Designer error
If you've used Visual Studio with Windows Forms objects, you're used to double clicking on a file in the Solution Explorer and seeing the visual representation of the Form object in the designer. This works for controls as well as forms. I ran into a problem when I created the base class CrystalControl. First, I right clicked on the Solution Explorer, clicked Add, New Item, selected Custom Control, named the file CrystalControl.cs, and started working. The problem was that Visual Studio then decided that CrystalControl needed to be displayed in the designer by default, rather than code. (In this case, CrystalControl is a base class that doesn't draw anything and is only useful when inherited.) No doubt many people have seen the nasty error page "The class X can be designed, but is not the first class in the file…"

namespace Attilan.Crystal.Controls
{
    /// <summary>
    /// Base class for all Crystal controls, derives from ScrollableControl.
    /// </summary>
    [System.ComponentModel.DesignerCategory("code")]
    [ToolboxItem(false)]
    public class CrystalControl : ScrollableControl


If you look at the documentation, Visual Studio is supposed to treat a .cs file as a Designer item by the System.ComponentModel.DesignerCategory attribute inside the code. Seeing that I had omitted this, I included it before the class declaration and recompiled.

designer attributes in solution explorer
Unfortunately, I still had the same result: double clicking on CrystalControls.cs resulted in the error page. Even though you can click on the Code button in the Solution Explorer, it's one of those little things that drive me nuts. Looking at the Solution Explorer, I could see that CrystalControls.cs and CrystalLabel.cs had different icons from my other .cs (code only) extension files that behaved correctly. Then I figured out that because of the way I created these .cs files (selecting Custom Control in the Add dialog), extra metadata must have been written to my project files.

Crystal Toolkit project file
Sure enough, on close examination of CrystalToolkit.csproj, I found extra attributes for the c-sharp files with bad behavior and incorrect icon status. Under each of them was the <subtype>Component</subtype> attribute. When I removed these attributes and saved the .csproj file, everything was fine.

correct attributes in solution explorer
All my files reverted to code-only status and the default double-click action was restored, along with my sanity. Just to be clear, you absolutely need to set the DesignerCategory attribute to "code" in order for this to work, but if Visual Studio somehow inserts that metadata, you may be slightly hosed.

CrystalLabel.jpg

One question I often see getting asked on forums is how to make a label transparent on Windows Forms. Since I already had CrystalGradientControl working with the TransparentMode property (setting to true makes the background transparent), I decided to make the CrystalLabel control. As with CrystalTrackBar, you can use this with a gradient background (when TransparentMode is false) also.

The base class does most of the work, all I had to do was to override OnPaint and draw the text:

protected override void OnPaint(PaintEventArgs pe)
{
    // Paint text
    Rectangle rc = new Rectangle(0,0,this.Width,this.Height);
    TextRenderer.DrawText(pe.Graphics,this.Text,this.Font,
        rc, this.ForeColor, Color.Transparent);

    // Calling the base class OnPaint
    base.OnPaint(pe);
}

The control must resize the rectangle with the text changes by overriding OnTextChanged (and OnFontChanged as well):

protected override void OnTextChanged(EventArgs e)
{
    this.Size = TextRenderer.MeasureText(this.Text,this.Font);
    RedrawControl();
    base.OnTextChanged(e);
}

I'm not sure how far I'm going to proceed with Transparent controls. I implemented CrystalTrackBar for a specific application and CrystalLabel was very quick to implement. I would be curious to know which controls would be a good candidate for transparent or gradient properties.

You can download CrystalLabel (and the Crystal Toolkit) in the Downloads section. It comes with full source code and demo programs.

1 2 Next