Example #1
0
 /// <summary>
 /// Creates a new virtual cell for the report.
 /// </summary>
 /// <param name="iContent">The content of the cell.</param>
 /// <param name="reportColumn">The row to which the cell belongs.</param>
 /// <param name="reportRow">The column to which the cell belongs.</param>
 public ReportCell(IContent iContent, ReportColumn reportColumn, ReportRow reportRow)
 {
     // Initialize the object.
     this.IsObsolete   = false;
     this.isFocused    = false;
     this.Content      = iContent;
     this.ReportColumn = reportColumn;
     this.ReportRow    = reportRow;
 }
Example #2
0
        /// <summary>
        /// Creates an explicitly empty cell for the report.
        /// </summary>
        private ReportCell()
        {
            // Initialize the object.
            this.IsObsolete = false;
            this.isFocused  = false;

            this.Content      = null;
            this.ReportColumn = null;
            this.ReportRow    = null;
        }
        /// <summary>
        /// Recursively add a column to the collection of rows.
        /// </summary>
        /// <param name="iContent">The content of the current row in the recursion.</param>
        /// <param name="rowTemplate">The template describing the layout of data in the current row.</param>
        internal void RecursivelyAddColumns(IContent iContent, RowTemplate rowTemplate)
        {
            // This section of code will create a virtual cell where data can be found and coordinates in the document space assigned.  The existing row is
            // pulled apart and the properties are examined.  If a template has been associated with the property's type, then an association can be made
            // between the row and the column where this property should be displayed.
            ReportRow reportRow = null;

            if (this.dictionary.TryGetValue(iContent.Key, out reportRow))
            {
                foreach (PropertyInfo propertyInfo in iContent.GetType().GetProperties())
                {
                    IContent childContent = propertyInfo.GetValue(iContent, null) as IContent;
                    if (childContent != null)
                    {
                        string columnId;
                        if (this.reportGrid.reportFieldCollection.ColumnIdMap.TryGetValue(childContent.GetType(), out columnId))
                        {
                            ReportColumn reportColumn;
                            if (this.reportGrid.reportColumnCollection.TryGetValue(columnId, out reportColumn))
                            {
                                if (!reportRow.ContainsKey(reportColumn))
                                {
                                    reportRow.Add(reportColumn, new ReportCell(childContent, reportColumn, reportRow));
                                }
                            }
                        }
                    }
                }
            }

            // This will recurse into the report's hierarchy.  Each row template can specify a child row and the property that is
            // used to create that child row.  These, in turn, can be recursed into ad infinitum.
            foreach (RowTemplate childRowDefinition in rowTemplate.Children)
            {
                PropertyInfo propertyInfo = iContent.GetType().GetProperty(childRowDefinition.Path);
                Object       property     = propertyInfo.GetGetMethod().Invoke(iContent, null);
                if (property is IEnumerable)
                {
                    IEnumerable iEnumerable = property as IEnumerable;
                    foreach (IContent childContent in iEnumerable)
                    {
                        RecursivelyAddColumns(childContent, childRowDefinition);
                    }
                }
            }
        }
        /// <summary>
        /// Removes a row from the collection.
        /// </summary>
        /// <param name="reportRow">The row to be removed.</param>
        public void Remove(ReportRow reportRow)
        {
            // Remove the row from this collection.
            this.dictionary.Remove(reportRow);

            // This will remove the visual elements from the screen that are associated with this row.
            foreach (ReportCanvas reportCanvas in this.reportGrid.reportCanvases)
            {
                reportCanvas.Remove(reportRow);
            }

            // The report needs to purge itself of obviated user interface elements from the obsolete columns.
            this.viewport = this.reportGrid.reportCanvases[3].Viewport;
            if ((viewport.Top <= reportRow.Top && reportRow.Top < viewport.Bottom) ||
                (viewport.Top <= reportRow.Top + reportRow.Height && reportRow.Top + reportRow.Height < viewport.Bottom))
            {
                this.reportGrid.InvalidateMeasure();
            }
        }
