PageRenderTime 25ms CodeModel.GetById 15ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 0ms

/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//===================================================================================
 17using System;
 18using System.Collections.Generic;
 19using System.Collections.Specialized;
 20using System.Windows;
 21using System.Windows.Controls;
 22using Microsoft.Practices.Prism.Properties;
 23
 24namespace Microsoft.Practices.Prism.Regions.Behaviors
 25{
 26    /// <summary>
 27    /// Behavior that generates <see cref="TabItem"/> containers for the added items
 28    /// and also keeps the <see cref="TabControl.SelectedItem"/> and the <see cref="IRegion.ActiveViews"/> in sync.
 29    /// </summary>
 30    public class TabControlRegionSyncBehavior : RegionBehavior, IHostAwareRegionBehavior
 31    {
 32        ///<summary>
 33        /// The behavior key for this region sync behavior.
 34        ///</summary>
 35        public const string BehaviorKey = "TabControlRegionSyncBehavior";
 36
 37        private static readonly DependencyProperty IsGeneratedProperty =
 38            DependencyProperty.RegisterAttached("IsGenerated", typeof(bool), typeof(TabControlRegionSyncBehavior), null);
 39
 40        private TabControl hostControl;
 41
 42        /// <summary>
 43        /// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
 44        /// </summary>
 45        /// <value>A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
 46        /// This is usually a <see cref="FrameworkElement"/> that is part of the tree.</value>
 47        public DependencyObject HostControl
 48        {
 49            get
 50            {
 51                return this.hostControl;
 52            }
 53
 54            set
 55            {
 56                TabControl newValue = value as TabControl;
 57                if (newValue == null)
 58                {
 59                    throw new InvalidOperationException(Resources.HostControlMustBeATabControl);
 60                }
 61
 62                if (IsAttached)
 63                {
 64                    throw new InvalidOperationException(Resources.HostControlCannotBeSetAfterAttach);
 65                }
 66
 67                this.hostControl = newValue;
 68            }
 69        }
 70
 71        /// <summary>
 72        /// Override this method to perform the logic after the behavior has been attached.
 73        /// </summary>
 74        protected override void OnAttach()
 75        {
 76            if (this.hostControl == null)
 77            {
 78                throw new InvalidOperationException(Resources.HostControlCannotBeNull);
 79            }
 80
 81            this.SynchronizeItems();
 82
 83            this.hostControl.SelectionChanged += this.OnSelectionChanged;
 84            this.Region.ActiveViews.CollectionChanged += this.OnActiveViewsChanged;
 85            this.Region.Views.CollectionChanged += this.OnViewsChanged;
 86        }
 87
 88        /// <summary>
 89        /// Gets the item contained in the <see cref="TabItem"/>.
 90        /// </summary>
 91        /// <param name="tabItem">The container item.</param>
 92        /// <returns>The item contained in the <paramref name="tabItem"/> if it was generated automatically by the behavior; otherwise <paramref name="tabItem"/>.</returns>
 93        protected virtual object GetContainedItem(TabItem tabItem)
 94        {
 95            if (tabItem == null) throw new ArgumentNullException("tabItem");
 96            if ((bool)tabItem.GetValue(IsGeneratedProperty))
 97            {
 98                return tabItem.Content;
 99            }
100
101            return tabItem;
102        }
103
104        /// <summary>
105        /// Override to change how TabItem's are prepared for items.
106        /// </summary>
107        /// <param name="item">The item to wrap in a TabItem</param>
108        /// <param name="parent">The parent <see cref="DependencyObject"/></param>
109        /// <returns>A tab item that wraps the supplied <paramref name="item"/></returns>
110        protected virtual TabItem PrepareContainerForItem(object item, DependencyObject parent)
111        {
112            TabItem container = item as TabItem;
113            if (container == null)
114            {
115                object dataContext = GetDataContext(item);
116                container = new TabItem();
117                container.Content = item;
118                container.Style = TabControlRegionAdapter.GetItemContainerStyle(parent);
119                container.DataContext = dataContext; // To run with SL 2
120                container.Header = dataContext; // To run with SL 3                  
121                container.SetValue(IsGeneratedProperty, true);
122            }
123
124            return container;
125        }
126
127        /// <summary>
128        /// Undoes the effects of the <see cref="PrepareContainerForItem"/> method.
129        /// </summary>
130        /// <param name="tabItem">The container element for the item.</param>
131        protected virtual void ClearContainerForItem(TabItem tabItem)
132        {
133            if (tabItem == null) throw new ArgumentNullException("tabItem");
134            if ((bool)tabItem.GetValue(IsGeneratedProperty))
135            {
136                tabItem.Content = null;
137            }
138        }
139
140        /// <summary>
141        /// Creates or identifies the element that is used to display the given item.
142        /// </summary>
143        /// <param name="item">The item to get the container for.</param>
144        /// <param name="itemCollection">The parent's <see cref="ItemCollection"/>.</param>
145        /// <returns>The element that is used to display the given item.</returns>
146        protected virtual TabItem GetContainerForItem(object item, ItemCollection itemCollection)
147        {
148            if (itemCollection == null) throw new ArgumentNullException("itemCollection");
149            TabItem container = item as TabItem;
150            if (container != null && ((bool)container.GetValue(IsGeneratedProperty)) == false)
151            {
152                return container;
153            }
154
155            foreach (TabItem tabItem in itemCollection)
156            {
157                if ((bool)tabItem.GetValue(IsGeneratedProperty))
158                {
159                    if (tabItem.Content == item)
160                    {
161                        return tabItem;
162                    }
163                }
164            }
165
166
167            return null;
168        }
169
170        /// <summary>
171        /// 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.
172        /// Otherwise, we just us the item as the data context.
173        /// </summary>
174        private static object GetDataContext(object item)
175        {
176            FrameworkElement frameworkElement = item as FrameworkElement;
177            return frameworkElement == null ? item : frameworkElement.DataContext;
178        }
179
180        private void SynchronizeItems()
181        {
182            List<object> existingItems = new List<object>();
183            if (this.hostControl.Items.Count > 0)
184            {
185                // Control must be empty before "Binding" to a region
186                foreach (object childItem in this.hostControl.Items)
187                {
188                    existingItems.Add(childItem);
189                }
190            }
191
192            foreach (object view in this.Region.Views)
193            {
194                TabItem tabItem = this.PrepareContainerForItem(view, this.hostControl);
195                this.hostControl.Items.Add(tabItem);
196            }
197
198            foreach (object existingItem in existingItems)
199            {
200                this.Region.Add(existingItem);
201            }
202        }
203
204        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
205        {
206            // e.OriginalSource == null, that's why we use sender.
207            if (this.hostControl == sender)
208            {
209                foreach (TabItem tabItem in e.RemovedItems)
210                {
211                    object item = this.GetContainedItem(tabItem);
212
213                    // check if the view is in both Views and ActiveViews collections (there may be out of sync)
214                    if (this.Region.Views.Contains(item) && this.Region.ActiveViews.Contains(item))
215                    {
216                        this.Region.Deactivate(item);
217                    }
218                }
219
220                foreach (TabItem tabItem in e.AddedItems)
221                {
222                    object item = this.GetContainedItem(tabItem);
223                    if (!this.Region.ActiveViews.Contains(item))
224                    {
225                        this.Region.Activate(item);
226                    }
227                }
228            }
229        }
230
231        private void OnActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
232        {
233            if (e.Action == NotifyCollectionChangedAction.Add)
234            {
235                this.hostControl.SelectedItem = this.GetContainerForItem(e.NewItems[0], this.hostControl.Items);
236            }
237            else if (e.Action == NotifyCollectionChangedAction.Remove
238                && this.hostControl.SelectedItem != null
239                && e.OldItems.Contains(this.GetContainedItem((TabItem)this.hostControl.SelectedItem)))
240            {
241                this.hostControl.SelectedItem = null;
242            }
243        }
244
245        private void OnViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
246        {
247            if (e.Action == NotifyCollectionChangedAction.Add)
248            {
249                int startingIndex = e.NewStartingIndex;
250                foreach (object newItem in e.NewItems)
251                {
252                    TabItem tabItem = this.PrepareContainerForItem(newItem, this.hostControl);
253                    this.hostControl.Items.Insert(startingIndex, tabItem);
254                }
255            }
256            else if (e.Action == NotifyCollectionChangedAction.Remove)
257            {
258                foreach (object oldItem in e.OldItems)
259                {
260                    TabItem tabItem = this.GetContainerForItem(oldItem, this.hostControl.Items);
261                    this.hostControl.Items.Remove(tabItem);
262                    this.ClearContainerForItem(tabItem);
263                }
264            }
265        }
266    }
267}