C++/VB - PolyLine and autoscrolling canvas

Asked By Luigino
26-Jan-10 11:33 AM
Hello everyone!!!!

I am creating a task manager's-like-scroll-graphic.
So for this I was thinking to create a vector of vectors of values,
where the main vector is to draw more sources, and inside each
containing values would be the same as rect.right converted in points
using ((float)rc.right / (float)dwDPI) * 72) where dwDPI was read from
the registry to get the current DPI screen.
BUT, since I thought to have a wide size of array due for resizing/
enlarging window, since I was thinking to use PolyPolyLine, how I can
make it drawing so the last element of the array would be drawn at
rc.right of the drawing region?... Or maybe there is a better way to
draw it avoiding also flickering?....

Thanks to everyone in advance
Ciao,
Luigi
ParentWndRect.right
(1)
CMyDLL
(1)
MSourceMaxSizeArray
(1)
SetWindowExtEx
(1)
CClientDC
(1)
CPaintDC
(1)
CMemDC
(1)
RegisterWindowClass
(1)
  Stephen Myers replied to Luigino
26-Jan-10 12:29 PM
The trick to avoiding flickering is to double buffer.  Draw into an
offscreen DC and then bitblt to the actual device.  This means that
stationary features do not change and the polyline drawing seems to move
over the top of it.

I would capture the information without any particular regard to the
screen size and let PolyLine() deal with it.  You can manipulate the
viewport to show the part of the vector that you are interested in.

I would use a std::deque which would allow you to remove data that you
can no longer display.  You will have to do some research to determine
the available size for you X axis.  I believe the limit is on the order
of 28 bits, but do not quote me.

Steve
  Luigino replied to Stephen Myers
27-Jan-10 06:34 AM
Hello Stephen,

first, thank you for your reply...
What I am making is a CWnd class component that will be integrated in
another application which will pass some data. I am thinking about a
DLL component because in this component I will have to draw lines with
sliding screen in a tab of the application, and some vertical
rectangle bars in another tab of same application.
I was in doubt about the calculation of drawing position basing on DPI
because if I move the application in another PC which could have
different screen resolution or different monitor...

I was thinking first to put values in a vector, sized as the whole
screen width so if I want to maximize the application those values
that are "hided" in the left would appear, and at the same time to
calculate the Y axis in a vector of POINT (a precisation, both
vectors, values and POINT, could be also vector of vector if I have
more than one source) starting from the end of the vector (I guess) so
PolyPolyLine will draw the vector from the right side of the client...

Would that be a good idea?... If yes, since values are simply double
values, to represent them on the screen, is there isomething like a
formula which would put convert a certain value in POINT Y considering
an interval from 0 to clientrect.bottom?
And considering this way I am doing, do you think is good using vector
or would be better using std::deque as you suggested for performance
(I see in the help it has resize() so I could set n-elements as
ParentWndRect.right )?...

Thanks again in advance for the little help
Ciao
Luigi
  Stephen Myers replied to Luigino
27-Jan-10 10:38 AM
Luigi,

I would try to separate the data collection and storage from the display
of the data.  Sort of like the MFC document/view.  Collect each data
point and store it in a vector (or deque) as a CPoint().  The x value
being time and y your measurement.  I'd then let the CDC drawing methods
do the manipulations required to get from your data point to the
window's canvas.

Look at CDC::SetMapMode(MM_ANISOTROPIC).  Your X and Y coordinates will
need to be integer, and can be scaled as the data is collected.

This means that resizing the window requires you to reinitialize the
memory DC and then you plot the captured data.

Let CDC do all the hard stuff.

The selection of deque or vector depends on how many data points you
need to keep around and under what circumstances they become obsolete.

Trying to plot exactly as many points as are needed is probably more
trouble than it is worth.  It might make sense to limit the size of the
vector/deque to 2000 points (or some other fairly arbitrary value such
as screen width), but that is as much as I would do.

Steve
  Luigino replied to Stephen Myers
29-Jan-10 05:32 AM
Hello Steve,


I am trying to let CDC doing the hard stuff as you suggested.... just
only a thing I would ask: Is there a way to assign the POINT.x the
current time as LONG since time functions are most string functions
and time_t is the time elapsed since 1970?...

Thanks
Ciao
Luigi
  Stephen Myers replied to Luigino
29-Jan-10 10:34 AM
I normally use GetTickCount() for this sort of thing.  Save off an
initial time and then record future times as offsets.  This gives
reasonable resolution and is easy to access.

