1  /* ===================================================================
  2  
  3  C2DPushGraph Control (2DPushGraph.h and 2DPushGraph.cpp)
  4  
  5  Author:  Stuart Konen
  6  Contact: skonen@gmail.com (Job information welcome)
  7  
  8  Description: A push graph control similiar to the graph control located
  9  in Microsoft's Task Manager.
 10  
 11  ====================================================================*/
 12  
 13  
 14  #include "stdafx.h"
 15  #include "2DPushGraph.h"
 16  
 17  
 18  #ifdef _DEBUG
 19  #define new DEBUG_NEW
 20  #undef THIS_FILE
 21  static char THIS_FILE[] = __FILE__;
 22  #endif
 23  
 24  #define PUSHGRAPH_MAX(num, max, type) \
 25  ((num > max) ? (type)(max) : (type)(num))
 26  
 27  #define PUSHGRAPH_MIN(num, min, type) \
 28  ((num < min) ? (type)(min) : (type)(num))
 29  
 30  
 31  /* Self-Register */
 32  BOOL C2DPushGraph::m_bRegistered = C2DPushGraph::RegisterClass(); 
 33  
 34  
 35  /////////////////////////////////////////////////////////////////////////////
 36  // C2DPushGraph
 37  
 38  C2DPushGraph::C2DPushGraph()
 39  {
 40  	m_nMaxCoords       = -1;
 41  	m_nMoveOffset      = 0;
 42  	m_nPeekOffset      = 0;
 43  	m_bStylesModified  = false;
 44  	m_usLineInterval   = 2;
 45  
 46  	SetPeekRange(0, 100);
 47  	
 48  	SetLabelForMax("100%");
 49  	SetLabelForMin("0%");
 50  
 51  	SetGridSize(15);	
 52  	
 53  
 54  	SetBGColor  ( RGB(0, 0, 0)     );
 55  	SetGridColor( RGB(0, 150, 0)   );
 56  	SetTextColor( RGB(255, 255, 0) );	
 57  }
 58  
 59  
 60  // ===================================================================
 61  
 62  C2DPushGraph::~C2DPushGraph()
 63  {
 64  	while (m_aLines.GetSize())
 65  	{
 66  		delete m_aLines[0];
 67  		m_aLines.RemoveAt(0);
 68  	}
 69  }
 70  
 71  
 72  // ===================================================================
 73  
 74  BOOL C2DPushGraph::RegisterClass()
 75  {
 76  	/* Static function to automatically register this class */
 77  
 78  	WNDCLASS wc;
 79  	
 80  	ZeroMemory(&wc, sizeof(WNDCLASS));
 81                                   
 82      wc.lpfnWndProc   = ::DefWindowProc;
 83      wc.hInstance     = (HINSTANCE)::GetModuleHandle(NULL);        
 84      wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);                
 85      wc.lpszClassName = _T("C2DPushGraph");	
 86  
 87      return AfxRegisterClass(&wc);
 88  }
 89  
 90  
 91  BEGIN_MESSAGE_MAP(C2DPushGraph, CWnd)
 92  	//{{AFX_MSG_MAP(C2DPushGraph)
 93  	ON_WM_ERASEBKGND()
 94  	ON_WM_PAINT()
 95  	ON_WM_SIZING()
 96  	ON_WM_SIZE()
 97  	//}}AFX_MSG_MAP
 98  END_MESSAGE_MAP()
 99  
