Ejemplo n.º 1
0
        public int Compare(object x, object y)
        {
            if (x is SpreadsheetCell && y is SpreadsheetCell)
            {
                SpreadsheetCell cell1 = (SpreadsheetCell)x;
                SpreadsheetCell cell2 = (SpreadsheetCell)y;

                // If the two rows are the same, compare the column.
                int compare = cell1.RowIndex.CompareTo(cell2.RowIndex);
                return(compare == 0 ? cell1.ColumnIndex.CompareTo(cell2.ColumnIndex) : compare);
            }

            throw new Exception("The method or operation is not implemented for these types.");
        }
Ejemplo n.º 2
0
        public override void Reset()
        {
            // This variable is used to give each row a unique index as they are added.  It will count monotonically until the
            // table is reset.  Rows and columns are ordered to give each cell a cartesean [RowIndex, ColumnIndex] type of
            // address.
            this.rowIndex = 0;

            // This is the default action for selecting objects in the view.
            this.SelectionMode = DefaultSpreadsheet.SelectionMode;

            // The data in the table is ordered according to the primary key.  However, the displayed data is ordered by a
            // user-selectable order and can filter out data based on rules.  This filter and sort fields control the data that
            // is displayed in the viewer.
            this.spreadsheetRowView.RowFilter = string.Empty;
            this.spreadsheetRowView.Sort      = string.Empty;

            // Release any of the graphical objects in the current spreadsheet.
            foreach (SpreadsheetRow spreadsheetRow in this.Rows)
            {
                foreach (SpreadsheetColumn spreadsheetColumn in this.Columns)
                {
                    SpreadsheetCell spreadsheetCell = spreadsheetRow[spreadsheetColumn];
                    if (spreadsheetCell.Value is Bitmap)
                    {
                        ((Bitmap)spreadsheetCell.Value).Dispose();
                    }
                }
            }

            this.ViewColumns.Clear();

            this.styleTable.Clear();

            // A default style is always available.
            Style defaultStyle = new Style();

            defaultStyle.Id = DefaultSpreadsheet.StyleId;
            this.styleTable.Add(defaultStyle.Id, defaultStyle);

            // The old animation list is no longer valid after reading a new document.  Note that there is an implicit Mutex in the
            // animated list because a there is a background thread that also uses this list.
            this.animatedList.Clear();

            this.headerRectangle  = Rectangle.Empty;
            this.displayRectangle = Rectangle.Empty;

            base.Reset();
        }
Ejemplo n.º 3
0
 /// <summary>
 /// Removes the AnimatedCell from the animation task.
 /// </summary>
 /// <param name="spreadsheetCell">The cell to be removed.</param>
 public void Remove(SpreadsheetCell spreadsheetCell)
 {
     try
     {
         // Make sure the list is locked before removing the cell.  Note that the list is sorted, so the binary search picks
         // up the element quickly and removing the item using the index into the array instead of the reference is quicker
         // still.
         this.mutex.WaitOne();
         int index = this.arrayList.BinarySearch(spreadsheetCell, this.cellOrderComparer);
         if (index >= 0)
         {
             this.arrayList.RemoveAt(index);
         }
     }
     finally
     {
         this.mutex.ReleaseMutex();
     }
 }