Example #5
0
 /// <summary>
 /// Removes a ReportRow from the canvas.
 /// </summary>
 /// <param name="columnDefinition">The description of the column to be removed.</param>
 internal void Remove(ReportRow reportRow)
 {
     // Each visual element that matches the specified report row will be recycled and the data connection to the underlying
     // cell severed.
     for (int childIndex = 0; childIndex < this.Children.Count;)
     {
         UIElement uiElement = this.Children[childIndex];
         if (uiElement is FrameworkElement)
         {
             FrameworkElement frameworkElement = uiElement as FrameworkElement;
             ReportCell       reportCell       = DynamicReport.GetCell(frameworkElement);
             if (reportCell != null && reportCell.ReportRow == reportRow)
             {
                 this.Remove(reportCell);
             }
             else
             {
                 childIndex++;
             }
         }
     }
 }
        /// <summary>
        /// Constructs a collection of report rows based on the data content and the templates that describes the row.
        /// </summary>
        /// <param name="iContent">The content to be displayed in the row.</param>
        /// <param name="rowTemplate">The template that describes how the data is presented.</param>
        private void RecursivelyUpdateRow(IContent iContent, RowTemplate rowTemplate)
        {
            // Each IContent object has a unique key used to associate it with a report row.  If a given report row has already has been mapped to the
            // IContent's key, then the contents of that row will be updated.  Otherwise, a new row is created and associated with the IContent.
            ReportRow reportRow = null;

            if (iContent.Key != null && this.dictionary.TryGetValue(iContent.Key, out reportRow))
            {
                // Any change to the location or the height of the row will be animated so long as the current or target location is visible.  There is no
                // sense in animating the movement of invisible elements of the report.
                if (reportRow.Top != this.height || reportRow.Height != rowTemplate.Height)
                {
                    // At this point it has been determined that the row has moved.  An animation sequence will be created for those rows that appear in or
                    // disappear from the viewport.
                    Boolean isRowVisible = (this.viewport.Top <= reportRow.Top && reportRow.Top < this.viewport.Bottom) ||
                                           (this.viewport.Top <= reportRow.Top + reportRow.Height && reportRow.Top + reportRow.Height < this.viewport.Bottom);
                    Boolean willRowBeVisible = (this.viewport.Top <= this.height && this.height < this.viewport.Bottom) ||
                                               (this.viewport.Top <= this.height + rowTemplate.Height && this.height + rowTemplate.Height < this.viewport.Bottom);

                    // This creates a list of rows who's ZIndex must be set for the animation sequence.
                    if (isRowVisible || willRowBeVisible)
                    {
                        // At this point the report row either is currently visible or will be visible and has been moved or resized.  This means that the
                        // visible part of the virtual document should be measured.  Items that have become visible will need to be instantiated and items that
                        // are no longer visible need to be recycled.
                        this.isMeasurementRequired = true;

                        // The ZIndex of this row is set such that the rows that move the greatest distance will appear to float above the ones that only move a little.  This scheme
                        // makes the movement appear more natural.
                        reportRow.ZIndex = Math.Abs(reportRow.Ordinal - this.ordinal);

                        // When all the report rows have been examined and the Z order of the visible rows set, this list is used to set the ZIndex of all the
                        // graphical elements associated with this row.
                        this.animatedRows.Add(reportRow);
                    }

                    // The order of the row in the report is used for setting graphical cues such as shading and Z order.
                    reportRow.Ordinal = this.ordinal;

                    // If the location occupied by this row has changed then an animation sequence is constructed to move a row from one place to another.  If
                    // the row isn't visible or is not going to be visible, then just the location is modified.
                    if (reportRow.Top != this.height)
                    {
                        // This will move the row to its proper position in the virtual document.
                        reportRow.Top = this.height;

                        // The starting location is clipped by the visible part of the report.  This prevents a row that is moved to a far distant place from
                        // moving too fast to be seen on the screen.
                        // HACK - This should be repaired when time permits.
//						Double startLocation = (reportRow.ActualTop < viewport.Top) ? viewport.Top - reportRow.Height :
//							(reportRow.ActualTop > viewport.Bottom) ? viewport.Bottom : reportRow.ActualTop;
                        Double startLocation = reportRow.ActualTop;

                        // The ending location is also clipped by the visible part of the report for the same reason.  Without this code, rows appear to
                        // disappear instantly from the screen instead of moving smoothly to the nearest edge.
                        // HACK - This should be repaired when time permits.
//						Double endLocation = (reportRow.Top + reportRow.Height < viewport.Top) ? viewport.Top - reportRow.Height :
//							(reportRow.Top > viewport.Bottom) ? viewport.Bottom : reportRow.Top;
                        Double endLocation = reportRow.Top;

                        // This animates the movement of the row from its current position to the new position.  If the row is no longer visible then any
                        // animation that was associated with that row will be terminated.
                        if (isRowVisible || willRowBeVisible)
                        {
                            DoubleAnimation topAnimation = new DoubleAnimation();
                            Storyline.SetTargetObject(topAnimation, reportRow);
                            Storyline.SetTargetProperty(topAnimation, ReportRow.ActualTopProperty);
                            topAnimation.From     = startLocation;
                            topAnimation.To       = endLocation;
                            topAnimation.Duration = this.reportGrid.Duration;
                            this.storyline.Children.Add(topAnimation);
                        }
                        else
                        {
                            if (reportRow.HasAnimatedProperties)
                            {
                                reportRow.ApplyAnimationClock(ReportRow.ActualTopProperty, null);
                            }
                            reportRow.ActualTop = reportRow.Top;
                        }
                    }

                    // If the height occupied by this row has changed then an animation sequence is constructed to resize the row.  If the row isn't visible or
                    // is not going to be visible, then the property is modified without an animation sequence. This saves the processor from doing work when
                    // an element isn't visible.
                    if (reportRow.Height != rowTemplate.Height)
                    {
                        // This sets the height of the row.
                        reportRow.Height = rowTemplate.Height;

                        // This will animate the change in the row's height from the current height to the new target height.
                        if (isRowVisible || willRowBeVisible)
                        {
                            DoubleAnimation heightAnimation = new DoubleAnimation();
                            Storyline.SetTargetObject(heightAnimation, reportRow);
                            Storyline.SetTargetProperty(heightAnimation, ReportRow.ActualHeightProperty);
                            heightAnimation.From     = reportRow.ActualHeight;
                            heightAnimation.To       = reportRow.Height;
                            heightAnimation.Duration = this.reportGrid.Duration;
                            this.storyline.Children.Add(heightAnimation);
                        }
                        else
                        {
                            if (reportRow.HasAnimatedProperties)
                            {
                                reportRow.ApplyAnimationClock(ReportRow.ActualHeightProperty, null);
                            }
                            reportRow.ActualHeight = reportRow.Height;
                        }
                    }
                }

                // A virtual method is used to copy the current content over the existing content.  The event handlers will take
                // care of propagating the content and properties of the data to the screen elements.
                reportRow.IContent.Copy(iContent);

                // This indicates that the row is still in use and shouldn't be purged.
                reportRow.IsObsolete = false;
            }
            else
            {
                // This is where new rows are created to hold the given content and added to the report.
                reportRow          = new ReportRow();
                reportRow.IContent = iContent;
                this.dictionary.Add(iContent.Key, reportRow);

                // The order of the row in the report drives visual cues such as alternate line shading and Z order properties.
                reportRow.Ordinal = this.ordinal;

                // The target location and height are initialized here without animation.
                reportRow.Top    = this.height;
                reportRow.Height = rowTemplate.Height;

                // The actual top and height used when rendering the rows.
                reportRow.ActualTop    = reportRow.Top;
                reportRow.ActualHeight = reportRow.Height;

                // Rows that are added to the visible portion of the display will trigger the 'MeasureOverride' which will instantiate the new report elements.
                if ((this.viewport.Top <= reportRow.Top && reportRow.Top < this.viewport.Bottom) ||
                    (this.viewport.Top <= reportRow.Top + reportRow.Height && reportRow.Top + reportRow.Height < this.viewport.Bottom))
                {
                    this.isMeasurementRequired = true;
                }

                // The report row is really just a container for report cells.  The System.Reflection library is used to rip apart the content and create
                // columns that are tightly bound to the type of data found in the incoming record.
                foreach (PropertyInfo propertyInfo in iContent.GetType().GetProperties())
                {
                    IContent childContent = propertyInfo.GetValue(iContent, null) as IContent;
                    if (childContent != null)
                    {
                        string columnId;
                        if (this.reportGrid.reportFieldCollection.ColumnIdMap.TryGetValue(childContent.GetType(), out columnId))
                        {
                            ReportColumn reportColumn;
                            if (this.reportGrid.reportColumnCollection.TryGetValue(columnId, out reportColumn))
                            {
                                reportRow.Add(reportColumn, new ReportCell(childContent, reportColumn, reportRow));
                            }
                        }
                    }
                }
            }

            // This is used to keep track of the order of the row in the report.
            this.ordinal++;

            // This moves the virtual cursor up to the next available row in the virtual space.
            this.height += reportRow.Height;

            // The report can have any number of levels in the hierarchical arrangment or the rows.  This will recurse into the template that defines the
            // outline of the report and generate any rows that can be matched to the templates that are children of the current template.  The 'Path' property
            // on the template rows indicate which properties of the content row to follow when recursing.
            foreach (RowTemplate childRowDefinition in rowTemplate.Children)
            {
                PropertyInfo propertyInfo = iContent.GetType().GetProperty(childRowDefinition.Path);
                if (propertyInfo != null)
                {
                    Object property = propertyInfo.GetValue(iContent, null);
                    if (property is IEnumerable)
                    {
                        IEnumerable iEnumerable = property as IEnumerable;
                        foreach (IContent childContent in iEnumerable)
                        {
                            RecursivelyUpdateRow(childContent, childRowDefinition);
                        }
                    }
                }
            }
        }
 public RowDefinitionsEventArgs(ReportRow reportRow)
 {
     // Initialize the object.
     this.Rows = new List <ReportRow>();
     this.Rows.Add(reportRow);
 }
 /// <summary>
 /// Create the event arguments.
 /// </summary>
 /// <param name="row">The row the cell is in.</param>
 /// <param name="cell">The cell itself.</param>
 /// <param name="property">The property that changed.</param>
 public ReportCellPropertyChangedEventArgs(ReportRow row, ReportCell cell, String property)
 {
     this.row      = row;
     this.cell     = cell;
     this.property = property;
 }
