Exemple #1
0
        /// <summary>
        /// Prepares the specified element to display the specified item.
        /// </summary>
        /// <param name="element">The element used to display the specified item.</param>
        /// <param name="item">The item to display.</param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, Object item)
        {
            // If a separator is added to this ItemsControl without a specific style, then normally the original style for the separator in the WPF themes is chosen
            // which may not be appropriate for the gadgets and gadget bars.  This could be remedied by supplying an explicit style for ever separator used, but
            // this would be cumbersome in the XAML and prone to errors.  This logic will apply a specific separator style to every separator used by the gadget
            // making it possible to centralize the look of a separator.
            Separator separator = element as Separator;

            if (separator != null && separator.Style == null)
            {
                separator.SetResourceReference(FrameworkElement.StyleProperty, new ComponentResourceKey(typeof(Gadget), "GadgetSeperatorStyle"));
            }

            // This attached property allows an item to find the panel to which it belongs.  It is used for items that may not have a visual tree, for example, menu
            // items that are hidden in an overflow item have no visual tree to navigate.  The 'owner' is a type of proxy for the visual ancestor that needs to be
            // re-measured in order for any changes in this element to be reflected in the user interface.
            GadgetBar.SetOwner(element, this);

            // Generated containers don't seem to automatically be added as logical children.  This is bad because the item container generator uses the logical
            // parent when calling the 'ItemFromContainer' method.  This code allows the 'MeasureOverride' in the Panel to find an item given the container when
            // the container and item aren't the same.
            if (element != item)
            {
                this.AddLogicalChild(element);
            }

            // Allow the base class to handle the reset of the preparation.
            base.PrepareContainerForItemOverride(element, item);
        }
        /// <summary>
        /// Called when the Items property changes.
        /// </summary>
        /// <param name="e">The event data for the ItemsChanged event.</param>
        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            // Validate the arguments.
            if (e == null)
            {
                throw new ArgumentNullException("e");
            }

            // Allow the base class to handle the event.
            base.OnItemsChanged(e);

            // This property indicates that the control has items in the overflow panel.
            this.SetHasOverflowItems(this.Items.Count != 0);

            // This property indicates that the control has items in the overflow panel that were put there because there wasn't enough space.
            Boolean hasAsNeededItems = false;

            foreach (Object item in this.Items)
            {
                DependencyObject dependencyObject = item as DependencyObject;
                if (dependencyObject != null && GadgetBar.GetOverflowMode(dependencyObject) == OverflowMode.AsNeeded)
                {
                    hasAsNeededItems = true;
                }
            }
            this.SetHasAsNeededItems(hasAsNeededItems);
        }
Exemple #3
0
        /// <summary>
        /// Initializes a new instance of the GadgetPanel class.
        /// </summary>
        public GadgetPanel()
        {
            // Initialize the object.
            this.gadgetBar = null;

            // This list contains the visual children in this panel.
            this.uiElementCollection = new UIElementCollection(this, null);
        }
Exemple #4
0
 /// <summary>
 /// Sets the owner of an GadgetBar item.
 /// </summary>
 /// <param name="dependencyObject">The target Object for the value.</param>
 /// <param name="owner">The GadgetBar that originally owned the items.</param>
 internal static void SetOwner(DependencyObject dependencyObject, GadgetBar owner)
 {
     // This value is used when any of the attached properties change on an item that require the panels to be measured again.
     if (dependencyObject == null)
     {
         throw new ArgumentNullException("dependencyObject");
     }
     dependencyObject.SetValue(GadgetBar.ownerProperty, owner);
 }
Exemple #5
0
        /// <summary>
        /// Invoked when the effective property value of the ToolDock property changes.
        /// </summary>
        /// <param name="dependencyObject">The DependencyObject on which the property has changed value.</param>
        /// <param name="dependencyPropertyChangedEventArgs">Event data that is issued by any event that tracks changes to the effective value of this
        /// property.</param>
        static void OnToolDockPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // This will force the parent panel to evaluate the measurements of the children again.
            GadgetBar gadgetBar = GadgetBar.GetOwner(dependencyObject);

            if (gadgetBar != null && gadgetBar.gadgetPanel != null)
            {
                gadgetBar.gadgetPanel.InvalidateMeasure();
            }
        }
Exemple #6
0
        /// <summary>
        /// Invoked when the effective property value of the OverflowItemStyle property changes.
        /// </summary>
        /// <param name="dependencyObject">The DependencyObject on which the property has changed value.</param>
        /// <param name="dependencyPropertyChangedEventArgs">Event data that is issued by any event that tracks changes to the effective value of this
        /// property.</param>
        static void OnOverflowItemStylePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // This will give an explicit style to the OverflowItem if one is provided.  If none is provided, then an implicit style based on the OverflowItem type
            // is given provided one has been provided within the scope of the resource where this GadgetBar has been declared.
            GadgetBar gadgetBar = dependencyObject as GadgetBar;
            Style     style     = dependencyPropertyChangedEventArgs.NewValue as Style;

            if (style == null)
            {
                style = gadgetBar.TryFindResource(typeof(OverflowItem)) as Style;
            }
            gadgetBar.overflowItem.Style = style;
        }
