Example #1
0
        /// <summary>
        /// Overrides the default arrangement of a Canvas.
        /// </summary>
        /// <param name="arrangeSize">The largest possible size for the canvas.</param>
        /// <returns>The actual size used by this canvas.</returns>
        protected override Size MeasureOverride(Size constraint)
        {
            // The base class does the hard work of building the visible part of the report on the canvas.
            Size size = base.MeasureOverride(constraint);

            // This creates a group of geometries around all the selected cells.
            GeometryGroup geometryGroup = new GeometryGroup();

            foreach (UIElement uiElement in this.Children)
            {
                if (uiElement is FrameworkElement)
                {
                    FrameworkElement frameworkElement = uiElement as FrameworkElement;
                    ReportCell       reportCell       = DynamicReport.GetCell(frameworkElement);
                    if (reportCell != null)
                    {
                        if (reportCell.IsSelected)
                        {
                            geometryGroup.Children.Add(new RectangleGeometry(reportCell.Rect));
                        }
                    }
                }
            }

            // An path around all the selected cells is created from the aggregation of all the selected cells.  This path was
            // roughly backward engineered arond the one used for Microsoft Excel.
            this.selectedOutline.Data = geometryGroup.GetOutlinedPathGeometry();

            // This is the size of the canvas as measured by the base class.
            return(size);
        }
Example #2
0
        /// <summary>
        /// Invoked when an unhandled PreviewGotKeyboardFocus attached event reaches an element in the route derived from this class.
        /// This will place a border around the cell.
        /// </summary>
        /// <param name="e">The arguments describing the focus change.</param>
        //protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
        //{

        //    // If the visual element that received the focus is associated with a virtual cell, then a distinctive border is drawn
        //    // around it to indicate that it is selected.  This is roughly engineered around the Excel user interface.
        //    ReportCell reportCell = DynamicReport.GetCell(e.NewFocus as DependencyObject);
        //    bool isShiftKeyPressed = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
        //    if (reportCell != null)
        //    {
        //        //if ((!isShiftKeyPressed) || (e.RightButton != MouseButtonState.Pressed))
        //        if (!isShiftKeyPressed)
        //        {
        //            this.selectedRanges.Clear();
        //        }
        //        this.selectedRanges.Add(Rect.Inflate(reportCell.Rect, -1.0, -1.0));
        //        SelectCells();
        //    }

        //    // Allow the base class to handle the rest of the keyboard focus event.
        //    base.OnPreviewGotKeyboardFocus(e);

        //}

        /// <summary>
        /// Invoked when an unhandled PreviewLostKeyboardFocus attached event reaches an element in the route derived from this class.
        /// </summary>
        /// <param name="e">The arguments describing the focus change.</param>
        protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            // If the visual element that lost the focus was associated with a virtual cell, then all the distinctive borders are
            // removed from the report.
            DependencyObject dependencyObject = Keyboard.FocusedElement as DependencyObject;
            Boolean          isChildOfCell    = false;

            while (dependencyObject != null)
            {
                if (DynamicReport.GetCell(dependencyObject) != null)
                {
                    isChildOfCell = true;
                    break;
                }
                dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
            }
            if (!isChildOfCell)
            {
                this.selectedRanges.Clear();
                SelectCells();
            }

            // Allow the base class to handle the rest of the keyboard focus event.
            base.OnPreviewLostKeyboardFocus(e);
        }
Example #3
0
        /// <summary>
        /// Handles a change to the XAML source code property for this report.
        /// </summary>
        /// <param name="dependencyObject">The target object of the property change.</param>
        /// <param name="dependencyPropertyChangedEventArgs">The property change event arguments.</param>
        private static void OnSourceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // Changing the source code involves compiling a new core for the report.  This operation will require access to some
            // of the protected properties of the class and so an instance method must be called to handle the change.
            DynamicReport report = dependencyObject as DynamicReport;

            report.OnSourceChanged();
        }