Example #9
0
        /// <summary>
        /// Handles the mouse button being released over a row header.
        /// </summary>
        /// <param name="state">The thread initialization parameter.</param>
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            // This gets the location of the mouse in document coordinates.
            Point mouseLocation = e.GetPosition(this);

            // Evaluate the state of the keyboard.  Key combinations involving the shift and control keys will alter the areas that
            // are selected.
            bool isShiftKeyPressed   = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
            bool isControlKeyPressed = ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control);

            // The target row for any movement operations is the last row selected.
            ReportRow anchorRow = this.ReportGrid.Rows.FindRowAt(this.anchorPoint.Y);

            // The mouse state indications what action should be taken when the mouse button is released.
            switch (this.mouseState)
            {
            case MouseState.ResizingRow:

                // At the conclusion of the resizing operation a decision is made to remove or change the width of a row.  If
                // the size is reduced to zero (or less), this gesture is taken as a command to remove the row.  Any positive
                // value for a width will result in a row change command.
                double rowHeight = anchorRow.Height + (mouseLocation.Y - this.anchorPoint.Y);
                rowHeight = rowHeight < 0.0 ? 0.0 : rowHeight;
                if (this.ResizeMouseMove != null)
                {
                    this.ResizeMouseMove(this, new ResizeRowEventArgs(anchorRow, rowHeight, true));
                }

                // Note - this is where the logic should go for removing or resizing a row.  It should probably bubble up through
                // the Report and be handled by some higher logic that determins if removing or resizing a row is a legal action.
                // Currently it is not supported.

                // This will hide the popup until it is needed again.
                this.rowHeightPopup.IsOpen = false;

                // This is a momentary button: the column heading will loose the selection state when the button is released.
                foreach (ReportCell reportCell in this.headerCells)
                {
                    reportCell.IsSelected = false;
                }

                break;

            case MouseState.DraggingRow:

                // The action taken when the dragging operation is complete depends on whether a valid destination is selected or
                // whether the row is meant to be deleted.
                switch (this.destinationState)
                {
                case DropAction.Select:

                    // This will move the row from its current location to the desired location.
                    if (this.destinationPopup.Visibility == Visibility.Visible)
                    {
                        // TODO - This should generate an event to move a row from one place to another.
                    }

                    break;

                case DropAction.Delete:

                    // This will delete the row from the view.
                    this.ReportGrid.Rows.Remove(anchorRow);

                    break;
                }

                // This is a momentary button: the column heading will loose the selection state when the button is released.
                foreach (ReportCell reportCell in this.headerCells)
                {
                    reportCell.IsSelected = false;
                }

                break;
            }

            // This resets the state of the mouse for the next operation.
            this.mouseState = MouseState.ButtonUp;

            if (this.headerPopup.IsOpen)
            {
                this.headerPopup.IsOpen = false;
            }

            // Hide the destination cursor when the mouse is released.
            if (this.destinationPopup.IsOpen)
            {
                this.destinationPopup.IsOpen = false;
            }

            // Release the Hounds, errr... mouse.
            Mouse.Capture(null);
        }