100  
101  // ===================================================================
102  
103  bool C2DPushGraph::CreateFromStatic( UINT nStaticID, CWnd* pParent )
104  {
105  	CStatic  wndStatic;		
106  
107  	if (pParent == NULL || !wndStatic.SubclassDlgItem(nStaticID, pParent))
108  	{
109  		return false;
110  	}
111  	
112  
113  	/* Get the static windows rectangle and transform it into
114  	parent client coordinates, instead of screen coordinates */
115  
116  	CRect    rectStatic;
117  
118  	wndStatic.GetWindowRect(&rectStatic);
119  	pParent->ScreenToClient(&rectStatic);
120  
121  		
122  	if ( !CreateEx( wndStatic.GetExStyle(), 
123  		            NULL, NULL, WS_CHILD | WS_VISIBLE | wndStatic.GetStyle(),
124  		            rectStatic, pParent, nStaticID, NULL))
125  	{		
126  		wndStatic.DestroyWindow();
127  		return false;
128  	}
129  	
130  	
131  	wndStatic.DestroyWindow();
132  	return true;
133  }
134  
135  
136  // ==========================================================================
137  // Functions for Setting and Retrieving attributes
138  // ==========================================================================
139  
140  
141  COLORREF C2DPushGraph::GetGridColor() const
142  {
143  	return m_crGridColor;
144  }
145  
146  
147  // ===================================================================
148  
149  void C2DPushGraph::SetGridColor( COLORREF crColor )
150  {
151  	m_crGridColor = crColor;
152  }
153  
154  
155  // ===================================================================
156  
157  COLORREF C2DPushGraph::GetTextColor() const
158  {
159  	return m_crTextColor;
160  }
161  
162  
163  // ===================================================================
164  
165  void  C2DPushGraph::SetTextColor( COLORREF crColor )
166  {
167  	m_crTextColor = crColor;
168  }
169  
170  
171  // ===================================================================
172  
173  COLORREF C2DPushGraph::GetBGColor() const
174  {
175  	return m_crBGColor;
176  }
177  
178  
179  // ===================================================================
180  
181  void C2DPushGraph::SetBGColor( COLORREF crColor )
182  {
183  	m_crBGColor = crColor;
184  }
185  
186  
187  // ===================================================================
188  
189  int C2DPushGraph::GetGridSize() const
190  {
191  	return m_nGridSize;
192  }
193  
194  
195  // ===================================================================
196  
197  void C2DPushGraph::SetGridSize( unsigned short usWidthAndHeight )
198  {
199  	m_nGridSize = PUSHGRAPH_MIN(usWidthAndHeight, 3, int);
200  }
201  
202  
203  // ===================================================================
204  
205  int C2DPushGraph::GetMaxPeek() const
206  {
207  	return m_nMaxPeek;
208  }
209  
210  
211  // ===================================================================
212  
213  void C2DPushGraph::SetMaxPeek( int nMax )
214  {
215  	m_nMaxPeek = nMax;
216  }
217  
218  
219  // ===================================================================
220  
221  LPCTSTR C2DPushGraph::GetLabelForMax() const
222  {
223  	 return (LPCTSTR)m_strMaxLabel;
224  }
225  
226  
227  // ===================================================================
228  
229  void C2DPushGraph::SetLabelForMax( LPCTSTR lpszLabel )
230  {
231  	 m_strMaxLabel = lpszLabel;
232  }
233  
234  
235  // ===================================================================
236  
237  int C2DPushGraph::GetMinPeek() const
238  {
239  	return m_nMinPeek;
240  }
241  
242  
243  // ===================================================================
244  
245  void C2DPushGraph::SetMinPeek( int nMin )
246  {	
247  	if (nMin < 0)
248  	{
249  		m_nPeekOffset = 0 - nMin;		
250  	}
251  	else
252  	{
253  		m_nPeekOffset = 0;
254  	}
255  
256  	m_nMinPeek = nMin;
257  }
258  
259  
260  // ===================================================================
261  
262  LPCTSTR C2DPushGraph::GetLabelForMin() const
263  {
264  	 return (LPCTSTR)m_strMinLabel;
265  }
266  
267  
268  // ===================================================================
269  
270  void C2DPushGraph::SetLabelForMin( LPCTSTR lpszLabel )
271  {
272  	 m_strMinLabel = lpszLabel;
273  }
274  
275  
276  // ===================================================================
277  
278  void C2DPushGraph::SetPeekRange( int nMin, int nMax )
279  {
280  	ASSERT(nMin < nMax);
281  
282  	SetMinPeek(nMin);
283  	SetMaxPeek(nMax);
284  }
285  
286  
287  // ===================================================================
288  
289  unsigned short C2DPushGraph::GetInterval() const
290  {
291  	return m_usLineInterval;
292  }
293  
294  
295  // ===================================================================
296  
297  void C2DPushGraph::SetInterval( unsigned short usInterval )
298  {
299  	m_usLineInterval = usInterval;
300  	m_nMaxCoords = -1; // Forces reset
301  }
302  
303  
304  // ===================================================================
305  
306  bool C2DPushGraph::AddLine( UINT uiLineID, COLORREF crColor )
307  {
308  	PushGraphLine *pNewLine = new PushGraphLine(uiLineID);
309  
310  	if (!pNewLine)
311  	{
312  		ASSERT(pNewLine && "Out of memory");
313  		return false;
314  	}
315  
316  	pNewLine->crLine = crColor;
317  	m_aLines.Add(pNewLine);
318  
319  	return true;
320  }
321  
322  
323  // ===================================================================
324  
325  void C2DPushGraph::RemoveLine( UINT uiLineID )
326  {
327  	for (int n = m_aLines.GetSize()-1; n >= 0; --n)
328  	{
329  		if (m_aLines[n]->uiID == uiLineID)
330  		{
331  			delete m_aLines[n];
332  			m_aLines.RemoveAt(n);
333  
334  			--n; // Because we removed a line
335  		}
336  	}	
337  }
338  
339  
340  // ===================================================================
341  
342  bool C2DPushGraph::SetLineColor( COLORREF crColor, UINT uiLineID )
343  {
344  	PushGraphLine *pLine = internal_LineFromID(uiLineID);
345  	
346  	if (pLine == NULL)
347  	{
348  		return false; 
349  	}
350  	
351  	pLine->crLine = crColor;
352  	return true;
353  }
354  
355  
356  // ===================================================================
357  
358  COLORREF C2DPushGraph::GetLineColor( UINT uiLineID )
359  {
360  	PushGraphLine *pLine = internal_LineFromID(uiLineID);
361  	return (pLine) ? pLine->crLine : RGB(0, 0, 0);
362  }
363  
364  
365  // ===================================================================
366  
367  bool C2DPushGraph::Push( int nMagnitude, UINT uiLineID )
368  {
369  	PushGraphLine *pLine = internal_LineFromID(uiLineID);
370  	
371  	if (pLine == NULL)
372  	{
373  		return false; 
374  	}
375  
376  	
377  	/* Now add the magnitude (push point) to the array of push points, but
378  	first restrict it to the peek bounds */
379  
380  	if (nMagnitude > m_nMaxPeek) {
381  		nMagnitude = m_nMaxPeek;
382  	}
383  	else if (nMagnitude < m_nMinPeek) {
384  		nMagnitude = m_nMinPeek;
385  	}
386  	
387  	nMagnitude -= m_nMinPeek;
388  	nMagnitude += m_nPeekOffset;
389  	
390  	pLine->aMagnitudes.Add(nMagnitude);
391  	return true;
392  }
393  
394  
395  // ===================================================================
396  
397  void C2DPushGraph::ShowAsBar( UINT uiLineID, bool bAsBar )
398  {
399  	PushGraphLine *pLine = internal_LineFromID(uiLineID);
400  	
401  	if (pLine)
402  	{
403  		pLine->bShowAsBar = bAsBar;
404  	}
405  }
406  
407  
408  // ===================================================================
409  
410  void C2DPushGraph::Update()
411  {
412  	int nGreatest = 0;  // Largest push point count
413  	
414  	
415  	for (int n=0; n < m_aLines.GetSize(); ++n)
416  	{
417  		if (nGreatest < m_aLines[n]->aMagnitudes.GetSize())
418  		{
419  			nGreatest = m_aLines[n]->aMagnitudes.GetSize();
420  		}
421  	}
422  
423  	if (nGreatest >= m_nMaxCoords)
424  	{
425  		m_nMoveOffset = 
426  			(m_nMoveOffset - (((nGreatest-m_nMaxCoords)+1)*m_usLineInterval))
427  			% m_nGridSize;	
428  	}
429  
430  
431  	RedrawWindow();
432  }
433  
434  
435  // ===================================================================
436  
437  void C2DPushGraph::OnPaint() 
438  {	
439  	CPaintDC paintDC(this);	
440  	
441  
442  	/* First we create the back buffer */
443  	CDC &dc = internal_InitBackBuffer(paintDC);
444  	
445  
446  	/* Ensure we don't draw out of our client rectangle */
447  	
448  	CRect &rectClient = getClientRect();
449  	dc.IntersectClipRect(&rectClient);	
450  	
451  
452  	/* Fill the background */
453  	dc.FillSolidRect(&rectClient, m_crBGColor);
454  
455  
456  	dc.SetBkMode(TRANSPARENT);
457  	
458  
459  
460  	if (m_bShowMinMax)
461  	{
462  		/* Show maximum and minimum labels */
463  
464  		internal_DrawMinMax(dc, rectClient);
465  	}
466  
467  	if (m_bShowGrid)
468  	{
469  		/* Show the grid overlay */
470  
471  		internal_DrawGrid(dc, rectClient);
472  	}
473  
474  
475  	internal_DrawLines(dc, rectClient);
476  	internal_FreeBackBuffer(paintDC);	
477  
478  
479  	if (m_bStylesModified)
480  	{
481  		/* Reset styles if resized */
482  
483  		GetParent()->ModifyStyle(WS_CLIPCHILDREN, 0);
484  		ModifyStyle(WS_CLIPSIBLINGS, 0);
485  
486  		m_bStylesModified = false;
487  	}
488  }
489  
490  
491  // ===================================================================
492  
493  CDC& C2DPushGraph::internal_InitBackBuffer(CPaintDC &dc)
494  {
495  	/* Create the offscreen DC and associated bitmap */
496  
497  	m_dcBack.CreateCompatibleDC(&dc);
498  	m_bmBack.CreateCompatibleBitmap(&dc, getClientRect().Width(),
499  	         getClientRect().Height());
500  
501  	m_pOldBitmap = m_dcBack.SelectObject(&m_bmBack);
502  	return m_dcBack;
503  }
504  
505  
506  // ===================================================================
507  
508  void C2DPushGraph::internal_FreeBackBuffer(CPaintDC &dc)
509  {
510  	/* Copy the offscreen buffer to the onscreen CPaintDC.
511  	Then free the back buffer objects. */
512  
513  	dc.BitBlt(getClientRect().left, getClientRect().top, 
514                getClientRect().Width(),  getClientRect().Height(),
515                &m_dcBack, getClientRect().left, getClientRect().top,
516  			  SRCCOPY);
517  
518  	dc.SelectObject(m_pOldBitmap);
519  
520  	m_bmBack.DeleteObject();
521  	m_dcBack.DeleteDC();
522  }
523  
524  
525  // ===================================================================
526  
527  PushGraphLine* C2DPushGraph::internal_LineFromID( UINT uiLineID )
528  {
529  	/* Find the corresponding line to the passed ID */
530  
531  	for (int n = m_aLines.GetSize()-1; n >= 0; --n)
532  	{
533  		if (m_aLines[n]->uiID == uiLineID)
534  		{
535  			return m_aLines[n];			
536  		}
537  	}
538  
539  	return NULL;
540  }
541  
542  
543  // ===================================================================
544  
545  void C2DPushGraph::internal_DrawMinMax( CDC &dc, CRect& rect)
546  {
547  
548  	CSize MaxSize = dc.GetTextExtent(m_strMaxLabel);
549  	CSize MinSize = dc.GetTextExtent(m_strMinLabel);
550  
551  	int nTextWidth = 
552  		((MaxSize.cx > MinSize.cx) ? MaxSize.cx : MinSize.cx) + 6;		
553  
554  
555  	/* Draw the labels (max: Top) (min: Bottom) */
556  	
557  	dc.SetTextColor(m_crTextColor);
558  
559  	dc.TextOut(nTextWidth/2-(MaxSize.cx/2), 2, m_strMaxLabel);
560  	dc.TextOut(nTextWidth/2-(MinSize.cx/2), 
561  		rect.Height()-MinSize.cy-2, m_strMinLabel);
562  
563  
564  	/* Draw the bordering line */
565  
566  	CPen penBorder(PS_SOLID, 1, m_crGridColor);
567  	CPen *pOldPen = dc.SelectObject(&penBorder);
568  
569  	dc.MoveTo(nTextWidth + 6, 0);
570  	dc.LineTo(nTextWidth + 6, rect.Height());
571  
572  
573  	dc.SelectObject(pOldPen);
574  	penBorder.DeleteObject();
575  
576  
577  	/* Offset the graph rectangle so it doesn't overlap the labels */
578  
579  	rect.left = nTextWidth + 6;
580  }
581  
582  
583  // ===================================================================
584  
585  void C2DPushGraph::internal_DrawLines( CDC &dc, CRect& rect)
586  {
587  	CPen penLine;
588  	CPen *pOldPen = NULL;
589  
590  	int nGreatest = 0;
591  	
592  
593  	if (m_nMaxCoords == -1)
594  	{
595  		/* Maximum push points not yet calculated */
596  
597  		m_nMaxCoords = (rect.Width() / m_usLineInterval) + 2
598  		               + (rect.Width()%m_usLineInterval ? 1 : 0);
599  
600  		if (m_nMaxCoords <= 0)
601  		{
602  			m_nMaxCoords = 1;
603  		}
604  	}
605  
606  
607  	for (int n=0; n < m_aLines.GetSize(); ++n)
608  	{		
609  		if (nGreatest < m_aLines[n]->aMagnitudes.GetSize())
610  		{
611  			nGreatest = m_aLines[n]->aMagnitudes.GetSize();
612  		}
613  	}
614  
615  
616  	if (nGreatest == 0)
617  	{		
618  		return; // No lines to draw
619  	}
620  
621  
622  	for (n = 0; n < m_aLines.GetSize(); ++n)
623  	{
624  		/* If the line has less push points than the line with the greatest
625  		number of push points, new push points are appended with
626  		the same magnitude as the previous push point. If no push points
627  		exist for the line, one is added with the least magnitude possible. */
628  		
629  		PushGraphLine *pLine = m_aLines[n];
630  			
631  		if (!pLine->aMagnitudes.GetSize())
632  		{
633  			pLine->aMagnitudes.Add(m_nMinPeek);
634  		}
635  
636  		while (pLine->aMagnitudes.GetSize() < nGreatest)
637  		{			
638  			pLine->aMagnitudes.Add(
639  				pLine->aMagnitudes[pLine->aMagnitudes.GetSize()-1]);
640  		}
641  
642  
643  		while (m_aLines[n]->aMagnitudes.GetSize() >= m_nMaxCoords)
644  		{
645  			m_aLines[n]->aMagnitudes.RemoveAt(0);
646  		}
647  
648  		if (!m_aLines[n]->aMagnitudes.GetSize())
649  		{
650  			/* No push points to draw */
651  			return;			
652  		}
653  
654  
655  		/* Now prepare to draw the line or bar */
656  
657  		penLine.CreatePen(PS_SOLID, 1, m_aLines[n]->crLine);
658  		pOldPen = dc.SelectObject(&penLine);
659  
660  
661  		if (pLine->bShowAsBar)
662  		{
663  			dc.MoveTo(rect.left, rect.Height());
664  		}
665  		else
666  		{
667  			dc.MoveTo(rect.left, nGreatest == 1 ? rect.Height() : 
668  		    rect.Height()-(pLine->aMagnitudes[0] * 
669  		    rect.Height()/(m_nMaxPeek-m_nMinPeek)));	
670  		}
671  		
672  
673  		for (int n2 = 0; n2 < pLine->aMagnitudes.GetSize(); ++n2)
674  		{
675  			if (pLine->bShowAsBar)
676  			{
677  
678  				/* The line is set to be shown as a bar graph, so
679  				first we get the bars rectangle, then draw the bar */
680  
681  				CRect rectBar;
682  
683  				rectBar.left   = rect.left + (n2*(m_usLineInterval)) + 1;
684  				rectBar.right  = rectBar.left + GetInterval() - 1;
685  				rectBar.bottom = rect.Height();
686  				rectBar.top    = rect.Height() -
687  				                 (pLine->aMagnitudes[n2] * rect.Height() /
688  				                 (m_nMaxPeek-m_nMinPeek));				
689  
690  				internal_DrawBar(dc, rectBar, *pLine);
691  			}
692  
693  			else
694  			{
695  				/* Draw a line */
696  
697  				dc.LineTo(rect.left + (n2*m_usLineInterval),
698  				rect.Height() -
699  				(pLine->aMagnitudes[n2] * rect.Height() /
700  				(m_nMaxPeek-m_nMinPeek)) );
701  
702  				/*
703  				dc.FillSolidRect(rect.left + (n2*m_usLineInterval) - 2,
704  					rect.Height() -
705  				(pLine->aMagnitudes[n2] * rect.Height() /
706  				(m_nMaxPeek-m_nMinPeek)) - 2, 4, 4, m_aLines[n]->crLine); */
707  
708  			}
709  
710  		}
711  
712  		dc.SelectObject(pOldPen);	
713  		penLine.DeleteObject(); // Free Pen 
714  	}
715  
716  }
717  
718  
719  // ===================================================================
720  
721  void C2DPushGraph::internal_DrawBar( CDC &dc, CRect& rect, 
722                                       PushGraphLine& rLine )
723  {
724  	COLORREF &crFill = rLine.crLine;
725  	GraphColor gcTopLeft, gcBottomRight;
726  		
727  	
728  	/* gcTopLeft is the left and top frame color, gcBottomRight
729  	is the right and bottom frame color */	
730  
731  
732  	if (GetRValue(crFill))
733  	{
734  		gcTopLeft.bRed     = PUSHGRAPH_MAX(GetRValue(crFill)+40, 255, BYTE);
735  		gcBottomRight.bRed = PUSHGRAPH_MIN(GetRValue(crFill) - 40, 0, BYTE);
736  	}
737  
738  	if (GetBValue(crFill))
739  	{
740  		gcTopLeft.bBlue      = PUSHGRAPH_MAX(GetBValue(crFill)+40, 255, BYTE);
741  		gcBottomRight.bBlue  = PUSHGRAPH_MIN(GetBValue(crFill) - 40, 0, BYTE);
742  	}
743  
744  	if (GetGValue(crFill))
745  	{
746  		gcTopLeft.bGreen     = PUSHGRAPH_MAX(GetGValue(crFill)+40, 255, BYTE);
747  		gcBottomRight.bGreen = PUSHGRAPH_MIN(GetGValue(crFill) - 40, 0, BYTE);
748  	}
749  			 
750  
751  	dc.FillSolidRect(&rect, rLine.crLine);
752  	dc.Draw3dRect(&rect, gcTopLeft, gcBottomRight);
753  }
754  
755  
756  // ===================================================================
757  
758  void C2DPushGraph::internal_DrawGrid( CDC &dc, CRect& rect)
759  {
760  	/* Draw the grid overlay. 
761  	
762  	We use rect.left as our x offset instead of zero because
763  	if m_bShowMinMax is true, then rect.left is set to the 
764  	first pixel after the labels. */
765  
766  	CPen GridPen( PS_SOLID, 1, m_crGridColor );
767  	CPen *pOldPen = dc.SelectObject(&GridPen);
768  
769  
770  	for (int n = rect.Height()-1; n >= 0; n -= m_nGridSize)
771  	{
772  		dc.MoveTo(rect.left, n);
773  		dc.LineTo(rect.right, n);
774  	}
775  		
776  	for (n = rect.left + m_nMoveOffset; n < rect.right; n += m_nGridSize)
777  	{
778  		if (n < rect.left)
779  		{
780  			continue;
781  		}
782  
783  		dc.MoveTo(n, 0);
784  		dc.LineTo(n, rect.Height());
785  	}
786  
787  	dc.SelectObject(pOldPen);
788  	GridPen.DeleteObject();
789  }
790  
791  
792  // ===================================================================
793  
794  CRect& C2DPushGraph::getClientRect()
795  {
796  	static CRect rectClient;
797  	return (GetClientRect(&rectClient), rectClient);
798  }
799  
800  
801  // ===================================================================
802  
803  BOOL C2DPushGraph::OnEraseBkgnd(CDC*) 
804  {
805  	return FALSE;
806  }
807  
808  
809  // ===================================================================
810  
811  void C2DPushGraph::OnSizing(UINT fwSide, LPRECT pRect) 
812  {
813  
814  	if (GetParent()->GetStyle() & ~WS_CLIPCHILDREN)
815  	{
816  		/* Eliminate flickering */
817  
818  		m_bStylesModified = true;
819  		GetParent()->ModifyStyle(0, WS_CLIPCHILDREN);		
820  	}
821  
822  	ModifyStyle(0, WS_CLIPSIBLINGS);
823  
824  
825  	CWnd::OnSizing(fwSide, pRect);		
826  	(m_nMaxCoords = -1, Invalidate());	
827  }
828  
829  
830  // ===================================================================
831  
832  void C2DPushGraph::OnSize(UINT nType, int cx, int cy) 
833  {
834  	CWnd::OnSize(nType, cx, cy);	
835  	(m_nMaxCoords = -1, Invalidate());
836  }