Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

WPF Sliding Controls Collection – Part 1: Sliding Image Control

Rate me:
Please Sign up or sign in to vote.
4.82/5 (14 votes)
5 Jun 2012CPOL7 min read 65.8K   3.8K   40   23
The Sliding Image Control is a part of Sliding Controls, a small but useful WPF custom control collection.

SlidingImageControl.Test solution output

Sliding Controls Collection

Introduction

The Sliding Image Control is a part of Sliding Controls, a small but useful WPF custom control collection that will be present through a series of articles here. Various controls in the collection uses direct or indirect the Viewport rearranging of ImageBrush patterns to show and to slide image strips.

The Goals of The Project

The first goal was to display a picture in a defined viewport as a whole series of side by side laid tiles and thus either horizontally or vertically.

The first thought was: OK, this is quite easy, I can take a picture and draw it several times in a loop at the end of the other until the viewport has felt. The problem was that every time if the viewport has changed that new number of repetitions have to be calculated and the pictures must be redrawn.

The second goal was to display the single pictures in their original size or scaled proportionally to the width or height of the viewport. If the original size is chosen, with the viewport resizing only the number of repetitions changes, but if scaled mode is chosen, not only the number but also the positions of the single tiles changes.

The third goal was to allow setting the initial adjustment of the tiles relative to the viewport itself, and to allow changing this adjustment with an offset in the real time. In this point everything becomes a little more complicated.

The second thought was: the DrawingBrush makes fine all the things listed above, but in two directions! It would be great to try forcing the DrawingBrush to draw tiles in just one direction. And the idea was born!

According to the analysis it was quite clear: a new custom control is necessary to encapsulate all the calculations.

Background

A TileBrush is an abstract WPF class derived from Brush and ancestor for ImageBrush, DrawingBrush, and VisualBrush. TileBrush is used to fill the area of a shape with a repeating pattern. When you paint an area by using a TileBrush, you use three components: content, tiles, and output area.

Content: Depending on the inherited brush child type, the content can be an Image, a Drawing, or a Visual. The SlidingImage supports only ImageBrush, which is specified by its ImageSource dependency property. An ImageSource can be BitmapSource or DrawingImage type. See the WPF Brush classes hierarchy below.

Tiles: A TileBrush produces one or more tiles. If more tiles are produced, they evenly spread out in X and Y direction. The SlidingImage does not change this behavior, but tries by manipulation of the output area to leave the impression that tiles are spread out only in one direction depending on its orientation as if they build an endless uninterrupted stripe. By changing tiles offset, we can let the strip slide.

Output Area: The output area is the area where the brush paints. It is for the SlidingImage by default a rectangle that can (depending on other properties) be equal or smaller than a container's control area itself.

WPF Brush classes hierarchy:

System.Windows.Media.Brush
        System.Windows.Media.BitmapCacheBrush
        System.Windows.Media.GradientBrush
        System.Windows.Media.SolidColorBrush
            System.Windows.Media.TileBrush
                System.Windows.Media.DrawingBrush
                                     DrawingBrush.Drawing ->
                System.Windows.Media.ImageBrush
                                     ImageBrush.ImageSource ->
                System.Windows.Media.VisualBrush
                                     VisualBrush.Visual ->


System.Windows.Media.ImageSource
        System.Windows.Interop.D3DImage
        System.Windows.Media.DrawingImage 
                             DrawingImage.Drawing ->
        System.Windows.Media.Imaging.BitmapSource
            System.Windows.Interop.InteropBitmap
            System.Windows.Media.Imaging.BitmapFrame
            System.Windows.Media.Imaging.BitmapImage
            System.Windows.Media.Imaging.CachedBitmap
            System.Windows.Media.Imaging.ColorConvertedBitmap
            System.Windows.Media.Imaging.CroppedBitmap
            System.Windows.Media.Imaging.FormatConvertedBitmap
            System.Windows.Media.Imaging.RenderTargetBitmap
            System.Windows.Media.Imaging.TransformedBitmap
            System.Windows.Media.Imaging.WriteableBitmap