Example #4
0
        /// <summary>
        /// Handles a change to the Split dependency property.
        /// </summary>
        /// <param name="dependencyObject">The object that owns the property.</param>
        /// <param name="dependencyPropertyChangedEventArgs">A description of the changed property.</param>
        private static void OnSplitChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // The 'Scale' property is just a convinient way to generate a command that is used to set the scale factor.  The
            // commands are the only way to register events with the Undo manager.
            DynamicReport report = dependencyObject as DynamicReport;

            report.reportGrid.Split = (Size)dependencyPropertyChangedEventArgs.NewValue;
        }
Example #5
0
        /// <summary>
        /// Handles a change to the content of the report.
        /// </summary>
        /// <param name="dependencyObject">The object that owns the property.</param>
        /// <param name="dependencyPropertyChangedEventArgs">A description of the changed property.</param>
        private static void OnContentChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // Load the new content into the core report.
            DynamicReport report   = dependencyObject as DynamicReport;
            IContent      iContent = dependencyPropertyChangedEventArgs.NewValue as IContent;

            if (report.reportGrid != null)
            {
                report.reportGrid.reportRowCollection.Load(iContent);
            }
        }
Example #6
0
        /// <summary>
        /// Handles a change to the IsHeaderFrozen dependency property.
        /// </summary>
        /// <param name="dependencyObject">The object that owns the property.</param>
        /// <param name="dependencyPropertyChangedEventArgs">A description of the changed property.</param>
        private static void OnIsHeaderFrozenChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            // The headers can be frozen to prevent any changes to the position, sort order, visibility, etc.
            DynamicReport report         = dependencyObject as DynamicReport;
            Boolean       isHeaderFrozen = (Boolean)dependencyPropertyChangedEventArgs.NewValue;

            if (report.reportGrid != null)
            {
                report.reportGrid.IsColumnHeaderFrozen = isHeaderFrozen;
                report.reportGrid.IsRowHeaderFrozen    = isHeaderFrozen;
            }
        }
Example #7
0
 /// <summary>
 /// Brings all the cells in a given column to the top of the Z axis.
 /// </summary>
 /// <param name="frontColumn">The column to be moved in front of the other columns.</param>
 internal void SetZIndex(List <ReportRow> reportRows)
 {
     // Each visual element that matches the specified report row will be recycled and the data connection to the underlying
     // cell severed.
     foreach (UIElement uiElement in this.Children)
     {
         ReportCell reportCell = DynamicReport.GetCell(uiElement);
         if (reportCell != null)
         {
             Canvas.SetZIndex(uiElement, reportCell.ReportRow.ZIndex);
         }
     }
 }
Example #8
0
 /// <summary>
 /// Removes all elements from a canvas.
 /// </summary>
 internal void Clear()
 {
     // This will reset the canvas and reclaim all the visual elements associated with virtual cells.
     foreach (UIElement uiElement in this.Children)
     {
         if (uiElement is FrameworkElement)
         {
             FrameworkElement frameworkElement = uiElement as FrameworkElement;
             ReportCell       reportCell       = DynamicReport.GetCell(frameworkElement);
             if (reportCell != null)
             {
                 this.Remove(reportCell);
             }
         }
     }
 }