Exemple #7
0
        /// <summary>
        /// Arranges and sizes GadgetBar items inside a GadgetBarPanel.
        /// </summary>
        /// <param name="finalSize">The size that the GadgetBarPanel assumes to position its children.</param>
        /// <returns>The size of the panel.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            // The margins keep track of how much space has been used by the items as they are placed in the panel.
            Double nearMargin = 0.0;

            // The elements aligned on the far side of the panel need a starting point.  Since they're 'far-justified', the starting point will need to be
            // calculated backwards from the far margin.
            Double farMargin = finalSize.Width;

            foreach (UIElement uiElement in this.uiElementCollection)
            {
                if (GadgetBar.GetToolDock(uiElement) == ToolDock.Far)
                {
                    farMargin -= uiElement.DesiredSize.Width;
                }
            }

            // The logic for this panel is pretty simple.  If the item docks to the near side, then dock it and update the near margin.  If it docks to the far
            // margin, then dock it and update the far margin.  The panel's orientation determins whether 'near' or 'far' is interpreted as left or right for
            // horizontal, or top and bottom for vertical.
            foreach (UIElement uiElement in this.uiElementCollection)
            {
                // The location and size of the current element is calculated here based on the orientation of the panel.
                Rect elementRect = new Rect();

                // This will place the tool bar item either to the near side of the panel or the far side based on the attached property.
                switch (GadgetBar.GetToolDock(uiElement))
                {
                case ToolDock.Near:

                    // This docks the control to the near size of the panel based on the orientation.
                    elementRect = new Rect(new Point(nearMargin, 0.0), new Size(uiElement.DesiredSize.Width, finalSize.Height));
                    nearMargin += uiElement.DesiredSize.Width;
                    break;

                case ToolDock.Far:

                    // This docks the control to the far side of the panel based on the orientation.
                    elementRect = new Rect(new Point(farMargin, 0.0), new Size(uiElement.DesiredSize.Width, finalSize.Height));
                    farMargin  += uiElement.DesiredSize.Width;
                    break;
                }

                // Once the location and size have been calculated above, the item can be placed in the panel.
                uiElement.Arrange(elementRect);
            }

            // No adjustments are made to the final size during the arrangement of the items.
            return(finalSize);
        }
Exemple #8
0
        /// <summary>
        /// Invoked when the effective property value of the ItemsSource property changes.
        /// </summary>
        /// <param name="dependencyObject">The DependencyObject on which the property has changed value.</param>
        /// <param name="dependencyPropertyChangedEventArgs">Event data that is issued by any event that tracks changes to the effective value of this
        /// property.</param>
        static void OnItemsSourcePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // Extract the event arguments.
            GadgetBar   gadgetBar = (GadgetBar)dependencyObject;
            IEnumerable newValue  = (IEnumerable)dependencyPropertyChangedEventArgs.NewValue;

            // The ItemsSource can be cleared by setting the property to 'null' (and providing it's not data bound).  Otherwise this handler will set the
            // source of the internal collection to use the supplied collection.
            if (dependencyPropertyChangedEventArgs.NewValue == null && !BindingOperations.IsDataBound(dependencyObject, GadgetBar.ItemsSourceProperty))
            {
                gadgetBar.items.ClearItemsSource();
            }
            else
            {
                gadgetBar.items.ItemsSource = newValue;
            }
        }
Exemple #9
0
        /// <summary>
        /// Indicates that the IsItemsHost property value has changed.
        /// </summary>
        /// <param name="oldIsItemsHost">The old property value.</param>
        /// <param name="newIsItemsHost">The new property value.</param>
        protected override void OnIsItemsHostChanged(Boolean oldIsItemsHost, Boolean newIsItemsHost)
        {
            // When a panel is going to host items, it needs to set up some housekeeping for the generator and notify the host.  Likewise, when the services of a
            // panel are no longer needed to host an item, the panel needs to clean up.
            if (newIsItemsHost)
            {
                // The GadgetPanel must communicate to the host when adding or removing items from the panel so that the logical relationships can be maintained. An
                // item will normally want to consider the GadgetBar as its logical parent, but if that item is moved into the overflow item then that relationship
                // needs to change.  Since the parent of an item is the only one who can add or remove logical children, it needs to be notified that a change has
                // taken place, for example, when an item is moved into the overflow item.
                this.gadgetBar = ItemsControl.GetItemsOwner(this) as GadgetBar;

                // Conversly, the GadgetBar needs to know what panel is hosting the items so it can force a re-measurement when the properties on items change.  For
                // example, when the docking or overflow mode changes, the panel needs to be re-measured and re-arranged.
                this.gadgetBar.gadgetPanel = this;

                // This will initialize the generator of containers for the panel and hook it into the handler for updating the panel.
                IItemContainerGenerator iItemContainerGenerator = this.gadgetBar.ItemContainerGenerator;
                if (iItemContainerGenerator != null)
                {
                    this.itemContainerGenerator = iItemContainerGenerator.GetItemContainerGeneratorForPanel(this);
                    if (this.itemContainerGenerator != null)
                    {
                        this.itemContainerGenerator.ItemsChanged += new ItemsChangedEventHandler(this.OnItemsChanged);
                    }
                }
            }
            else
            {
                // This will clean up the relationship with the owner when the panel is no longer needed as a host for items.
                this.gadgetBar.gadgetPanel = null;
                this.gadgetBar             = null;

                // With no owner, there can be no container generators either.
                if (this.itemContainerGenerator != null)
                {
                    this.itemContainerGenerator.ItemsChanged -= new ItemsChangedEventHandler(this.OnItemsChanged);
                }
                this.itemContainerGenerator = null;
            }

            // The base class must be called for the panel to be properly connected to the GadgetBar.  If it isn't, the base class won't be aware of its panel and
            // the keyboard and mouse will not work properly when navigating.
            base.OnIsItemsHostChanged(oldIsItemsHost, newIsItemsHost);
        }
