I need to make an interactive graph using MFC
It will be sort of like an equalizer control, where the user should be able to click on a point on the equalizer drag it to change it's y axis value
I am also just starting to learn MFC
Upto this point I have used CPaintDC in the OnPaint() function to draw the graph in a dialog box. For now the graph is very simple, with rectangle boarder, filled with white colour, and 4 points on the graph. I use OnMouseMove() function to know if the cursor is inside the graph area and OnLButtonDown() function to know where the user has clicked. If the user has clicked at a position which implies I want to change the y axis value of the graph point at that location, I repaint the figure using Invalidate() and calling OnPaint() inside OnLButtonDown(). However, every time the graph has to update, I can see a flicker. it is not a problem now, but I will need to extend this graph so that it has at least 64 changeable points, with the ability to change the y axis value for a point by dragging instead of just clicking where I want it to go. Will the flickering problem increase as I increase the number of points and the complexity of the appearance of the graph? The graph will need to have axes, gridlines, labels, etc later on. Is the flickering something I should be concerned about? Is there any way I can prevent it?
----UPDATE ----
This is how I updated my OnPaint() function according to how I understood CodeDreamer's suggestion
void Cgraph_on_dlgboxDlg::OnPaint()
{
CPaintDC dc_blt(this);
CDC dc;
CBitmap bmpDC;
CRect rcClient;
GetClientRect(rcClient);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
dc.CreateCompatibleDC(&dc);
bmpDC.CreateCompatibleBitmap(&dc, theGraph.width,theGraph.height );
dc.SelectObject(&bmpDC);
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
dc.SetViewportOrg(theGraph.x1, theGraph.y1);
dc.SelectObject(&pen);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the clinet origin
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
// dc_blt.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dc, 0, 0, SRCCOPY);
dc_blt.BitBlt(theGraph.x1,theGraph.y1,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
}
I will need to change the origins of the viewport a number of times and my guess is that this could be one of the reasons for the error. Any suggestions will be welcome.
This is what my output look like without double buffering
This is what it looks like with my attempt at double buffering
In this case, a general solution is 'double buffering'.
The principle is that it creates a compatible memory dc for drawing in advance, and when drawing is ended, it outputs on screen dc.
A code sample is below.
//in OnPaint() function
CPaintDC dc(this);
CDC dcMem;
CBitmap bmpDC;
CRect rcClient;
GetClientRect(&rcClient);
dcMem.CreateCompatibleDC(pDC);
bmpDC.CreateCompatibleBitmap(pDC, rcClient.Width(), rcClient.Height());
dcMem.SelectObject(&bmpDC);
CRect rect(0, 0, 100, 200);
dcMem.Rectangle(rect);
dc.BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &dcMem, 0, 0, SRCCOPY);
A couple of references are below.
introduction
another reference
I hope this will help you a little.
Try the CMemDC MFC class.
In your OnPaintFunction you would have this:
CPaintDC DC(this);
CMemDC mDC(&DC, this);
// now use mDC instead of DC
Also look here fore more samples and explanations.
Related
I'm trying to write a text overlay function that generates a semitransparent background with text on it in the top right hand corner of the viewport. I wrote a test MFC application project with mostly default settings (I don't remember exactly, but AFAIK, none of the settings should cause the problems I'm seeing).
Here is the code:
void DrawSemitransparentRect(CDC& destDC, CRect rect, float percentGrayBackground, COLORREF overlayColour, float overlayPercentOpaque)
{
rect.NormalizeRect();
CDC temp_dc; // Temp dc for semitransparent text background
temp_dc.CreateCompatibleDC(&destDC);
CBitmap layer; // Layer for semitransparent text background
layer.CreateCompatibleBitmap(&destDC, 1, 1);
CBitmap* pOldBitmap = temp_dc.SelectObject(&layer);
BLENDFUNCTION blendFunction = { AC_SRC_OVER, 0, 0, 0 };
auto DrawSemitransparentRectHelper = [&](COLORREF colour, float transparency)
{
temp_dc.SetPixel(0, 0, colour);
blendFunction.SourceConstantAlpha = BYTE(transparency * 255 / 100);
// Draw semitransparent background
VERIFY(destDC.AlphaBlend(rect.left, rect.top, rect.Width(), rect.Height()
, &temp_dc, 0, 0, 1, 1, blendFunction));
};
// Lighten up the area to make more opaque without changing overlay colour.
DrawSemitransparentRectHelper(RGB(255, 255, 255), percentGrayBackground);
// Draw overlay colour
DrawSemitransparentRectHelper(overlayColour, overlayPercentOpaque);
temp_dc.SelectObject(pOldBitmap);
}
void DrawOverlayText(CDC & dc, CFont &windowFont, CRect const& windowRectDP, CString const& overlayText, CRect* pBoundingRectDP)
{
static bool debug = true;
int savedDC = dc.SaveDC();
::SetMapMode(dc.GetSafeHdc(), MM_TWIPS);
// Reset the window and viewport origins to (0, 0).
CPoint windowOrg, viewportOrg;
::SetWindowOrgEx(dc.GetSafeHdc(), 0, 0, &windowOrg);
::SetViewportOrgEx(dc.GetSafeHdc(), 0, 0, &viewportOrg);
LOGFONT logFont;// = { 12 * 10, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 255, _T("Times New Roman") };
windowFont.GetLogFont(&logFont);
logFont.lfHeight = 12 * 10; // 12 point font? Why isn't this *20? TWIPS are 20ths of a point.
// Font for the overlay text
CFont font;
font.CreatePointFontIndirect(&logFont, &dc);
CFont* pOldFont = dc.SelectObject(&font);
// window rect in Logical Points
CRect windowRect(windowRectDP);
dc.DPtoLP(windowRect);
// Get text extent in Logical Points
CRect textRect;
dc.DrawText(overlayText, textRect, DT_CALCRECT);
// inflation rectangle to add pixels around text
CRect inflate(8, 0, 8, 4);
dc.DPtoLP(&inflate);
// Create the bounding rect on the right hand of the view, making it a few pixels wider.
CRect boundingRect(textRect);
if (!debug)
{
boundingRect.InflateRect(inflate);
}
boundingRect.NormalizeRect();
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), 0);
CRect boundingRectDP(boundingRect);
if (pBoundingRectDP || !debug)
{
// Get the bounding rect in device coordinates
dc.LPtoDP(boundingRectDP);
*pBoundingRectDP = boundingRectDP;
}
if (!debug)
{
// round the bottom corners of the text box by clipping it
CRgn clip;
boundingRectDP.NormalizeRect();
clip.CreateRoundRectRgn(
boundingRectDP.left + 1 // +1 needed to make rounding coner match more closely to bottom right coner
, boundingRectDP.top - boundingRectDP.Height() // Getting rid of top rounded corners
, boundingRectDP.right
, boundingRectDP.bottom + 1
, 16, 16 // rounding corner may have to be more dynamic for different DPI screens
);
::SelectClipRgn(dc.GetSafeHdc(), (HRGN)clip.GetSafeHandle());
clip.DeleteObject();
}
// Calculatte centre position of text
CPoint centrePos(
boundingRect.left + (boundingRect.Width() - textRect.Width()) / 2 + 1
, boundingRect.top + (boundingRect.Height() - textRect.Height()) / 2 + 1);
if (debug)
{
// in debug mode, output text and then put semitransparent bounding rect over it.
dc.SetBkMode(debug ? OPAQUE : TRANSPARENT);
dc.SetBkColor(RGB(255, 0, 0));
dc.SetTextColor(RGB(0, 0, 0));
dc.TextOut(centrePos.x, centrePos.y, overlayText);
DrawSemitransparentRect(dc, boundingRect, 60, RGB(0, .25 * 255, .75 * 255), 40);
}
else
{
// 2 pixel offset in Logical Points
CPoint textShadowOffset(2, 2);
dc.DPtoLP(&textShadowOffset);
// in !debug mode, output semitransparent bounding rect and then put text over it.
DrawSemitransparentRect(dc, boundingRect, 60, RGB(0, .25 * 255, .75 * 255), 40);
dc.SetBkMode(debug ? OPAQUE : TRANSPARENT);
dc.SetTextColor(RGB(0, 0, 0));
dc.TextOut(centrePos.x, centrePos.y, overlayText);
dc.SetTextColor(RGB(255, 255, 255));
dc.TextOut(centrePos.x - textShadowOffset.x, centrePos.y - textShadowOffset.y, overlayText);
}
// Restore DC's state
dc.SelectObject(pOldFont);
dc.RestoreDC(savedDC);
}
// OnPaint() function for CView derived class.
void COverlayOnCViewView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CString m_overlayText = _T("abcdefg ABCDEFG");
CFont windowFont;
LOGFONT logFont = { -12, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, _T("Segoe UI") };
windowFont.CreatePointFontIndirect(&logFont, &dc);
CRect windowRect;
GetClientRect(windowRect);
DrawOverlayText(dc, windowFont, windowRect, m_overlayText, nullptr);
}
Now, this works perfectly well in the default project, where I get the following:
But when I put it into another preexisting project, I get this:
You can see that the text is actually positioned above the translucent rectangle.
If I move the rectangle down the height of the text box, by changing
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), 0);
to
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), textRect.Height());
I get:
It's like the text function is specifying the bottom left corner rather than the top left corner for placement.
I wrote the free functions so that it should work with any DC, even if that DC has had its coordinate system manipulated, but perhaps I've forgotten to reset something?
The default project is using MFC 14.0.24212.0, but the project I tried to import this code into is using MFC 12.0.21005.1. Could that be an issue? I'm not sure how to change the default project to use the earlier version of MFC to test that.
Edit
Note that in the default project, I could have put the code into the OnDraw() function like this:
void COverlayOnCViewView::OnDraw(CDC* pDC)
{
COverlayOnCViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: add draw code for native data here
CString m_overlayText = _T("abcdefg ABCDEFG");
CFont windowFont;
LOGFONT logFont = { -12, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, _T("Segoe UI") };
windowFont.CreatePointFontIndirect(&logFont, pDC);
CRect windowRect;
GetClientRect(windowRect);
DrawOverlayText(*pDC, windowFont, windowRect, m_overlayText, nullptr);
}
The only reason why I didn't was because the application I'm putting this into doesn't have one and I wanted to mimic that project as closely as possible. If you create a default application to test this, remember either to put the ON_WM_PAINT() macro in the MESSAGE MAP or use the OnDraw() function shown instead. They both seem to have the same results in the default project.
I am in the process of developing an artificial horizon, as used in planes. This horizon has a background containing ground (brown) and sky (blue). Depending on the roll-angle/pitch of the plane, this background is rotated.
In order to keep it simple and keep CPU usage low, instead of drawing everything every pass, I wish to use an oversized static background image, which I will rotate as needed, and from which I will then copy/paste a square section to the screen.
The problem I'm having, is that I can't get cairo to rotate the surface FIRST and THEN copy/paste a section. It does copy/paste correctly, only rotates AFTER this has been done.
The code I have so far:
#define WINDOW_WIDTH 320
#define WINDOW_HEIGHT 240
double deg2rad( double degrees )
{
return((double)((double)degrees * ( (double)M_PI/(double)180.0 )));
}
static gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data)
{
cairo_surface_t *source;
cairo_t *bck;
cairo_pattern_t *source_pattern;
gint s_width, s_height, d_width, d_height,source_x, source_y;
// load the image from disk
source = cairo_image_surface_create_from_png ("/home/henri/dev/art_horiz_bck.png");
bck = cairo_create(source);
cairo_set_source_surface (bck, source,0,0);
s_width = cairo_image_surface_get_width(source);
s_height = cairo_image_surface_get_height(source);
// rotate around center of image
cairo_translate(bck, s_width/2, s_height/2);
cairo_rotate(cr, deg2rad(30));
cairo_paint(bck);
// after rotation, the image size should have been changed (increased)
s_width = cairo_image_surface_get_width(cairo_get_target (bck));
s_height = cairo_image_surface_get_height(cairo_get_target (bck));
d_width = gtk_widget_get_allocated_width (widget);
d_height = gtk_widget_get_allocated_height (widget);
// get the center 'viewport'
source_x = (s_width/2)-(d_width/2);
source_y = (s_height/2)-(d_height/2);
// copy this rectangle to the destination surface
cairo_set_source_surface (cr, source, -source_x, -source_y);
cairo_rectangle (cr, 0, 0, 320, 200);
cairo_fill (cr);
return FALSE;
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *grid;
GtkWidget *topbar;
GtkWidget *bottombar;
GtkWidget *da;
gtk_init (&argc, &argv);
topbar = gtk_image_new_from_file ("/home/henri/dev/topbar.png");
bottombar = gtk_image_new_from_file ("/home/henri/dev/bottombar.png");
da = gtk_drawing_area_new();
gtk_widget_set_size_request (da, WINDOW_WIDTH, WINDOW_HEIGHT);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_decorated(GTK_WINDOW (window), 0);
gtk_window_set_default_size (GTK_WINDOW (window), WINDOW_WIDTH, WINDOW_HEIGHT);
g_signal_connect (da, "draw", G_CALLBACK(draw_cb), NULL);
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
grid = gtk_grid_new ();
gtk_container_add (GTK_CONTAINER (window), grid);
gtk_grid_attach (GTK_GRID (grid), topbar, 0, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid), da, 0, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid), bottombar, 0, 2, 1, 1);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
As stated, this almost does what I want, besides the rotation not getting applied at cairo_paint(bck). So i FIRST want cairo to rotate the image and THEN get a square horizontal rectangle from it. Now it first gets the rectangle, and then rotates this.
//edit
to make this more clear. The app will run on an embedded device with a 320x240 tft screen. It will be fullscreen. Above and below the horizon windows there wil be small static bars. This question only handles the drawing area in between.
I have this background image:
http://s10.postimg.org/3xuvr2dyh/art_horiz_bck.png
This is sufficiently oversized to cover all possible roll and pitch angles of the airplane in question. Now suppose the plane is flying in a 10 degree nose up attitude (so it's climbing) and is rolling with 5 degrees roll-angle.
Now what I want to do is to rotate the above background by 5 degrees, and then take a rectangular section out of it, above the horizon, so that the 10 degree nose up attitude is also displayed. So from the above image, I want to distill this image:
http://s9.postimg.org/a4u8m4oan/Naamloos.png
Note that this second image is cropped to 320x240, the size of the drawing area it will be drawn on.
//edit 2
the below posted code by Uli Schlachter does seem to do what I want, however, goes wrong at this point:
cairo_matrix_translate (&matrix, -(s_width-d_width)/2.0, -(s_height - d_height)/2.0)
This is because it uses the dimensions of the original, unrotated image. I need it to use the dimensions of the already rotated image there.
No idea if this works the way you want, but hopefully it helps you to figure out how to do what you want:
static gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer data)
{
cairo_surface_t *source;
cairo_t *bck;
cairo_pattern_t *source_pattern;
gint s_width, s_height, d_width, d_height,source_x, source_y;
cairo_matrix_t matrix;
// load the image from disk
source = cairo_image_surface_create_from_png ("/home/henri/dev/art_horiz_bck.png");
source_pattern = cairo_pattern_create_for_surface (source);
s_width = cairo_image_surface_get_width(source);
s_height = cairo_image_surface_get_height(source);
d_width = gtk_widget_get_allocated_width (widget);
d_height = gtk_widget_get_allocated_height (widget);
cairo_surface_destroy (source);
// rotate around center of image
cairo_matrix_init_identity (&matrix)
cairo_matrix_translate (&matrix, s_width/2.0, s_height/2.0)
cairo_matrix_rotate (&matrix, deg2rad(30));
cairo_matrix_translate (&matrix, -(s_width-d_width)/2.0, -(s_height-d_height)/2.0)
cairo_pattern_set_matrix (source_pattern, &matrix);
// copy this rectangle to the destination surface
cairo_set_source (cr, source_pattern);
cairo_pattern_destroy (source_pattern);
cairo_rectangle (cr, 0, 0, 320, 200);
cairo_fill (cr);
return FALSE;
}
I'm trying to activate different visuals by pressing different keys. For example, when I press "z" or "Z", an ellipse on a specific position flows down, leaving a trace behind. If I wait for it to reach the canvas border, there's no problem but when I press "0" which activates another ellipse on another position to do the same thing, the ellipse of "z"/"Z" stops flowing. It also happens when I press the same key before the ellipse reaches canvas border. How can I fix this? When I started to code this, I didn't know classes and objects, then I learned and tried to solve it by using creating an object but it got worse, ellipses didn't work at all. I want the first ellipse to continue until the border even if I press another key while it's on its way.
Another thing is I wanted them to fade out after a short time, so I drew semi-transparent rectangles on canvas which seems very primitive to me. Would you suggest a different way? This is less important but it would be better to have them completely fade over time rather than leaving a slight trace.
Here's my code, I cleaned irrelevant parts to make it look more understandable:
void setup() {
size(640, 500);
background(bgRenk);
frameRate(60);
colorMode(HSB);
noStroke();
}
int bgRenk = #e7e7e7;
int C3Yer;
int C3Y;
int Fd6Yer;
int Fd6Y;
void draw() {
// This part probably sucks because it's a primitive solution to make ellipses fade out by putting semi-transparent rectangles on the canvas.
fill(bgRenk, 10);
rect(0, 0, 640, 500);
// I basically map x and y positions to hue and opacity.
float C3Renk= map(C3Yer, 0, width, 0, 255);
float C3Opak = map(C3Y, height, 0, 0, 200);
// When z/Z is pressed, an ellipse appears and goes down, leaving trace behind.
if (key == 'z' || key == 'Z') {
C3Yer = 10;
fill(C3Renk, 255, 255, C3Opak);
ellipse(C3Yer, C3Y, 20, 20);
C3Y += 20;
}
if (key == '0') {
// Same mapping and ellipse thing.
float Fd6Renk= map(Fd6Yer, 0, width, 0, 255);
float Fd6Opak = map(Fd6Y, height, 0, 0, 200);
Fd6Yer = 630;
fill(Fd6Renk, 255, 255, Fd6Opak);
ellipse(Fd6Yer, Fd6Y, 20, 20);
Fd6Y += 20;
}
}
void keyPressed() {
if (key == 'z' || key == 'Z') {
// When z/Z is pressed, y position of those ellipses are reset.
C3Y = 10;
}
if (key == '0') {
// Same reset thing.
Fd6Y = 10;
}
}
Thank you for your time.
Problem with creating new ellipse while other is still flowing is with variable key which always contains the value of the most recent key used (for more see). So when you press any key after "z" your ellipse will stop being drawn.
You can avoid this using global variables indicating which ellipses should be drawn.
But I would suggest using creating objects that will have defined state (position, color, opacity) and will be stored inside array or list. It will also help you with fading so find a time and read more about classes and objects. On processing site you can find nice tutorials like this one
But because you sad you already tried creating objects and i don't works I will give you few tips and basic example of such a class:
class Ball {
float x, y; // position of ball
color col = 10; // color of ball
int opac = 255; // opacity of ball
Ball(float _x, float _y) {
x = _x;
y = _y;
}
void move() {
y += 20;
opac -= 10;
}
void display() {
fill(col, 255, 255, opac);
ellipse(x, y, 20, 20);
}
}
Then you can create new ball objects:
void mousePressed() {
//this will create new ball on mouse position
balls.add( new Ball(mouseX, mouseY) );
}
and store them into ArrayList like this:
ArrayList<Ball> balls = new ArrayList();
Last thing that you need to do is display and move all balls within draw method.
for (Ball b : balls) // for each ball in list
{
b.display(); // first display ball
b.move(); // move and change opacity
}
This is very basic example and should be improved but I hope it will help you with understanding of classes.
I am trying to make an interactive graph using MFC where the y axis of a sample point in the graph can be changed using mouse click. I implemented double buffering using this tutorial
enter link description here. I should also point out that I need to change the origins of the viewport from time to time for my program. However, when I click on the graph for the sample point to be updated, I can still see it flicker. It's not an inconvenience, but I need to extend this graph to include lots of sample points and other features such as gridlines, axes, labels, boundary areas, etc and I am worried that the flickering might become a problem for me in the future as the size of this project grows. Implementing double buffering did not seem to make any changes to the output. Moreover, now that I have implemented double buffering, the program seems to stop in the middle of execution (when I am running it in Debug mode in Visual Studio) with this error:
Unhandled exception at 0xffffff3a in graph_on_dlgbox.exe: 0xC0000005: Access violation reading location 0xffffff3a.
I am still not sure what causes it to appear, but seems to happen if I start randomly clicking around the graph area rapidly. Since I have not seen this error (yet) in my code that does not use double buffering, I am assuming it has something to do with the double buffering code, but I am not sure.
Anyway I would like to tackle this one problem at a time, and the first problem is the flicker. Here is my code without double buffering:
void Cgraph_on_dlgboxDlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// Dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the clinet origin
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
}
and here is the modified version with double buffering:
void Cgraph_on_dlgboxDlg::OnPaint()
{
// /*****
CPaintDC dc_blt(this);
CDC dc;
CBitmap bmpDC;
// CRect rcClient;
// GetClientRect(rcClient);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
dc.CreateCompatibleDC(&dc_blt);
// bmpDC.CreateCompatibleBitmap(&dc_blt, rcClient.Width(),rcClient.Height());
bmpDC.CreateCompatibleBitmap(&dc_blt, theGraph.width,theGraph.height );
dc.SelectObject(&bmpDC);
// ----------- After this point, do all operations considering (0,0) to be the origin of the bitmap
// consider bitmap coordinates a device coordinates for Viewport operations
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
// dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
// dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the client area origin
// New origin defined in terms of the Bitmap's origin
dc.SetViewportOrg(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
dc.SetViewportOrg(0, 0);
// dc_blt.BitBlt(rcClient.left+100,rcClient.top+50,rcClient.Width(), rcClient.Height(), &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dc, theGraph.x1, theGraph.y1, SRCCOPY);
dc_blt.BitBlt(theGraph.x1,theGraph.y1, theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// --- Bring the bitmap to this particular location on screen specified by (theGraph.x1,theGraph.y1, theGraph.width, theGraph.height)
// dc_blt.BitBlt(0,0,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(theGraph.x1,theGraph.y1,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// *****/
m_bMyDraw = FALSE;
}
Here is a sample screenshot of the output:
The y axis values of the sample points on the graph can be changed by clicking, and the program redraws the graph after every click by calling InvalidateRect() with the area of the graph as the rectangle to be repainted.
The coordinates of the sample points are stored in a array of CPoint objects, and it's members are updated every time the graph is clicked at the appropriate area. The graph then repaints, due to the call to InvalidateRect(), but with a flicker; unless of course, the program crashes in debug mode with this error:
How do I remove the flickering?
---- UPDATE ----
BOOL Cgraph_on_dlgboxDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
if ( m_bMyDraw )
return TRUE;
else
return CDialogEx::OnEraseBkgnd(pDC);
}
This function has been made this way since it was done like this in the tutorial I mentioned earlier
------ UPDATE 2 ----------
If I just put return TRUE; in the body of the above function, the flicker seems to vanish, but now the output looks like this
The dialog box background seems to have taken the contents of my Visual Studio window. How do I prevent this?
You're close! The idea of double buffering is to paint every pixel in your window exactly once. If it is painted zero times, artifacts like Visual Studio remain. And if it is painted a first time, and then painted again with a different color, you will see flicker. So, to make sure every pixel is painted, create your compatible dc the full width and height of the window so that when it is copied to the CPaintDC, it covers the entire area and not just theGraph. Keep returning TRUE in OnEraseBkgnd, so that the pixels are not first painted in OnEraseBkgnd, and then again in OnPaint.
Two things :
Have you made sure OnEraseBkgnd() just returns TRUE and doesn't call the base class to erase the view?
You don't need to do all that drawing for the double buffering in OnPaint(). All you need to do in the OnPaint() is the BitBlt. You can do the drawing to the memory bitmap in a UpdateRect() function which gets called whenever you need to update the screen, which then calls InvalidateRect() to update the screen. I've posted some code about a flicker-free double buffering method I've used many times here which might help.
The way that flicker prevention work is that first you return TRUE from OnEraseBkgnd to suppress the default erase. But then your OnPaint code must include a full erase of the window. You don't do that so you get the background image of your source code or whatever was there before. So add a FillSolidRect call to your OnPaint to clear the window.
Your creation of a CPaintDC before calling CDialogEx::OnPaint destroys the dialog's ability to properly paint itself, since that function also creates a CPaintDC. But only one call to CPaintDC is permitted for each paint message. To avoid this problem you need a completely different approach. The dialog should have a picture control on it (a CStatic), and you should paint your graph in a class you derive from CStatic.
Well I am trying to build a few small windows applications using MFC and trying to learn how things work, and while drawing a country's flag I got stuck. Following lines of code draw three rectangles and a circle right at the center of middle rectangle, what need to do next is draw spokes in to circle, i.e 8 diameters separated by an angle of 45 degrees.
void CMainWindow::OnPaint (){
CPaintDC dc(this);
for (int i=0;i <=100;i+=50) {
dc.SetBkMode(TRANSPARENT);
CRect rect;
CPen pen(PS_SOLID, 1, RGB(0,0,0));
CPen *oldPen = dc.SelectObject(&pen);
if (i == 0){
CBrush brush(RGB(255,130,0));
CBrush *oldBrush = dc.SelectObject(&brush);
dc.Rectangle(75,(i+50),275,(i+100));
}
else if(i == 50) {
CBrush brush(RGB(255,255,255));
CBrush *oldBrush = dc.SelectObject(&brush);
dc.Rectangle(75,(i+50),275,(i+100));
CPen pen2(PS_SOLID, 1,RGB(0,0,255));
CPen *oldPen = dc.SelectObject(&pen2);
dc.Ellipse(150,100,200,150);
}
else {
CBrush brush(RGB(34,139,34));
CBrush *oldBrush = dc.SelectObject(&brush);
dc.Rectangle(75,(i+50),275,(i+100));
}
}
I have no clue how to do that, I tried to find it in the MFC library but no luck!
Here is an example of the sin() cos() method.
I didn't see the point of putting the whole code block in a for loop, show I removed that part.
#include "math.h"
#define PI 3.1415926535898
void DrawIndiaFlag(CDC & dc , int x, int y)
{
dc.SetBkMode(TRANSPARENT);
CRect rect;
CPen pen(PS_SOLID, 1, RGB(0,0,0));
CPen *oldPen = dc.SelectObject(&pen);
{
CBrush brush(RGB(255,130,0));
CBrush *oldBrush = dc.SelectObject(&brush);
dc.Rectangle(x,(y),x+200,(y+50));
dc.SelectObject(oldBrush);
}
{
CBrush brush(RGB(255,255,255));
CBrush *oldBrush = dc.SelectObject(&brush);
dc.Rectangle(x,(50+y),x+200,(y+100));
CPen pen2(PS_SOLID, 1,RGB(0,0,255));
CPen *oldPen = dc.SelectObject(&pen2);
dc.Ellipse(x+75,y+50,x+125,y+100);
// Draw spokes
int nOriginX = x+100;
int nOriginY = y+75;
int nRadius = 25;
int nSpokes = 24;
double fAngle = 2*PI/nSpokes;
for (int i =0; i<nSpokes; i++)
{
dc.MoveTo(nOriginX,nOriginY);
int nX = (int)ceil(cos((fAngle)*i)*(nRadius)+nOriginX);
int nY = (int)ceil(sin((fAngle)*i)*(nRadius)+nOriginY);
dc.LineTo(nX,nY);
}
dc.SelectObject(oldPen);
}
{
CBrush brush(RGB(34,139,34));
CBrush *oldBrush = dc.SelectObject(&brush);
dc.Rectangle(x,(100+y),x+200,(150+y));
dc.SelectObject(oldBrush);
}
dc.SelectObject(oldPen);
}
I'm not familiar with the API your are using, but you could;
start with a thin rectangle (assuming there is no line function) as your line
draw it from the centre of the circle to the top, this should be an easy co-ordinate to work out
take those starting positions from your first line and rotate them around the centre of the circle, if their is no API function for this you could do it manually as described here http://www.euclideanspace.com/maths/geometry/affine/aroundPoint/index.htm
Use these new rotated co-ordinates to draw your next line, then just repeat
I think what you're after is the CDC::LineTo method (you can use CDC::MoveTo to get to the start point). More info:
"Draws a line from the current position up to, but not including, the point specified by x and y (or point)... The line is drawn with the selected pen. The current position is set to x, y or to point."