Click here to Skip to main content
15,885,949 members
Articles / Desktop Programming / Windows Forms

Photo Album in C#

Rate me:
Please Sign up or sign in to vote.
2.21/5 (17 votes)
14 Dec 20044 min read 134.8K   5.4K   82  
An article describing how to make a photo album like program.
using System;
using System.IO;
using System.Collections;

namespace Photo_Album_Structure
{
	/// <summary>
	/// PhotoItem represents the base class for both Photo and Album
	/// </summary>
	public abstract class PhotoItem : System.IDisposable//, System.IComparable
	{
		#region "Properties"
		#region "Property: Path"
		private string path;
		public string Path
		{
			get
			{
				return this.path;
			}
		}
		internal string i_Path
		{
			set
			{
				this.path = value;
				this.OnRename(this);
			}
		}
		//internal void set_Path(string path)
		//{
		//	this.path = path;
		//	this.OnRename(this);
		//}
		#endregion

		protected PhotoItemUpdater photoItemUpdater;

		#region "Property: Parent"
		private Album m_Parent = null;
		internal Album i_Parent
		{
			get
			{
				return this.m_Parent;
			}
			set
			{
				if(this.m_Parent == value) return;
				Album OldParent = this.m_Parent;
				this.m_Parent = value;
				this.OnParentChange(this, OldParent);
			}
		}
		public Album Parent
		{
			get
			{
				return this.i_Parent;
			}
		}
		#endregion

        // depreciated
		#region "Property: ChildIndex"
		internal int childIndex = -1;
		public int ChildIndex
		{
			get
			{
				return this.childIndex;
			}
		}
		#endregion

		#region "Property: Date"
		public virtual DateTime BeginDate
		{
			get
			{
				return DateTime.MinValue;
			}
		}
		public virtual DateTime EndDate
		{
			get
			{
				return DateTime.MinValue;
			}
		}
		#endregion

        #region "Array Properties"
        public virtual int Count
        {
            get
            {
                return 1;
            }
        }
        public virtual PhotoItem this[string s]
        {
            get
            {
                return this.FindMember(s);
            }
        }
        public virtual PhotoItem this[int i]
        {
            get
            {
                if (i != 0) throw new IndexOutOfRangeException();
                return this;
            }
        }
        #endregion
		#endregion

		#region "Events"
		#region "Event: Date"
		public event DatesChangeEventHandler DatesChange;
		protected virtual void OnDatesChange(PhotoItem pi)
		{
			if(this.DatesChange != null)
				this.DatesChange(pi);

			// forward the event...
			if(this.Parent != null)
				this.Parent.OnDatesChange(pi);
		}
		public delegate void DatesChangeEventHandler(PhotoItem sender);
		#endregion

		#region "Event: PropertyChange"
		public event PropertyChangeEventHandler PropertyChange;
        protected virtual void OnPropertyChange(PhotoItem pi, string propertyName, object newValue)
        {
			if(this.PropertyChange != null)
				this.PropertyChange(pi, propertyName, newValue);

			// forward the event...
			if(this.Parent != null)
				this.Parent.OnPropertyChange(pi, propertyName, newValue);
		}
		public delegate void PropertyChangeEventHandler(
            PhotoItem sender, string propertyName, object newValue);
		#endregion

		#region "Event: Rename"
		public event RenameEventHandler Rename;
		protected virtual void OnRename(PhotoItem pi)
		{
			if(this.Rename != null)
				this.Rename(pi);

			// forward the event...
			if(this.Parent != null)
				this.Parent.OnRename(pi);
		}
		public delegate void RenameEventHandler(PhotoItem sender);
		#endregion

		#region "Event: ParentChange"
		public event ParentChangeEventHandler ParentChange;
		protected virtual void OnParentChange(PhotoItem pi, Album oldParent)
		{
			if(this.ParentChange != null)
				this.ParentChange(pi, oldParent);

			// forward the event...
			if(this.Parent != null)
				this.Parent.OnParentChange(pi,oldParent);
		}
		public delegate void ParentChangeEventHandler(PhotoItem sender, Album OldParent);
		#endregion

