Click here to Skip to main content
15,892,059 members
Articles / Multimedia / GDI+

Rotating Picture Tray

Rate me:
Please Sign up or sign in to vote.
4.29/5 (9 votes)
17 Nov 2009CPOL10 min read 60.4K   1.3K   34  
Allows the user to view a collection of pictures by selecting them from a rotating tray.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

namespace cLibPicTray
{
    public class cLibPicTray
    {
                [Serializable]
        public struct udtTrayLoc
        {
            public double angle;
            public double x;
            public double y;
        }

        [Serializable]
        public struct udtPicBoxTrayElement
        {
            public PictureBox picBox;
            public udtTrayLoc trayLoc;
            public double dblXOverYRatio;
        }

        public struct udtCartesian
        {
            public double x;
            public double y;
        }

        public enum enuTurning { Clockwise = -1, notTurning, CounterClockwise }

        public udtPicBoxTrayElement[] udrPicBoxTrayElements;
        public classTransparentCLabel lblTransparentLabel = new classTransparentCLabel();

        public double dblCurrentAngle;
        double dblMinSizeFraction = 0.5;
        udtCartesian udrMaxSizeFraction = new udtCartesian();
        udtCartesian udrCenterFraction = new udtCartesian();
        double dblTurnAngleStep;
        enuTurning turningAngle;

        double dblCircleRadius;
        double dblCircleRadiusFactor;

        public PictureBox picBackground = new PictureBox();

        udtCartesian udrMaxSize; // maximum picture size
        udtCartesian udrCenter;  // center of the circle
        udtCartesian udrMouse;
        public int intSelected;
        public bool bolMouseMoveOverAutoSelect = false;
        public Thread clickPicThread;
        public bool bolClickPicBool;
        public bool bolPopUpClickedPic = false;
        

        System.Windows.Forms.Timer tmrRotate = new System.Windows.Forms.Timer();
        public cLibPicTray()
        {
            picBackground.SizeChanged += new EventHandler(picBackground_SizeChanged);
            tmrRotate.Tick += new EventHandler(tmrRotate_Tick);
            lblTransparentLabel.MouseClick += new MouseEventHandler(lblTransparentLabel_MouseClick);
            lblTransparentLabel.MouseMove += new MouseEventHandler(lblTransparentLabel_MouseMove);

            resetStandardParameters();

            udrPicBoxTrayElements = null;            
            turningAngle = enuTurning.notTurning;

            handle_SizeChange();
            picBackground.Controls.Add(lblTransparentLabel);
            lblTransparentLabel.Dock = DockStyle.Fill;
        }

        public void resetStandardParameters()
        {
            dblTurnAngleStep = Math.PI / 64;
            setCircleRadius(1.5);
            setMaxHeightFraction(0.7);
            setMaxWidthFraction(1);
            setMinSizeFraction(0.2);
            setCenterFraction_X(0.5);
            setCenterFraction_Y(0.15);
            bolMouseMoveOverAutoSelect = true;
            bolClickPicBool = false;
            bolPopUpClickedPic = false;
        }

        public void setCenterFraction_X(double dblFractionFromLeftOfPanel)
        { udrCenterFraction.x = dblFractionFromLeftOfPanel; }

        public void setCenterFraction_Y(double dblFractionFromTopOfPanel)
        { udrCenterFraction.y = dblFractionFromTopOfPanel; }

        public void setBackGroundImage(string strFilename)
        { picBackground.Load(strFilename); }

        public void setBackGroundImage(Image imgLocal)
        { picBackground.Image = imgLocal; }

        public void setCircleRadius(double dblFactor)
        { dblCircleRadiusFactor = dblFactor; }

        public void setMinSizeFraction(double dblMinSize)
        {
            if (dblMinSizeFraction < 0 || dblMinSizeFraction > 1)
                return;
            dblMinSizeFraction = dblMinSize;
        }

        public void setMaxWidthFraction(double dblFractionOfPanelWidth)
        { udrMaxSizeFraction.x = dblFractionOfPanelWidth; }

        public void setMaxHeightFraction(double dblFractionOfPanelHeight)
        { udrMaxSizeFraction.y = dblFractionOfPanelHeight; }

        void lblTransparentLabel_MouseMove(object sender, MouseEventArgs e)
        {
            if (bolMouseMoveOverAutoSelect)
            {
                int intTempSelected = getSelectedImage(e);
                if (intTempSelected >= 0)
                {
                    intSelected = intTempSelected;
                    handle_newSelection();
                }
            }
        }