Example #9
0
        /// <summary>
        /// Removes a ReportCell from the canvas.
        /// </summary>
        /// <param name="frameworkElement"></param>
        /// <param name="reportCell"></param>
        private void Remove(ReportCell reportCell)
        {
            // The visual element associated with this cell can be found using the mapping table.  Once the element is found
            // it is reclaimed for use later on and the data connection is severed.
            FrameworkElement frameworkElement;

            if (this.elementTable.TryGetValue(reportCell, out frameworkElement))
            {
                // This recycles the element by putting it back in the cache associated with the cell's datatype.  This visual element
                // can be used again when another cell with the same data type needs to be instantiated.
                Type contentType = reportCell.Content.GetType();
                Stack <FrameworkElement> elementStack;
                if (!this.elementCache.TryGetValue(contentType, out elementStack))
                {
                    elementStack = new Stack <FrameworkElement>();
                    this.elementCache.Add(contentType, elementStack);
                }
                elementStack.Push(frameworkElement);

                // The association between the visual element and the abstract cell is removed when the element is removed from the
                // canvas.
                DynamicReport.SetCell(frameworkElement, ReportCell.Empty);
                this.elementTable.Remove(reportCell);

                // When a virtual cell is removed from the report -- as opposed to simply being made invisible -- the virtual focus
                // is reset.  Resetting the virtual focus is a problem because the FocusScope of the parent focus scope (as well as
                // the current focus scope) maintains a link to the last visual element that had the focus.  When that visual
                // element goes away, there's no good way to tell the parent focus scope to change.  If you sent a FocusScope's
                // FocusedElement to anything other than 'null', the handler tries to set the keyboard focus as well.  This has the
                // undesireable effect of stealing the keyboard focus away from some other window (there's no guarantee that the
                // keyboard focus is on this canvas when removing rows).  Resetting the current and parent focus scopes is the best
                // of some bad options.
                if (reportCell.IsFocused)
                {
                    this.ReportGrid.ClearFocusedCell();
                    FocusManager.SetFocusedElement(this.ReportGrid, null);
                    FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this.ReportGrid.Parent), null);
                    if (frameworkElement.IsKeyboardFocusWithin)
                    {
                        Keyboard.Focus(this.ReportGrid);
                    }
                }

                // The visual element can now be safely removed from the window.
                this.Children.Remove(frameworkElement);
            }
        }
Example #10
0
 /// <summary>
 /// Brings all the cells in a given column to the top of the Z axis.
 /// </summary>
 /// <param name="frontColumn">The column to be moved in front of the other columns.</param>
 internal void BringToFront(List <ReportColumn> reportColumns)
 {
     // Each visual element that matches the specified report row will be recycled and the data connection to the underlying
     // cell severed.
     foreach (UIElement uiElement in this.Children)
     {
         if (uiElement is FrameworkElement)
         {
             FrameworkElement frameworkElement = uiElement as FrameworkElement;
             ReportCell       reportCell       = DynamicReport.GetCell(frameworkElement);
             if (reportCell != null)
             {
                 Canvas.SetZIndex(frameworkElement, reportColumns.Contains(reportCell.ReportColumn) ? 1 : 0);
             }
         }
     }
 }
Example #11
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++;
             }
         }
     }
 }
Example #12
0
        /// <summary>
        /// Returns List of currently selectedCells
        /// </summary>
        /// <returns></returns>
        public List <FrameworkElement> GetSelectedCells()
        {
            List <FrameworkElement> selectedCells = new List <FrameworkElement>();

            foreach (UIElement uiElement in this.Children)
            {
                if (uiElement is FrameworkElement)
                {
                    FrameworkElement frameworkElement = uiElement as FrameworkElement;
                    ReportCell       reportCell       = DynamicReport.GetCell(frameworkElement);
                    if (reportCell != null)
                    {
                        if (reportCell.IsSelected)
                        {
                            selectedCells.Add(frameworkElement);
                        }
                    }
                }
            }

            return(selectedCells);
        }