Example #10
0
        /// <summary>
        /// Handles the movement of the mouse in the row header.
        /// </summary>
        /// <param name="e">The mouse movement event arguments.</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // This gets the location of the mouse in document coordinates.
            Point mouseLocation = e.GetPosition(this);

            // The anchorRow describes the starting row for an extended selection.  The selectedRow is the one over which
            // the mouse is currently.
            ReportRow selectedRow = this.ReportGrid.Rows.FindRowAt(mouseLocation.Y);
            ReportRow anchorRow   = this.ReportGrid.Rows.FindRowAt(this.anchorPoint.Y);

            // The action taken by a mouse movement in the row heading of a viewer is driven by a set of states.  These states
            // are driven, in turn, by where the mouse started and what buttons are pressed.  They can instruct the viewer to
            // resize the rows, move the row, delete the row or sort the row.
            switch (this.mouseState)
            {
            case MouseState.Selecting:

                // When dragging the mouse around the row headings, the most recent range will be replaced.  The new range consist
                // of every row between the anchor row and the row over which the mouse is currently.

                // HACK - Fix this when the selected range is guaranteed to have rows in it.
                if (this.selectedRanges.Count > 0)
                {
                    List <ReportRow> lastRange = this.selectedRanges[this.selectedRanges.Count - 1];
                    if ((lastRange[lastRange.Count - 1] != selectedRow) && (selectedRow != null))
                    {
                        // This will select each row between the last selected row and the one just selected.  Note that the
                        // list of rows is distinct, so duplicate rows are ignored.
                        ReportRow firstRow  = anchorRow.Top < selectedRow.Top ? anchorRow : selectedRow;
                        ReportRow secondRow = anchorRow.Top < selectedRow.Top ? selectedRow : anchorRow;
                        lastRange.Clear();
                        foreach (ReportRow reportRow in this.ReportGrid.Rows)
                        {
                            if (firstRow.Top <= reportRow.Top && reportRow.Top <= secondRow.Top)
                            {
                                lastRange.Add(reportRow);
                            }
                        }

                        // Add the row Header to the range also so the it will be highlight too.
                        lastRange.Add(ReportGrid.reportRowCollection.FindRowAt(0));

                        // This will select all the cells in the range of rows that have been selected.  Everything else will be
                        // cleared.  The active cell -- the one that has the input focus -- is the first cell of the anchor row.
                        SelectRows();
                    }
                }

                break;

            case MouseState.ResizingRow:

                // This will calculate the current row width based on the difference between the anchor point and the current mouse
                // location.
                double rowHeight = selectedRow.Height + (mouseLocation.Y - this.anchorPoint.Y);

                // The owner of the header canvas is called to draw the row boundary line so that it follows the mouse when the
                // user is resizing the rows.  This operation can't be done from this canvas because the size of the scrolling
                // canvas -- where the row boundary cursor line is displayed -- isn't known here.
                if (this.ResizeMouseMove != null)
                {
                    this.ResizeMouseMove(this, new ResizeRowEventArgs(selectedRow, rowHeight, false));
                }

                // As the size of the row is dragged around, the Popup widow displays a human readable form of the width.
                this.rowHeightPopup.Content = rowHeight;

                break;

            case MouseState.ButtonDown:

                // When the user presses the top mouse button, they initiate some drag operation and the mouse activity is
                // captured by the row header window.  If the user is simply moving the mouse over the window, then feedback is
                // given in the shape of the cursor. This formula determins if the mouse has moved an absolute distance of four
                // pixels from the original location. If it has, the user has selected a movement operation for the row.
                // Otherwise, the mouse operation will be interpreted as a request for a new sort order when the top mouse button
                // is lifted.
                if (Math.Sqrt(Math.Pow(mouseLocation.Y - this.mouseDownLocation.Y, 2.0) +
                              Math.Pow(mouseLocation.Y - this.mouseDownLocation.Y, 2.0)) > DynamicReport.headerDragTrigger)
                {
                    // At this point the mouse movements are intepreted as drag-and-drop operations for the row headers. The
                    // drop states determines what happens when the mouse button is released.  It can either be moved, deleted or
                    // have no action taken.
                    this.mouseState       = MouseState.DraggingRow;
                    this.destinationState = DropAction.NoAction;

                    // When dragging a row, the proposed destination appears as a set of two red arrows marking where the row
                    // will reside if dropped.  The scale of the destination arrows must match the scale of the report.
                    this.destinationPopup.Scale      = this.ReportGrid.DynamicReport.Scale;
                    this.destinationPopup.Visibility = Visibility.Hidden;
                    this.destinationPopup.IsOpen     = true;

                    // This sets up the dragging operation by creating a destination cursor (the red arrows that point to where the
                    // row will 'snap' into place), a row cursor (it looks like the row header was ripped out of the page)
                    // and positions the row cursor at the tip of the current mouse location.
                    this.headerPopup.Resources = this.ReportGrid.Resources;
                    this.headerPopup.Content   = this.headerCells;
                    this.headerPopup.Scale     = this.ReportGrid.DynamicReport.Scale;
                    this.headerPopup.Height    = selectedRow.Height;
                    this.headerPopup.Width     = this.ActualWidth;
                    this.headerPopup.Location  = mouseLocation;
                    this.headerPopup.IsOpen    = true;
                }

                break;

            case MouseState.DraggingRow:

                // If the window that contains the row headings contains the cursor, then it's possible that a destination is
                // selected for the row drag-and-drop operation.  If the cursor is outside of the header quadrant, the row
                // will be deleted when the mouse button is released.
                if (this.Viewport.Contains(mouseLocation))
                {
                    // Any operation inside the visible header gets the basic pointing arrow for a cursor.
                    this.Cursor = this.selectRow;

                    // When the mouse is inside the header quadrant but there is no destination selected, then nothing will happen
                    // when the mouse button is release.
                    this.destinationState = DropAction.NoAction;

                    // This attempts to find a destination for the row operation.
                    if (selectedRow != null)
                    {
                        // A row can't be its own destination.
                        if (anchorRow != selectedRow)
                        {
                            // A destination is selected if the left edge of the target column is entirely visible in the header
                            // quadrant and the left half of the column header contains the current mouse location.
                            Rect testAreaTop = new Rect(this.Viewport.Left, selectedRow.Top, this.Viewport.Width,
                                                        selectedRow.Height / 2.0);
                            if (testAreaTop.Contains(mouseLocation) && anchorRow.Bottom != selectedRow.Top)
                            {
                                this.destinationState = DropAction.Select;
                                this.destinationRow   = selectedRow;
                            }

                            // This will test the right half of each of the colum headers.  If the cursor is over the right half
                            // and the rightmost part of the destination is entirely visible in the header, then it can be a
                            // destination.
                            Rect testAreaBottom = new Rect(this.Viewport.Left, selectedRow.Top + selectedRow.Height / 2.0,
                                                           this.Viewport.Width, selectedRow.Height / 2.0);
                            if (testAreaBottom.Contains(mouseLocation) && selectedRow.Bottom != anchorRow.Top)
                            {
                                this.destinationState = DropAction.Select;
                                this.destinationRow   = null;
                                foreach (ReportRow nextRow in this.ReportGrid.Rows)
                                {
                                    if (nextRow.Top == selectedRow.Bottom)
                                    {
                                        this.destinationRow = nextRow;
                                    }
                                }
                            }
                        }
                    }

                    // If a valid destination was found in the search above, move the set of red arrows (the destination cursor)
                    // over the exact spot where the row will be moved.
                    if (this.destinationState == DropAction.Select)
                    {
                        this.destinationPopup.VerticalOffset = this.destinationRow == null ?
                                                               this.ReportGrid.ExtentHeight : this.destinationRow.Top;
                        this.destinationPopup.Visibility = Visibility.Visible;
                    }
                    else
                    {
                        this.destinationPopup.Visibility = Visibility.Hidden;
                    }
                }
                else
                {
                    // If the mouse isn't over the row header quadrant, a big 'X' instead of the destination cursor give the
                    // user feedback that the row will be dropped from the viewer if they release the mouse button.
                    this.destinationPopup.Visibility = Visibility.Hidden;
                    this.Cursor = this.bigEx;

                    // This will instruct the 'mouse up' action to delete the currently selected row.
                    this.destinationState = DropAction.Delete;
                }

                // The cursor row is really a floating window, not a cursor.  It needs to be moved to match the location of the
                // mouse.  Note that the floating window doesn't have a parent, so the coordinates are in screen units.
                this.headerPopup.Location = mouseLocation;

                break;

            case MouseState.ButtonUp:

                // This will determine which cursor should be used when the button isn't pressed while moving the mouse: a horizontal
                // size cursor or a regular arrow cursor.  If the mouse is over the bottom or top edge of the row, then the
                // horizontal resizing cursor is used.
                bool isResizingRow = false;

                // This will attempt to hit test the current row to see if it is a candidate for resizing.
                if (selectedRow != null)
                {
                    // This is a 'Hit Test' for the bottom edge of the row header tile to see if the user is trying to change the
                    // size of the row.
                    if (selectedRow.Top + selectedRow.Height - DynamicReport.splitBorder <= mouseLocation.Y &&
                        mouseLocation.Y < selectedRow.Top + selectedRow.Height)
                    {
                        isResizingRow = true;
                    }

                    // This is a 'Hit Test' for the top edge of the row header tile to see if the user is trying to change the
                    // size of the row.
                    if (selectedRow.Top <= mouseLocation.Y &&
                        mouseLocation.Y < selectedRow.Top + DynamicReport.splitBorder)
                    {
                        isResizingRow = true;
                    }
                }

                // Only the selection cursor is used when the header is frozen.  When unfrozen, select the resizing cursor when
                // the mouse is over the edge of the column header.
                this.Cursor = this.IsHeaderFrozen || !isResizingRow ? this.selectRow : this.horizontalSplit;

                break;
            }
        }