        int getSelectedImage(MouseEventArgs e)
        {
            udrMouse.x = e.X; udrMouse.y = e.Y;

            // set current image selected by mouse position - front to back
            int[] intRankingIndices = getRankingIndices();
            if (intRankingIndices == null)
                return -1;
            int intLocalSelected = -1;

            for (int intPicCounter = intRankingIndices.Length - 1; intPicCounter >= 0; intPicCounter--)
            {
                if (udrMouse.x > udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Left
                    && udrMouse.x < udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Left + udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Width)
                    if (udrMouse.y > udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Top
                        && udrMouse.y < udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Top + udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Height)
                    {
                        intLocalSelected = intRankingIndices[intPicCounter];
                        break;
                    }
            }
            return intLocalSelected;
        }

        void lblTransparentLabel_MouseClick(object sender, MouseEventArgs e)
        {            
            intSelected = getSelectedImage(e);
            bolClickPicBool = true;
            handle_newSelection();
            if (clickPicThread != null)
                clickPicThread.Start();
            if (bolPopUpClickedPic)
                handle_PopUpPic(intSelected);
        }

        void handle_PopUpPic(int intSelected)
        {
            try
            {
                classPicBoxViewer.classPicBoxViewer cLibPicBox = new classPicBoxViewer.classPicBoxViewer(udrPicBoxTrayElements[intSelected].picBox.Image, "");
                cLibPicBox.resizeToFitImage();
            }
            catch (Exception) { }
        }

        void handle_newSelection()
        {
            if (intSelected >= udrPicBoxTrayElements.Length)
                intSelected = udrPicBoxTrayElements.Length - 1;
            if (intSelected >= 0)
            {
                if (udrPicBoxTrayElements[intSelected].trayLoc.x > 0)
                    turningAngle = enuTurning.Clockwise;
                else
                    turningAngle = enuTurning.CounterClockwise;
                tmrRotate.Enabled = true;
            }
        }
        
        void tmrRotate_Tick(object sender, EventArgs e)
        {
            dblCurrentAngle += (int)turningAngle * dblTurnAngleStep;
            if (dblCurrentAngle < 0)
                dblCurrentAngle += (Math.PI * 2);
            else if (dblCurrentAngle > 2 * Math.PI)
                dblCurrentAngle -= (Math.PI * 2);
            repositionTray();
        }

        public void setTurningPeriod(int intDelayInMilliseconds)
        {
            if (intDelayInMilliseconds > 0)
            {
                tmrRotate.Interval = intDelayInMilliseconds;
                tmrRotate.Enabled = true;
            }
            else
                tmrRotate.Enabled = false;
        }

        public void setTurningAngularStepSize(double dblAngularStepSize)
        {
            dblTurnAngleStep = Math.Sign(dblTurnAngleStep) * dblAngularStepSize;
            tmrRotate.Enabled = (dblTurnAngleStep != 0);
        }

        public void setRotatingDirection(enuTurning turningDirection)
        {
            turningAngle = turningDirection;
            tmrRotate.Enabled = (turningDirection != enuTurning.notTurning);
            intSelected = -1;
        }

        void picBackground_SizeChanged(object sender, EventArgs e)
        { handle_SizeChange(); }

        public void handle_SizeChange()
        {
            udrMaxSize.x = (picBackground.Width * udrMaxSizeFraction.x);
            udrMaxSize.y = picBackground.Height * udrMaxSizeFraction.y;
            udrCenter.x = picBackground.Width  * udrCenterFraction.x;
            udrCenter.y = picBackground.Height * udrCenterFraction.y;

            double dblMaxWidthCircle = picBackground.Width * 0.5;
            double dblMaxHeightCircle = picBackground.Height * 0.5;
            if (dblMaxHeightCircle < dblMaxWidthCircle)
                dblCircleRadius = dblMaxHeightCircle / 5;
            else
                dblCircleRadius = dblMaxWidthCircle / 5;
            dblCircleRadius *= dblCircleRadiusFactor;
            repositionTray();
        }

        public void deletePicture()
        { deletePicture(intSelected); }

        public void deletePicture(int intIndexToDelete)
        {
            if (intIndexToDelete >= 0 && intIndexToDelete < udrPicBoxTrayElements.Length)
            {
                udrPicBoxTrayElements[intIndexToDelete].picBox.Dispose();
                for (int intPicCounter = intIndexToDelete; intPicCounter < udrPicBoxTrayElements.Length - 1; intPicCounter++)
                    udrPicBoxTrayElements[intPicCounter] = udrPicBoxTrayElements[intPicCounter + 1];
                Array.Resize<udtPicBoxTrayElement>(ref udrPicBoxTrayElements, udrPicBoxTrayElements.Length - 1);
            }
            repositionTray();
            handle_newSelection();
        }