Example #13
0
        /// <summary>
        /// Overrides the default arrangement of a Canvas.
        /// </summary>
        /// <param name="arrangeSize">The largest possible size for the canvas.</param>
        /// <returns>The actual size used by this canvas.</returns>
        protected override Size MeasureOverride(Size constraint)
        {
            // This calculates the visible area of the virtual canvas.  Note that the 'MeasureOverride' is called before the
            // 'OnRenderSizeChanged' so the rectangle used for clipping can't be used here because it hasn't been calclated
            // properly yet.
            Rect viewport = new Rect(this.rectangleGeometry.Rect.Location, constraint);

            // IMPORTANT CONCEPT: The FrameworkElements are recycled for performance.  Creating the user interface elements and
            // adding and removing them from the canvas are very time consuming tasks.  The XAML for the reports contains templates
            // used to create FrameworkElements for a given CLR type.  It turns out that any CLR object of a given type can use any
            // FrameworkElement created for that given type.  So a factory of sorts is created for building and reusing the
            // framework elements based on a CLR type.  A screen is drawn by first reclaiming all the elements that are no longer
            // visible.  These elements are not removed from the screen in the first pass.  The secod pass draws the visible area
            // of the report using reclaimed elements when it can, creating new FrameworkElements when it can't.  The important
            // performance kick here is that the elements are not actually removed from the screen during the reclaimation pass and
            // the drawing pass.  All elements are put into this list when during the reclaimation cycle.  If they are still in
            // this list after the drawing pass, then they are not visible and can be removed from the canvas. This is the
            // reclamation cycle.  All the user interface elements that have become invisible are pushed back into the element
            // cache where they can be used again when the viewport is updated.
            foreach (UIElement uiElement in this.Children)
            {
                if (uiElement is FrameworkElement)
                {
                    // This element will be examined to see if it needs to be recycled.
                    FrameworkElement frameworkElement = uiElement as FrameworkElement;

                    // The 'ReportCell' is the virtual information behind the user interface element and is attached to all the
                    // visual elements in the canvas.  It is used here to indicate whether the associated FrameworkElement is
                    // invisible.
                    ReportCell reportCell = DynamicReport.GetCell(frameworkElement);
                    try
                    {
                        if (reportCell != null)
                        {
                            // This will test the virtual cell to see if it is invisible.  Note that any cell that touches the right or
                            // bottom edge of the viewport is considered to be invisible.  There is no part of these cells that can
                            // actually be seen, whereas the left and top edges are visible.
                            Rect rect = reportCell.ActualRect;
                            if (((viewport.Top > rect.Top || rect.Top >= viewport.Bottom) &&
                                 (viewport.Top >= rect.Bottom || rect.Bottom > viewport.Bottom)) ||
                                ((viewport.Left > rect.Left || rect.Left >= viewport.Right) &&
                                 (viewport.Left >= rect.Right || rect.Right > viewport.Right)))
                            {
                                // When the framework element has become invisible, it is placed back in the cache based on the CLR
                                // type of the object it can display.  It can be recycled for use with similar types when the new
                                // viewport is constructed in the second pass.
                                Type contentType = reportCell.Content.GetType();
                                Stack <FrameworkElement> elementStack;
                                if (!this.elementCache.TryGetValue(contentType, out elementStack))
                                {
                                    elementStack = new Stack <FrameworkElement>();
                                    this.elementCache.Add(contentType, elementStack);
                                }
                                elementStack.Push(frameworkElement);

                                // This disconnects the visual element from the virtual ReportCell and the virtual ReportCell from the
                                // visual element.
                                DynamicReport.SetCell(frameworkElement, ReportCell.Empty);
                                this.elementTable.Remove(reportCell);

                                // The focus scope for this canvas must be cleared when the element that had the focus is recycled.
                                // This method 'virtualizes' the handling of the focus.  The virtual focus is saved in the 'IsFocused'
                                // property of the ReportCell while the cell is invisible.  When this virtual cell becomes visible
                                // again, the keyboard focus will be given back to to user interface element used to instantiate the
                                // virtual cell.
                                if (reportCell.IsFocused)
                                {
                                    FocusManager.SetFocusedElement(this.ReportGrid, null);
                                    if (frameworkElement.IsKeyboardFocusWithin)
                                    {
                                        Keyboard.Focus(this.ReportGrid);
                                    }
                                }

                                // All these bindings need to be released when a cell is no longer visible.  There seems to be a very
                                // practical limitation to how many binding updates can be done by the operating system.  Failure to
                                // release a binding when a user element is no longer visible will quickly result all the resources
                                // being used.
                                BindingOperations.ClearBinding(frameworkElement, Canvas.LeftProperty);
                                BindingOperations.ClearBinding(frameworkElement, Canvas.TopProperty);
                                BindingOperations.ClearBinding(frameworkElement, FrameworkElement.WidthProperty);
                                BindingOperations.ClearBinding(frameworkElement, FrameworkElement.HeightProperty);
                                BindingOperations.ClearBinding(frameworkElement, DynamicReport.IsEvenProperty);
                                BindingOperations.ClearBinding(frameworkElement, DynamicReport.IsSelectedProperty);
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        String message = reportCell.ReportColumn == null ? "ReportColumn is null" : reportCell.ReportRow == null ? "ReportRow is null" : "not sure why";
                        Log.Error("{0}: {1} ({2})\n{3}", exception.GetType(), exception.Message, message, exception.StackTrace);
                    }
                }
            }

            // This pass will construct the visible part of the canvas.  The main idea is to find the virtual cells that appear in
            // the viewport and associate the cell with a visual element.  If a visual element can be found in the cache, it is
            // used, otherwise one is created from a template.
            foreach (ReportRow reportRow in this.ReportGrid.Rows)
            {
                if (reportRow.IsEmpty)
                {
                    continue;
                }

                // Only rows that appear in the visible part of the canvas are considered.  Evaluating the rows this way quickly
                // removes the number of cells that need to be checked.
                if (((viewport.Top <= reportRow.Top && reportRow.Top < viewport.Bottom) ||
                     (viewport.Top < reportRow.Bottom && reportRow.Bottom <= viewport.Bottom)))
                {
                    // At this point we have a row that is part of the visible canvas.  This will see which of the columns are
                    // visible.
                    foreach (ReportColumn reportColumn in this.ReportGrid.Columns)
                    {
                        // Note that columns that fall on the right edge are excluded.  Even though they appear to intersect with
                        // the viewport, there is no part of these columns that's actally visible.
                        if ((viewport.Left <= reportColumn.Left && reportColumn.Left < viewport.Right) ||
                            (viewport.Left < reportColumn.Right && reportColumn.Right <= viewport.Right))
                        {
                            // The cell exists at a virtual location in the document coordinate system.  They are instantiated and
                            // added to the canvas when the become visible and are remove from the canvas when not visible.  This
                            // is how a very large document can be viewed with a relatively small number of visual elements and
                            // bindings.
                            ReportCell reportCell = reportRow[reportColumn];

                            // If this cell doesn't have a framework element associated with it, then one is either recycled
                            // from the cache or created from a template.
                            FrameworkElement frameworkElement;
                            if (!this.elementTable.TryGetValue(reportCell, out frameworkElement))
                            {
                                // Each Framework element will work with one and only one CLR type.  The content of the
                                // ReportCell determines what kind of FrameworkElement instance is used to display the data in
                                // that cell.
                                Type contentType = reportCell.Content.GetType();

                                // The CLR type of the content is used to find a stack of FrameworkElements that can be used to
                                // display that content.
                                Stack <FrameworkElement> elementStack;
                                if (!this.elementCache.TryGetValue(contentType, out elementStack))
                                {
                                    elementStack = new Stack <FrameworkElement>();
                                    this.elementCache.Add(contentType, elementStack);
                                }

                                // If there are no instances of a visual element that can be used to display the content of
                                // this ReportCell, then one is generated from the template.  Otherwise, a recycled element is
                                // used.  Note that all default focus styles are removed from visual elements on the canvas as
                                // there these elements have a custom style applied which functions like the Microsoft Excel
                                // focus and selection.
                                if (elementStack.Count == 0)
                                {
                                    DataTemplateKey dataTemplateKey = new DataTemplateKey(reportCell.Content.GetType());
                                    DataTemplate    dataTemplate    = this.ReportGrid.Resources[dataTemplateKey] as DataTemplate;
                                    if (dataTemplate != null)
                                    {
                                        frameworkElement = dataTemplate.LoadContent() as FrameworkElement;
                                        frameworkElement.FocusVisualStyle = null;
                                    }
                                }
                                else
                                {
                                    frameworkElement = elementStack.Pop();
                                }

                                // This associates the visual element with the ReportCell that contains the information about
                                // the location of that element in the virtual document coordinate space.  The 'elementTable'
                                // contains the reciprocal relation.
                                DynamicReport.SetCell(frameworkElement, reportCell);
                                this.elementTable.Add(reportCell, frameworkElement);

                                // The data context for the XAML elements is the content of the cell, not the ReportCell.
                                // This is done intensionally to hide the implementation details of how elements are positioned
                                // on the screen.  It is also done to simplify the programming model for the XAML code.
                                frameworkElement.DataContext = reportCell.Content;

                                // Bind the element's 'Left' property of the element to the column's position.
                                Binding leftBinding = new Binding("ActualLeft");
                                leftBinding.Source = reportCell.ReportColumn;
                                BindingOperations.SetBinding(frameworkElement, Canvas.LeftProperty, leftBinding);

                                // Bind the element's 'Top' property of the element to the row's position.
                                Binding topBinding = new Binding("ActualTop");
                                topBinding.Source = reportCell.ReportRow;
                                BindingOperations.SetBinding(frameworkElement, Canvas.TopProperty, topBinding);

                                // Bind the element's 'Width' property of the element to the column's width.
                                Binding widthBinding = new Binding("ActualWidth");
                                widthBinding.Source = reportCell.ReportColumn;
                                BindingOperations.SetBinding(frameworkElement, FrameworkElement.WidthProperty, widthBinding);

                                // Bind the element's 'Height' property of the element to the row's height.
                                Binding heightBinding = new Binding("ActualHeight");
                                heightBinding.Source = reportCell.ReportRow;
                                BindingOperations.SetBinding(frameworkElement, FrameworkElement.HeightProperty, heightBinding);

                                // Bind the element's 'IsSelected' property to the cell's property.  This property is used to
                                // highlight the selected cells.
                                Binding isSelectedBinding = new Binding("IsSelected");
                                isSelectedBinding.Source = reportCell;
                                BindingOperations.SetBinding(frameworkElement, DynamicReport.IsSelectedProperty, isSelectedBinding);

                                // Bind the 'Height' property of the element to the row's 'IsEven' property.  This property is
                                // generally used to shade every other line to make it easier to follow the information on a
                                // row.
                                Binding evenBinding = new Binding("IsEven");
                                evenBinding.Source = reportCell.ReportRow;
                                BindingOperations.SetBinding(frameworkElement, DynamicReport.IsEvenProperty, evenBinding);
                            }

                            // New and recycled elements need to be added to the canvas to become part of the visible report.
                            if (frameworkElement.Parent == null)
                            {
                                this.Children.Add(frameworkElement);
                            }

                            // The logical (and keyboard) focus is restored to the element that has the virtual focus when the
                            // virtual cell is made visible again.  Note that the logical focus is only moved when the canvas
                            // has the keyboard focus.  This prevents a scenario where the canvas grabs the input focus away
                            // from another window when the input focus is made visible again.
                            if (reportCell.IsFocused)
                            {
                                if (this.IsKeyboardFocusWithin)
                                {
                                    if (FocusManager.GetFocusedElement(this.ReportGrid) != frameworkElement)
                                    {
                                        FocusManager.SetFocusedElement(this.ReportGrid, frameworkElement);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // At this point, all the elements that can be recycled have been recycled.  The remaining elements on the canvas that
            // are not associated with a virtual report element need to be removed.
            for (int index = 0; index < this.Children.Count;)
            {
                UIElement uiElement = this.Children[index];
                if (DynamicReport.GetCell(uiElement) == ReportCell.Empty)
                {
                    this.Children.RemoveAt(index);
                }
                else
                {
                    index++;
                }
            }

            // The base class takes care of the hard work of measuring the visible user interface elements on the canvas.
            return(base.MeasureOverride(constraint));
        }
Example #14
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)
        {
            // This state variable will control how the 'Mouse Move' and 'Mouse Up' event handlers interpret the user action.  The
            // 'selectedColumn' 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);

            // The mouse selected with the mouse button is used as an anchor point unless the shift key is pressed.  The anchor
            // point allows for ranges of columns to be selected.  Everything between the anchor and the currently selected column
            // will be selected when the shift key is down.  This is modeled after the Excel extended range selection keys.
            Point mouseDownLocation = e.GetPosition(this);

            // Do not clear the select cells in the Report Grid if the right mouse button is pressed.
            if (e.RightButton != MouseButtonState.Pressed)
            {
                //Since the user is selecting a cell, it will invalidate any row selection that there may be.
                foreach (List <ReportRow> block in this.ReportGrid.SelectedRowBlocks)
                {
                    foreach (ReportRow row in block)
                    {
                        foreach (ReportCell cell in row.Cells)
                        {
                            cell.IsSelected = false;
                        }
                    }
                }
                this.ReportGrid.SelectedRowBlocks.Clear();
                this.ReportGrid.SelectedRowHeaderBlocks.Clear();
            }


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

            // 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 column selection, every column between the last column selected
                // and the current column is selected.
                if (isShiftKeyPressed)
                {
                    // This is an ordered rectangle that encompasses all the selected cells.
                    Rect selectedRectangle = GetSelectionRectangle(mouseDownLocation);
                    SetSelectedRectangle(selectedRectangle);
                }

                // When the control key is pressed a single column is added to the range of columns selected.
                if (isControlKeyPressed)
                {
                    // A point marks the range when the control key is pressed.  This is almost identical to pressing the mouse
                    // button except the previous ranges are not cleared.  This is part of the extended selection algorithm that is
                    // modeled after Excel.
                    Rect selectedRange = new Rect(mouseDownLocation, new Size(0, 0));

                    // This removes any previous instance of this column in the selection.
                    foreach (Rect existingRange in this.selectedRanges)
                    {
                        if (selectedRange == existingRange)
                        {
                            this.selectedRanges.Remove(existingRange);
                            break;
                        }
                    }

                    // The new range becomes the starting point for any further extended selection operations.
                    this.selectedRanges.Add(selectedRange);
                }

                // This instructs the event handlers how the mouse movement is to be interpreted.
                this.mouseState = MouseState.Selecting;
            }
            else
            {
                // Evaluate if we need to be able to determine the when we right click within the currently selected range then do not clear the selectedRanges.
                if (!((e.RightButton == MouseButtonState.Pressed) && (CurrentSelectedCell != null) && (CurrentSelectedCell.IsSelected)))                 //MIGHT try this --> if (reportCell.Rect.IntersectsWith(selectedRange))
                {
                    // Clear the selected ranges as we do not have a right mouse button pressed with the currently Selected ranges.

                    // A simple selection that doesn't involve the modifier keys will clear out any previously selected ranges.
                    this.selectedRanges.Clear();
                    // The column is added at the start of a new range of selected columns.
                    this.selectedRanges.Add(new Rect(mouseDownLocation, new Size(0, 0)));
                }
            }


            // Evaluate if we need to be able to determine the when we right click within the currently selected range then do not clear the selectedRanges.
            if (!((e.RightButton == MouseButtonState.Pressed) && (CurrentSelectedCell != null) && (CurrentSelectedCell.IsSelected)))             //MIGHT try this --> if (reportCell.Rect.IntersectsWith(selectedRange))
            {
                // This will select all the columns in the selected ranges of columns and remove the selection from all the rest of the cells.
                SelectCells();
            }
        }
Example #15
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;
                        }
                    }
                }
            }
        }