InvalidOperationException: Removing DockingWindow with IRegionMemberLifetime

Docking/MDI for WPF Forum

Posted 6 years ago by Alan McCormick
Version: 12.2.0570
Platform: Silverlight Controls
Environment: Windows 7 (64-bit)
Avatar

I'm using Prism and the open source Prism interop library. I have a TabbedMdiHost in my applications Shell. The problem started when I implemented IRegionMemberLifetime.KeepAlive. When closing a tab that contains a view that returns false for KeepAlive, I get the following exception:

InvalidOperationException {"Cannot have nested BeginInit calls on the same instance."}

   at System.Windows.FrameworkElement.BeginInit()
   at System.Windows.Controls.ItemsControl.BeginInit()
   at ActiproSoftware.Windows.Controls.Docking.DockingWindowContainer.BeginInit()
   at ActiproSoftware.Windows.Controls.Docking.DockSite.#Lt(DependencyObject #lp)
   at ActiproSoftware.Windows.Controls.Docking.DockSite.#uu(#ki #4j)
   at ActiproSoftware.Windows.Controls.Docking.DockSite.#wu(DockingWindow #sb)
   at ActiproSoftware.Windows.Controls.Docking.DockSite.#Cv(DockingWindow #sb, Boolean #zFf)
   at ActiproSoftware.Windows.Controls.Docking.DockSite.#Su(IList`1 #BEd, IList #lF, Int32 #ahb, DockingWindowItemType #4Ub)
   at ActiproSoftware.Windows.Controls.Docking.DockSite.#Ku(Object #xhb, NotifyCollectionChangedEventArgs #yhb)
   at ActiproSoftware.Windows.EnumerableView`1.#Web(NotifyCollectionChangedEventArgs #yhb)
   at ActiproSoftware.Windows.EnumerableView`1.#CM(Object #xhb, NotifyCollectionChangedEventArgs #yhb)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.DeferrableObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.RemoveItem(Int32 index)
   at System.Collections.ObjectModel.Collection`1.RemoveAt(Int32 index)
   at ActiproSoftware.Windows.EnumerableView`1.#aXb(Int32 #ahb, Int32 #LX)
   at ActiproSoftware.Windows.EnumerableView`1.#9Wb(Object #xhb, NotifyCollectionChangedEventArgs #yhb)
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.Prism.Regions.DockSiteRegionViewsCollection.NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.Prism.Regions.DockSiteRegionViewsCollection.OnEnumerableViewCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.EnumerableView`1.#Web(NotifyCollectionChangedEventArgs #yhb)
   at ActiproSoftware.Windows.EnumerableView`1.#CM(Object #xhb, NotifyCollectionChangedEventArgs #yhb)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.DeferrableObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.RemoveItem(Int32 index)
   at System.Collections.ObjectModel.Collection`1.RemoveAt(Int32 index)
   at ActiproSoftware.Windows.EnumerableView`1.#aXb(Int32 #ahb, Int32 #LX)
   at ActiproSoftware.Windows.EnumerableView`1.#9Wb(Object #xhb, NotifyCollectionChangedEventArgs #yhb)
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.RemoveItem(Int32 index)
   at System.Collections.ObjectModel.Collection`1.Remove(T item)
   at ActiproSoftware.Windows.Prism.Regions.DockSiteRegion.Remove(Object view)
   at Microsoft.Practices.Prism.Regions.Behaviors.RegionMemberLifetimeBehavior.OnActiveViewsChanged(Object sender, NotifyCollectionChangedEventArgs e) in c:\release\WorkingDir\PrismLibraryBuild\PrismLibrary\Desktop\Prism\Regions\Behaviors\RegionMemberLifetimeBehavior.cs:line 75
   at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.Prism.Regions.DockSiteRegionViewsCollection.NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.Prism.Regions.DockSiteRegionViewsCollection.OnEnumerableViewCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.EnumerableView`1.#Web(NotifyCollectionChangedEventArgs #yhb)
   at ActiproSoftware.Windows.EnumerableView`1.#CM(Object #xhb, NotifyCollectionChangedEventArgs #yhb)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.DeferrableObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at ActiproSoftware.Windows.DeferrableObservableCollection`1.EndUpdate()
   at ActiproSoftware.Windows.EnumerableView`1.#bXb(Int32 #ahb, T #G3, Boolean #JJf)
   at ActiproSoftware.Windows.EnumerableView`1.ReevaluateAt(Int32 index)
   at ActiproSoftware.Windows.EnumerableView`1.Reevaluate(T item)
   at ActiproSoftware.Windows.Prism.Regions.DockSiteRegionViewsCollection.OnItemMetadataChanged(Object sender, EventArgs e)
   at System.EventHandler.Invoke(Object sender, EventArgs e)
   at Microsoft.Practices.Prism.Regions.ItemMetadata.InvokeMetadataChanged() in c:\release\WorkingDir\PrismLibraryBuild\PrismLibrary\Desktop\Prism\Regions\ItemMetadata.cs:line 86
   at Microsoft.Practices.Prism.Regions.ItemMetadata.DependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) in c:\release\WorkingDir\PrismLibraryBuild\PrismLibrary\Desktop\Prism\Regions\ItemMetadata.cs:line 94
   at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
   at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
   at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
   at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
   at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
   at Microsoft.Practices.Prism.Regions.ItemMetadata.set_IsActive(Boolean value) in c:\release\WorkingDir\PrismLibraryBuild\PrismLibrary\Desktop\Prism\Regions\ItemMetadata.cs:line 72
   at ActiproSoftware.Windows.Prism.Regions.DockSiteRegion.Deactivate(Object view)
   at ActiproSoftware.Windows.Prism.Regions.Behaviors.DockSiteRegionBehavior.OnDockSiteWindowDeactivated(Object sender, DockingWindowEventArgs e)

Here's what's in the my application's main shell Window

<docking:DockSite x:Name="dockSite" 
						  prism:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}" 
						  CanDocumentWindowsRaft="True"
						  CanToolWindowsRaft="True">
			<docking:Workspace>
				<docking:TabbedMdiHost x:Name="tabbedMdiHost">
					<docking:TabbedMdiContainer>
					</docking:TabbedMdiContainer>
				</docking:TabbedMdiHost>
			</docking:Workspace>
		</docking:DockSite>

 Views are added to my region like so.. (this is all standard stuff)

BitmapImage image = new BitmapImage(new Uri(file));
				ImageViewModel viewModel = new ImageViewModel() { Image = image };

				var regionManager = (IRegionManager)ServiceLocator.Current.GetInstance<IRegionManager>();
				regionManager.Regions[RegionNames.MainRegion].Add(new ImageView() { ImageViewModel = viewModel });

For some reason, removing my ImageView (which is very simple and only contains only an image) works, but other views do not. Here's an example of a view that is successfully removed with I click the X on the tab. (all my view's implement DockingViewBase which implements IRegionMemberLifetime, IActiveAware, IDockingWindowInitializer, and IDockingWindowItemTypeProvider. They should supply the exact same values for both these views)

<local:DockingViewBase x:Class="Project.Infrastructure.Views.ImageView"
			 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
			 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
			 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
			 xmlns:local="clr-namespace:Project.Infrastructure.Views"
			 mc:Ignorable="d" 
			 d:DesignHeight="300" d:DesignWidth="300"
			 Title="{Binding Path=Image.UriSource.LocalPath}">
	<Image Name="image1" Source="{Binding Image}" HorizontalAlignment="Center" VerticalAlignment="Center"
		   StretchDirection="DownOnly" />
</local:DockingViewBase>

 Here's an example of one that fails with the exception above:

<inf:DockingViewBase x:Class="Project.Module.Views.ViewExample2"
			xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
			xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
			xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
			xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
			mc:Ignorable="d" 
			d:DesignHeight="249" d:DesignWidth="569"
			Title="{Binding Path=Signals.Name}">
	<Grid>
		<DataGrid ItemsSource="{Binding Signals}" AutoGenerateColumns="False">
			<DataGrid.Resources>
				<conv:FrequencyStringConverter x:Key="FrequencyConverter"/>
				<conv:DecibelStringConverter x:Key="DecibelConverter"/>
			</DataGrid.Resources>
			<DataGrid.Columns>
				<DataGridTextColumn Header="Frequency" Binding="{Binding Frequency, Converter={StaticResource FrequencyConverter}}"/>
				<DataGridTextColumn Header="Bandwidth" Binding="{Binding Bandwidth, Converter={StaticResource FrequencyConverter}}"/>
				<DataGridTextColumn Header="Amplitude" Binding="{Binding Amplitude, Converter={StaticResource DecibelConverter}}"/>
				<DataGridTextColumn Header="Comments" Binding="{Binding Comments}"/>
			</DataGrid.Columns>
		</DataGrid>
	</Grid>
</inf:DockingViewBase>  

 

Any idea what's going on? Why is BeginInit being called when I'm closing something? How can I fix it?

[Modified 6 years ago]

Comments (14)

Posted 6 years ago by Alan McCormick
Avatar

Ok I don't understand... If I set the KeepAlive to false, it attempts to reuse the same tab for each view now? I do not want to do that,but I want to enable my views to get garbage collected.

edit: Ok.. so I set KeepAlive back to true.. but now my views are never garabage collected. DockSite.AreDockingWindowsDestroyedOnClose is set to true.I'd think that when the X is clicked, close is called, destroy would be called, and my DockSiteRegion would no longer contain a reference to it anymore?

Also, I'd think that setting KeepAlive = false would not cause some sort of weird exception, or cause it to appear to reuse DockingWindows somehow when I open more than one.

[Modified 6 years ago]

Posted 6 years ago by Alan McCormick
Avatar

For some reason.... DockSiteViewsCollection's EnumerableViews collection is calling Remove if I add a 2nd View to the region while having KeepAlive = false;

I was able to step some of the code here:

private void OnEnumerableViewCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
			List<object> oldItems = null;
			if (e.OldItems != null)
				oldItems = e.OldItems.Cast<ItemMetadata>().Select(m => m.Item).ToList();

			List<object> newItems = null;
			if (e.NewItems != null)
				newItems = e.NewItems.Cast<ItemMetadata>().Select(m => m.Item).ToList();

			NotifyCollectionChangedEventArgs eventArgs = null;
			switch (e.Action) {
				case NotifyCollectionChangedAction.Add:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, e.NewStartingIndex);
					break;
				case NotifyCollectionChangedAction.Move:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, oldItems, e.NewStartingIndex, e.OldStartingIndex);
					break;
				case NotifyCollectionChangedAction.Remove:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, e.OldStartingIndex);
					break;
				case NotifyCollectionChangedAction.Replace:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, e.OldStartingIndex);
					break;
				case NotifyCollectionChangedAction.Reset:
				default:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
					break;
			}

			this.NotifyCollectionChanged(eventArgs);
		}

 I add 1 window, Add gets hit once. I add 2nd window Add gets hit, then immediately Remove gets hit with the first window. Stack trace involves a lot of private code. No idea why it would remove my first view.

Here's part of the stack.. (Break point is hit on the previous code .Remove case with the previous window as argument when nothing should be removed)

 	ActiproSoftware.Docking.Interop.Prism.Wpf40.dll!ActiproSoftware.Windows.Prism.Regions.DockSiteRegionViewsCollection.OnEnumerableViewCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) Line 235	C#
 	ActiproSoftware.Shared.Wpf.dll!ActiproSoftware.Windows.EnumerableView<System.__Canon>.#Web(System.Collections.Specialized.NotifyCollectionChangedEventArgs #yhb) + 0x1b bytes	
 	ActiproSoftware.Shared.Wpf.dll!ActiproSoftware.Windows.EnumerableView<System.__Canon>.#CM(object #xhb, System.Collections.Specialized.NotifyCollectionChangedEventArgs #yhb) + 0x1a bytes	
 	System.dll!System.Collections.ObjectModel.ObservableCollection<Microsoft.Practices.Prism.Regions.ItemMetadata>.OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + 0x52 bytes	
 	ActiproSoftware.Shared.Wpf.dll!ActiproSoftware.Windows.DeferrableObservableCollection<System.__Canon>.OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + 0x39 bytes	
 	ActiproSoftware.Shared.Wpf.dll!ActiproSoftware.Windows.DeferrableObservableCollection<System.__Canon>.EndUpdate() + 0xd2 bytes	
 	ActiproSoftware.Shared.Wpf.dll!ActiproSoftware.Windows.EnumerableView<Microsoft.Practices.Prism.Regions.ItemMetadata>.#bXb(int #ahb, Microsoft.Practices.Prism.Regions.ItemMetadata #G3, bool #JJf) + 0x889 bytes	
 	ActiproSoftware.Shared.Wpf.dll!ActiproSoftware.Windows.EnumerableView<Microsoft.Practices.Prism.Regions.ItemMetadata>.ReevaluateAt(int index) + 0x13b bytes	
 	ActiproSoftware.Shared.Wpf.dll!ActiproSoftware.Windows.EnumerableView<System.__Canon>.Reevaluate(System.__Canon item) + 0x3d bytes	
 	ActiproSoftware.Docking.Interop.Prism.Wpf40.dll!ActiproSoftware.Windows.Prism.Regions.DockSiteRegionViewsCollection.OnItemMetadataChanged(object sender, System.EventArgs e) Line 257 + 0x11 bytes	C#
 	Microsoft.Practices.Prism.dll!Microsoft.Practices.Prism.Regions.ItemMetadata.InvokeMetadataChanged() Line 87	C#
 	Microsoft.Practices.Prism.dll!Microsoft.Practices.Prism.Regions.ItemMetadata.DependencyPropertyChanged(System.Windows.DependencyObject dependencyObject, System.Windows.DependencyPropertyChangedEventArgs args) Line 96	C#
 	WindowsBase.dll!System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e) + 0x4c bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.NotifyPropertyChange(System.Windows.DependencyPropertyChangedEventArgs args) + 0x3c bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex entryIndex, System.Windows.DependencyProperty dp, System.Windows.PropertyMetadata metadata, System.Windows.EffectiveValueEntry oldEntry, ref System.Windows.EffectiveValueEntry newEntry, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType) + 0x71b bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.SetValueCommon(System.Windows.DependencyProperty dp, object value, System.Windows.PropertyMetadata metadata, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType, bool isInternal) + 0x2eb bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.SetValue(System.Windows.DependencyProperty dp, object value) + 0x35 bytes	
 	Microsoft.Practices.Prism.dll!Microsoft.Practices.Prism.Regions.ItemMetadata.IsActive.set(bool value) Line 72 + 0x33 bytes	C#
 	ActiproSoftware.Docking.Interop.Prism.Wpf40.dll!ActiproSoftware.Windows.Prism.Regions.DockSiteRegion.Deactivate(object view) Line 204 + 0xc bytes	C#
 	ActiproSoftware.Docking.Interop.Prism.Wpf40.dll!ActiproSoftware.Windows.Prism.Regions.Behaviors.DockSiteRegionBehavior.OnDockSiteWindowDeactivated(object sender, ActiproSoftware.Windows.Controls.Docking.DockingWindowEventArgs e) Line 117 + 0x1f bytes	C#
 	[Native to Managed Transition]	
 	mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args) + 0x77 bytes	
 	PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeEventHandler(System.Delegate genericHandler, object genericTarget) + 0x93 bytes	
 	PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) + 0x29 bytes	
 	PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x3e bytes	
 	PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0xbe bytes	
 	PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) + 0x79 bytes	
 	PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs e) + 0x17 bytes	
 	ActiproSoftware.Docking.Wpf.dll!ActiproSoftware.Windows.Controls.Docking.DockSite.OnActiveWindowChanged(ActiproSoftware.Windows.Controls.Docking.DockingWindow oldValue, ActiproSoftware.Windows.Controls.Docking.DockingWindow newValue) + 0x6b bytes	
 	ActiproSoftware.Docking.Wpf.dll!ActiproSoftware.Windows.Controls.Docking.DockSite.#1s(System.Windows.DependencyObject #QOd, System.Windows.DependencyPropertyChangedEventArgs #yhb) + 0x63 bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e) + 0x4c bytes	
 	PresentationFramework.dll!System.Windows.FrameworkElement.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e) + 0x50 bytes	
 	ActiproSoftware.Docking.Wpf.dll!ActiproSoftware.Windows.Controls.Docking.DockSite.OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e) + 0x9c bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.NotifyPropertyChange(System.Windows.DependencyPropertyChangedEventArgs args) + 0x3c bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.UpdateEffectiveValue(System.Windows.EntryIndex entryIndex, System.Windows.DependencyProperty dp, System.Windows.PropertyMetadata metadata, System.Windows.EffectiveValueEntry oldEntry, ref System.Windows.EffectiveValueEntry newEntry, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType) + 0x71b bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.SetValueCommon(System.Windows.DependencyProperty dp, object value, System.Windows.PropertyMetadata metadata, bool coerceWithDeferredReference, bool coerceWithCurrentValue, System.Windows.OperationType operationType, bool isInternal) + 0x2eb bytes	
 	WindowsBase.dll!System.Windows.DependencyObject.SetValue(System.Windows.DependencyPropertyKey key, object value) + 0x40 bytes	
 	ActiproSoftware.Docking.Wpf.dll!ActiproSoftware.Windows.Controls.Docking.DockSite.ActiveWindow.set(ActiproSoftware.Windows.Controls.Docking.DockingWindow #Ld) + 0x1c bytes	
 	ActiproSoftware.Docking.Wpf.dll!ActiproSoftware.Windows.Controls.Docking.DockSite.OnPreviewGotKeyboardFocus(System.Windows.Input.KeyboardFocusChangedEventArgs e) + 0x121 bytes	

[Modified 6 years ago]

Posted 6 years ago by Alan McCormick
Avatar

Apparently.. when the ItemMetadata changes to set 'IsActve" of the first view to false then EnumerableView.reevaluate(item) gets called and for some reason decides to remove the view from collection because the next thing I get is a Remove event for that item.

Posted 6 years ago by Alan McCormick
Avatar

Aha... I believe I found a bug! Turns out that EnumerableView is filtered to show ActiveViews, or so it is SUPPOSED to


Check this:

(x => !x.IsActive) 

 

/// <summary>
/// Gets a read-only view of the collection of all the active views in the region.
/// </summary>
/// <value>An <see cref="IViewsCollection"/> of all the active views.</value>
public IViewsCollection ActiveViews {
	get {
		if (this.activeViews == null)
			this.activeViews = new DockSiteRegionViewsCollection(this.ItemMetadataCollection, x => !x.IsActive) {
				SortComparison = this.sortComparison,
			};
		return this.activeViews;
	}
}

 Notice, I do not believe it makes sense that the ActiveViews show views that are not active. I need to go for the day.. I'll check to see if this fixes all or some of my problems tomorrow.. Feel free to double check this, maybe the open source project can be updated if this is a problem.

[Modified 6 years ago]

Posted 6 years ago by Alan McCormick
Avatar

Ok I lied a bit, but leaving now.. but think I found some more suspect code..

from DockSiteRegionBehavior.cs

if (region.Views.Contains(dockingWindow))
				region.Activate(dockingWindow);
			// 8/3/2011 - Changed Activate to use DataContext instead of Content to allow retention of visuals
			else if (region.Views.Contains(dockingWindow.DataContext))
				region.Activate(dockingWindow.DataContext);

 It seems that the first if statement there will never get hit in this case, because my "views" in the debugger are all my custom views that implement the IDockingWindowInitializer etc.. They are provided a DockingWindow but they are not a DockingWindow.

It seems that I may have to get the item from the window and check that instead

Posted 6 years ago by Alan McCormick
Avatar

ok I believe the else if statement already does that... but now that I fixed the IsActive enumeration none of my windows are getting IsActive notifiations. It appears to call Activate on it, but it appears the metadata show IsActive = true already and nothing changes.. However ActiveViews is empty now.

When I add a new view to the region is actually sets the metadata IsActive, but that doesn't notify my View which is IActiveAware anymore

[Modified 6 years ago]

Posted 6 years ago by Alan McCormick
Avatar

Ahh ok, your filter filters OUT stuff.. the logic is backwards from what I thought... ok now I'm back to square one.. maybe seems like all the previous logic was ok.

Posted 6 years ago by Alan McCormick
Avatar

Ok here is what i think the problem is now...

 

Microsoft's code from

RegionMemberLifetimeBehavior.cs

 private void OnActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // We only pay attention to items removed from the ActiveViews list.
            // Thus, we expect that any ICollectionView implementation would
            // always raise a remove and we don't handle any resets
            // unless we wanted to start tracking views that used to be active.
            if (e.Action != NotifyCollectionChangedAction.Remove) return;

            var inactiveViews = e.OldItems;
            foreach (var inactiveView in inactiveViews)
            {
                if (!ShouldKeepAlive(inactiveView))
                {
                    this.Region.Remove(inactiveView);
                }
            }
        }

Note, that an active view is only removed from a region when ShouldKeepAlive returns false. This happens only if your view or view's DataContext implements IRegionMemberLifetime or sets the RegionMemberLifetimeAttribute and specifies false for KeepAlive.

However, the DockingWindows are set Inactive whenever you click on a new one in the tabs.. It's "Not active" in the sense that the other tab is clicked on, but it is "Active" in the sense that it should still exist in the region. At least... I think so... 

If I do not specify RegionMemberLifetime with KeepAlive false, there seems to be no way to actually kill a view. However, if I do set it, then my views die when I simply activate a new document.

Posted 6 years ago by Alan McCormick
Avatar

Alright, it seems to me that the DockSiteRegionBehavior needs to listen to the DockSite.WindowClosed (or WindowUnregistered see later comment) event and actually remove the DockingWindow's View from the Region when the DockingWindow is closed. Here's my first guess at what needs to happen..

private void OnDockSiteWindowClosed(object sender, DockingWindowEventArgs e)
{
	// Ensure the DockingWindow exists and is generated for an item
	DockingWindow dockingWindow = e.Window;
	if (this.hostControl == null || dockingWindow == null || !dockingWindow.IsContainerForItem)
		return;

	// Initialize variables
	object item = dockingWindow.DataContext ?? dockingWindow;

	// remove the item from the region
	this.Region.Remove(item);
}
		
/// <summary>
/// Override this method to perform the logic after the behavior has been attached.
/// </summary>
protected override void OnAttach() {
	IRegion region = this.Region;
	if (region != null)
		region.ActiveViews.CollectionChanged += this.OnRegionActiveViewsCollectionChanged;

	DockSite dockSite = this.hostControl;
	if (dockSite != null) {
		dockSite.WindowActivated += this.OnDockSiteWindowActivated;
		dockSite.WindowDeactivated += this.OnDockSiteWindowDeactivated;
		dockSite.WindowRegistered += this.OnDockSiteWindowRegistered;
		dockSite.WindowClosed += this.OnDockSiteWindowClosed;
	}
}

 Originally I tried to handle WindowUnregistered. However, this event does not get fired. When does the WindowUnregistered event happen? The documentation is not helpful "Occurs after a DockingWindow has been unregistered." 

[Modified 6 years ago]

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Alan,

I believe that the way the Prism interop was implemented was such that any docking window that is "open" (our term) in the layout is considered "active" in the Prism sense.  Now open (our term) can mean that it's in a tabbed container and its tab is not selected.  When we close (our term) a docking window, then there is no tab or container that represents it visible in the UI.  Closing a docking window should be removing it from the Prism active views list.

The WindowClosed event occurs when a docking window is closed, per the description above, meaning it no longer has any UI in the layout.  Document windows by default destroy themselves when they are closed, but tool windows do not.  We use the term "destroy" to mean they become dissociated from the DockSite and no longer appear in the "DocumentWindows" or "ToolWindows" collections.

Documents auto-destroy by default since their lifetimes are generally just while they are open in the layout.  Tool windows are meant to be opened and closed frequently, so they don't auto-destroy themselves.

Anyhow, the WindowUnregistered event fires when a docking window is destroyed, and no longer associated with a DockSite.


Actipro Software Support

Posted 6 years ago by Alan McCormick
Avatar

Alright, thanks. That is my general understanding as well.

Can you explain why the DockSite.WindowUnregistered event doesn't fire when a DockingWindow gets closed and should also be auto-destroyed? Is it because the DocumentItemsSource is being set to the region's views which is a ReadOnly EnumerableView?

So far, the way I handled Closed sorta works. But, for instance if I didn't auto-destroy DocumentWindows, I'm not sure this would work. For example, say a tool window gets closed, it then gets disassociated with the region but not the docksite. Then what? To be reassociated with the DockSiteRegion it looks like WindowRegistered would need to occur, but that wouldn't happen because it is still registered.

Posted 6 years ago by Alan McCormick
Avatar

I need a little guidance on the proper solution. I can't have my DockSiteRegion containing a reference to every view that was ever opened. I need to make sure those are garbage collected when they should have been destroyed.

Maybe something like this?

private void OnDockSiteWindowClosed(object sender, DockingWindowEventArgs e)
		{
			// Ensure the DockingWindow exists and is generated for an item
			DockingWindow dockingWindow = e.Window;
			if (this.hostControl == null || dockingWindow == null || !dockingWindow.IsContainerForItem)
				return;

			// Initialize variables
			object item = dockingWindow.DataContext ?? dockingWindow;

			if (dockingWindow is DocumentWindow)
			{
				if (hostControl.AreDocumentWindowsDestroyedOnClose)
					this.Region.Remove(item);
			}
			else if (dockingWindow is ToolWindow)
			{
				// do nothing
			}
		}

If Unregistered were being called I think it would be more appropriate event to handle.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi, I think you're headed is the right way to do things.

The main problem is that anything you bind to the DockSite.DocumentItemsSource or ToolItemsSource properties (for MVVM support) is read-only, and similarly for Prism region view model registrations.  So right now when you close a window now, it's still registered and can be reopened at any time.

You can manually remove the view by attaching a handler to the DockSite.WindowClosed event, as you did, and then remove the view model.


Actipro Software Support

Posted 6 years ago by Alan McCormick
Avatar

I ended up changing the way this works. Here is my reasoning:

  1. When calling region.Remove inside the DockSiteRegionBehavior from Closed, the events were in an odd order. Specifically, the Unregistered event was being called before the Closed Event finished (because remove was being called then)
  2. It would seem that the code which is responsible for adding the view to the region, should also be responsible for removing the view from the region

So here's my new plan

The View (DockingWindow) should only have an X (close) button if and only if the ViewModel exposes the functionality to close iteself. Logically the View should not be responsible for deciding what to display. Therefore, there needs to be an interface between the view & viewmodel which indicates whether the ViewModel can be removed. I call this interface IRemovable:

public interface IRemovable
{
	event EventHandler Removed;
	bool Removable { get; }
	bool Remove();
}

 My base view class handles the DockSite events for Opened, Closing, Closed such that it can fire its own events/virtual methods for them. In OnClosing, I check if the DataContext is IRemovable and execute its bool Remove() method. The result of which is set to the e.Cancel from DocumentWindow. For example, if a file in the ViewModel needs saved, it requests interaction with the user to give a file name, the user clicks "cancel", remove returns false, and the docking window should cancel closing. The "Closable" dependency property gets bound to the "Removable" property of the ViewModel;

Here's snippets from my base view class

public static readonly DependencyProperty ClosableProperty =
	DependencyProperty.Register(
	"Closable",
	typeof(bool),
	typeof(DockingViewBase),
	new PropertyMetadata(false, OnClosableChanged));

private static void OnClosableChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
	DockingViewBase view = (o as DockingViewBase);
	if (view.DockingWindow != null)
		view.DockingWindow.CanClose = (bool)e.NewValue;
}

public bool Closable
{
	get
	{
		return (bool)GetValue(ClosableProperty);
	}
	set
	{
		SetValue(ClosableProperty, value);
	}
}

public void Initialize(DockingWindow window, object item)
{
	this.DockingWindow = window;
	DockingWindow.Title = Title;
	DockingWindow.CanClose = Closable;
	DockingWindow.CanDrag = true;
	DockingWindow.CanRaft = true;
	DockingWindow.AllowDrop = true;
	DockingWindow.CanAttach = true;

	DockingWindow.DockSite.WindowOpened += DockSite_WindowOpened;
	DockingWindow.DockSite.WindowClosing += DockSite_WindowClosing;
	DockingWindow.DockSite.WindowClosed += DockSite_WindowClosed;
	DockingWindow.DockSite.WindowUnregistered += DockSite_WindowUnregistered;
}
private void DockSite_WindowUnregistered(object sender, DockingWindowEventArgs e)
{
	DockSite site = (DockSite)sender;
	site.WindowOpened -= DockSite_WindowOpened;
	site.WindowClosing -= DockSite_WindowClosing;
	site.WindowClosed -= DockSite_WindowClosed;
	site.WindowUnregistered -= DockSite_WindowUnregistered;
}
private void DockSite_WindowOpened(object sender, DockingWindowEventArgs e)
{
		if (e.Window == DockingWindow)
			OnOpened(EventArgs.Empty);
}
private void DockSite_WindowClosing(object sender, DockingWindowEventArgs e)
{
	if (e.Window == DockingWindow)
	{
		CancelEventArgs args = new CancelEventArgs(e.Cancel);
		OnClosing(args);
		e.Cancel = args.Cancel;
	}
}
private void DockSite_WindowClosed(object sender, DockingWindowEventArgs e)
{
	if (e.Window == DockingWindow)
		OnClosed(EventArgs.Empty);
}

public event EventHandler Opened = delegate { };
public event EventHandler Closed = delegate { };
public event CancelEventHandler Closing = delegate { };
protected virtual void OnOpened(EventArgs e)
{
	Opened(this, e);
}
protected virtual void OnClosing(CancelEventArgs e)
{
	IRemovable item = (DataContext as IRemovable);
	if (item != null)
		e.Cancel = !item.Remove();

	Closing(this, e);
}
protected virtual void OnClosed(EventArgs e)
{
	Closed(this, e);
}

 Lastly, in my controller which initially created the view, I wire to the event for View.Closed and then call IRegion.Remove(view); This means the controller both adds the views to the region and removes them from the region (And it doesn't have to, it could allow re-opening if I weren't destroying the DocumentWindows or if they were tool windows). It also means the view will get unregistered from the DockSite without modifying a behavior.

[Modified 6 years ago]

The latest build of this product (v2018.1 build 0675) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.