        public void addPicBox(ref PictureBox PicThisBox)
        {
            if (udrPicBoxTrayElements == null)
                udrPicBoxTrayElements = new udtPicBoxTrayElement[1];
            else
                Array.Resize<udtPicBoxTrayElement>(ref udrPicBoxTrayElements, udrPicBoxTrayElements.Length + 1);
            udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].picBox = PicThisBox;
            picBackground.Controls.Add(udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].picBox);
            PicThisBox.SizeMode = PictureBoxSizeMode.AutoSize;
            udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].dblXOverYRatio = (double)udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].picBox.Width / (double)udrPicBoxTrayElements[udrPicBoxTrayElements.Length - 1].picBox.Height;

            PicThisBox.SizeMode = PictureBoxSizeMode.StretchImage;
            handle_SizeChange();
            intSelected = udrPicBoxTrayElements.Length - 1;
            repositionTray();
            handle_newSelection();
        }

        private int[] getRankingIndices()
        {
            // returns array of indices of elements as they appear back to front 
            //(nearest top of first first in order until reach the one nearest the bottom of the form as measured around unit circle)

            int[] intRetVal;
            //= new int[udrPicBoxTrayElements.Length];
            try
            { intRetVal = new int[udrPicBoxTrayElements.Length]; }
            catch (Exception) { return null; }
            // rank them front to back
            for (int intPicBoxCounter = 0; intPicBoxCounter < udrPicBoxTrayElements.Length; intPicBoxCounter++)
                intRetVal[intPicBoxCounter] = intPicBoxCounter;
            int intBestIndex, intTemp, intInnerLoop, intOuterLoop;
            double dblBestValue;
            for (intOuterLoop = 0; intOuterLoop < intRetVal.Length - 1; intOuterLoop++)
            {
                intBestIndex = intOuterLoop;
                dblBestValue = udrPicBoxTrayElements[intRetVal[intOuterLoop]].trayLoc.y;
                for (intInnerLoop = intOuterLoop + 1; intInnerLoop < intRetVal.Length; intInnerLoop++)
                {
                    if (dblBestValue < udrPicBoxTrayElements[intRetVal[intInnerLoop]].trayLoc.y)
                    {
                        dblBestValue = udrPicBoxTrayElements[intRetVal[intInnerLoop]].trayLoc.y;
                        intBestIndex = intInnerLoop;
                    }
                }
                if (intBestIndex != intOuterLoop)
                {
                    intTemp = intRetVal[intOuterLoop];
                    intRetVal[intOuterLoop] = intRetVal[intBestIndex];
                    intRetVal[intBestIndex] = intTemp;
                }
            }
            return intRetVal;
        }

        public void repositionTray()
        {
            double dblDeltaAngle;
            lblTransparentLabel.Visible = false;

            if (udrPicBoxTrayElements == null)
                return;
            else if (udrPicBoxTrayElements.Length == 0)
                return;

            // distribute pics around the circle evenly : set their respective angles around the circle
            if (udrPicBoxTrayElements.Length - 1 > 0)
            {
                dblDeltaAngle = (2 * Math.PI) / (udrPicBoxTrayElements.Length);
                for (int intPicBoxCounter = 0; intPicBoxCounter < udrPicBoxTrayElements.Length; intPicBoxCounter++)
                    udrPicBoxTrayElements[intPicBoxCounter].trayLoc.angle = Math.PI / 2 + intPicBoxCounter * dblDeltaAngle;
            }
            else
                udrPicBoxTrayElements[0].trayLoc.angle = Math.PI / 2;

            int intXSignOfSelectedPicBeforeMove = 0, intXSignOfSelectedPicAfterMove = 0;
            try
            { intXSignOfSelectedPicBeforeMove = Math.Sign(udrPicBoxTrayElements[intSelected].trayLoc.x); }
            catch (Exception)
            { }

            // place on the cartesian plane using the radius of circle and each picture's angle + circle rotation angle
            for (int intPicBoxCounter = 0; intPicBoxCounter < udrPicBoxTrayElements.Length; intPicBoxCounter++)
            {
                udrPicBoxTrayElements[intPicBoxCounter].trayLoc.x = Math.Cos(udrPicBoxTrayElements[intPicBoxCounter].trayLoc.angle + dblCurrentAngle - Math.PI / 2);
                udrPicBoxTrayElements[intPicBoxCounter].trayLoc.y = Math.Sin(udrPicBoxTrayElements[intPicBoxCounter].trayLoc.angle + dblCurrentAngle - Math.PI / 2);
            }

            int[] intRankingIndices = getRankingIndices();
            if (intRankingIndices == null)
                return;
            

            double dblMaxYReference = udrCenter.y + udrMaxSize.y;
            udtCartesian udrCirclePt;
            //create a ranking indice array to display them from back to front
            for (int intPicCounter = 0; intPicCounter < intRankingIndices.Length; intPicCounter++)
            {
                /// -   set their size accordingly (back is smaller, front is bigger) 
                double dblSizeAttenuationFactor; /// -1 is at the front, 1 is at the back
                double dblDistanceToFront = 1 + udrPicBoxTrayElements[intRankingIndices[intPicCounter]].trayLoc.y;
                // dblDistanceToFront ranges from 0(front) to 2(back)
                double dblFactorRange = 1 - dblMinSizeFraction;
                dblSizeAttenuationFactor = 1 - dblFactorRange * (dblDistanceToFront / 2);
                resizePic(ref udrPicBoxTrayElements[intRankingIndices[intPicCounter]], attenuateUdtCartesian(udrMaxSize, dblSizeAttenuationFactor));

                /// -   place them on the form (left,top)
                ///     - center the x value of pic loc on circle Pt, place top of pic on circle Pt
                udrCirclePt.x = udrCenter.x + dblCircleRadius * Math.Cos(udrPicBoxTrayElements[intRankingIndices[intPicCounter]].trayLoc.angle + dblCurrentAngle - Math.PI / 2);
                udrCirclePt.y = udrCenter.y - dblCircleRadius * Math.Sin(udrPicBoxTrayElements[intRankingIndices[intPicCounter]].trayLoc.angle + dblCurrentAngle - Math.PI / 2);
                udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Left = (int)(udrCirclePt.x - udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Width / 2);
                udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.Top = (int)(udrCirclePt.y);

                /// -   bring to front (will be covered by those that follow)
                udrPicBoxTrayElements[intRankingIndices[intPicCounter]].picBox.BringToFront();
            }

            try
            { picBackground.Refresh(); }
            catch (Exception)
            { }

            try
            {
                // the transparent label allows the user to click on the label and select an image using the transparentLabel's event handlers
                lblTransparentLabel.BringToFront();
                lblTransparentLabel.Refresh();
                lblTransparentLabel.Visible = true;
            }
            catch (Exception)
            {}
            
            try
            { intXSignOfSelectedPicAfterMove = Math.Sign(udrPicBoxTrayElements[intSelected].trayLoc.x); }
            catch (Exception)
            { }

            // when the user's selection crosses the X-axis and is last in the ranking indice array then 
            // it is in front and rotation can stop
            if (intSelected == intRankingIndices[intRankingIndices.Length-1])
                if (intXSignOfSelectedPicAfterMove != intXSignOfSelectedPicBeforeMove )
                    tmrRotate.Enabled = false;
        }

        udtCartesian attenuateUdtCartesian(udtCartesian udrCar, double dblFactor)
        {
            udtCartesian udrRetVal = new udtCartesian();
            udrRetVal.x = udrCar.x * dblFactor;
            udrRetVal.y = udrCar.y * dblFactor;
            return udrRetVal;
        }

        void resizePic(ref udtPicBoxTrayElement udrThisTrayElement, udtCartesian udrMaxDimensions)
        {
            double dblMDXOverY = udrMaxDimensions.x / udrMaxDimensions.y;
            if (udrThisTrayElement.dblXOverYRatio > dblMDXOverY)
            {   // width is limiting factor
                udrThisTrayElement.picBox.Width = (int)udrMaxDimensions.x;
                udrThisTrayElement.picBox.Height = (int)(udrThisTrayElement.picBox.Width / udrThisTrayElement.dblXOverYRatio);
            }
            else
            {  // height is limiting factor
                udrThisTrayElement.picBox.Height = (int)udrMaxDimensions.y;
                udrThisTrayElement.picBox.Width = (int)(udrThisTrayElement.picBox.Height * udrThisTrayElement.dblXOverYRatio);
            }
        }

        public void resetPicTray()
        {
            for (int intPicCounter =0; intPicCounter< udrPicBoxTrayElements.Length; intPicCounter++)
            {
                udrPicBoxTrayElements[intPicCounter].picBox.Visible = false;
                udrPicBoxTrayElements[intPicCounter].picBox.Dispose();
            }
            udrPicBoxTrayElements = null;

            dblTurnAngleStep = Math.PI / 64;
            dblMinSizeFraction = 0.5;
            udrMaxSizeFraction.x = udrMaxSizeFraction.y = 0.45;
            udrCenterFraction.x = 0.5;
            udrCenterFraction.y = 0.3;
            bolMouseMoveOverAutoSelect = false;
            bolClickPicBool = false;

            udrPicBoxTrayElements = null;
            turningAngle = enuTurning.notTurning;
        }
    }
}

public class classTransparentCLabel : System.Windows.Forms.Label
{
    public classTransparentCLabel()
    {
        this.SetStyle(System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, false);
        this.SetStyle(System.Windows.Forms.ControlStyles.Opaque, true);
    }

}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
CEO unemployable
Canada Canada
Christ Kennedy grew up in the suburbs of Montreal and is a bilingual Quebecois with a bachelor’s degree in computer engineering from McGill University. He is unemployable and currently living in Moncton, N.B. writing his next novel.

Comments and Discussions