Beispiel #1
0
        /// <summary>
        /// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the FrameworkElement-derived
        /// class.
        /// </summary>
        /// <param name="constraint">
        /// The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to
        /// whatever content is available.
        /// </param>
        /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
        protected override Size MeasureOverride(Size constraint)
        {
            // This keeps track of the space needed by this row.
            Size totalSize = new Size();

            // Evaluate the size of all the columns in this row.  Note that even though the cell may only want a fraction of the space available to it, we will
            // reserve all the space that the header has asked for (horizontally, that is).  The standard version of this control tries to auto-calculate the width
            // of the column but it can only do that for the visible items.  Since it is a virtualizing panel, there's no way to tell how wide each of the columns
            // needs to be without creating each one and measuring it.  This would result in horrible performance.  Therefore, it's not possible to automatically
            // calculate the width of a column without incurring a terrible performance penalty.  This design simplifies the process of calculating the column width
            // by assuming the column have given us the same width for every column in the virtual display.
            if (this.Columns != null)
            {
                for (Int32 columnIndex = 0; columnIndex < this.Columns.Count; columnIndex++)
                {
                    ColumnViewColumn columnViewColumn = this.Columns[columnIndex];
                    UIElement        uiElement        = this.Children[columnIndex];
                    Double           remainingWidth   = Math.Max(0.0, constraint.Width - totalSize.Width);
                    Double           width            = columnIndex == 0 ? columnViewColumn.Width - DetailsViewRowPresenter.hangingIndent : columnViewColumn.Width;
                    Size             elementSize      = new Size(Math.Min(remainingWidth, width), constraint.Height);
                    uiElement.Measure(elementSize);
                    totalSize.Width += elementSize.Width;
                    totalSize.Height = Math.Max(totalSize.Height, uiElement.DesiredSize.Height);
                }
            }

            // This is the total space required for this row.
            return(totalSize);
        }
        /// <summary>
        /// Handles a change to the MinWidth property.
        /// </summary>
        /// <param name="dependencyObject">The Object that originated the event.</param>
        /// <param name="dependencyPropertyChangedEventArgs">The event arguments.</param>
        static void OnMinWidthChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // Don't let the width exceed the minimum value.
            ColumnViewColumn columnViewColumn = dependencyObject as ColumnViewColumn;

            columnViewColumn.CoerceValue(ColumnViewColumn.WidthProperty);
        }
        /// <summary>
        /// Handles a change to the Width property.
        /// </summary>
        /// <param name="dependencyObject">The owner of the property.</param>
        /// <param name="dependencyPropertyChangedEventArgs">The event arguments.</param>
        static void OnWidthChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // If the column width is set to Double.NaN then we need to auto size the column.  Otherwise the column is given a specific width for all rows.
            ColumnViewColumn columnViewColumn = dependencyObject as ColumnViewColumn;

            columnViewColumn.OnPropertyChanged(WidthProperty.Name);
        }
        /// <summary>
        /// Coerce the maximum value of the range.
        /// </summary>
        /// <param name="dependencyObject">The Object on which the property exists.</param>
        /// <param name="value">The new value of the property, prior to any coercion attempt.</param>
        /// <returns>The coerced value (with appropriate type).</returns>
        static Object CoerceWidth(DependencyObject dependencyObject, Object value)
        {
            // Don't let the width exceed the minimum value.
            ColumnViewColumn columnViewColumn = dependencyObject as ColumnViewColumn;

            return(Math.Max((Double)value, columnViewColumn.MinWidth));
        }
