Recently in Windows Forms Category

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.

ToolStrip Items
I'm finally ready to release alpha version 0.66 of Crystal Toolkit that contains the CrystalTrackBar control for Windows Forms and .NET 2.0 Framework. It's a replacement for the TrackBar control that contains many of the same properties (Maximum, Minimum, TickFrequency, Orientation, etc.) with some new features. Before I dive into that list, I'd like to answer the question: "Why replace the TrackBar at all? Doesn't it already work just fine?" It does work great, but I ran into a couple of situations where it wasn't right for my applications.

ToolStripTrackBar
My first problem with the TrackBar was the visual look that it has when hosted on a ToolStrip (see my earlier article which how to do this using ToolStripControlHost). When the ToolStrip's RenderMode is set to ManagerRenderMode, the background of the control has a nice, smooth gradient effect (depending on the Theme you selected in Windows). But the TrackBar doesn't know anything about the ToolStrip or the RenderMode. It continues to paint its background using the default control color. The picture above is an example of this. Not a pleasing sight for anyone, is it?

Crystal Toolkit class diagram
I fiddled with the ToolStripControlHosted TrackBar for a while, trying all kinds of hacks to make it work seamlessly. I came to the conclusion that I was going to have to write my own TrackBar control in order to have full authority over the painting. I started playing around with some base classes that would allow me to create controls with gradient backgrounds and color angles. I tried this with my early versions of CrystalTrackBar and liked the results I saw when it was placed on a Form. When placed on a ToolStrip, it was better than what I had before, but still not great. Then I discovered how to make a control's background totally transparent (see my earlier article on my sources for transparent controls). My solution was to put a property in my base class (CrystalGradientControl) called TransparentMode.

CrystalToolStripTrackBar
When TransparentMode is set to true, the control's background is invisible. When TransparentMode is set to false, the control's background can have a gradient effect (or a single solid color if you set both Color1 and Color2 to the same value). With CrystalTrackBar in TransparentMode, I was able to make it blend in perfectly with a Form that contains a background image. I created a ToolStrip host called CrystalToolStripTrackBar and the result was the same: the transparent background allowed the gradient effect of the RenderMode to be exposed. I decided to set the TickStyle to none on the CrystalToolStripTrackBar by default, because most applications that have a slider on the ToolStrip don't have them.

CrystalTrackBar Demo App
In order to produce CrystalTrackBar, I had to take full control of all the painting. For the slider bar (or TrackBar as I call it inside the code) and the ticks running next to the slider, I used the TrackBarRenderer class. I started using the TrackBarRenderer class to draw the thumb as well, but ran into a few problems. TrackBarRenderer's methods to draw the thumb take a regular Rectangle structure with integers. In order to make the Thumb position itself correctly above each tick, the coordinates I calculated were floats-so I had to use RectangleF. I decided to create my own bitmaps for the Thumb and an enum called CrystalThumbState to represent different states (Normal, Hover, Pressed, Erasing). CrystalTrackBar's thumb has a light blue glow when you hover over the control and a dark blue outline when you drag it. I'm not the greatest icon artist in the world, and if you care to replace my thumb bitmaps with your own, please do so-just send me a copy (richard at attilan dot com).

It wasn't my initial goal to make a full TrackBar replacement and I don't think I've matched it 100%. However, I didn't feel right releasing even an alpha version without supporting TrackBar's Minimum, Maximum, TickFrequency, TickStyle, and Orientation properties, in addition to supporting the ValueChanged event. CrystalTrackBar has a couple of other minor features. KeyboardControl is a Boolean value that will allow you to turn off keyboard input for the trackbar. By default, if the CrystalTrackBar is horizontal, a few keystrokes (Left, Right, Home, End) can change the values. In vertical mode the keystrokes are Up, Down, Home, End. Some applications use these keystrokes for other operations, so you can turn this off here. TickValues is a list (of integers) representing the tick marks on the CrystalTrackBar. If you have set Minimum to 0, Maximum to 100, and TickFrequency to 10, the TickValues list will be 0, 10, 20, 30, etc., all the way to 100. This could be useful for adding these values to a ComboBox.

You can download CrystalTrackBar in the Downloads section. It comes with full source code and demo programs.

1 2 Next
Creative Commons License
This weblog is licensed under a Creative Commons License.