		#region "Event: Request Refresh"
		/*
		 * this is needed because it could happen that the root folder initially
		 *	containes only photos.  However, if folder is pasted in (from the outside),
		 *	the album needs to become an AlbumAlbum.  However, from inside the code, we can't
		 *	change the "this" pointer.  Therefore we externally request that we be refreshed.
		 */
		public event EventHandler RequestRefresh;
		protected void OnRequestRefresh()
		{
			if(this.Parent != null)
			{
				this.Parent.OnRequestRefresh();
				return;
			}
			
			this.RequestRefresh(this, EventArgs.Empty);
		}
		#endregion

        #region "Event: PhotoItemIndexChange Event"
        public event PhotoItemIndexChangeEventHandler PhotoItemIndexChange;
        public delegate void PhotoItemIndexChangeEventHandler(
            PhotoItem sender, PhotoItem pi, int oldIdx, int newIdx);

        protected virtual void OnPhotoItemIndexChange(
            PhotoItem sender, PhotoItem pi, int oldIdx, int newIdx)
        {
            if (this.PhotoItemIndexChange != null)
                this.PhotoItemIndexChange(sender, pi, oldIdx, newIdx);

            // forward the event if necessary
            if (this.Parent != null)
                this.Parent.OnPhotoItemIndexChange(sender, pi, oldIdx, newIdx);
        }
        protected void OnPhotoItemAdd(PhotoItem pi, int idx)
        {
            this.OnPhotoItemIndexChange(this, pi, -1, idx);
        }
        #endregion

        #region "Event: Deleted"
        public event DeleteEventHandler Delete;
        protected virtual void OnDelete(PhotoItem pi)
        {
            if (this.Delete != null)
                this.Delete(pi);

            // forward the event...
            if (this.Parent != null)
                this.Parent.OnDelete(pi);
        }
        public delegate void DeleteEventHandler(PhotoItem sender);
        #endregion
		#endregion



		protected PhotoItem(string path)
		{
			this.path = path;
		}
		internal virtual PhotoItem FindMember(string path)
		{
			if(path.Length == 0) return this;
			if(path == this.Path) return this;

			return null;
		}
		//private static PhotoItem FindMember(PhotoItem pi, string path)
		//{
		//	if(pi.Path == path) return pi;
		//	if(pi is Album)
		//	{
		//		((Album)pi)
		//	}
		//	throw new IndexOutOfRangeException(
		//		"Unable to find PhotoItem with path: \""+path+"\"");
		//}


		#region "File Operations"
		static internal void Move(PhotoItem pi, string newPath)
		{
			if(pi is Photo)
				File.Move(pi.Path, newPath);
			else
				Directory.Move(pi.Path, newPath);
		}
		#endregion

		#region IDisposable Members
		protected virtual void Dispose(bool disposing)
		{
			if(this.photoItemUpdater != null)
				this.photoItemUpdater.Dispose();
		}