// Initialization
m_BaseTime = GetTickCount();

...
// capture data
timeX = GetTickCount() - m_BaseTime;


This assumes that for the data acquisition, times do not need more than
10s of millisecond resolution.

Steve
  Luigino replied to Stephen Myers
30-Jan-10 01:40 PM
I am still keeping trying to solve...

Actually I have made this, in the OnInitDialog (it is a CDialog form) I
coded:

CRect rc;
GetClientRect(&rc);
time_t t;

for (int i=0; i<=rect.right; i++) {
POINT singlepoint;
t = time(NULL);
singlepoint.x = t;
singlepoint.y = (LONG)((double)rand() / RAND
CDC* pDC = GetWindowDC();
pDC->SetBkColor(RGB(0,0,0));
pDC->SetMapMode(MM_ANISOTROPIC);
t =
  Luigino replied to Stephen Myers
30-Jan-10 01:40 PM
HI Steve,
I am still keeping trying to solve...

Actually I have made this, in the OnInitDialog (it is a CDialog form) I
coded:

CRect rc;
GetClientRect(&rc);
time_t t;

for (int i=0; i<=rect.right; i++) {
POINT singlepoint;
t = time(NULL);
singlepoint.x = t;
singlepoint.y = (LONG)((double)rand() / (RAND_MAX + 1)*
(rect.bottom-1)+1);
myQueuePoints.push_back(singlepoint);
}

CDC* pDC = GetWindowDC();
pDC->SetBkColor(RGB(0,0,0));
pDC->SetMapMode(MM_ANISOTROPIC);
t = time(NULL);
HDC hdc = ::GetDC(m_hWnd);
SetWindowExtEx(hdc, t, rect.bottom, NULL);


and in the OnDraw(CDC* pDC) I implemented:

CMemDC mDC(pDC);    <--- is an header of Keith Rule to prevent
flickering..
CRect rect;
GetClientRect(&rect);
mDC->FillRect(rect, CBrush::FromHandle((HBRUSH)GetStockObject
(BLACK_BRUSH)));
CPen qPen(PS_SOLID, 1, RGB(0,0,0));
mDC->SelectObject(qPen);
mDC->PolyLine(&myQueuePoints[0], myQueuePoints.size());
qPen.DeleteObject();

thought I have scaled correctly but it does not show anything... (in
the OnEraseBkgnd event I changed the return to FALSE)... maybe am I
missing something about SetWindowExtEx or similia?....

Thanks a lot
Ciao, Luigi
  Stephen Myers replied to Luigino
29-Jan-10 01:02 PM
//
//
m_BaseTime = GetTickCount();
// unless rect is related to your data acquisition, do not use it here.
// t is likely a constant here.  This loop is not long enough for t to
change.
// do the SetWindowExt() in the OnDraw, on mDC
//
// I would expect SetWindowExtEx() on mDC here
//

You need to do a bitblt() to get the bitmapped data from mDC into pDC's
bitmap
  Joseph M. Newcomer replied to Luigino
01-Feb-10 08:39 AM
See below...

****
There is something deeply suspicious here about getting a window DC.  Then, having gotten
the DC, you set mapping modes, set other parameters, and of course, the DC is never going
to be accessed again, so why bother?  And, oh yes, you have now leaked a DC, since you
never free it, so it sits there, being completely useless.  You only want a Window DC if
you are drawing the entire window, including borders, captions, etc.

Get rid of ALL of this DC-related code; it has no business being here.  It does nothing,
and serves no purpose.
****
***
This code is even worse.  Why in the world would you call ::GetDC for anything?  Let alone
call it in OnIntDialog, where it does nothing useful?  And you have leaked another DC, that
will never be seen again.  So whatever settings you do affect this DC, and this DC alone,
and it is never, ever seen again by anyone!

If you were to need a DC to a specific window, for example, for a rubber-band line or to
compute some other parameter such as a text size, you would do
CClientDC dc(this);
There are almost no GDI calls that you will need that work with raw GDI.  Due to an
unfortunately oversight which has not been rectified in nearly 20 years, SetGraphicsMode
is one of them.

But generally assume under normal conditions that writing ::GetDC, or calling CWnd::GetDC,
is a programming error.
****
****
And what possible relationship does the rectangle at OnInitDialog time have to the
rectangle at drawing time?  You do not want to compute these parameters until you have the
actual window to draw into.  In some misguided attempt at "optimization" you may think
this saves some time, but it saves no time of any consequence whatsoever.  Get rid of this
code as well.
****
****
You have not set the mapping mode or the window extent or viewport extent.  These must be
done not to some randomly-chosen and never-seen-again DC in OnInitDialog/OnInitialUpdate,
but to the CDC* that is passed into OnDraw!  Fix you code so you have the correct
coordinate system at the point where you are drawing.

In addition to this, your idea of keeping screen resolution in the Registry is complete
nonsense.  The screen resolution could change at any time, without warning.  You should not
need it at all in this case, because you should be scaling to the actual data, but if you
need it, use CDC::GetDeviceCaps.  And I do not think any display in the last 10 years had
72dpi (this was low-resolution VGA, not seen since the late 1980s; 640x480 at 72dpi was an
8.8*6.6, or an 11" diagonal screen area, not seen in modern programming; most modern
screens are either 96dpi or 100dpi).  But the resolution does not matter.  What you should
be tracking is the max/min of the data, and the GetClientRect of the area you are drawing
in as determined in the OnDraw/OnPaint handler the instant before you start drawing.
Screen resolution does not matter at all in this case.
joe
****
  Luigino replied to Stephen Myers
02-Feb-10 02:22 PM
Since I am trying to slide graphic from right to left so I decide to
set the origin of coordinates to the right-bottom corner of the client
so the graphic would be like all "negative" doing this:

CClientDC dc(this);
CRect rc;
GetClientRect(&rc);
dc.SetMapMode(MM_ANISOTROPIC);
dc.SetViewportOrg(rc.right, rc.bottom);
dc.SetWindowExt(rc.right, rc.bottom);
dc.SetViewportExt(-(rc.right), -(rc.bottom));

so if I am right, now the graphic area coordinates starts from right-
bottom corner going to left-bottom corner and right-top corner.
so I set an array of POINT where I have:

{x=-10,y=-20;x=-9,y=-13;x=-8,y=-17;x=-7,y=-7;x=-6,y=-9;...x=-1,y=-11;x=0,y=0}

and with PolyLine it should draw in the "negative" area I set right
after SetMapMode...but it is like clear, is there isomething I missed or
made wrong around what I said right before?...

Thanks
Ciao
Luigi
  Luigino replied to Luigino
02-Feb-10 02:22 PM
Hello again to all...

I figured out about to set correctly coordinates so graphic would
slide from right to left. But when I resize the window, it repaints
but it does not scale at all!... I mean the array has values and the
CDC should do the rest of hard stuff about scaling as you, Stephen,
said...
In fact I see vertical and horizontal lines are redrawn but the
graphic (using PolyLine function) draws same thing as it was before
resizing the window...

I have to say this class control is a CWnd based so I have OnPaint()
event where I call an own OnDraw()...
I settled in the creation function:

CPaintDC dc(this);
CRect rc;
dc.SetBkColor(RGB(0,0,0));
dc.SetMapMode(MM_ANISOTROPIC);   <----- so CDC would do the hard stuff
dc.SetWindowOrg(rc.BottomRight());   <----- having origin point at
bottom-right corner
dc.SetViewportOrg(0, 0);
dc.SetViewportExt(-1, -1);                     <----- those three
should make representing values as positive
dc.SetWindowExt(1, 1);                                  when in
reality they are negative

and in the OnPaint() I call again CPaintDC dc(this); because the
method does not pass any CDC parameter...
and inside I implemented:

dc.SetViewportExt(-1, -1);
dc.SetWindowExt(1, 1);

so it should supposed to re-scale the client DC to redraw correctly
the graphic everytime I resize itself....
Since I am using also CMemDC, declared in OnDraw event simply as:
CMemDC mDC(pDC); to prevent flickering, I have a class which creates a
DC and when variable destroys it applies bitblt, so since the class
has GetBkColor to fill rect and I settled as above the background as
BLACK, when it paints, its WHITE instead...

Any idea?...
Thanks all!
Ciao, Luigi
  Stephen Myers replied to Luigino
02-Feb-10 10:35 AM
You are scaling the wrong device context, at the wrong time.

Each time OnPaint gets called expect a new DC.  Anything you do to the
CPaintDC goes away when the method exits.

The only thing you should do to the CPaintDC Create mDC.

All the scaling (SetWindow, SetMapMode, SetViewport) should be applied
to mDC, in OnPaint().

All drawing should be on mDC as well.

Almost all drawing functions are available as part of the CDC class.
global functions that require a handle will work but the MFC methods are
much prefered.  That's a big part of why you are using MFC to start with.

I would have to see the CMemDC class in order to offer an opinion on it.
Again, I would expect to do ALL drawing on mDC.  If you want to do the
memory DC creation and bitblt, that makes sense.  I do not see where you
can do a GetBkColor and get anything useful.

I assume that CMemDC is based on CDC
Creates a compatible DC
Creates a compatible bitmap sized properly

Also, take a careful look at Dr Newcomer's posts.  He gives an very good
explanation of what not to do.

Steve
  Luigino replied...
04-Feb-10 06:28 AM
Hello Joe and Stephen,

maybe I am a bit lost about those OnDraw and OnPaint things... so I
modified the code to put everything only in the OnPaint so the
behaviour about DC should be correct BUT when I set MM_ANISOTROPIC and
new origin to bottom right in the OnPaint as you said it does not make
the graphic sliding from right to left...

Here I paste the full code of this test app (the CMemDC mDC(&dc) is an
external class):

In the .H file I have:


class MYDLL_IO CMyDLL : public CWnd
{
DECLARE_DYNCREATE(CMyDLL)

// matrix of POINT for each element to represent
typedef vector<POINT> m_fPoints;                //      array
representing points values for each single source
typedef vector<m_fPoints> m_Points;             //      array
representing list of sources having their POINT values

public:
CMyDLL(CWnd *parent, CWnd *parentcontrol, CRect parentrect,
int nparentID);
virtual ~CMyDLL();

public:
void set_MinMax(int minvalue, int maxvalue, int maxsizearray);
void AddElement(UINT nX, UINT nY, const char * description,
double value);

protected:
BOOL scrollState;
UINT iTimerVal;
UINT nElapse;
BOOL bSetDraw;
m_Points
mPoints;                                               // matrix of
sources representing POINT values
int mSourceMaxSizeArray;

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyDLL)
protected:
virtual void PreSubclassWindow();
//}}AFX_VIRTUAL

protected:
BOOL EnableTimer(BOOL tmrstate);
int OnCreate(LPCREATESTRUCT lpCreateStruct);

public:
static BOOL RegisterWindowClass();

protected:
//{{AFX_MSG(CMyDLL)
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnTimer(UINT TimerVal);
afx_msg void OnWindowPosChanged(WINDOWPOS* lpwndpos);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

In the .CPP file I have:


name

IMPLEMENT_DYNAMIC(CMyDLL, CWnd)

CMyDLL::CMyDLL(CWnd *parent, CWnd *parentcontrol, CRect parentrect,
int nparentID)
{
RegisterWindowClass();

scrollState = TRUE;
gridOffset = 0;
bSetDraw = TRUE;
nElapse = 1000;
iTimerVal = 0;

// Initializing graph's DC...
  Luigino replied...
04-Feb-10 06:28 AM
Hello Joe and Stephen,

maybe I am a bit lost about those OnDraw and OnPaint things... so I
modified the code to put everything only in the OnPaint so the
behaviour about DC should be correct BUT when I set MM_ANISOTROPIC and
new origin to bottom right in the OnPaint as you said it does not make
the graphic sliding from right to left...

Here I paste the full code of this test app (the CMemDC mDC(&dc) is an
external class):

In the .H file I have:


class MYDLL_IO CMyDLL : public CWnd
{
DECLARE_DYNCREATE(CMyDLL)

// matrix of POINT for each element to represent
typedef vector<POINT> m_fPoints;                //      array
representing points values for each single source
typedef vector<m_fPoints> m_Points;             //      array
representing list of sources having their POINT values

public:
CMyDLL(CWnd *parent, CWnd *parentcontrol, CRect parentrect,
int nparentID);
virtual ~CMyDLL();

public:
void set_MinMax(int minvalue, int maxvalue, int maxsizearray);
void AddElement(UINT nX, UINT nY, const char * description,
double value);

protected:
BOOL scrollState;
UINT iTimerVal;
UINT nElapse;
BOOL bSetDraw;
m_Points
mPoints;                                               // matrix of
sources representing POINT values
int mSourceMaxSizeArray;

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyDLL)
protected:
virtual void PreSubclassWindow();
//}}AFX_VIRTUAL

protected:
BOOL EnableTimer(BOOL tmrstate);
int OnCreate(LPCREATESTRUCT lpCreateStruct);

public:
static BOOL RegisterWindowClass();

protected:
//{{AFX_MSG(CMyDLL)
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnTimer(UINT TimerVal);
afx_msg void OnWindowPosChanged(WINDOWPOS* lpwndpos);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

In the .CPP file I have:


name

IMPLEMENT_DYNAMIC(CMyDLL, CWnd)

CMyDLL::CMyDLL(CWnd *parent, CWnd *parentcontrol, CRect parentrect,
int nparentID)
{
RegisterWindowClass();

scrollState = TRUE;
gridOffset = 0;
bSetDraw = TRUE;
nElapse = 1000;
iTimerVal = 0;

// Initializing graph's DC...
  Luigino replied...
04-Feb-10 06:28 AM
Hello Joe and Stephen,

maybe I am a bit lost about those OnDraw and OnPaint things... so I
modified the code to put everything only in the OnPaint so the
behaviour about DC should be correct BUT when I set MM_ANISOTROPIC and
new origin to bottom right in the OnPaint as you said it does not make
the graphic sliding from right to left...

Here I paste the full code of this test app (the CMemDC mDC(&dc) is an
external class):

In the .H file I have:


class MYDLL_IO CMyDLL : public CWnd
{
DECLARE_DYNCREATE(CMyDLL)

// matrix of POINT for each element to represent
typedef vector<POINT> m_fPoints;                //      array
representing points values for each single source
typedef vector<m_fPoints> m_Points;             //      array
representing list of sources having their POINT values

public:
CMyDLL(CWnd *parent, CWnd *parentcontrol, CRect parentrect,
int nparentID);
virtual ~CMyDLL();

public:
void set_MinMax(int minvalue, int maxvalue, int maxsizearray);
void AddElement(UINT nX, UINT nY, const char * description,
double value);

protected:
BOOL scrollState;
UINT iTimerVal;
UINT nElapse;
BOOL bSetDraw;
m_Points
mPoints;                                               // matrix of
sources representing POINT values
int mSourceMaxSizeArray;

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyDLL)
protected:
virtual void PreSubclassWindow();
//}}AFX_VIRTUAL

protected:
BOOL EnableTimer(BOOL tmrstate);
int OnCreate(LPCREATESTRUCT lpCreateStruct);

public:
static BOOL RegisterWindowClass();

protected:
//{{AFX_MSG(CMyDLL)
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnTimer(UINT TimerVal);
afx_msg void OnWindowPosChanged(WINDOWPOS* lpwndpos);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

In the .CPP file I have:


name

IMPLEMENT_DYNAMIC(CMyDLL, CWnd)

CMyDLL::CMyDLL(CWnd *parent, CWnd *parentcontrol, CRect parentrect,
int nparentID)
{
RegisterWindowClass();

scrollState = TRUE;
gridOffset = 0;
bSetDraw = TRUE;
nElapse = 1000;
iTimerVal = 0;

// Initializing graph's DC...
  Stephen Myers replied to Luigino
04-Feb-10 11:54 AM
Luigino wrote:
Create New Account
help
Thanks. . . VC MFC Discussions Windows XP (1) ASSERTs (1) Vista (1) XP (1) SomeMemoryDcClass (1) CClientDC (1) GetLastError (1) ReleaseDC (1) See below. . . * ** * Any program that requires a Sleep to work I am not at all sure why a GetDC is being done; it should be CClientDC dc(wnd); where wnd is the CWnd * pointer in hand; the DC will be released much in my first message. - - Doug Harrison Visual C++ MVP I was not aware of CClientDC. Thanks, I will look into it. It will make some of my code nicer. I
CRect(&rect)); Thanks for answers. Manu VC MFC Discussions Visual Studio 2008 (1) CWindowDC (1) CClientDC (1) GetClientRect (1) LpBitSource (1) OnPaint (1) OnDraw (1) CImage (1) It asserts because "m_Image in represents a serious problem. * ** * * ** * Do you really want a window DC? Why? Wouldn't CClientDC dc(this) be more appropriate? * ** * * ** * Once it is loaded, it is loaded forever, and any
scenario as opposed to this way? / / - -- -- -- -- -- --Begin OnChangeFontClick function- -- -- -- -- -- - / / void CTry_2View::OnChangeFontClick() { / / Create instance of CClientDC class to this ( CWnd* ) CClientDC dc(this); / / create CFont Ptr to current font obj of listbox / / The m_FormListBoxObj is a
the current window, the ONLY correct way to get it in MFC is to write CClientDC dc(this); In general, you will replace 'this' with a pointer to the CWnd that no idea where the caret position should be in terms of pixels. If you use CClientDC or CPaintDC, the DC will be released by the destructor, and you do not have
If you need a DC for an arbitrary window's client area, you will write CClientDC dc(wnd); but in this case, since all the code here is nonsense, there is