Ejemplo n.º 4
0
        /// <summary>
        /// Add an address to the list of animated cells.
        /// </summary>
        /// <param name="spreadsheetCell">A Cell Address to be animated.</param>
        /// <returns>The cell that was added to the list.</returns>
        public SpreadsheetCell Add(SpreadsheetCell spreadsheetCell)
        {
            try
            {
                // Make sure no other threads modify the list while an address is being added.
                this.mutex.WaitOne();

                // The list must be ordered to prevent thrashing during the animation.
                int index = this.arrayList.BinarySearch(spreadsheetCell, this.cellOrderComparer);
                if (index < 0)
                {
                    this.arrayList.Insert(~index, spreadsheetCell);
                }

                // This cell will now be animated.
                return(spreadsheetCell);
            }
            finally
            {
                // Other threads can now use the list.
                this.mutex.ReleaseMutex();
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Provides highlighting of modified cells that fade over time.
        /// </summary>
        private void AnimationThread()
        {
            // This loop will use a timer to cycle through all the styles associated with an animated cell.  When an animation
            // attribute is declared for a Font style, an array of styles with colors blending from the start color to the actual
            // cell color is created.  As the timer ticks off, those styles are selected for the cell in sequence, giving the
            // effect of a burst to the 'StartColor' and a gradual fade to the original color of the style.
            while (this.IsAnimationRunning)
            {
                // This region will be filled in with the rectangle of any cells that have changed color.
                bool   hasUpdatedCells = false;
                Region region          = new Region();
                region.MakeEmpty();

                try
                {
                    // IMPORTANT CONCEPT:  Make sure that the data model of the viewer doesn't change while modifying the styles on
                    // the cells that are being animated.  The list of animated cells must also be locked because the XML Reader
                    // can add items to the animated list.  BE CAREFUL not to change the order of the locks.  If the locks are not
                    // always aquired in the same order, the potential for a deadlock exists.
                    this.readerWriterLock.AcquireWriterLock(Timeout.Infinite);
                    this.animatedList.Mutex.WaitOne();

                    // Cycle through all the cells that have been animated.  The index is used instead of the iterator so the
                    // animation can be removed from the list when it has cycled through all its colors.
                    for (int animatedCellIndex = 0; animatedCellIndex < this.animatedList.Count; animatedCellIndex++)
                    {
                        // The 'animatedList' is a ordered list of row and column addresses, along with members that track the
                        // series of animated styles.  The ordering of the list of animated cells is important because there can be
                        // many changes on a document and it's important to quickly access them when purging them.
                        SpreadsheetCell spreadsheetCell = this.animatedList[animatedCellIndex];

                        // The spreadsheet cell addressed by the animatedCell will have its style changed to the next color in the
                        // sequence.  When all the styles (that contain different colors) have been used, the color of the cell
                        // will return to the normal color of the cell.  When this happens, the cell is removed from the list of
                        // animated cells.  Keeping the list small is important for performance.
                        spreadsheetCell.Style = spreadsheetCell.AnimationArray[spreadsheetCell.AnimationIndex++];
                        if (spreadsheetCell.AnimationIndex == spreadsheetCell.AnimationArray.Length)
                        {
                            this.animatedList.Remove(spreadsheetCell);
                        }

                        // This region will update all the modified cells on the screen when the loop is finished.
                        if (spreadsheetCell.ColumnViewIndex != int.MinValue)
                        {
                            hasUpdatedCells = true;
                            region.Union(spreadsheetCell.DisplayRectangle);
                        }
                    }
                }
                finally
                {
                    // The viewer's data and the animated list can be use by other threads now.
                    this.readerWriterLock.ReleaseWriterLock();
                    this.animatedList.Mutex.ReleaseMutex();
                }

                // Broadcast a message to update the screen if there were any cells updated with new styles.
                if (hasUpdatedCells)
                {
                    OnDataInvalidated(region);
                }

                // This provides the time dimension for the animation.
                Thread.Sleep(Spreadsheet.animationPeriod);
            }
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Measures the document, row and cell dimensions.
        /// </summary>
        /// <returns>A region of the document that has changed.</returns>
        internal Region MeasureData()
        {
            // This area will collect all the rectangles that need to be updated in the viewer.
            Region region = new Region();

            region.MakeEmpty();

            // This is used to collect smaller row and cell rectangles into the largest possible area.  The idea is to minimzed the
            // number of rectangles that the GDI has to handle when the document is redrawn.  Of course, this could be done with an
            // infinite region, but the idea is also to minimize the amount of drawing that needs to be done.
            Rectangle aggregateRectangle = Rectangle.Empty;

            // This is used as a relatively static area where the dimensions of a row in the current view are calcualted.
            Rectangle rowRectangle = Rectangle.Empty;

            // These provide cursors for keeping track of the location as the document is measured.
            Point displayCursor = Point.Empty;
            Point printerCursor = Point.Empty;

            // Initialize the total area of the screen and printed document.  The width of the documents has been calculated by
            // this point (it is calculated in 'MeasureHeader', but the length has not yet been calculated.
            this.displayRectangle = new Rectangle(0, 0, this.headerRectangle.Width, 0);

            // The default view defines which rows are visible and in what order the appear.  It defines the 'visible' document.
            // This code will run through the visible document and assign coordinates to the rows and cells according to their
            // place in the view.
            int rowIndex = 0;

            foreach (DataRowView dataRowView in this.spreadsheetRowView)
            {
                // The underlying row will be updated with coordinates that represent its location in the view.
                SpreadsheetRow spreadsheetRow = (SpreadsheetRow)dataRowView.Row;

                // This index gives the row position within the sorted and filtered view.
                spreadsheetRow.RowViewIndex = rowIndex++;

                // This define the coordinates of this row in the view.
                rowRectangle.X      = displayCursor.X;
                rowRectangle.Y      = displayCursor.Y;
                rowRectangle.Width  = this.displayRectangle.Width;
                rowRectangle.Height = spreadsheetRow.rectangle.Height;

                // The code below will check the coordinates of the rows and cells with the goal of preserving the data that hasn't
                // changed.  However, if the row is invalid, there's no need to add the cells to the update region.  If every cell
                // of every row were entered into the region, it would choke the GDI.  If the row is out of place, this flag will
                // skip the cell checking logic.
                bool isRowValid = true;

                // If the position and height of the row don't agree with where it should be in the DataView, then the coordinates
                // will have to be updated and the row will be added to the list of things that need redrawing.
                if (spreadsheetRow.rectangle.Location != rowRectangle.Location ||
                    spreadsheetRow.rectangle.Height != rowRectangle.Height || !spreadsheetRow.IsVisible)
                {
                    // The entire row will be added to the update region, there's no need to add the cell rectangles as well.
                    // This is done as an optimization; if ever cell in a large document were modified at once, it would blow out
                    // the storage are for a region.
                    isRowValid = false;

                    // Update the coordinates of the row with the calculated rectangle from the view.
                    spreadsheetRow.rectangle = rowRectangle;

                    // This will combine the rectangles when the top and bottom borders coincide.  The aggregate rectangle collects
                    // individual rows into one large rectangle and places the largest contiguous rectangle possible into the
                    // region.  This simplifies the work the GDI has to do to process the invalid areas of the document.
                    if (aggregateRectangle.Left == rowRectangle.Left && aggregateRectangle.Width == rowRectangle.Width &&
                        aggregateRectangle.Bottom == rowRectangle.Top)
                    {
                        aggregateRectangle = Rectangle.Union(aggregateRectangle, rowRectangle);
                    }
                    else
                    {
                        if (!aggregateRectangle.IsEmpty)
                        {
                            region.Union(aggregateRectangle);
                        }
                        aggregateRectangle = rowRectangle;
                    }
                }

                // This will calculate the location of each of the cells in document coordinates.  If the coordinates are
                // different from the current coordinates that cell will be added to the update region, assuming the row hasn't
                // already been added.
                foreach (SpreadsheetColumn spreadsheetColumn in this.ViewColumns)
                {
                    SpreadsheetCell spreadsheetCell = spreadsheetRow[spreadsheetColumn];

                    // This is the rectangle that this cell should occupy.
                    Rectangle cellRectangle = new Rectangle(displayCursor, new Size(spreadsheetColumn.Rectangle.Width,
                                                                                    spreadsheetRow.rectangle.Height));

                    // This is the test to see if this cell needs to be redrawn.  If the row isn't valid, then we don't need
                    // to redraw the cell because it will be redrawn when the row is painted.  If the dimensions of the cell
                    // have changed, then it needs to be repainted.  Finally, if there was an original version of this cell and
                    // it isn't the same as the suggested cell, it needs to be repainted.
                    if (isRowValid && (spreadsheetCell.DisplayRectangle != cellRectangle || spreadsheetCell.IsModified))
                    {
                        // This will aggregate the cells into larger rectangles if their edges cooincide.  The large contiguous
                        // rectangles are added to the area that needs to be redrawn.
                        if (aggregateRectangle.Top == cellRectangle.Top && aggregateRectangle.Height == cellRectangle.Height &&
                            aggregateRectangle.Right == cellRectangle.Left)
                        {
                            aggregateRectangle = Rectangle.Union(aggregateRectangle, cellRectangle);
                        }
                        else
                        {
                            if (!aggregateRectangle.IsEmpty)
                            {
                                region.Union(aggregateRectangle);
                            }
                            aggregateRectangle = cellRectangle;
                        }
                    }

                    spreadsheetCell.DisplayRectangle = cellRectangle;

                    // This flag is set after the cell is measured.  The cell won't be updated again until this flag is set
                    // again.
                    spreadsheetCell.IsModified = false;

                    // Move the cursor up to the next column and test the next cell.
                    displayCursor.X += spreadsheetColumn.Rectangle.Width;
                }

                // This will reset the cursor for the next row.
                displayCursor.X  = 0;
                displayCursor.Y += spreadsheetRow.rectangle.Height;
            }

            // If all the rows and all the cells have been measured and checked for changes, then check one last time to see if
            // there is anything in the aggregate rectangel that needs to be redrawn.
            if (!aggregateRectangle.IsEmpty)
            {
                region.Union(aggregateRectangle);
            }

            // This is the height of the displayed document.
            this.displayRectangle.Height = displayCursor.Y;

            // This contains all the areas that need to be redrawn.
            return(region);
        }