Example #11
0
        /// <summary>
        /// Handles the mouse button being pressed.
        /// </summary>
        /// <param name="e">The event arguments.</param>
        protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            // The sort order is maintained in this structure.
            this.sortOrder = new List <SortItem>();

            // This gets the location of the mouse in document coordinates.
            Mouse.Capture(this);

            // This state variable will control how the 'Mouse Move' and 'Mouse Up' event handlers interpret the user action.  The
            // 'selectedRow' field is used as the starting point for any drag-and-drop type of operation.
            this.mouseState = MouseState.ButtonDown;

            // Evaluate the state of the keyboard.  Key combinations involving the shift and control keys will alter the areas that
            // are selected.
            bool isShiftKeyPressed   = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
            bool isControlKeyPressed = ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control);

            // This will use the current position for an anchor unless the shift key is pressed.
            this.mouseDownLocation = e.GetPosition(this);
            if (!isShiftKeyPressed)
            {
                this.anchorPoint = this.mouseDownLocation;
            }

            // The mouse indicates which column has been selected and the anchor indicates the starting point of the selection in
            // an extended selection operation.
            ReportRow selectedRow = this.ReportGrid.Rows.FindRowAt(this.mouseDownLocation.Y);
            ReportRow anchorRow   = this.ReportGrid.Rows.FindRowAt(this.anchorPoint.Y);

            Point mouseDownLocation = e.GetPosition(this);

            if (!isShiftKeyPressed)
            {
                IInputElement    iInputElement    = this.InputHitTest(mouseDownLocation);
                DependencyObject dependencyObject = iInputElement as DependencyObject;
                while (dependencyObject != null)
                {
                    if (DynamicReport.GetCell(dependencyObject) != null)
                    {
                        Keyboard.Focus(dependencyObject as IInputElement);
                        break;
                    }
                    dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
                }
            }

            // Every cell that appears in the header canvas is considered part of the selectable header.  This will collect all the
            // selected cells in a list while creating a rectangle that is the union of all those selected cells.
            this.headerCells.Clear();
            foreach (ReportColumn reportColumn in this.ReportGrid.Columns)
            {
                ReportCell reportCell = selectedRow[reportColumn];
                if (reportColumn.Left < this.ActualWidth)
                {
                    this.headerCells.Add(reportCell);
                }
            }

            // If a row is selected then the position and movement of the mouse will suggest one of several gestures that need
            // to be interpreted: is a row being moved, is it being resized, is it being deleted or selected?  The code below
            // will begin to interpret the input gesture.
            if (selectedRow != null)
            {
                // The header has two modes: when the headers are frozen, only selection operations are enabled with the mouse.
                // When not frozen, the rows can be moved, resized, resorted and removed.
                if (this.IsHeaderFrozen)
                {
                    // The shift and control key extend the selection operation in the same way as Microsoft Excel.
                    if (isShiftKeyPressed || isControlKeyPressed)
                    {
                        // When the shift key is pressed during row selection, every row between the last row selected
                        // and the current row is selected.
                        if (isShiftKeyPressed)
                        {
                            // In the unlikely event that the shift key was down during the setting of the anchor point, this will
                            // create a dummy entry in the list of selected row ranges.
                            if (this.selectedRanges.Count == 0)
                            {
                                List <ReportRow> reportRows = new List <ReportRow>();
                                reportRows.Add(selectedRow);
                                this.selectedRanges.Add(reportRows);
                            }

                            // The most recent range will be replaced with a new range when the mouse is dragged around the row
                            // headers.  This has the effect of clearing the rows that are no longer selected and selecting only
                            // the rows between the anchor and the currently selected row.
                            List <ReportRow> lastRange = this.selectedRanges[this.selectedRanges.Count - 1];

                            // This will select each row between the last selected row and the one just selected.  Note that the
                            // list of rows is distinct, so duplicate rows are ignored.
                            ReportRow firstRow  = anchorRow.Top < selectedRow.Top ? anchorRow : selectedRow;
                            ReportRow secondRow = anchorRow.Top < selectedRow.Top ? selectedRow : anchorRow;
                            lastRange.Clear();
                            foreach (ReportRow reportRow in this.ReportGrid.Rows)
                            {
                                if (firstRow.Top <= reportRow.Top && reportRow.Top <= secondRow.Top)
                                {
                                    lastRange.Add(reportRow);
                                }
                            }
                        }

                        // When the control key is pressed a single row is added to the range of rows selected.
                        if (isControlKeyPressed)
                        {
                            // This removes any previous instance of this row in the selection.
                            foreach (List <ReportRow> rowRange in this.selectedRanges)
                            {
                                if (rowRange.Contains(selectedRow))
                                {
                                    rowRange.Remove(selectedRow);
                                }
                            }

                            // The row is added (or re-added) at the start of the range of selected rows.
                            List <ReportRow> reportRows = new List <ReportRow>();
                            reportRows.Add(selectedRow);
                            this.selectedRanges.Add(reportRows);
                        }
                    }
                    else
                    {
                        // The row is added at the start of a new range of selected rows.
                        List <ReportRow> reportRows = new List <ReportRow>();

                        // The if condition below fixes issue that allows the right click to not clear the selected rows.
                        // Hence it allows every other condition to clear the state.
                        if (e.RightButton != MouseButtonState.Pressed)
                        {
                            // A simple selection that doesn't involve the modifier keys will clear out any previously selected ranges.
                            this.selectedRanges.Clear();
                        }
                        else
                        {
                            // This removes any previous instance of this row in the selection.
                            foreach (List <ReportRow> rowRange in this.selectedRanges)
                            {
                                if (rowRange.Contains(selectedRow))
                                {
                                    rowRange.Remove(selectedRow);
                                }
                            }
                        }

                        reportRows.Add(selectedRow);
                        this.selectedRanges.Add(reportRows);
                    }

                    // This will select all the rows in the selected ranges of rows and remove the selection from all the
                    // rest of the cells.
                    SelectRows();

                    // This instructs the event handlers how the mouse movement is to be interpreted.
                    this.mouseState = MouseState.Selecting;
                }
                else
                {
                    // The top mouse button can either select the row or begin a resizing operation.  This will perform a 'Hit
                    // Test' to see which operation should be performed.
                    if (e.LeftButton == MouseButtonState.Pressed)
                    {
                        // This is a 'Hit Test' for the bottom edge of the row header tile to see if the user is trying to change
                        // the size of the row.  If the mouse is close to the bottom edge, then the drag operation to change the
                        // size of the tile is begun.
                        if (selectedRow.Bottom - DynamicReport.splitBorder <= this.mouseDownLocation.Y &&
                            this.mouseDownLocation.Y < selectedRow.Bottom)
                        {
                            this.resizeStart    = selectedRow.Bottom;
                            this.mouseState     = MouseState.ResizingRow;
                            this.destinationRow = null;
                        }
                        else
                        {
                            // This is a 'Hit Test' for the top edge of the row header tile to see if the user is trying to change
                            // the size of the row.  Note that because the top edge really belongs to the previous row header when
                            // resizing, that the previous row is selected for the operation.
                            if (selectedRow.Top <= this.mouseDownLocation.Y &&
                                this.mouseDownLocation.Y < selectedRow.Top + DynamicReport.splitBorder)
                            {
                                this.resizeStart = selectedRow.Top;
                                this.mouseState  = MouseState.ResizingRow;
                                foreach (ReportRow reportRow in this.ReportGrid.Rows)
                                {
                                    if (reportRow.Bottom == selectedRow.Top)
                                    {
                                        selectedRow = reportRow;
                                    }
                                }
                                this.destinationRow = null;
                            }
                        }
                    }

                    // At this point, a resizing operation has been selected from the input gesture of the mouse.
                    if (this.mouseState == MouseState.ResizingRow)
                    {
                        // The parent window will watch for this event to tell it how to draw the row width indicator lines. The
                        // dimension and location of those lines are outside of this window and must be handled by the parent.
                        if (this.ResizeMouseMove != null)
                        {
                            this.ResizeMouseMove(this, new ResizeRowEventArgs(selectedRow, selectedRow.Height,
                                                                              false));
                        }

                        // This window provides quantitative feedback for the new width of the row.  The offsets were arrived at
                        // empirically from reverse engineering Excel.
                        this.rowHeightPopup.VerticalOffset = this.mouseDownLocation.Y - 2.0;
                        this.rowHeightPopup.Content        = selectedRow.Height;
                        this.rowHeightPopup.IsOpen         = true;
                    }
                    else
                    {
                        // This will select the button momentarily for drag-and-drop and sorting operations.
                        foreach (ReportCell reportCell in this.headerCells)
                        {
                            reportCell.IsSelected = true;
                        }
                    }
                }
            }
        }