/// <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); }
/// <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); }
/// <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(); }
/// <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; }
/// <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); } }
/// <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; } }
/// <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); } } }
/// <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); } } } }
/// <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); } }
/// <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); } } } }
/// <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> /// 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); }
/// <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)); }
/// <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(); } }
/// <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; } } } } }