Click here to Skip to main content
15,885,931 members
Articles / Desktop Programming / WPF

Another Screensaver with WPF

Rate me:
Please Sign up or sign in to vote.
4.87/5 (17 votes)
27 Jan 2012CDDL8 min read 71.9K   4.3K   63  
Lessons learnt from writing a screensaver with WPF
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace RZWScreenSaver{
    public class PictureChangedEventArgs : EventArgs{
        public PictureChangedEventArgs(string path, DateTime fileDate, ImageSource source){
            Path = path;
            Picture = source;
            FileDate = fileDate;

            for(var count=0; count < ProvidedRandomValue; ++count)
                randomValues[count] = MainApp.Rand();
        }
        public int Random(int slot, int maxValue){
            return (int) (maxValue*(long)randomValues[slot]/int.MaxValue);
        }
        const int ProvidedRandomValue = 4;
        public DateTime FileDate { get; private set; }
        public string Path { get; private set; }
        public ImageSource Picture { get; private set; }
        readonly int[] randomValues = new int[ProvidedRandomValue];
    }
    public class PictureSource : IPictureSource{
        static readonly string[] supportedImage = {".bmp", ".gif", ".jpg", ".jpeg", ".png", ".tiff", ".ico"};

        #region ctors
        public PictureSource(FolderCollectionSet folderSet, SlideMode slideMode, int slideDelay){
            this.folderSet = folderSet;
            this.slideMode = slideMode;

            timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(slideDelay) };
            timer.Tick += changePictureEvent;
            Debug.Assert(!timer.IsEnabled);

            regeneratePictureList(folderSet.Default);
            applySlideMode();
        }

        #endregion

        #region Engine Control

        public int PictureIndex{
            get { return pictureList.Count - slideOrder.Count; }
        }
        public bool IsPaused{
            get { return !IsStarted && pauseCall > 0; }
        }
        public bool IsStarted{
            get { return timer.IsEnabled; }
        }
        public void Start(){
            if (!IsStarted && pictureList.Count > 0){
                timer.Start();
                notifyNextImage();
            }
            pauseCall = 0;
        }
        public void Stop(){
            pauseCall = 1;  // assume pause state
            timer.Stop();
        }
        int pauseCall;
        public void Pause(){
            if (pauseCall++ == 0)
                Stop();
        }
        public void Resume(){
            if (--pauseCall == 0)
                Start();
            Debug.Assert(pauseCall >= 0);
        }
        #endregion
        public string CurrentPictureFile{
            get { return currentPictureIndex == -1 ? String.Empty : pictureList[currentPictureIndex].Path; }
        }
        public ImageSource CurrentPicture{
            get { return currentPicture; }
        }
        public void RestorePicturePosition(int lastPosition){
            if (slideMode != SlideMode.Random)
                // random order doesn't make sense to be reponsitioned.
                if (slideOrder.Count < lastPosition)
                    applySlideMode();
                else
                    for(int discardCount=0; discardCount < lastPosition; ++discardCount)
                        slideOrder.Dequeue();
        }
        /// <summary>
        /// Delete current picture from physical disk storage!
        /// </summary>
        /// <returns>true - if picture can be deleted.</returns>
        public bool DeleteCurrentPicture(){
            if (currentPictureIndex == -1){
                // in case there is no picture in the collection and user sends this command.
                return true;
            }
            var filePath = pictureList[currentPictureIndex].Path;
            bool success;
            try{
                File.Delete(filePath);
                success = true;
                Debug.WriteLine("Deleted " + filePath);
            }
            catch (IOException e){
                Trace.WriteLine("ERROR: File " + filePath + " is in used.\n" + e.Message);
                success = false;
            }
            pictureList.RemoveAt(currentPictureIndex);
            return success;
        }
        public bool MoveCurrentPictureTo(string targetFileAndFolder){
            if (currentPictureIndex == -1){
                // in case there is no picture in the collection and user sends this command.
                return true;
            }
            var targetFile = CurrentPictureFile;
            try{
                var newPath = targetFileAndFolder;
                File.Move(targetFile, newPath);
                var currentImagePath = pictureList[currentPictureIndex];
                currentImagePath.Path = newPath;
                pictureList[currentPictureIndex] = currentImagePath;
                Debug.Write("File ");
                Debug.Write(targetFile);
                Debug.Write(" is moved to ");
                Debug.WriteLine(newPath);
                return true;
            }
            catch(UnauthorizedAccessException e){
                Trace.WriteLine("Unauthorized to access " + targetFileAndFolder);
                Debug.WriteLine(e);
            }
            catch(PathTooLongException e){
                Trace.WriteLine("Path too long: " + targetFileAndFolder);
                Debug.WriteLine(e);
            }
            return false;
        }
        public void SwitchToSet(int setIndex){
            folderSet.SelectedIndex = setIndex;
            regeneratePictureList(folderSet.Default);
            applySlideMode();

            if (IsStarted){
                var handler = PictureSetChanged;
                if (handler != null)
                    handler(this, EventArgs.Empty);
                notifyNextImage();
            }
        }
        public event EventHandler PictureSetChanged;
        public event EventHandler<PictureChangedEventArgs> PictureChanged;
        void applySlideMode(){
            if (slideOrder == null)
                slideOrder = new Queue<int>(pictureList.Count);
            if (pictureList.Count == 0)
                return;
            IEnumerable<int> order;
            switch (slideMode){
            case SlideMode.Sequence:
                order = Enumerable.Range(0, pictureList.Count);
                break;
            case SlideMode.SortedByFilenamePerFolder:
                order = from path in pictureList
                        group path by Path.GetDirectoryName(path.Path) into g
                        from p in g
                        orderby p.Path select p.ID;
                break;
            case SlideMode.SortedByFilenameAllFolders:
                order = from path in pictureList orderby Path.GetFileName(path.Path) select path.ID;
                break;
            case SlideMode.SortedByDatePerFolder:
                order = from path in pictureList
                        group path by Path.GetDirectoryName(path.Path) into g
                        from p in g
                        orderby p.FileDate select p.ID;
                break;
            case SlideMode.SortedByDateAllFolders:
                order = from path in pictureList orderby path.FileDate select path.ID;
                break;
            case SlideMode.Random:
                order = generateRandomSequence(pictureList.Count);
                break;
            default:
                Trace.WriteLine("Unhandled slide mode " + slideMode);
                order = Enumerable.Range(0, pictureList.Count);
                break;
            }
            foreach (var i in order){
                slideOrder.Enqueue(i);
            }
        }
        void changePictureEvent(object sender, EventArgs e){
            notifyNextImage();
        }
        static IEnumerable<int> generateRandomSequence(int count){
            var sequence = Enumerable.Range(0, count).ToArray();
            shuffleItemByItem(count, sequence);
            sequence = shuffleSequenceDeck(count, sequence);
            shuffleItemByItem(count, sequence);
            return sequence;
        }
        static int[] shuffleSequenceDeck(int count, int[] sequence) {
            var output = new int[sequence.Length];
            for(int i=0; i < count; ++i){
                var pos1 = MainApp.Rand(count);
                var pos2 = MainApp.Rand(count);
                sequence.Shuffle(pos1, pos2, ref output);
                swap(ref sequence, ref output);
            }
            return sequence;
        }
        static void shuffleItemByItem(int count, int[] sequence){
            for(int i=0; i < count; ++i){
                var pos1 = MainApp.Rand(count);
                var pos2 = MainApp.Rand(count);
                sequence.Swap(pos1, pos2);
            }
        }
        static void swap<T>(ref T a, ref T b){
            T t = a;
            a = b;
            b = t;
        }
        void notifyNextImage() {
            ImageSource image;
            string fileName;
            DateTime fileDate;
            do{
                image = fetchNextPicture(out fileName, out fileDate);
            } while (image == null && pictureList.Count > 0);
            if (image != null){
                currentPicture = image;
                var @event = PictureChanged;
                if (@event != null){
                    @event(this, new PictureChangedEventArgs(fileName, fileDate, image));
                }
            }else{
                Debug.Assert(pictureList.Count == 0, "Picture list supposes to be all removed because the invalid image file.");
                timer.Stop();
            }
        }
        ImageSource fetchNextPicture(out string fileName, out DateTime fileDate){
            string pictureFile = String.Empty;
            try{
                currentPictureIndex = slideOrder.Dequeue();
                if (slideOrder.Count == 0)
                    applySlideMode();
                fileName = pictureFile = pictureList[currentPictureIndex].Path;
                fileDate = pictureList[currentPictureIndex].FileDate;
                using(var s = File.OpenRead(pictureFile)){
                    // use stream so file won't be locked.
                    var decoder = BitmapDecoder.Create(s, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
                    return decoder.Frames[0];
                }
            }
            catch (ArgumentException){
                // strange exception thrown from the BitmapDecoder sometimes, dunno why yet.
                Trace.Write("Cannot process file (.Net error): ");
                Trace.WriteLine(pictureFile);
            }
            catch (NotSupportedException){
                Trace.WriteLine(pictureFile + " is not a recognized image format!");
                pictureList.RemoveAt(currentPictureIndex);
                currentPictureIndex = -1;
            }
            fileName = String.Empty;
            fileDate = DateTime.MinValue;
            return null;
        }
        void regeneratePictureList(FolderCollection folderList){
            if (folderList == null || folderList.Count == 0){
                pictureList = new List<ImagePath>();
                return;
            }
            pictureList = new List<ImagePath>(folderList.Count * 100 /* expected files in sub-folders */);
            var excludedFolders =
                (from folder in folderList
                 where folder.Inclusion == InclusionMode.Exclude 
                 select folder.Path).ToArray();
            var id = 0;
            foreach (var folder in folderList){
                IEnumerable<string> fileList;
                switch (folder.Inclusion){
                case InclusionMode.Recursive:
                    fileList = getImageFileRecursive(folder.Path, excludedFolders);
                    break;
                case InclusionMode.Single:
                    fileList = getImageFileSingle(folder.Path);
                    break;
                case InclusionMode.Exclude:
                    continue;
                default:
                    Debug.WriteLine("Unhandled folder inclusion " + folder.Inclusion);
                    continue;
                }
                foreach (var filePath in fileList){
                    pictureList.Add(new ImagePath { ID = id, Path = filePath, FileDate = File.GetCreationTime(filePath)});
                    ++id;
                }
            }
        }
        static IEnumerable<string> getImageFileSingle(string path){
            return from file in Directory.GetFiles(path)
                   where supportedImage.Contains(Path.GetExtension(file), StringComparer.OrdinalIgnoreCase)
                   select file;
        }
        static IEnumerable<string> getImageFileRecursive(string path, string[] excludedFolders){
            if (Array.FindIndex(excludedFolders, folder => path.StartsWith(folder, StringComparison.OrdinalIgnoreCase)) != -1){
                Debug.WriteLine("Exclude: " + path);
                return new string[0];
            }
            var subImageFiles = from dir in Directory.GetDirectories(path)
                                from file in getImageFileRecursive(dir, excludedFolders)
                                select file;
            return getImageFileSingle(path).Union(subImageFiles);
        }
        #region Structures
        struct ImagePath{
            public int ID;
            public string Path;
            public DateTime FileDate;
        }
        #endregion

        List<ImagePath> pictureList;
        readonly DispatcherTimer timer;
        Queue<int> slideOrder;
        readonly SlideMode slideMode;
        int currentPictureIndex;
        ImageSource currentPicture;
        readonly FolderCollectionSet folderSet;
    }
}

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 Common Development and Distribution License (CDDL)


Written By
Architect
Thailand Thailand
C/C++ and C# programmer.

Comments and Discussions