Beispiel #5
0
        /// <summary>
        /// Presents the user with a dialog box for managing the columns.
        /// </summary>
        /// <param name="sender">The object that originated the event.</param>
        /// <param name="e">The event data.</param>
        void OnMore(Object sender, ExecutedRoutedEventArgs e)
        {
            // This will extract the context menu that generated this event from the generic arguments.
            ContextMenu contextMenu = sender as ContextMenu;

            // From the context menu, we need the target that orignated the event and from that we can finally get the column set on which we want to operate.
            ColumnViewColumnHeader columnViewColumnHeader = contextMenu.PlacementTarget as ColumnViewColumnHeader;
            ListView   listView   = VisualTreeExtensions.FindAncestor <ListView>(columnViewColumnHeader);
            ColumnView columnView = listView.View as ColumnView;

            // This dialog box is used to add, remove, move or resize the columns in the view.  It can't operate directly on the set of columns because those are
            // linked dynamically to the view, so we'll create a shallow clone of the values.
            ColumnViewChooseDetail columnViewChooseDetail = new ColumnViewChooseDetail();

            foreach (ColumnViewColumn columnViewColumn in columnView.Columns)
            {
                columnViewChooseDetail.ListBox.Items.Add(new ColumnDescription(columnViewColumn));
            }

            // Present the user with the chance to manage the columns.  If the OK key is hit then copy the values out of the shallow copy and into the live column
            // set where they'll update the view.
            if (columnViewChooseDetail.ShowDialog() == true)
            {
                foreach (ColumnDescription columnDescription in columnViewChooseDetail.ListBox.Items)
                {
                    ColumnViewColumn columnViewColumn = columnDescription.Column;
                    columnViewColumn.IsVisible = columnDescription.IsVisible;
                    columnViewColumn.Width     = columnDescription.Width;
                }
            }
        }
Beispiel #6
0
        /// <summary>
        /// Removes the column from the row.
        /// </summary>
        /// <param name="columnViewColumn">The ColumnViewColumn to be removed.</param>
        void RemoveCell(Int32 columnIndex)
        {
            // This will use the attached property to find the column to which this cell is bound through a weak listener.  It will remove the listner (though this
            // isn't a requirement for the cell to be garbage collected) and finally remove the cell from the row.
            ColumnViewColumn columnViewColumn = this.Children[columnIndex].GetValue(ColumnViewRowPresenter.ColumnProperty) as ColumnViewColumn;

            PropertyChangedEventManager.RemoveListener(columnViewColumn, this, String.Empty);
            this.Children.RemoveAt(columnIndex);
        }
Beispiel #7
0
        /// <summary>
        /// Installs the cell in the presenter.
        /// </summary>
        /// <param name="frameworkElement"></param>
        /// <param name="columnViewColumn"></param>
        void InstallCell(FrameworkElement frameworkElement, ColumnViewColumn columnViewColumn)
        {
            // Without the weak listener, the rows would not get updates when the column changes.  If we went with a strong event listener reference, then the rows
            // could not be garbage collected.  As the rows are managed by the ItemsGenerator, there is no chance to use an IDisposable interface on them, so the
            // weak event listener is the only chance we have of getting messages about changes to the columns.
            PropertyChangedEventManager.AddListener(columnViewColumn, this, String.Empty);

            // This allows the cell to find the cell which produced it and to which it is bound through a weak listner.  Though not essential to remove the listener
            // when the element is removed from this row, it is good housekeeping.
            frameworkElement.SetValue(ColumnViewRowPresenter.ColumnProperty, columnViewColumn);
        }
        /// <summary>
        /// Handles a change to the Column property.
        /// </summary>
        /// <param name="dependencyObject">The object that originated the event.</param>
        /// <param name="dependencyPropertyChangedEventArgs">The property change event arguments.</param>
        static void OnColumnPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // Extract the ColumnViewColumnHeader and the property from the generic event arguments.
            ColumnViewColumnHeader columnViewColumnHeader = dependencyObject as ColumnViewColumnHeader;
            ColumnViewColumn       columnViewColumn       = dependencyPropertyChangedEventArgs.NewValue as ColumnViewColumn;

            // This will force the sorting properties to reflect the sort order of the underlying column.
            columnViewColumnHeader.UpdateSortDirection();

            // This will keep the header property 'HasFilters' reconciled to the same property in the column it represents.
            columnViewColumnHeader.SetValue(ColumnViewColumnHeader.hasFiltersPropertyKey, columnViewColumnHeader.Column.HasFilters);
        }