System.Windows.Media.Drawing
        System.Windows.Media.DrawingGroup
        System.Windows.Media.GeometryDrawing
        System.Windows.Media.GlyphRunDrawing
        System.Windows.Media.ImageDrawing
        System.Windows.Media.VideoDrawing

System.Windows.Media.Visual
        System.Windows.Media.ContainerVisual
        System.Windows.Media.Media3D.Viewport3DVisual
	System.Windows.UIElement

Duh, all that together sees at first sight maybe a little bit confusing. But, I recommend you to make yourself familiar with the MSDN documentation about ImageBrush to be able to understand how it works. Relevant classes for this series of articles are marked bold.

 SlidingControls classes hierarchy:

System.Windows.FrameworkElement
         TB.Instruments.SlidingImageBase
             TB.Instruments.SlidingImageControl
             TB.Instruments.SlidingParallaxLayer

System.Windows.Controls.Panel
        System.Windows.Controls.Grid
            TB.Instruments SlidingParallaxControl

System.Windows.Controls.Control
    System.Windows.Controls.ContentControl
        TB.Instruments.SlidingPanoramaControl
        TB.Instruments.SlidingCompassControl

    ...

The Solution

SlidingImage is composed of two classes: a SlidingImageBase and a SlidingImageControl. The SlidingImageBase class is derived from a FrameworkElement and is the parent for the SlidingImageControl class.

Under the motto "keep it as small as possible, but not smaller" is the FrameworkElement chosen as parent. The FrameworkElement derives from UIElement and adds support for styling, tooltips, and context menus. It is the first base class that takes part in the logical tree and so it supports data binding and resource lookup. Furthermore and most important for this application, it provides support for the WPF layout system. The SlidingImageBase class implements all functionality except mouse interaction that is implemented in the SlidingImageControl.

Exposed members:

The SlidingImageBase class exposes the following members.

ConstructorsDescription
SlidingImageBase()Initializes a new instance of the SlidingImageBase class.

PropertiesDescription
BackgroundGets or sets a brush that describes the background of a SlidingImageBase.
SlidingDirectionGets or sets a SlidingDirection enumeration that specifies a sliding direction when the ImageOffset is modified.
ImageSourceGets or sets the image displayed by an ImageBrush in an image strip.
ImageHorizontalAlignmentGets or sets the horizontal alignment of the image strip in the SlidingImageBase area.
ImageVerticalAlignmentGets or sets the verticall alignment of the image strip in the SlidingImageBase area.
ImageOffsetGets or sets an offset to the image position of the content in a TileBrush tile.
ImageScaleGets the factor which scale real to actual image size.
ImageSizeGets real image size.

EventsDescription
ImageOffsetChangedOccurs when the ImageOffset is modified.

MethodsDescription
MeasureOverride(Size)Called to measure a SlidingImageBase class.
Overrides FrameworkElement.MeasureOverride(Size).
ArrangeOverride(Size)Called to arrange and size the content of a SlidingImageBase object.
Overrides FrameworkElement.ArrangeOverride(Size).
OnRender()Called to participates in rendering operations that are directed by the layout system.
Overrides UIElement.OnRemnder().

FieldsDescription
BackgroundPropertyIdentifies the Background dependency property.
ImageSourcePropertyIdentifies the ImageSourceProperty dependency property.
ImageHorizontalAlignmentPropertyIdentifies the ImageHorizontalAlignmentProperty dependency property.
ImageVerticalAlignmentProperty Identifies the ImageVerticalAlignmentProperty dependency property.
SlidingDirectionPropertyIdentifies the SlidingDirectionProperty dependency property.
ImageOffsetPropertyIdentifies the ImageOffsetProperty dependency property.

Except constructor and MouseWheelDelta property the SlidingImageControl doesn’t expose additional members.

ConstructorsDescription
SlidingImageControl()Initializes a new instance of the SlidingImageControl class.

PropertiesDescription
MouseWheelDeltaGets a value that indicates the amount that the ImageOffset has changed with every MouseWheel event. Default value is 120.


SlidingImage Layout:

SlidingImageControl.Test solution output