Exemple #10
0
        protected override Size MeasureOverride(Size availableSize)
        {
            // This panel can provide no useful measurement or layout if it isn't an items host.
            if (!this.IsItemsHost)
            {
                return(availableSize);
            }

            // This is the collection that is associated with the user interface in this panel.  Items that can fit in the panel or have been explicitly set to be
            // visible will be added to this collection.
            this.uiElementCollection.Clear();

            // This is the only place where the decision about what items actually appear in the panel is made.  It is desired for an event to be associated with
            // this control that is invoked when items are added or removed from the panel.  However, the GadgetPanel will go through several passes of adding and
            // removing items before it determines what fits and what doesn't.  For this reason a simple trigger on the list wouldn't work because there'd be too
            // many false signals as items are measured.  To provide the required functionality of a trigger that only fires when items are added and removed from
            // the final version of the panel, a copy of the list is made here before the measurement and will be used later to determine if the final set of items
            // has changed.
            List <Object> originalList = new List <Object>();

            foreach (Object item in this.gadgetBar.PanelItems)
            {
                originalList.Add(item);
            }

            // After each pass through the measuring the child controls the logical items will be shifted around.  Some of them will be moved to the overflow panel
            // and some will remain in the main panel.  This confuses the items container generator when it comes time to creating the containers as there is a
            // dependency within the generator on the logical parent.  Therefore, The original logical relationship of the items is restored here in order to set
            // things right for the ItemsContainerGenerator.  Note that the base ItemsControl class is used as a source for the logical items because it contains
            // the overflow item as well.
            this.gadgetBar.PanelItems.Clear();
            this.gadgetBar.OverflowItems.Clear();
            ItemsControl itemsControl = this.gadgetBar as ItemsControl;

            foreach (Object item in itemsControl.Items)
            {
                this.gadgetBar.PanelItems.Add(item);
            }

            // The order of the items as they come out of the generator is used to determine their order in the overflow panel.  This table is used to determine the
            // relative order of items as they are moved to the overflow panel.
            Dictionary <Object, Int32> itemTable = new Dictionary <object, int>();

            // Used to indicate that a seperator has been added to the overflow menu.
            Boolean hasSeparator = false;

            // The main panel must make a proper measurement of all the child controls to determine if there's enough space.  If there isn't, then the items are
            // moved out of the main panel and into an overflow panel in a very well prescribed order.  The trouble with this is that items don't move out of their
            // containers very well.  They seem to be broken when another items container tries to use the same item.  In this case, the items container for the
            // items in the main panel are broken when the overflow panel creates menu item containers for them.  There seems to be no mechanism to repair a broken
            // container so they are regenerated here.  Also, recycling doesn't appear to work.  Once a container is broken, apparently it must be discarded so each
            // time through the measure override a new set of containers is generated.
            IItemContainerGenerator iItemContainerGenerator = this.itemContainerGenerator as ItemContainerGenerator;

            iItemContainerGenerator.RemoveAll();

            // When items are moved out of the main panel and into the overflow panel they keep the same relative order.  The algorithm to do this is a bit tricky
            // as the items are moved in several passes.  The first pass takes the items that are marked to always appear in the overflow panel.  The next pass
            // takes the items from the near side of the visible panel that are marked to be moved as needed.  The final pass takes the items from the far side of
            // the main panel. These items provided cursors to manage the ordering of the overflow panel as it is filled.
            Int32 itemIndex         = 0;
            Int32 nearOverflowIndex = 0;
            Int32 farOverflowIndex  = 0;
            Int32 nearPanelIndex    = 0;
            Int32 farPanelIndex     = 0;

            // This variable will capture the overflow menu item, if it exists as part of the members of the panel.  The overflow menu item has special properties
            // in that an item that doesn't fit into the panel will be made children of this item.
            OverflowItem overflowItem = null;

            // This will generate a collection of containers from the items hosted by the parent.  Note that these items are not associated with a user interface
            // yet.  This collection will be split into items that appear on the panel and those available through the overflow control.  The containers must be
            // created each time through the measure override because broken containers can't be repaired or recycled.  When another container has usurped the
            // contained item, the original container is broken.  This happens when an item is moved from the main panel to the overflow panel.
            using (iItemContainerGenerator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward))
            {
                // This will generate a new container for each item and determin whether it belongs in the main panel or the overflow panel.
                UIElement uiElement;
                while ((uiElement = iItemContainerGenerator.GenerateNext() as UIElement) != null)
                {
                    // This allows the host to make modifications to the container before it is displayed.  For example, a GadgetBar would assign a style to the item
                    // container based on the item type.  Logical relationships for the container can also be established at this time.
                    iItemContainerGenerator.PrepareItemContainer(uiElement);

                    // Overflow menu items have special meaning for the panel.  Any item that doesn't fit into the panel will be added to this visual element.
                    OverflowItem generatedOverflowItem = uiElement as OverflowItem;
                    if (generatedOverflowItem != null)
                    {
                        // Capture the element for use later.  The items the items can't fit into the panel will be added to this item.
                        overflowItem = generatedOverflowItem;

                        // Overflow elements that are added "As Needed" are a special case.  These items will remain hidden until room runs out on the panel for
                        // the other items.  At that point, the OverflowItem will appear as a menu item and those items that don't fit will be moved to the
                        // OverflowItem.
                        if (GadgetBar.GetOverflowMode(overflowItem) == OverflowMode.AsNeeded)
                        {
                            continue;
                        }
                    }

                    // This constructs a mapping of the item to its ordinal and is used to determine the relative order of the overflow items as they are added to
                    // the overflow panel in multiple passes.
                    Object item = this.itemContainerGenerator.ItemFromContainer(uiElement);
                    itemTable.Add(item, itemIndex++);

                    // The ToolDock is used to determine whether the new item container is added to the near or far side of the panel.
                    ToolDock toolDock = GadgetBar.GetToolDock(uiElement);

                    // The OverflowMode attached property can be use to force an item into the overflow panel.
                    switch (GadgetBar.GetOverflowMode(uiElement))
                    {
                    case OverflowMode.Always:

                        // A separator is injected into the overflow panel when there are both near and far aligned items.
                        if (toolDock == ToolDock.Far && nearOverflowIndex != 0 && !hasSeparator)
                        {
                            hasSeparator = true;
                            this.gadgetBar.OverflowItems.Insert(nearOverflowIndex, new Separator());
                            farOverflowIndex++;
                        }

                        // These items always appear in the overflow panel in the order they were added to the Items property of the GadgetBar.
                        this.gadgetBar.PanelItems.Remove(item);

                        // Conversely the item removed from the main panel is moved into the overflow panel.  The same item can not be the logical child of two
                        // windows at the same time.  The observable lists will take care of removing the child from one parent and giving it to the other.
                        this.gadgetBar.OverflowItems.Insert(toolDock == ToolDock.Near ? nearOverflowIndex : farOverflowIndex, uiElement);
                        nearOverflowIndex += toolDock == ToolDock.Near ? 1 : 0;
                        farOverflowIndex++;

                        // Remove the generated item if it has no place in the main panel.
                        Int32             containerIndex    = this.itemContainerGenerator.IndexFromContainer(uiElement);
                        GeneratorPosition generatorPosition = iItemContainerGenerator.GeneratorPositionFromIndex(containerIndex);
                        iItemContainerGenerator.Remove(generatorPosition, 1);

                        break;

                    default:

                        // These items include the ones that will move to the overflow panel if needed and the ones that never move.  A second pass will determine
                        // if any of the 'As Needed' items need to be moved.  Note that the collection of containers is not the same as the collection of items.
                        // The collection of items is a logical organization whereas the collection of containers is visual.
                        this.uiElementCollection.Insert(toolDock == ToolDock.Near ? nearPanelIndex : farPanelIndex, uiElement);
                        nearPanelIndex += toolDock == ToolDock.Near ? 1 : 0;
                        farPanelIndex++;

                        break;
                    }
                }
            }

            // This keeps track of how much space in the panel is occupied by the items as they are laid out.
            Size allocatedSize = new Size();

            // This constraint is used to allow the controls to measure themselves out in the direction in which the panel is oriented.  That is, if it has a
            // horizontal orientation then an infinite amount of space is given during the measurement process in this direction.  It allows the controls the
            // calculate their theoretical size.  If the item doesn't fit, it will be removed from the panel and its desired size will be recalculated inside the
            // overflow panel.  If it does fit, then the desired size is the actual size it is given in the panel.
            Size infiniteSize = new Size(Double.PositiveInfinity, availableSize.Height);

            // This pass will measure everything that wants to appear in this panel.  An infinite amount of room is given in the direction in which this panel is
            // oriented so the measurement operation won't be constrained.  Another pass will actually determine if the items fit or not.
            foreach (UIElement uiElement in this.uiElementCollection)
            {
                uiElement.Measure(infiniteSize);
                allocatedSize = new Size(allocatedSize.Width + uiElement.DesiredSize.Width, Math.Max(allocatedSize.Height, uiElement.DesiredSize.Height));
            }

            // This will attempt to make sure that everything can fit into the alloted space.  If there isn't enough room and individual items are willing to be
            // placed into the overflow panel, then they are removed from this panel.  This concept is very important because moving logical children from one
            // container to another breaks the container and it can't be repaired.  That is why the items must be regenerated each time the panel is measured.  The
            // items are moved in two passes: the items on the near side of the panel are removed before the items on the far side.  When an overflow of items does
            // occurs a control will appear on the panel that allows the user to access the overflow items.
            Double availableLength = availableSize.Width;
            Double allocatedLength = allocatedSize.Width;

            if (allocatedLength > availableLength || this.gadgetBar.OverflowItems.Count != 0)
            {
                // When an overflow occurs a special item appears on the panel and provides access to the overflow panel for the user.  When this special item is
                // added to the panel the measuring algorithm will need to consider its size when trying to work out what items fit and what items need to be
                // pushed into the overflow panel.  When this item appears it will always have priority over the items that can be moved into the overflow panel.
                // Note that the logical ordering of the children has no impact on how they behave visually so the element can just be added to the logical
                // children.
                if (overflowItem != null && GadgetBar.GetOverflowMode(overflowItem) != OverflowMode.Never)
                {
                    this.uiElementCollection.Insert(nearPanelIndex, overflowItem);
                    this.gadgetBar.PanelItems.Add(overflowItem);
                    overflowItem.Measure(infiniteSize);
                    allocatedLength += overflowItem.DesiredSize.Width;
                }

                // The calculation of what items can appear in the overflow panel is accomplished in two passes.  The first pass will look at the near-aligned items
                // and move them into the overflow panel starting from the farthest item to the item to the nearest until the items fit in the available space.
                Int32 index = nearPanelIndex - 1;
                while (allocatedLength > availableLength && index >= 0)
                {
                    // This will move the next element in the panel into the overflow panel as needed.  Note that the overflow button is never moved into the
                    // overflow panel and that only the near items are considered during the first pass.
                    UIElement uiElement = this.uiElementCollection[index];
                    if (GadgetBar.GetOverflowMode(uiElement) == OverflowMode.AsNeeded)
                    {
                        // This element will no longer appear on the main panel.  This will remove both the visual and the logical relationship.  If the logical
                        // relationship isn't broken then this item can't be added to the overflow panel as an item can have only one logical parent at a time.  If
                        // the proper logical relation isn't made then the containers and the contents won't pick up the proper styles.  This is particularly
                        // important for menu items as top level items behave differently than sub-menu items.
                        this.uiElementCollection.Remove(uiElement);
                        Object item = this.itemContainerGenerator.ItemFromContainer(uiElement);
                        this.gadgetBar.PanelItems.Remove(item);

                        // This will provide the housekeeping with the ItemsContainerGenerator by removing containers that aren't needed for this panel.
                        Int32             containerIndex    = this.itemContainerGenerator.IndexFromContainer(uiElement);
                        GeneratorPosition generatorPosition = iItemContainerGenerator.GeneratorPositionFromIndex(containerIndex);
                        iItemContainerGenerator.Remove(generatorPosition, 1);

                        // Insert the item into the overflow panel in the same relative order that the item had in the origian Items list of the host.
                        Int32 newItemIndex = itemTable[item];
                        for (Int32 oldItemIndex = 0; oldItemIndex <= nearOverflowIndex; oldItemIndex++)
                        {
                            if (oldItemIndex == nearOverflowIndex)
                            {
                                this.gadgetBar.OverflowItems.Insert(nearOverflowIndex, item);
                            }
                            else
                            if (newItemIndex < itemTable[this.gadgetBar.OverflowItems[oldItemIndex]])
                            {
                                this.gadgetBar.OverflowItems.Insert(oldItemIndex, item);
                                break;
                            }
                        }

                        // These act as cursors when ordering the items in the overflow panel.
                        nearOverflowIndex++;
                        farOverflowIndex++;
                        nearPanelIndex--;

                        // Adjust the available space by the size of the item that was just removed to the overflow panel.
                        allocatedLength -= uiElement.DesiredSize.Width;

                        // Adding items to the overflow panel can change its role in the GadgetBar.  Each time through this iteration the overflow item needs to be
                        // re-measured to account for any changes.  Failure to re-measure the overflow item can lead to a infinite loop of measuring the panel.
                        allocatedLength -= overflowItem.DesiredSize.Width;
                        overflowItem.Measure(infiniteSize);
                        allocatedLength += overflowItem.DesiredSize.Width;
                    }

                    // Consider the next container in the list of near-aligned items.
                    index--;
                }

                // If there is still not enough room in the visible panel the far-aligned items will be moved to the overflow panel.
                index = nearPanelIndex + 1;
                while (allocatedLength > availableLength && index < this.uiElementCollection.Count)
                {
                    // This will move the next element in the panel into the overflow panel as needed.  Note that the overflow button is never moved into the
                    // overflow panel and that only the near items are considered during the first pass.
                    UIElement uiElement = this.uiElementCollection[index];
                    if (GadgetBar.GetOverflowMode(uiElement) == OverflowMode.AsNeeded)
                    {
                        // This element will no longer appear on the main panel.  This will remove both the visual and the logical relationship.  If the logical
                        // relationship isn't broken then this item can't be added to the overflow panel as an item can have only one logical parent at a time.  If
                        // the proper logical relation isn't made then the containers and the contents won't pick up the proper styles.  This is particularly
                        // important for menu items as top level items behave differently than sub-menu items.
                        this.uiElementCollection.Remove(uiElement);
                        Object item = this.itemContainerGenerator.ItemFromContainer(uiElement);
                        this.gadgetBar.PanelItems.Remove(item);

                        // This will provide the housekeeping with the ItemsContainerGenerator by removing containers that aren't needed for this panel.
                        Int32             containerIndex    = this.itemContainerGenerator.IndexFromContainer(uiElement);
                        GeneratorPosition generatorPosition = iItemContainerGenerator.GeneratorPositionFromIndex(containerIndex);
                        iItemContainerGenerator.Remove(generatorPosition, 1);

                        // A seperator is created when far-aligned items occupy the same overflow panel as near-aligned items.
                        if (nearOverflowIndex != 0 && !hasSeparator)
                        {
                            hasSeparator = true;
                            this.gadgetBar.OverflowItems.Insert(nearOverflowIndex, new Separator());
                            farOverflowIndex++;
                        }

                        // Insert the item into the overflow panel in the same relative order that the item had in the origian Items list of the host.  Note that
                        // the position of the seperator will change the starting point for examining the far-aligned items for the proper order.
                        Int32 newItemIndex  = itemTable[item];
                        Int32 startingPoint = hasSeparator ? nearOverflowIndex + 1 : nearOverflowIndex;
                        for (Int32 oldItemIndex = startingPoint; oldItemIndex <= farOverflowIndex; oldItemIndex++)
                        {
                            if (oldItemIndex == farOverflowIndex)
                            {
                                this.gadgetBar.OverflowItems.Insert(farOverflowIndex, item);
                            }
                            else
                            if (newItemIndex < itemTable[this.gadgetBar.OverflowItems[oldItemIndex]])
                            {
                                this.gadgetBar.OverflowItems.Insert(oldItemIndex, item);
                                break;
                            }
                        }

                        // This act as cursors when ordering the items in the overflow panel.
                        farOverflowIndex++;

                        // Adjust the available space by the size of the item that was just removed to the overflow panel.
                        allocatedLength -= uiElement.DesiredSize.Width;

                        // Adding items to the overflow panel can change its role in the GadgetBar.  Each time through this iteration the overflow item needs to be
                        // re-measured to account for any changes.  Failure to re-measure the overflow item can lead to a infinite loop of measuring the panel.
                        allocatedLength -= overflowItem.DesiredSize.Width;
                        overflowItem.Measure(infiniteSize);
                        allocatedLength += overflowItem.DesiredSize.Width;
                    }
                    else
                    {
                        // Consider the next far-aligned item to see if it can be moved out of the visible panel.
                        index++;
                    }
                }
            }

            // This will advise any listeners that the items in the panel have changed.  Since panels are not part of a standard template, this information is
            // normally inaccessible to a parent class except as events bubbled up through the visual tree hierarchy.
            Boolean isEqual = this.gadgetBar.PanelItems.Count == originalList.Count;

            for (Int32 index = 0; isEqual && index < this.gadgetBar.PanelItems.Count; index++)
            {
                isEqual = Object.Equals(this.gadgetBar.PanelItems[index], originalList[index]);
            }
            if (!isEqual)
            {
                this.RaiseEvent(new RoutedEventArgs(GadgetPanel.ItemsChangedEvent, this));
            }

            // This is how much room is needed for the panel.  Note that the maximum height (or width) of the tool panel is determined by all the items whether they
            // appear in the tool panel or the overflow panel.  This one-size-fits-all approach keeps the panel from jumping around as items are added from or
            // removed to the overflow panel.
            return(new Size(allocatedLength, allocatedSize.Height));
        }