Beispiel #9
0
        /// <summary>
        /// Handles a change to the column property.
        /// </summary>
        /// <param name="sender">The Object that originated the event.</param>
        /// <param name="propertyChangedEventArgs">The event arguments.</param>
        protected override void OnColumnPropertyChanged(Object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            // Validate the parameters.
            if (sender == null)
            {
                throw new ArgumentNullException("sender");
            }
            if (propertyChangedEventArgs == null)
            {
                throw new ArgumentNullException("propertyChangedEventArgs");
            }

            // This will get the column where the property change occurred from the sender.
            ColumnViewColumn columnViewColumn = sender as ColumnViewColumn;

            // If the column is visible, then it has a visual element which needs to have its properties reconciled with the ColumnViewColumn properties.
            if (columnViewColumn.IsVisible)
            {
                // This will find the visual element what has properites that must be reconciled with the header.
                Int32 columnIndex = this.Columns.IndexOf(columnViewColumn);
                ColumnViewColumnCell columnViewColumnCell = this.Children[columnIndex] as ColumnViewColumnCell;

                // This will apply the property in the column to the visual element representing that column in the current row.
                switch (propertyChangedEventArgs.PropertyName)
                {
                case "CellTemplate":
                case "CellTemplateSelector":
                case "DisplayMemberBinding":

                    // Recreate the cell based on the new properties.
                    this.Children.RemoveAt(columnIndex);
                    FrameworkElement frameworkElement = ColumnViewRowPresenter.CreateCell(columnViewColumn);
                    this.InstallCell(frameworkElement, columnViewColumn);
                    this.Children.Insert(columnIndex, frameworkElement);
                    break;

                case "Width":
                case "MinWidth":

                    // Forcing a re-measurement of the row will pick up the changes made to the column's width.
                    this.InvalidateMeasure();
                    break;
                }
            }
        }
Beispiel #10
0
        /// <summary>
        /// Create a visual element based on the properties of the column.
        /// </summary>
        /// <param name="columnViewColumn">The column that defines the cell.</param>
        /// <returns>A visual element that holds the content of the cell.</returns>
        public static FrameworkElement CreateCell(ColumnViewColumn columnViewColumn)
        {
            // Validate the parameters.
            if (columnViewColumn == null)
            {
                throw new ArgumentNullException("columnViewColumn");
            }

            // This will either create an element based on the templates associated with the ColumnViewColumn, or for simple binding, will create a simple cell and
            // bind it using the binding provided in the DisplayMemberBinding property.  Both variations return a FrameworkElement that is used as an element in the
            // row to display the data associated with the column.
            FrameworkElement frameworkElement;

            if (columnViewColumn.DisplayMemberBinding == null)
            {
                // This will create a generic ContentPresenter using the templates associated with the ColumnView and bind the content of the generic control to
                // the data context (which contains the row data).  It is the responsibility of the cell to select which property of the row data it wants to
                // display.  It is important to remember that a ContentPresenter is not like other controls.  The DataContext is not inheritied from the logical
                // parent, it is set to the  Content property of the ContentPresenter.  The main problem we're solving here is that the child control that's
                // generated from the ContentPresenter's template needs a DataContext, but the ContentPresenter doesn't have one since it's pointing to itself.  To
                // solve this, we'll bind the Content of the ContentPresenter to the ColumnViewRowPresenter that is it's parent.  This effectively restores the
                // DataContext inheritance that all other controls have.  Clear as mud?
                ContentPresenter contentPresenter = new ContentPresenter();
                contentPresenter.ContentTemplate         = columnViewColumn.CellTemplate;
                contentPresenter.ContentTemplateSelector = columnViewColumn.CellTemplateSelector;
                Binding dataContextBinding = new Binding()
                {
                    Path = new PropertyPath("DataContext")
                };
                dataContextBinding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ColumnViewRowPresenter), 1);
                BindingOperations.SetBinding(contentPresenter, ContentPresenter.ContentProperty, dataContextBinding);
                frameworkElement = contentPresenter;
            }
            else
            {
                // This will create a cell to display the data provided by the 'DisplayMemberBinding' which selects which property of the row data is to be
                // displayed.
                ColumnViewColumnCell columnViewColumnCell = new ColumnViewColumnCell();
                BindingOperations.SetBinding(columnViewColumnCell, ColumnViewColumnCell.ContentProperty, columnViewColumn.DisplayMemberBinding);
                frameworkElement = columnViewColumnCell;
            }

            // This element is used to present the data.
            return(frameworkElement);
        }
        /// <summary>
        /// Handles a change to a generic property.
        /// </summary>
        /// <param name="dependencyObject">The Object that originated the event.</param>
        /// <param name="dependencyPropertyChangedEventArgs">The property change event arguments.</param>
        static void OnPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // This will raise the INotifyPropertyChange event for any of the generic properties attached to this handler.
            ColumnViewColumn columnViewColumn = dependencyObject as ColumnViewColumn;

            columnViewColumn.OnPropertyChanged(dependencyPropertyChangedEventArgs.Property.Name);

            // This will provide a default value for the description when a string header is defined.  The designer can always override it with an explicit
            // description of the column.  The description shows up in the context menu and the 'Choose Column Details' dialog box.
            if (dependencyPropertyChangedEventArgs.Property == ColumnViewColumn.HeaderProperty)
            {
                String headerText = dependencyPropertyChangedEventArgs.NewValue as String;
                if (headerText != null && columnViewColumn.Description == null)
                {
                    columnViewColumn.Description = headerText;
                }
            }
        }