How it works

First at all we need a suitable image to be shown. The image can be a bitmap image as a BitmapSource, a vector image as a DrawingImage, or Direct3D surface as a D3DImage. After the image to the ImageSource property of SlidingImageBase class is assigned, the OnImageSourcePropertyChanged method will be called. In this method will be the corresponding ImageSize ascertained and a suitable ImageStrip of ImageBrush type assembled. After that, the MeasureOverride and the ArrangeOverride methods will be called successively. In the MeasureOverride method will be an ImageScale and an ImageStripSize/Location-pair calculated. In the ArrangeOverride method will be an ActualImageSize/Location-pair calculated and assigned to the ImageTile.Viewport.

If the ImageSource is a BitmapSource type, the ImageSize will be multiplied with the ratio of the Image DPI and Screen DPI (Dot Per Inch). Screen DPI is given from the actual desktop using the GDI GetDeviceCaps function. Note that the ImageSize can be different from the ActualImageSize if Strech alignment is used.

The SlidingImageControl.Test solution should show clearly the behavior described above. The title image features two test groups enclosed in the solution.

The first test group shows the SlidingImageBase classes and its customization with properties:

  1. Using Enum-type properties as ObjectDataProviders for ListBox and ComboBox.
  2. Using DrawingImage with GeometryDrawing and LinearGradientBrush as an ImageSource.
  3. Using DrawingImage with GeometryDrawing and VisualBrush as an ImageSource.

The second test group shows SlidingImageControl classes and its mouse interaction:

  1. Using a code-behind generated BitmapSource with a RenderTargetBitmap as an ImageSource.
  2. Using six transparent bitmap images of identical size but different DPIs (Dots Per Inch).
  3. Using one bitmap sliding in both directions.

The most important parts of the SlidingImageBase class:

C#
// Calculate ImageSize considering screen DPI (Dot-Per-Inch) values.
private Size CalculateImageSize()
{
    double Sx = 1.0;
    double Sy = 1.0;

    BitmapSource aBitmapSource = ImageSource as BitmapSource;
    if (aBitmapSource != null) // if ImageSource is a bitmap image
    {
        Sx = aBitmapSource.DpiX /
            (Double)DeviceHelper.PixelsPerInch(Orientation.Horizontal);
        Sy = aBitmapSource.DpiY /
            (Double)DeviceHelper.PixelsPerInch(Orientation.Vertical);
    }

    return new Size(Sx * ImageSource.Width, Sy * ImageSource.Height);
}

// Create a new ImageTile of the ImageBrush type
private static void OnImageSourcePropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
{
    SlidingImageBase aSlidingImageBase = (SlidingImageBase)d;
    ImageSource aImageSource = (ImageSource)e.NewValue;

    if (aImageSource != null)
    {
        aSlidingImageBase._ImageSize =
           aSlidingImageBase.CalculateImageSize();

        aSlidingImageBase._ImageTile = new ImageBrush();
        aSlidingImageBase._ImageTile.ImageSource = aImageSource;
        aSlidingImageBase._ImageTile.TileMode = TileMode.Tile;
        aSlidingImageBase._ImageTile.Stretch = Stretch.Fill;
        aSlidingImageBase._ImageTile.ViewportUnits =
           BrushMappingMode.Absolute;
    }
    else
    {
        aSlidingImageBase._ImageTile = null;
    }
}