Exemple #11
0
        /// <summary>
        /// Measures the child elements of a Teraque.BreadcrumbPanel prior to arranging them during the ArrangeOverride pass.
        /// </summary>
        /// <param name="availableSize">A maximum Size to not exceed.</param>
        /// <returns>A Size that represents the element size you want.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            // This panel can provide no useful measurement or layout if it isn't an items host.
            if (!this.IsItemsHost)
            {
                return(availableSize);
            }

            // This is the collection that is associated with the user interface in this panel.  Items that can fit in the panel or have been explicitly set to be
            // visible will be added to this collection.
            this.UIElementCollection.Clear();

            // This is the only place where the decision about what items actually appear in the panel is made.  An event associated with this control is invoked
            // when visible items are added or removed.  Since the GadgetBar will go through several passes of adding and removing items before it determines what
            // fits and what doesn't, a simple ObservableList-type trigger wouldn't work because there'd be too many false signals.  To provide the required
            // functionality, a copy of the list is made here before the measurement and will be used later to determine if the final set of items has changed.
            List <Object> originalList = new List <Object>();

            foreach (Object item in this.GadgetBar.PanelItems)
            {
                originalList.Add(item);
            }

            // This observable list maintains the logical relationship with the parent menu.  When items are added to or removed from this list a trigger in the
            // GadgetBar will add them to or remove them from the logical children of that control.
            this.GadgetBar.PanelItems.Clear();

            // This observable list maintains the logical relationship with the overflow menu.  These relationships must be managed manually.  When an item is added
            // to the overflow panel it must be removed from the main panel.  It must also be added at the exact same time or orphaned MenuItems will generate
            // messages about data binding failures.
            this.GadgetBar.OverflowItems.Clear();

            // After each pass through the measuring the child controls the logical items will be shifted around.  Some of them will be moved to the overflow panel
            // and some will remain in the main panel.  This confuses the items container generator when it comes time to creating the containers.  The original
            // logical relationship of the items is restored here in order to set things right for the ItemsContainerGenerator.
            ItemsControl itemsControl = this.GadgetBar as ItemsControl;

            foreach (Object item in itemsControl.Items)
            {
                this.GadgetBar.PanelItems.Add(item);
            }

            // The main panel must make a proper measurement of all the child controls to determine if there's enough space.  If there isn't, then the items are
            // moved out of the main panel and into an overflow panel in a very well prescribed order.  The trouble with this is that items don't move out of their
            // containers very well.  They seem to be broken when another items container tries to use the same item.  In this case, the items container for the
            // items in the main panel are broken when the overflow panel creates menu item containers for them.  There seems to be no mechanism to repair a broken
            // container so they are regenerated here.  Also, recycling doesn't appear to work.  Once a container is broken, apparently it must be discarded so each
            // time through the measure override a new set of containers is generated.
            IItemContainerGenerator iItemContainerGenerator = this.ItemContainerGenerator as ItemContainerGenerator;

            iItemContainerGenerator.RemoveAll();

            // The order of the items as they come out of the generator is used to determine their order in the overflow panel.  This table is used to determine the
            // relative order of items as they are moved to the overflow panel.
            Dictionary <Object, Int32> itemTable = new Dictionary <object, int>();

            // When items are moved out of the main panel and into the overflow panel they keep the same relative order.  The algorithm to do this is a bit tricky
            // as the items are moved in several passes.  The first pass takes the items that are marked to always appear in the overflow panel.  The next pass
            // takes the items from the visible panel that are marked to be moved as needed.
            Int32 itemIndex     = 0;
            Int32 overflowIndex = 0;
            Int32 panelIndex    = 0;

            // This variable will capture the overflow menu item, if it exists as part of the members of the panel.  The overflow menu item has special properties
            // in that an item that doesn't fit into the panel will be made children of this item.
            OverflowItem overflowItem = null;

            GadgetBar gadgetBar = VisualTreeExtensions.FindAncestor <GadgetBar>(this) as GadgetBar;

            System.ComponentModel.ICollectionView iCollectionView = System.Windows.Data.CollectionViewSource.GetDefaultView(gadgetBar.ItemsSource);
            iCollectionView.Refresh();

            // This will generate a collection of containers from the items hosted by the parent.  Note that these items are not associated with a user interface
            // yet.  This collection will be split into items that appear on the panel and those available through the overflow control.  The containers must be
            // created each time through the measure override because broken containers can't be repaired or recycled.  When another container has usurped the
            // contained item, the original container is broken.  This happens when an item is moved from the main panel to the overflow panel.
            using (iItemContainerGenerator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward))
            {
                // This will generate a new container for each item and determin whether it belongs in the main panel or the overflow panel.
                UIElement uiElement;
                while ((uiElement = iItemContainerGenerator.GenerateNext() as UIElement) != null)
                {
                    // This allows the host to make modifications to the container before it is displayed.  For example, a GadgetBar would assign a style to the item
                    // container based on the item type.
                    iItemContainerGenerator.PrepareItemContainer(uiElement);

                    // Overflow menu items have special meaning for the panel.  Any item that doesn't fit into the panel will be added to this item.
                    OverflowItem generatedOverflowItem = uiElement as OverflowItem;
                    if (generatedOverflowItem != null)
                    {
                        overflowItem = generatedOverflowItem;
                        continue;
                    }

                    // This constructs a mapping of the item to its ordinal and is used to determine the relative order of the overflow items as they are added to
                    // the overflow panel in multiple passes.
                    Object item = this.ItemContainerGenerator.ItemFromContainer(uiElement);
                    itemTable.Add(item, itemIndex++);

                    // The OverflowMode attached property can be use to force an item into the overflow panel.
                    switch (GadgetBar.GetOverflowMode(uiElement))
                    {
                    case OverflowMode.Always:

                        // These items always appear in the overflow panel in the order they were added to the Items property of the GadgetBar.
                        this.GadgetBar.PanelItems.Remove(item);

                        // Conversely the item removed from the main panel is moved into the overflow panel.  The same item can not be the logical child of two
                        // windows at the same time.  The observable lists will take care of removing the child from one parent and giving it to the other.
                        this.GadgetBar.OverflowItems.Insert(overflowIndex, uiElement);
                        overflowIndex++;

                        // Remove the generated item if it has no place in the main panel.
                        Int32             containerIndex    = this.ItemContainerGenerator.IndexFromContainer(uiElement);
                        GeneratorPosition generatorPosition = iItemContainerGenerator.GeneratorPositionFromIndex(containerIndex);
                        iItemContainerGenerator.Remove(generatorPosition, 1);

                        break;

                    default:

                        // These items include the ones that will move to the overflow panel if needed and the ones that never move.  Note that the collection of
                        // containers is not the same as the collection of items. The collection of items is a logical organization whereas the collection of
                        // containers is visual.  This pass is organizing the visual elements. Also note that the overflow button is always part of the
                        // BreadcrumbBar and always appears as the first item.
                        this.UIElementCollection.Insert(panelIndex, uiElement);
                        panelIndex++;

                        break;
                    }
                }
            }

            // This will insert the overflow item when it has child items that can be displayed.  The overflow item always appears at the left edge of the
            // BreadcrumbBar when it's visible.  In the case where there is only one element at the root of the directory, the overflow item is hidden because
            // there is nothing to navigate to in this situation.
            if (overflowItem.Items.Count != 0)
            {
                this.UIElementCollection.Insert(0, overflowItem);
            }

            // This keeps track of how much space in the panel is occupied by the items as they are laid out.
            Size allocatedSize = new Size();

            // This constraint is used to allow the controls to measure themselves out in the direction in which the panel is oriented.  That is, if it has a
            // horizontal orientation then an infinite amount of space is given during the measurement process in this direction.  It allows the controls the
            // calculate their theoretical size.  If the item doesn't fit, it will be removed from the panel and its desired size will be recalculated inside the
            // overflow panel.  If it does fit, then the desired size is the actual size it is given in the panel.
            Size infiniteSize = new Size(Double.PositiveInfinity, availableSize.Height);

            // This pass will measure everything that wants to appear in this panel.  An infinite amount of room is given in the direction in which this panel is
            // oriented so the measurement operation won't be constrained.  Another pass will actually determine if the items fit or not.
            foreach (UIElement uiElement in this.UIElementCollection)
            {
                uiElement.Measure(infiniteSize);
                allocatedSize = new Size(allocatedSize.Width + uiElement.DesiredSize.Width, Math.Max(allocatedSize.Height, uiElement.DesiredSize.Height));
            }

            // This will attempt to make sure that everything can fit into the alloted space.  If there isn't enough room and individual items are willing to be
            // placed into the overflow panel, then they are removed from this panel.  This concept is very important because moving logical children from one
            // container to another breaks the container and it can't be repaired.  That is why the items must be regenerated each time the panel is measured.  The
            // items are moved in two passes: the items on the near side of the panel are removed before the items on the far side.
            Double availableLength = availableSize.Width;
            Double allocatedLength = allocatedSize.Width;

            if (allocatedLength > availableLength)
            {
                // The calculation of what items can appear in the overflow panel is accomplished in two passes.  The first pass will look at the near-aligned items
                // and move them into the overflow panel starting from the farthest item to the item to the nearest until the items fit in the available space.
                Int32 index = 0;
                while (allocatedLength > availableLength && index >= 0 && index < this.UIElementCollection.Count)
                {
                    // This element will be examined to see if it can be removed from the panel when there are too many elements to fit.
                    UIElement uiElement = this.UIElementCollection[index];

                    // The overflow item is never considered for removal from the main panel.
                    if (uiElement is OverflowItem)
                    {
                        index++;
                        continue;
                    }

                    // This will move the next element in the panel into the overflow panel as needed.  Note that the overflow button is never moved into the
                    // overflow panel.
                    if (GadgetBar.GetOverflowMode(uiElement) == OverflowMode.AsNeeded)
                    {
                        // This element will no longer appear on the main panel.  This will remove both the visual and the logical relationship.  If the logical
                        // relationship isn't broken then this item can't be added to the overflow panel as an item can have only one logical parent at a time.  If
                        // the proper logical relation isn't made then the containers and the contents won't pick up the proper styles.  This is particularly
                        // important for menu items as top level items behave differently than sub-menu items.
                        this.UIElementCollection.Remove(uiElement);
                        Object item = this.ItemContainerGenerator.ItemFromContainer(uiElement);
                        this.GadgetBar.PanelItems.Remove(item);

                        // This will provide the housekeeping with the ItemsContainerGenerator by removing containers that aren't needed for this panel.
                        Int32             containerIndex    = this.ItemContainerGenerator.IndexFromContainer(uiElement);
                        GeneratorPosition generatorPosition = iItemContainerGenerator.GeneratorPositionFromIndex(containerIndex);
                        iItemContainerGenerator.Remove(generatorPosition, 1);

                        // Insert the item into the overflow panel in the same order in which it appears in the panel.
                        this.GadgetBar.OverflowItems.Insert(0, item);

                        // These act as cursors when ordering the items in the overflow panel.
                        overflowIndex++;
                        panelIndex--;

                        // Adjust the available space by the size of the item that was just removed to the overflow panel.
                        allocatedLength -= uiElement.DesiredSize.Width;
                    }
                    else
                    {
                        // Consider the next container in the panel.
                        index++;
                    }
                }
            }

            // This will advise any listeners that the items in the panel have changed.  Since panels are not part of a standard template, this information is
            // normally inaccessible to a parent class except as events bubbled up through the visual tree hierarchy.
            Boolean isEqual = this.GadgetBar.PanelItems.Count == originalList.Count;

            for (Int32 index = 0; isEqual && index < this.GadgetBar.PanelItems.Count; index++)
            {
                isEqual = Object.Equals(this.GadgetBar.PanelItems[index], originalList[index]);
            }
            if (!isEqual)
            {
                this.RaiseEvent(new RoutedEventArgs(GadgetPanel.ItemsChangedEvent, this));
            }

            // This is how much room is needed for the panel.  Note that the maximum height (or width) of the tool panel is determined by all the items whether they
            // appear in the tool panel or the overflow panel.  This one-size-fits-all approach keeps the panel from jumping around as items are added from or
            // removed to the overflow panel.
            return(new Size(allocatedLength, allocatedSize.Height));
        }