Beispiel #12
0
        /// <summary>
        /// Handles a change to the column collection associated with this presenter.
        /// </summary>
        /// <param name="notifyCollectionChangedEventArgs">The object that originated the event.</param>
        /// <param name="notifyCollectionChangedEventArgs">Provides data for the CollectionChanged event.</param>
        protected override void OnColumnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
            // The base class needs to adjust the column collection before we can operate on it.  This will force the visual children that display the headers to
            // align with the column definitions.
            base.OnColumnCollectionChanged(sender, notifyCollectionChangedEventArgs);

            // This will cycle through the columns and set the 'IsFirst' property on the first visual header in the presenter.  This property is used to give a
            // distinctive style to the first column, such as a hanging indent.
            if (this.Columns != null)
            {
                for (Int32 columnIndex = 0; columnIndex < this.Columns.Count; columnIndex++)
                {
                    ColumnViewColumn        columnViewColumn        = this.Columns[columnIndex] as ColumnViewColumn;
                    DetailsViewColumnHeader detailsViewColumnHeader = this.GetHeaderFromColumn(columnViewColumn) as DetailsViewColumnHeader;
                    detailsViewColumnHeader.SetValue(DetailsViewColumnHeader.isFirstPropertyKey, columnIndex == 0);
                }
            }
        }
Beispiel #13
0
        /// <summary>
        /// Adds a child object.
        /// </summary>
        /// <param name="value">The child object to add.</param>
        public void AddChild(Object value)
        {
            // Validate the parameters.
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            // Validate the parameter.
            ColumnViewColumn columnViewColumn = value as ColumnViewColumn;

            if (columnViewColumn == null)
            {
                throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, "Illegal type of child {0}", value.GetType()));
            }

            // Add the column definition to the view.
            this.Columns.Add(columnViewColumn);
        }
Beispiel #14
0
        /// <summary>
        /// Positions child elements and determines a size for a FrameworkElement derived class.
        /// </summary>
        /// <param name="arrangeSize">The final area within the parent that this element should use to arrange itself and its children.</param>
        /// <returns>The actual size used.</returns>
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            // This will lay out all the element horizontally across the space of the RowPresenter using either the fixed width or the width of the item.
            Point  location       = new Point();
            Double remainingWidth = arrangeSize.Width;

            // Evaluate the size of all the columns in this header.
            if (this.Columns != null)
            {
                for (Int32 columnIndex = 0; columnIndex < this.Columns.VisibleCount; columnIndex++)
                {
                    ColumnViewColumn columnViewColumn = this.Columns[columnIndex];
                    UIElement        uiElement        = this.Children[columnIndex];
                    uiElement.Arrange(new Rect(location.X, 0.0, columnViewColumn.Width, arrangeSize.Height));
                    remainingWidth -= columnViewColumn.Width;
                    location.X     += columnViewColumn.Width;
                }
            }

            // This is the size of the row after laying everything out.
            return(arrangeSize);
        }