// Callculate convinient ImageTile size:
protected override Size MeasureOverride(Size availableSize)
{
    if (ImageSource != null)
    {
        if (SlidingDirection == SlidingDirection.Horizontal)
        {
            if (ImageVerticalAlignment == VerticalAlignment.Stretch)
                _ImageScale = availableSize.Height / _ImageSize.Height;
            else
                _ImageScale = 1.0;

            _ImageStrip.Width = availableSize.Width;
            _ImageStrip.Height = _ImageScale * _ImageSize.Height;

            _ImageStrip.X = 0.0;
            switch (ImageVerticalAlignment)
            {
                case VerticalAlignment.Top:
                default:
                    _ImageStrip.Y = 0.0;
                    break;
                case VerticalAlignment.Stretch:
                case VerticalAlignment.Center:
                    _ImageStrip.Y =
                        (availableSize.Height - _ImageStrip.Height) / 2.0;
                    break;
                case VerticalAlignment.Bottom:
                    _ImageStrip.Y =
                        availableSize.Height - _ImageStrip.Height;
                    break;
            }
        }
        else
        {
            if (ImageHorizontalAlignment == HorizontalAlignment.Stretch)
                _ImageScale = availableSize.Width / _ImageSize.Width;

            else
                _ImageScale = 1.0;

            _ImageStrip.Width = _ImageScale * _ImageSize.Width;
            _ImageStrip.Height = availableSize.Height;

            switch (ImageHorizontalAlignment)
            {
                case HorizontalAlignment.Left:
                default:
                    _ImageStrip.X = 0.0;
                    break;
                case HorizontalAlignment.Stretch:
                case HorizontalAlignment.Center:
                    _ImageStrip.X =
                       (availableSize.Width - _ImageStrip.Width) / 2.0;
                    break;
                case HorizontalAlignment.Right:
                    _ImageStrip.X =
                        availableSize.Width - _ImageStrip.Width;
                    break;
            }
            _ImageStrip.Y = 0.0;
        }
    }

    return base.MeasureOverride(availableSize);
}

// Callculate convinient ImageTile position
protected override Size ArrangeOverride(Size finalSize)
{
    if (ImageSource != null)
    {
        Size _ActualImageSize =
          new Size(_ImageScale * _ImageSize.Width,
                   _ImageScale * _ImageSize.Height);

        Point _ActualImageLocation;
        if (SlidingDirection == SlidingDirection.Horizontal)
        {
            double horizontalDisplacement;
            switch (ImageHorizontalAlignment)
            {
                case HorizontalAlignment.Left:
                default:
                    horizontalDisplacement = 0.0;
                    break;
                case HorizontalAlignment.Stretch:
                case HorizontalAlignment.Center:
                    horizontalDisplacement =
                      (finalSize.Width - _ActualImageSize.Width) / 2.0;
                    break;
                case HorizontalAlignment.Right:
                    horizontalDisplacement =
                       finalSize.Width - _ActualImageSize.Width;
                    break;
            }
            _ActualImageLocation = new Point(_ImageScale * ImageOffset %
                _ActualImageSize.Width + horizontalDisplacement,
                _ImageStrip.Y);
        }
        else
        {
            double verticalDisplacement;
            switch (ImageVerticalAlignment)
            {
                case VerticalAlignment.Top:
                default:
                    verticalDisplacement = 0.0;
                    break;
                case VerticalAlignment.Stretch:
                case VerticalAlignment.Center:
                    verticalDisplacement =
                       (finalSize.Height - _ActualImageSize.Height) / 2.0;
                    break;
                case VerticalAlignment.Bottom:
                    verticalDisplacement =
                       finalSize.Height - _ActualImageSize.Height;
                    break;

            }
            _ActualImageLocation = new Point(_ImageStrip.X, _ImageScale *
              ImageOffset % _ActualImageSize.Height + verticalDisplacement);
        }

        _ImageTile.Viewport =
           new Rect(_ActualImageLocation, _ActualImageSize);

    }

    return base.ArrangeOverride(finalSize);
}

// Redraw Background and ImageTile
protected override void OnRender(DrawingContext dc)
{
    Rect rect = new Rect(0.0, 0.0, this.RenderSize.Width, this.RenderSize.Height);

    if (ClipToBounds)
        dc.PushClip(new RectangleGeometry(rect));

    if (Background != null)
        dc.DrawRectangle(Background, (Pen)null, rect);

    if (_ImageTile != null)
        dc.DrawRectangle(_ImageTile, (Pen)null, _ImageStrip);

    if (ClipToBounds)
        dc.Pop();
}

