/************************************************************************************* Extended WPF Toolkit Copyright (C) 2007-2013 Xceed Software Inc. This program is provided to you under the terms of the Microsoft Public License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license For more features, controls, and fast professional support, pick up the Plus Edition at http://xceed.com/wpf_toolkit Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids ***********************************************************************************/ using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Windows; using Xceed.Wpf.Toolkit.Core; namespace Xceed.Wpf.Toolkit.Zoombox { public sealed class ZoomboxViewStack : Collection, IWeakEventListener { #region Constructors public ZoomboxViewStack( Zoombox zoombox ) { _zoomboxRef = new WeakReference( zoombox ); } #endregion #region SelectedView Property public ZoomboxView SelectedView { get { int currentIndex = this.Zoombox.ViewStackIndex; return ( currentIndex < 0 || currentIndex > Count - 1 ) ? ZoomboxView.Empty : this[ currentIndex ]; } } #endregion #region AreViewsFromSource Internal Property internal bool AreViewsFromSource { get { return _cacheBits[ ( int )CacheBits.AreViewsFromSource ]; } set { _cacheBits[ ( int )CacheBits.AreViewsFromSource ] = value; } } #endregion #region Source Internal Property internal IEnumerable Source { get { return _source; } } // if the view stack is generated by items within the ViewStackSource collection // of the Zoombox, then we maintain a strong reference to the source private IEnumerable _source; //null #endregion #region IsChangeFromSource Private Property private bool IsChangeFromSource { get { return _cacheBits[ ( int )CacheBits.IsChangeFromSource ]; } set { _cacheBits[ ( int )CacheBits.IsChangeFromSource ] = value; } } #endregion #region IsMovingViews Private Property private bool IsMovingViews { get { return _cacheBits[ ( int )CacheBits.IsMovingViews ]; } set { _cacheBits[ ( int )CacheBits.IsMovingViews ] = value; } } #endregion #region IsResettingViews Private Property private bool IsResettingViews { get { return _cacheBits[ ( int )CacheBits.IsResettingViews ]; } set { _cacheBits[ ( int )CacheBits.IsResettingViews ] = value; } } #endregion #region IsSettingInitialViewAfterClear Private Property private bool IsSettingInitialViewAfterClear { get { return _cacheBits[ ( int )CacheBits.IsSettingInitialViewAfterClear ]; } set { _cacheBits[ ( int )CacheBits.IsSettingInitialViewAfterClear ] = value; } } #endregion #region Zoombox Private Property private Zoombox Zoombox { get { return _zoomboxRef.Target as Zoombox; } } // maintain a weak reference to the Zoombox that owns the stack private WeakReference _zoomboxRef; #endregion internal void ClearViewStackSource() { if( this.AreViewsFromSource ) { this.AreViewsFromSource = false; this.MonitorSource( false ); _source = null; using( new SourceAccess( this ) ) { this.Clear(); } this.Zoombox.CoerceValue( Zoombox.ViewStackModeProperty ); } } internal void PushView( ZoomboxView view ) { // clear the forward stack int currentIndex = this.Zoombox.ViewStackIndex; while( this.Count - 1 > currentIndex ) { this.RemoveAt( Count - 1 ); } this.Add( view ); } internal void SetViewStackSource( IEnumerable source ) { if( _source != source ) { this.MonitorSource( false ); _source = source; this.MonitorSource( true ); this.AreViewsFromSource = true; this.Zoombox.CoerceValue( Zoombox.ViewStackModeProperty ); this.ResetViews(); } } protected override void ClearItems() { this.VerifyStackModification(); bool currentDeleted = ( this.Zoombox.CurrentViewIndex >= 0 ); base.ClearItems(); this.Zoombox.SetViewStackCount( Count ); // if resetting the views due to a change in the view source collection, just return if( this.IsResettingViews ) return; if( this.Zoombox.EffectiveViewStackMode == ZoomboxViewStackMode.Auto && this.Zoombox.CurrentView != ZoomboxView.Empty ) { this.IsSettingInitialViewAfterClear = true; try { this.Add( this.Zoombox.CurrentView ); } finally { this.IsSettingInitialViewAfterClear = false; } this.Zoombox.ViewStackIndex = 0; if( currentDeleted ) { this.Zoombox.SetCurrentViewIndex( 0 ); } } else { this.Zoombox.ViewStackIndex = -1; this.Zoombox.SetCurrentViewIndex( -1 ); } } protected override void InsertItem( int index, ZoomboxView view ) { this.VerifyStackModification(); if( this.Zoombox.HasArrangedContentPresenter && this.Zoombox.ViewStackIndex >= index && !this.IsSettingInitialViewAfterClear && !this.IsResettingViews && !this.IsMovingViews ) { bool oldUpdatingView = this.Zoombox.IsUpdatingView; this.Zoombox.IsUpdatingView = true; try { this.Zoombox.ViewStackIndex++; if( this.Zoombox.CurrentViewIndex != -1 ) { this.Zoombox.SetCurrentViewIndex( this.Zoombox.CurrentViewIndex + 1 ); } } finally { this.Zoombox.IsUpdatingView = oldUpdatingView; } } base.InsertItem( index, view ); this.Zoombox.SetViewStackCount( Count ); } protected override void RemoveItem( int index ) { this.VerifyStackModification(); bool currentDeleted = ( this.Zoombox.ViewStackIndex == index ); if( !this.IsMovingViews ) { // if an item below the current index was deleted // (or if the last item is currently selected and it was deleted), // adjust the ViewStackIndex and CurrentViewIndex values if( this.Zoombox.HasArrangedContentPresenter && ( this.Zoombox.ViewStackIndex > index || ( currentDeleted && this.Zoombox.ViewStackIndex == this.Zoombox.ViewStack.Count - 1 ) ) ) { // if removing the last item, just clear the stack, which ensures the proper // behavior based on the ViewStackMode if( currentDeleted && this.Zoombox.ViewStack.Count == 1 ) { this.Clear(); return; } bool oldUpdatingView = this.Zoombox.IsUpdatingView; this.Zoombox.IsUpdatingView = true; try { this.Zoombox.ViewStackIndex--; if( this.Zoombox.CurrentViewIndex != -1 ) { this.Zoombox.SetCurrentViewIndex( this.Zoombox.CurrentViewIndex - 1 ); } } finally { this.Zoombox.IsUpdatingView = oldUpdatingView; } } } base.RemoveItem( index ); // if the current view was deleted, we may need to update the view index // (unless a non-stack view is in effect) if( !this.IsMovingViews && currentDeleted && this.Zoombox.CurrentViewIndex != -1 ) { this.Zoombox.RefocusView(); } this.Zoombox.SetViewStackCount( Count ); } protected override void SetItem( int index, ZoomboxView view ) { this.VerifyStackModification(); base.SetItem( index, view ); // if the set item is the current item, update the zoombox if( index == this.Zoombox.CurrentViewIndex ) { this.Zoombox.RefocusView(); } } private static ZoomboxView GetViewFromSourceItem( object item ) { ZoomboxView view = ( item is ZoomboxView ) ? item as ZoomboxView : ZoomboxViewConverter.Converter.ConvertFrom( item ) as ZoomboxView; if( view == null ) throw new InvalidCastException( string.Format( ErrorMessages.GetMessage( "UnableToConvertToZoomboxView" ), item ) ); return view; } private void InsertViews( int index, IList newItems ) { using( new SourceAccess( this ) ) { foreach( object item in newItems ) { ZoomboxView view = ZoomboxViewStack.GetViewFromSourceItem( item ); if( index >= this.Count ) { this.Add( view ); } else { this.Insert( index, view ); } index++; } } } private void MonitorSource( bool monitor ) { if( _source != null && ( _source is INotifyCollectionChanged ) ) { if( monitor ) { CollectionChangedEventManager.AddListener( _source as INotifyCollectionChanged, this ); } else { CollectionChangedEventManager.RemoveListener( _source as INotifyCollectionChanged, this ); } } } private void MoveViews( int oldIndex, int newIndex, IList movedItems ) { using( new SourceAccess( this ) ) { int currentIndex = this.Zoombox.ViewStackIndex; int indexAfterMove = currentIndex; // adjust the current index, if it was affected by the move if( !( ( oldIndex < currentIndex && newIndex < currentIndex ) || ( oldIndex > currentIndex && newIndex > currentIndex ) ) ) { if( currentIndex >= oldIndex && currentIndex < oldIndex + movedItems.Count ) { indexAfterMove += newIndex - oldIndex; } else if( currentIndex >= newIndex ) { indexAfterMove += movedItems.Count; } } this.IsMovingViews = true; try { for( int i = 0; i < movedItems.Count; i++ ) { this.RemoveAt( oldIndex ); } for( int i = 0; i < movedItems.Count; i++ ) { this.Insert( newIndex + i, ZoomboxViewStack.GetViewFromSourceItem( movedItems[ i ] ) ); } if( indexAfterMove != currentIndex ) { this.Zoombox.ViewStackIndex = indexAfterMove; this.Zoombox.SetCurrentViewIndex( indexAfterMove ); } } finally { this.IsMovingViews = false; } } } private void OnSourceCollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) { switch( e.Action ) { case NotifyCollectionChangedAction.Add: this.InsertViews( e.NewStartingIndex, e.NewItems ); break; case NotifyCollectionChangedAction.Move: this.MoveViews( e.OldStartingIndex, e.NewStartingIndex, e.OldItems ); break; case NotifyCollectionChangedAction.Remove: this.RemoveViews( e.OldStartingIndex, e.OldItems ); break; case NotifyCollectionChangedAction.Replace: this.ResetViews(); break; case NotifyCollectionChangedAction.Reset: this.ResetViews(); break; } } private void ResetViews() { using( new SourceAccess( this ) ) { int currentIndex = this.Zoombox.ViewStackIndex; this.IsResettingViews = true; try { this.Clear(); foreach( object item in _source ) { ZoomboxView view = ZoomboxViewStack.GetViewFromSourceItem( item ); this.Add( view ); } currentIndex = Math.Min( Math.Max( 0, currentIndex ), this.Count - 1 ); this.Zoombox.ViewStackIndex = currentIndex; this.Zoombox.SetCurrentViewIndex( currentIndex ); this.Zoombox.RefocusView(); } finally { this.IsResettingViews = false; } } } private void RemoveViews( int index, IList removedItems ) { using( new SourceAccess( this ) ) { for( int i = 0; i < removedItems.Count; i++ ) { this.RemoveAt( index ); } } } private void VerifyStackModification() { if( this.AreViewsFromSource && !this.IsChangeFromSource ) throw new InvalidOperationException( ErrorMessages.GetMessage( "ViewStackCannotBeManipulatedNow" ) ); } #region IWeakEventListener Members public bool ReceiveWeakEvent( Type managerType, object sender, EventArgs e ) { if( managerType == typeof( CollectionChangedEventManager ) ) { this.OnSourceCollectionChanged( sender, ( NotifyCollectionChangedEventArgs )e ); } else { return false; } return true; } #endregion #region Private Fields // to save memory, store bool variables in a bit vector private BitVector32 _cacheBits = new BitVector32( 0 ); #endregion #region SourceAccess Nested Type private sealed class SourceAccess : IDisposable { public SourceAccess( ZoomboxViewStack viewStack ) { _viewStack = viewStack; _viewStack.IsChangeFromSource = true; } ~SourceAccess() { this.Dispose(); } public void Dispose() { _viewStack.IsChangeFromSource = false; _viewStack = null; GC.SuppressFinalize( this ); } private ZoomboxViewStack _viewStack; } #endregion #region CacheBits Nested Type private enum CacheBits { AreViewsFromSource = 0x00000001, IsChangeFromSource = 0x00000002, IsResettingViews = 0x00000004, IsMovingViews = 0x00000008, IsSettingInitialViewAfterClear = 0x00000010, } #endregion } }