Beispiel #15
0
 /// <summary>
 /// Create the visual element for the header.
 /// </summary>
 /// <param name="columnViewColumn">The ColumnViewColumn to be created.</param>
 /// <returns>The visual element representing the logical ColumnViewColumn.</returns>
 protected override ColumnViewColumnHeader CreateHeader(ColumnViewColumn columnViewColumn)
 {
     // This is the default column that is created for this presenter.  Inheriting classes can override this for customer header.
     return(new DetailsViewColumnHeader());
 }
Beispiel #16
0
        /// <summary>
        /// Handles a change to the column collection.
        /// </summary>
        /// <param name="notifyCollectionChangedEventArgs">The Object that originated the event.</param>
        /// <param name="notifyCollectionChangedEventArgs">Provides data for the CollectionChanged event.</param>
        protected override void OnColumnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
            Int32 columnIndex;

            // Validate the parameters.
            if (sender == null)
            {
                throw new ArgumentNullException("sender");
            }
            if (notifyCollectionChangedEventArgs == null)
            {
                throw new ArgumentNullException("notifyCollectionChangedEventArgs");
            }

            // This will reconcile the visual elements in this control with the definition of the columns.
            switch (notifyCollectionChangedEventArgs.Action)
            {
            case NotifyCollectionChangedAction.Add:

                // Create a cell to hold the value and add each it to the row at the given index.
                columnIndex = notifyCollectionChangedEventArgs.NewStartingIndex == -1 ? 0 : notifyCollectionChangedEventArgs.NewStartingIndex;
                foreach (ColumnViewColumn columnViewColumn in notifyCollectionChangedEventArgs.NewItems)
                {
                    if (columnViewColumn.IsVisible)
                    {
                        FrameworkElement frameworkElement = ColumnViewRowPresenter.CreateCell(columnViewColumn);
                        this.InstallCell(frameworkElement, columnViewColumn);
                        this.Children.Insert(columnIndex++, frameworkElement);
                    }
                    this.InvalidateMeasure();
                }

                break;

            case NotifyCollectionChangedAction.Move:

                // Columns that appear after the visible count in the collection are hidden items.  They exist only as a pool of potential columns.  The items below
                // the 'VisibleCount' are actually visible in this view and appear in the same order in which they appear in this control.  As items are moved in
                // and out of the visible range, we will create or destroy the visual elements used to show those columns in the viewer.
                Int32            oldIndex    = notifyCollectionChangedEventArgs.OldStartingIndex;
                Int32            newIndex    = notifyCollectionChangedEventArgs.NewStartingIndex;
                ColumnViewColumn movedColumn = this.Columns[newIndex];
                if (this.Children.Count == this.Columns.VisibleCount)
                {
                    // At this point, the collection hasn't changed.  We are just moving columns around.
                    UIElement uiElement = this.Children[oldIndex];
                    this.Children.RemoveAt(notifyCollectionChangedEventArgs.OldStartingIndex);
                    this.Children.Insert(notifyCollectionChangedEventArgs.NewStartingIndex, uiElement);
                    this.InvalidateArrange();
                }
                else
                {
                    // This will hide the column by destroying the visual elements used to display the column.
                    if (oldIndex < this.Children.Count)
                    {
                        this.Children.RemoveAt(oldIndex);
                    }

                    // This will create a visual element to display the now visible column.
                    if (movedColumn.IsVisible)
                    {
                        FrameworkElement frameworkElement = ColumnViewRowPresenter.CreateCell(movedColumn);
                        this.InstallCell(frameworkElement, movedColumn);
                        this.Children.Insert(newIndex, frameworkElement);
                    }
                }

                break;

            case NotifyCollectionChangedAction.Remove:

                // Remove the offending cells from the specified location.
                columnIndex = notifyCollectionChangedEventArgs.OldStartingIndex;
                foreach (ColumnViewColumn columnViewColumn in notifyCollectionChangedEventArgs.OldItems)
                {
                    this.RemoveCell(columnIndex++);
                }

                break;

            case NotifyCollectionChangedAction.Reset:

                // Clear them all.
                while (this.Children.Count != 0)
                {
                    this.RemoveCell(0);
                }

                break;
            }
        }