References and third-party software components:

  1. Microsoft: DrawingBrush Class - Examples of usage
  2. Marco Zhou: Windows Presentation Foundation FAQ - 7.1 How to use RenderTargetBitmap
  3. Dwayne Need: Blurry Bitmaps – Image Snapping to Pixels
  4. David Owens: Backgrounds with Style
  5. Jose Fajardo: Dobby the Penguin
  6. Jason Kemp: The Missing .NET #7 - Displaying Enums in WPF
  7. Dr. WPF: Making the slider slide with one click anywhere on the slider
  8. Walt Ritscher: Create an Auto-Centering Slider control with WPF

Conclusion

I hope that you picked up something useful from this article. Suggestions and comments are welcome. If you like it, please vote for it. If you use it, please describe your successful story in the comments below. If you wish to sell it or to distribute it commercially, please contact me.

History

  • v1.0 – 3 May, 2012 – The initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems Engineer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionContinuous animation? Pin
Tim Butler4-Jul-15 19:00
Tim Butler4-Jul-15 19:00 
GeneralMy vote of 5 Pin
Vasilis Kampouris18-Dec-13 5:04
Vasilis Kampouris18-Dec-13 5:04 
GeneralRe: My vote of 5 Pin
Tefik Becirovic9-Jan-14 23:23
Tefik Becirovic9-Jan-14 23:23 
GeneralNeed Suggestion and help Pin
Sidharth Penta8-Dec-12 0:52
Sidharth Penta8-Dec-12 0:52 
AnswerRe: Need Suggestion and help Pin
Tefik Becirovic9-Dec-12 2:18
Tefik Becirovic9-Dec-12 2:18 
GeneralRe: Need Suggestion and help Pin
Sidharth Penta9-Dec-12 20:25
Sidharth Penta9-Dec-12 20:25 
GeneralRe: Need Suggestion and help Pin
Tefik Becirovic9-Dec-12 22:49
Tefik Becirovic9-Dec-12 22:49 
GeneralMy vote of 5 Pin
Madhan Mohan Reddy P5-Jun-12 23:03
professionalMadhan Mohan Reddy P5-Jun-12 23:03 
GeneralRe: My vote of 5 Pin
Tefik Becirovic6-Jun-12 0:09
Tefik Becirovic6-Jun-12 0:09 
QuestionMy vote of 5 Pin
Modro More5-Jun-12 11:40
Modro More5-Jun-12 11:40 
AnswerRe: My vote of 5 Pin
Tefik Becirovic5-Jun-12 18:31
Tefik Becirovic5-Jun-12 18:31 
QuestionHi , a question Pin
Wu Zhe7-May-12 2:21
Wu Zhe7-May-12 2:21 
AnswerRe: Hi , a question Pin
Tefik Becirovic7-May-12 4:00
Tefik Becirovic7-May-12 4:00 
GeneralRe: Hi , a question Pin
Wu Zhe7-May-12 4:06
Wu Zhe7-May-12 4:06 
GeneralRe: Hi , a question Pin
Tefik Becirovic7-May-12 4:42
Tefik Becirovic7-May-12 4:42 
GeneralRe: Hi , a question Pin
Wu Zhe7-May-12 4:44
Wu Zhe7-May-12 4:44 
GeneralRe: Hi , a question Pin
Wu Zhe8-May-12 15:57
Wu Zhe8-May-12 15:57 
GeneralRe: Hi , a question Pin
Tefik Becirovic8-May-12 22:18
Tefik Becirovic8-May-12 22:18 
QuestionI agree Pin
Dave Kreskowiak5-May-12 2:24
mveDave Kreskowiak5-May-12 2:24 
AnswerRe: I agree Pin
Tefik Becirovic5-May-12 11:37
Tefik Becirovic5-May-12 11:37 
SuggestionNeeds explanation Pin
Shahin Khorshidnia4-May-12 0:16
professionalShahin Khorshidnia4-May-12 0:16 
GeneralRe: Needs explanation Pin
Tefik Becirovic4-May-12 1:01
Tefik Becirovic4-May-12 1:01 
GeneralRe: Needs explanation Pin
Shahin Khorshidnia4-May-12 5:31
professionalShahin Khorshidnia4-May-12 5:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.