		public void Dispose()
		{
			if(this.photoItemUpdater == null) return;
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion
		#region IComparable Members

		public virtual int CompareTo(object obj)
		{
			if(obj is PhotoItem) 
			{
				return this.BeginDate.CompareTo(((PhotoItem)obj).BeginDate);
			}
        
			throw new ArgumentException("object is not a PhotoItem");
			return 0;
		}
		/*public static bool operator < (PhotoItem pi1, PhotoItem pi2)
		{
			return pi1.CompareTo(pi2) < 0;
		}
		public static bool operator <= (PhotoItem pi1, PhotoItem pi2)
		{
			return pi1.CompareTo(pi2) <= 0;
		}
		public static bool operator > (PhotoItem pi1, PhotoItem pi2)
		{
			return pi1.CompareTo(pi2) > 0;
		}
		public static bool operator >= (PhotoItem pi1, PhotoItem pi2)
		{
			return pi1.CompareTo(pi2) >= 0;
		}
		public static bool operator == (PhotoItem pi1, PhotoItem pi2)
		{
			if((object)pi1 == null && (object)pi2 == null) return true;
			if((object)pi1 == null || (object)pi2 == null) return false;
			return pi1.CompareTo(pi2) == 0;
		}
		public static bool operator != (PhotoItem pi1, PhotoItem pi2)
		{
			return !(pi1 == pi2);
		}
		public override bool Equals(object obj)
		{
			if(obj is PhotoItem)
				return ((PhotoItem)obj) == this;

			return base.Equals(obj);
		}
		public override int GetHashCode()
		{
			return this.BeginDate.GetHashCode();
		}*/


		#endregion

		public override string ToString()
		{
			System.Text.StringBuilder s = new System.Text.StringBuilder();
			s.Append(System.IO.Path.GetFileNameWithoutExtension(this.Path));
			s.Append(" : ");
			s.Append(this.BeginDate.ToString());
			return s.ToString();
		}


		public class PhotoItemUpdater : System.IDisposable
		{
			private System.IO.FileSystemWatcher FileWatcher;
			private System.IO.FileSystemWatcher DirectoryWatcher;
			private PhotoItem photoItem;
			public PhotoItemUpdater(PhotoItem pi)
			{
				this.photoItem = pi;
				// set up the File Watcher
				this.FileWatcher = new System.IO.FileSystemWatcher(pi.Path);

				// set up the Directory Watcher
				this.DirectoryWatcher = new FileSystemWatcher(pi.path);

				this.FileWatcher.IncludeSubdirectories = 
					this.DirectoryWatcher.IncludeSubdirectories = true;

				this.FileWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
				this.DirectoryWatcher.NotifyFilter = NotifyFilters.DirectoryName;

				// Hook up events:
				this.FileWatcher.Changed += new FileSystemEventHandler(FileWatcher_Changed);
				this.FileWatcher.Created += new FileSystemEventHandler(FileWatcher_Created);
				this.FileWatcher.Deleted += new FileSystemEventHandler(FileWatcher_Deleted);
				this.FileWatcher.Renamed += new RenamedEventHandler(FileWatcher_Renamed);

				// For some reason this fires all of the time, so we choose not to listen
				// for it.  It can always be re-enabled
				//this.DirectoryWatcher.Changed += new FileSystemEventHandler();
				this.DirectoryWatcher.Created += new FileSystemEventHandler(DirectoryWatcher_Created);
				this.DirectoryWatcher.Deleted += new FileSystemEventHandler(DirectoryWatcher_Deleted);
				this.DirectoryWatcher.Renamed += new RenamedEventHandler(DirectoryWatcher_Renamed);

				this.FileWatcher.EnableRaisingEvents =
					this.DirectoryWatcher.EnableRaisingEvents = true;
			}
			#region IDisposable Members
			protected void Dispose(bool disposing)
			{
				if(this.FileWatcher != null)
					this.FileWatcher.Dispose();

				if(this.DirectoryWatcher != null)
					this.DirectoryWatcher.Dispose();
			}

			public void Dispose()
			{
				this.Dispose(true);
				GC.SuppressFinalize(this);
			}

			#endregion

			internal static bool VerifyFileType(string path)
			{
				string ext = System.IO.Path.GetExtension(path).ToLower();
				return (ext == ".jpeg" || ext == ".jpg" || ext == ".tiff" || ext == ".tif");
			}
			internal static string GetRealFullPath(string fullPath)
			{
				return Directory.GetFileSystemEntries(System.IO.Path.GetDirectoryName(fullPath),
					System.IO.Path.GetFileName(fullPath))[0];
			}
			#region "FileWatcher events"
			private void FileWatcher_Created(object sender, FileSystemEventArgs e)
			{
				if(!VerifyFileType(e.FullPath)) return;

				Album album = (Album)
					this.photoItem.FindMember(System.IO.Path.GetDirectoryName(e.FullPath));

				// we need to fail silently, because when we are setting up
				// the directory structure in memory, we alter the filesystem.
				// therefore, if we have not yet read in the directory, will
				// not be found by findmember!
				if(album == null) return;

				if(album.AlbumType != AlbumType.Photo)
				{
					File.Move(e.FullPath, 
						album.GetMiscellaneousDirectoryName()+"\\"+
						System.IO.Path.GetFileName(GetRealFullPath(e.FullPath)));
					return;
				}

				album.Add(new Photo(e.FullPath));
			}
			private void FileWatcher_Changed(object sender, FileSystemEventArgs e)
			{
				if(e.ChangeType != WatcherChangeTypes.Changed) return;

				if(!VerifyFileType(e.FullPath)) return;

				Photo photo = (Photo)this.photoItem.FindMember(e.FullPath);
				
				if(photo == null) return;

				Photo.RefreshProperties(photo);
			}
			private void FileWatcher_Deleted(object sender, FileSystemEventArgs e)
			{
				if(!VerifyFileType(e.FullPath)) return;

				PhotoItem pi = this.photoItem.FindMember(e.FullPath);
				
				if(pi == null) return;
				
				pi.Parent.Remove(pi);
                pi.OnDelete(pi);
                pi.Dispose();
			}
			private void FileWatcher_Renamed(object sender, RenamedEventArgs e)
			{
				// rename is special
				//OldName: eg:"testing\filename" or "testing";


                if (VerifyFileType(e.OldFullPath) && VerifyFileType(e.FullPath))
                {
                    PhotoItem pi = this.photoItem.FindMember(e.OldFullPath);

                    if (pi == null) return;

                    Album newParent = (Album)this.photoItem.FindMember(
                        System.IO.Path.GetDirectoryName(e.FullPath));

                    if (newParent == null) return;

                    pi.i_Path = GetRealFullPath(e.FullPath);

                    if (pi.Parent == newParent) return;

                    pi.Parent.Remove(pi);
                    newParent.Add(pi);
                }
                else if (VerifyFileType(e.OldFullPath))
                {
                    PhotoItem pi = this.photoItem.FindMember(e.OldFullPath);

                    if (pi == null) return;

                    pi.Parent.Remove(pi);
                    pi.OnDelete(pi);
                    pi.Dispose();
                }
                else if (VerifyFileType(e.FullPath))
                {
                    Album album = (Album)
                        this.photoItem.FindMember(System.IO.Path.GetDirectoryName(e.FullPath));

                    if (album == null) return;

                    if (album.AlbumType != AlbumType.Photo)
                    {
                        File.Move(e.FullPath,
                            album.GetMiscellaneousDirectoryName() + "\\" +
                            System.IO.Path.GetFileName(GetRealFullPath(e.FullPath)));
                        return;
                    }

                    album.Add(new Photo(e.FullPath));
                }
            }
			#endregion

			#region "DirectoryWatcher events"
			private void DirectoryWatcher_Created(object sender, FileSystemEventArgs e)
			{
				Album album = (Album)
					this.photoItem.FindMember(System.IO.Path.GetDirectoryName(e.FullPath));

				// we need to fail silently, because when we are setting up
				// the directory structure in memory, we alter the filesystem.
				// therefore, if we have not yet read in the directory, will
				// not be found by findmember!
				if(album == null) return;

				if(album.AlbumType != AlbumType.Album)
				{
					PhotoItem [] pis = new PhotoItem[album.Count];
					for(int idx = album.Count-1;idx >= 0;idx--)
					{
						pis[idx] = album[0];
						album.Remove(pis[idx]);
					}

					album.i_AlbumType = AlbumType.Album;
					string miscDir = album.GetMiscellaneousDirectoryName();
					DirectoryInfo di = new DirectoryInfo(miscDir);
					if(!di.Exists) di.Create();
					
					for(int i=0;i<pis.Length;i++)
					{
						File.Move(pis[i].Path, miscDir+"\\"+System.IO.Path.GetFileName(pis[i].Path));
					}
				}

				album.Add(Album.CreateAlbum(GetRealFullPath(e.FullPath)));
			}
			private void DirectoryWatcher_Deleted(object sender, FileSystemEventArgs e)
			{
				PhotoItem pi = this.photoItem.FindMember(e.FullPath);
				
				if(pi == null) return;
				
				pi.Parent.Remove(pi);
				pi.Dispose();
			}
			private void DirectoryWatcher_Renamed(object sender, RenamedEventArgs e)
			{
				// rename is special
				//OldName: eg:"testing\filename" or "testing";
				PhotoItem pi = this.photoItem.FindMember(e.OldFullPath);

				if(pi == null) return;

				Album newParent = (Album)this.photoItem.FindMember(
					System.IO.Path.GetDirectoryName(e.FullPath));

				if(newParent == null) return;

				pi.i_Path = GetRealFullPath(e.FullPath);

				if(pi.Parent == newParent) return;

				pi.Parent.Remove(pi);
				newParent.Add(pi);
			}
			#endregion

		}
	}
}

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


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

Comments and Discussions