Beispiel #17
0
        /// <summary>
        /// Occurs when a particular instance of a context menu opens.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        void OnOpened(Object sender, RoutedEventArgs e)
        {
            // Extract the context menu from the generic event arguments.
            ContextMenu contextMenu = sender as ContextMenu;

            // We're going to clean out the context menu and re-evaluate the contents based on the context where the menu was opened.
            contextMenu.Items.Clear();

            // This is the header that opened the context menu.  From it, we need the ListView that hosts the ColumnView (that contains all the columns that we're
            // trying to manage with this context menu).
            ColumnViewColumnHeader columnViewColumnHeader = contextMenu.PlacementTarget as ColumnViewColumnHeader;
            ListView   listView   = VisualTreeExtensions.FindAncestor <ListView>(columnViewColumnHeader);
            ColumnView columnView = listView.View as ColumnView;

            // The task now is to fill in the menu items.  We don't allow the user to resize a padding column, but will allow them to resize all the columns.
            if (columnViewColumnHeader.Role != ColumnViewColumnHeaderRole.Padding)
            {
                this.Items.Add(new MenuItem()
                {
                    Command = Commands.FitColumn, CommandTarget = columnViewColumnHeader, Header = "Size Column to Fit"
                });
            }
            this.Items.Add(new MenuItem()
            {
                Command = Commands.FitAllColumns, CommandTarget = listView, Header = "Size All Columns to Fit"
            });
            this.Items.Add(new Separator());

            // This will order the fixed menu items by their ordinal.  An 'ordinal' is a numeric value that the designer can assign to the fixed menu items to
            // determine the order (or even if they'll appear) as a fixed menu item (that is, a quick way to add or remove a column, as opposed to selecting the
            // 'More...' button and using the list box to select an column).
            SortedList <Int32, ColumnViewColumn> commonColumns = new SortedList <Int32, ColumnViewColumn>();

            foreach (ColumnViewColumn columnViewColumn in columnView.Columns)
            {
                if (columnViewColumn.Ordinal.HasValue && !commonColumns.ContainsKey(columnViewColumn.Ordinal.Value))
                {
                    commonColumns.Add(columnViewColumn.Ordinal.Value, columnViewColumn);
                }
            }

            // This will create a dedicated menu item for each of the columns that has been assigned an 'ordinal'.  This allows the user to quickly add or remove
            // frequently used columns.  Note that we attempt to format the column headers the way they would appear in the actual column header.
            foreach (KeyValuePair <Int32, ColumnViewColumn> keyValuePair in commonColumns)
            {
                ColumnViewColumn columnViewColumn = keyValuePair.Value;
                MenuItem         menuItem         = new MenuItem();
                Binding          isVisibleBinding = new Binding()
                {
                    Path = new PropertyPath(ColumnViewColumn.IsVisibleProperty)
                };
                isVisibleBinding.Source = columnViewColumn;
                BindingOperations.SetBinding(menuItem, MenuItem.IsCheckedProperty, isVisibleBinding);
                menuItem.Header      = columnViewColumn.Description;
                menuItem.IsCheckable = true;
                this.Items.Add(menuItem);
            }

            // Place a separator in the context menu if there is a section above it with dedicated menu items for the column visibility settings.
            if (commonColumns.Count != 0)
            {
                this.Items.Add(new Separator());
            }

            // Finally, a general purpose dialog box for managing the column set.
            this.Items.Add(new MenuItem()
            {
                Command = Commands.More, Header = "More..."
            });
        }