All the controls missing in WPF. Over 1 million downloads.
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

/*************************************************************************************
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
}
}