/V4/PrismLibrary/Silverlight/Prism/Regions/Behaviors/TabControlRegionSyncBehavior.cs

# · C# · 267 lines · 180 code · 28 blank · 59 comment · 42 complexity · f367b572840c6684ddbd8967ffec8e2e MD5 · raw file

  1. //===================================================================================
  2. // Microsoft patterns & practices
  3. // Composite Application Guidance for Windows Presentation Foundation and Silverlight
  4. //===================================================================================
  5. // Copyright (c) Microsoft Corporation. All rights reserved.
  6. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
  7. // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
  8. // LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  9. // FITNESS FOR A PARTICULAR PURPOSE.
  10. //===================================================================================
  11. // The example companies, organizations, products, domain names,
  12. // e-mail addresses, logos, people, places, and events depicted
  13. // herein are fictitious. No association with any real company,
  14. // organization, product, domain name, email address, logo, person,
  15. // places, or events is intended or should be inferred.
  16. //===================================================================================
  17. using System;
  18. using System.Collections.Generic;
  19. using System.Collections.Specialized;
  20. using System.Windows;
  21. using System.Windows.Controls;
  22. using Microsoft.Practices.Prism.Properties;
  23. namespace Microsoft.Practices.Prism.Regions.Behaviors
  24. {
  25. /// <summary>
  26. /// Behavior that generates <see cref="TabItem"/> containers for the added items
  27. /// and also keeps the <see cref="TabControl.SelectedItem"/> and the <see cref="IRegion.ActiveViews"/> in sync.
  28. /// </summary>
  29. public class TabControlRegionSyncBehavior : RegionBehavior, IHostAwareRegionBehavior
  30. {
  31. ///<summary>
  32. /// The behavior key for this region sync behavior.
  33. ///</summary>
  34. public const string BehaviorKey = "TabControlRegionSyncBehavior";
  35. private static readonly DependencyProperty IsGeneratedProperty =
  36. DependencyProperty.RegisterAttached("IsGenerated", typeof(bool), typeof(TabControlRegionSyncBehavior), null);
  37. private TabControl hostControl;
  38. /// <summary>
  39. /// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
  40. /// </summary>
  41. /// <value>A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
  42. /// This is usually a <see cref="FrameworkElement"/> that is part of the tree.</value>
  43. public DependencyObject HostControl
  44. {
  45. get
  46. {
  47. return this.hostControl;
  48. }
  49. set
  50. {
  51. TabControl newValue = value as TabControl;
  52. if (newValue == null)
  53. {
  54. throw new InvalidOperationException(Resources.HostControlMustBeATabControl);
  55. }
  56. if (IsAttached)
  57. {
  58. throw new InvalidOperationException(Resources.HostControlCannotBeSetAfterAttach);
  59. }
  60. this.hostControl = newValue;
  61. }
  62. }
  63. /// <summary>
  64. /// Override this method to perform the logic after the behavior has been attached.
  65. /// </summary>
  66. protected override void OnAttach()
  67. {
  68. if (this.hostControl == null)
  69. {
  70. throw new InvalidOperationException(Resources.HostControlCannotBeNull);
  71. }
  72. this.SynchronizeItems();
  73. this.hostControl.SelectionChanged += this.OnSelectionChanged;
  74. this.Region.ActiveViews.CollectionChanged += this.OnActiveViewsChanged;
  75. this.Region.Views.CollectionChanged += this.OnViewsChanged;
  76. }
  77. /// <summary>
  78. /// Gets the item contained in the <see cref="TabItem"/>.
  79. /// </summary>
  80. /// <param name="tabItem">The container item.</param>
  81. /// <returns>The item contained in the <paramref name="tabItem"/> if it was generated automatically by the behavior; otherwise <paramref name="tabItem"/>.</returns>
  82. protected virtual object GetContainedItem(TabItem tabItem)
  83. {
  84. if (tabItem == null) throw new ArgumentNullException("tabItem");
  85. if ((bool)tabItem.GetValue(IsGeneratedProperty))
  86. {
  87. return tabItem.Content;
  88. }
  89. return tabItem;
  90. }
  91. /// <summary>
  92. /// Override to change how TabItem's are prepared for items.
  93. /// </summary>
  94. /// <param name="item">The item to wrap in a TabItem</param>
  95. /// <param name="parent">The parent <see cref="DependencyObject"/></param>
  96. /// <returns>A tab item that wraps the supplied <paramref name="item"/></returns>
  97. protected virtual TabItem PrepareContainerForItem(object item, DependencyObject parent)
  98. {
  99. TabItem container = item as TabItem;
  100. if (container == null)
  101. {
  102. object dataContext = GetDataContext(item);
  103. container = new TabItem();
  104. container.Content = item;
  105. container.Style = TabControlRegionAdapter.GetItemContainerStyle(parent);
  106. container.DataContext = dataContext; // To run with SL 2
  107. container.Header = dataContext; // To run with SL 3
  108. container.SetValue(IsGeneratedProperty, true);
  109. }
  110. return container;
  111. }
  112. /// <summary>
  113. /// Undoes the effects of the <see cref="PrepareContainerForItem"/> method.
  114. /// </summary>
  115. /// <param name="tabItem">The container element for the item.</param>
  116. protected virtual void ClearContainerForItem(TabItem tabItem)
  117. {
  118. if (tabItem == null) throw new ArgumentNullException("tabItem");
  119. if ((bool)tabItem.GetValue(IsGeneratedProperty))
  120. {
  121. tabItem.Content = null;
  122. }
  123. }
  124. /// <summary>
  125. /// Creates or identifies the element that is used to display the given item.
  126. /// </summary>
  127. /// <param name="item">The item to get the container for.</param>
  128. /// <param name="itemCollection">The parent's <see cref="ItemCollection"/>.</param>
  129. /// <returns>The element that is used to display the given item.</returns>
  130. protected virtual TabItem GetContainerForItem(object item, ItemCollection itemCollection)
  131. {
  132. if (itemCollection == null) throw new ArgumentNullException("itemCollection");
  133. TabItem container = item as TabItem;
  134. if (container != null && ((bool)container.GetValue(IsGeneratedProperty)) == false)
  135. {
  136. return container;
  137. }
  138. foreach (TabItem tabItem in itemCollection)
  139. {
  140. if ((bool)tabItem.GetValue(IsGeneratedProperty))
  141. {
  142. if (tabItem.Content == item)
  143. {
  144. return tabItem;
  145. }
  146. }
  147. }
  148. return null;
  149. }
  150. /// <summary>
  151. /// Return the appropriate data context. If the item is a FrameworkElement it cannot be a data context in Silverlight, so we use its data context.
  152. /// Otherwise, we just us the item as the data context.
  153. /// </summary>
  154. private static object GetDataContext(object item)
  155. {
  156. FrameworkElement frameworkElement = item as FrameworkElement;
  157. return frameworkElement == null ? item : frameworkElement.DataContext;
  158. }
  159. private void SynchronizeItems()
  160. {
  161. List<object> existingItems = new List<object>();
  162. if (this.hostControl.Items.Count > 0)
  163. {
  164. // Control must be empty before "Binding" to a region
  165. foreach (object childItem in this.hostControl.Items)
  166. {
  167. existingItems.Add(childItem);
  168. }
  169. }
  170. foreach (object view in this.Region.Views)
  171. {
  172. TabItem tabItem = this.PrepareContainerForItem(view, this.hostControl);
  173. this.hostControl.Items.Add(tabItem);
  174. }
  175. foreach (object existingItem in existingItems)
  176. {
  177. this.Region.Add(existingItem);
  178. }
  179. }
  180. private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  181. {
  182. // e.OriginalSource == null, that's why we use sender.
  183. if (this.hostControl == sender)
  184. {
  185. foreach (TabItem tabItem in e.RemovedItems)
  186. {
  187. object item = this.GetContainedItem(tabItem);
  188. // check if the view is in both Views and ActiveViews collections (there may be out of sync)
  189. if (this.Region.Views.Contains(item) && this.Region.ActiveViews.Contains(item))
  190. {
  191. this.Region.Deactivate(item);
  192. }
  193. }
  194. foreach (TabItem tabItem in e.AddedItems)
  195. {
  196. object item = this.GetContainedItem(tabItem);
  197. if (!this.Region.ActiveViews.Contains(item))
  198. {
  199. this.Region.Activate(item);
  200. }
  201. }
  202. }
  203. }
  204. private void OnActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
  205. {
  206. if (e.Action == NotifyCollectionChangedAction.Add)
  207. {
  208. this.hostControl.SelectedItem = this.GetContainerForItem(e.NewItems[0], this.hostControl.Items);
  209. }
  210. else if (e.Action == NotifyCollectionChangedAction.Remove
  211. && this.hostControl.SelectedItem != null
  212. && e.OldItems.Contains(this.GetContainedItem((TabItem)this.hostControl.SelectedItem)))
  213. {
  214. this.hostControl.SelectedItem = null;
  215. }
  216. }
  217. private void OnViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
  218. {
  219. if (e.Action == NotifyCollectionChangedAction.Add)
  220. {
  221. int startingIndex = e.NewStartingIndex;
  222. foreach (object newItem in e.NewItems)
  223. {
  224. TabItem tabItem = this.PrepareContainerForItem(newItem, this.hostControl);
  225. this.hostControl.Items.Insert(startingIndex, tabItem);
  226. }
  227. }
  228. else if (e.Action == NotifyCollectionChangedAction.Remove)
  229. {
  230. foreach (object oldItem in e.OldItems)
  231. {
  232. TabItem tabItem = this.GetContainerForItem(oldItem, this.hostControl.Items);
  233. this.hostControl.Items.Remove(tabItem);
  234. this.ClearContainerForItem(tabItem);
  235. }
  236. }
  237. }
  238. }
  239. }