You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
555 lines
15 KiB
555 lines
15 KiB
/*************************************************************************************
|
|
|
|
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<ZoomboxView>, 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
|
|
}
|
|
}
|
|
|