Drawing on a button click

I need some help. Can you please tell me how to draw in the window of a program after the user clicks a button in the window. I want to have a button that draws a few lines in the program's window when clicked.

Please help me.
Last edited on
I've done & commented this example code for you - I hope it helps, the functions used are:
http://msdn.microsoft.com/en-us/library/ms534247(VS.85).aspx - MoveToEx
http://msdn.microsoft.com/en-us/library/aa453296.aspx - LineTo
http://msdn.microsoft.com/en-us/library/ms632680(VS.85).aspx - CreateWindowEx
http://msdn.microsoft.com/en-us/library/ms632659(VS.85).aspx - LOWORD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <windows.h>

#define BUTTON1 3141
#define BUTTON1_TEXT "Draw lines"

//  Make the class name into a global variable
char szClassName[] = "WindowsApp";
// This is the handle for our window - global
HWND hwnd;
// This is the handle for our button - global
HWND button1;
// This is the handle to the device context for hwnd, we will set this value with GetDC in WinMain
HDC hdc;
//  Declare Windows procedure
//  This function is called by the Windows function DispatchMessage()

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  // handle the messages
    {
		case WM_CREATE: // when the window is created
		   break;
		case WM_COMMAND: // we've received a command
		   if(LOWORD(wParam)==BUTTON1) // is it our button?
		   {
			 // draw some lines
			 MoveToEx(hdc,100,100,0); // move the drawing position to 100, 100
			 LineTo(hdc,150,150); // draw a line from the drawing position to 150, 150
			 MoveToEx(hdc,200,200,0); // move the drawing position to 200, 200
			 LineTo(hdc,300,200); // draw a line from the drawing position to 300, 200
		   }
		   break;
        case WM_DESTROY:
            PostQuitMessage (0);       // send a WM_QUIT to the message queue
            break;
        default:                      // for messages that we don't deal with
            return DefWindowProc (hWnd, message, wParam, lParam);
    }

    return 0;
}


int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nCmdShow)

{
    MSG messages;            // Here messages to the application are saved
    WNDCLASSEX wc  ;        // Data structure for the windowclass

    // The Window structure
    wc.hInstance = hThisInstance;
    wc.lpszClassName = szClassName;
    wc.lpfnWndProc = WindowProcedure;      // This function is called by windows
    wc.style = CS_DBLCLKS;                 // Catch double-clicks
    wc.cbSize = sizeof (WNDCLASSEX);

    // Use default icon and mouse-pointer
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.lpszMenuName = NULL;                 // No menu
    wc.cbClsExtra = 0;                      // No extra bytes after the window class
    wc.cbWndExtra = 0;                      // structure or the window instance
    // Use Windows's default colour as the background of the window
    wc.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    // Register the window class, and if it fails quit the program
    if(!RegisterClassEx (&wc)){return 0;}

    // The class is registered, let's create the program
    hwnd = CreateWindowEx (
           0,                   // Extended possibilites for variation
           szClassName,         // Classname
           "Windows App",       // Title Text
           WS_OVERLAPPEDWINDOW, // default window
           CW_USEDEFAULT,       // Windows decides the position
           CW_USEDEFAULT,       // where the window ends up on the screen
           400,                 // The program's width
           300,                 // and height in pixels
           HWND_DESKTOP,        // The window is a child-window to desktop
           NULL,                // No menu
           hThisInstance,       // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Make the window visible on the screen
    ShowWindow (hwnd, nCmdShow);
	hdc = GetDC(hwnd); // this is setting hdc to hwnd's hdc - this is used for drawing
	// make our button
	button1 = CreateWindowEx (
           0,                   // Extended possibilites for variation
           "BUTTON",            // Classname
           BUTTON1_TEXT,        // Button Text
           WS_CHILD|WS_VISIBLE, // Child to the window, visible
           5,                   // X co-ordinate
           5,                   // Y co-ordinate
           80,                  // The button's width
           18,                  // and height in pixels
           hwnd,                // The button is a child to hwnd - our window
           (HMENU)BUTTON1,      // This value is sent to our WindowProcedure function when button is clicked
           NULL,                // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Run the message loop. It will run until GetMessage() returns 0
    while (GetMessage (&messages, NULL, 0, 0))
    {
        // Translate virtual-key messages into character messages
        TranslateMessage(&messages);
        // Send message to WindowProcedure
        DispatchMessage(&messages);
    }

    // The program return-value is 0 - The value that PostQuitMessage() gave
    return messages.wParam;
}
Above sample is wrong.
You must always draw on WM_PAINT
I'm going to sound like a jerk for saying this but the thread title "Windows Problem" is stating the obvious when you're posting it in the windows board. Perhaps you could choose a more meaningful subject line next time?
@Malachi
I really don't think so, because that I can't draw is a problem. And it is associated with windows, so I think my title is ok as it says I got a problem with windows.

@chris
I already read those MSDN articles, but they didn't seem to help me.
I will try your code, and I hope it works
As george135 said, you have to draw on WM_PAINT message so that what you draw won't be deleted on the next redraw.

I would modify the window procedure like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   static bool draw = false;

   switch (message)
   {
      case WM_COMMAND:
         if(LOWORD(wParam)==BUTTON1)
         {
            draw = true;
            RedrawWindow(hwnd,0,0,RDW_INVALIDATE);
         }
         break;
      case WM_DESTROY:
         PostQuitMessage (0);
         break;
      case WM_PAINT:
         if (draw)
         {
            MoveToEx(hdc,100,100,0);
            LineTo(hdc,150,150);
            MoveToEx(hdc,200,200,0);
            LineTo(hdc,300,200); 
         }
   }

   return DefWindowProc (hWnd, message, wParam, lParam);
}



A better title would be "Drawing After Button Click" as "Windows Problem" is too generic
Last edited on
@Bazzy
I also think your suggestion for my title is better. I am changing my title.
I will try your code and see which one is better.
To george135 & to Bazzy; although WM_PAINT will keep it always there, I am pretty sure (& it does for me anyway) it takes almost all the CPU it can & causes (for me) at least 95% CPU usage. In my opinion, it is better just find the cases where it would need to be redrawn (such as sizing the window or the window getting focus/loosing focus) & then put the code into there.
So maybe create a global variable if you need to redraw, then have your cases where it needs to redraw & your button click to activate this. I have used this code below & it works well for me, without the CPU usage of calling it on WM_PAINT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <windows.h>

#define BUTTON1 3141
#define BUTTON1_TEXT "Draw lines"

//  Make the class name into a global variable
char szClassName[] = "WindowsApp";
// This is the handle for our window - global
HWND hwnd;
// This is the handle for our button - global
HWND button1;
// This is the handle to the device context for hwnd, we will set this value with GetDC in WinMain
HDC hdc;
//  Declare Windows procedure
//  This function is called by the Windows function DispatchMessage()

bool DrawLines=false; // our variable for if we need to draw

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  // handle the messages
    {

		case WM_SIZE: // for resizing
		case WM_ACTIVATE: // works for focus changes & minimises
		if(DrawLines)
		{
		MoveToEx(hdc,100,100,0); // move the drawing position to 100, 100
		LineTo(hdc,150,150); // draw a line from the drawing position to 150, 150
		MoveToEx(hdc,200,200,0); // move the drawing position to 200, 200
		LineTo(hdc,300,200); // draw a line from the drawing position to 300, 200
		}
		break;
		case WM_COMMAND: // we've received a command
		   // is it our button?
		   if(LOWORD(wParam)==BUTTON1)
		   {
		   DrawLines=true; // set the DrawLines variable to true
		   SendMessage(hwnd,WM_SIZE,0,0); // send the message to SIZE just to redraw
		   }
		   break;
        case WM_DESTROY:
            PostQuitMessage (0);       // send a WM_QUIT to the message queue
            break;
        default:                      // for messages that we don't deal with
            return DefWindowProc (hWnd, message, wParam, lParam);
    }

    return 0;
}


int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nCmdShow)

{
    MSG messages;            // Here messages to the application are saved
    WNDCLASSEX wc  ;        // Data structure for the windowclass

    // The Window structure
    wc.hInstance = hThisInstance;
    wc.lpszClassName = szClassName;
    wc.lpfnWndProc = WindowProcedure;      // This function is called by windows
    wc.style = CS_DBLCLKS;                 // Catch double-clicks
    wc.cbSize = sizeof (WNDCLASSEX);

    // Use default icon and mouse-pointer
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.lpszMenuName = NULL;                 // No menu
    wc.cbClsExtra = 0;                      // No extra bytes after the window class
    wc.cbWndExtra = 0;                      // structure or the window instance
    // Use Windows's default colour as the background of the window
    wc.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    // Register the window class, and if it fails quit the program
    if(!RegisterClassEx (&wc)){return 0;}

    // The class is registered, let's create the program
    hwnd = CreateWindowEx (
           0,                   // Extended possibilites for variation
           szClassName,         // Classname
           "Windows App",       // Title Text
           WS_OVERLAPPEDWINDOW, // default window
           CW_USEDEFAULT,       // Windows decides the position
           CW_USEDEFAULT,       // where the window ends up on the screen
           400,                 // The program's width
           300,                 // and height in pixels
           HWND_DESKTOP,        // The window is a child-window to desktop
           NULL,                // No menu
           hThisInstance,       // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Make the window visible on the screen
    ShowWindow (hwnd, nCmdShow);
	hdc = GetDC(hwnd); // this is setting hdc to hwnd's hdc - this is used for drawing
	// make our button
	button1 = CreateWindowEx (
           0,                   // Extended possibilites for variation
           "BUTTON",            // Classname
           BUTTON1_TEXT,        // Button Text
           WS_CHILD|WS_VISIBLE, // Child to the window, visible
           5,                   // X co-ordinate
           5,                   // Y co-ordinate
           80,                  // The button's width
           18,                  // and height in pixels
           hwnd,                // The button is a child to hwnd - our window
           (HMENU)BUTTON1,      // This value is sent to our WindowProcedure function when button is clicked
           NULL,                // Program Instance handler
           NULL                 // No Window Creation data
           );

    // Run the message loop. It will run until GetMessage() returns 0
    while (GetMessage (&messages, NULL, 0, 0))
    {
        // Translate virtual-key messages into character messages
        TranslateMessage(&messages);
        // Send message to WindowProcedure
        DispatchMessage(&messages);
    }

    // The program return-value is 0 - The value that PostQuitMessage() gave
    return messages.wParam;
}



(also as a note, I forgot to delete in my code, in WindowProcedure, the WM_CREATE case statement - that's not necessary in my code a few posts ago).
Last edited on
closed account (z05DSL3A)
Read: http://msdn.microsoft.com/en-us/library/ms534870(VS.85).aspx
@chris
I alredy tryed your code, but my compiler gives some kind of strange error that says:

1>d:\data\1\tajjada_projects\tfifview\tfifviewdlg.cpp(291) : error C2440: '=' : cannot convert from 'CDC *' to 'HDC'
1> Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast


Please help me solve that error.
closed account (z05DSL3A)
Can you give more info on the project type, IDE/compiler?

I'm guessing that you have an MFC project (of some description) from the CDC* reference.
Last edited on
I am using VC++ 2008 and I have an MFC Project.
closed account (z05DSL3A)
It that case you put your drawing code on the OnPaint() method of the dialog class.

It will look something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void CTestMfcDlgDlg::OnPaint()
{
    (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
    {
        CDialog::OnPaint();
    }
}


In the else part of the if construct is where you would put your code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void CTestMfcDlgDlg::OnPaint()
{
    (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
    {
        CPaintDC dc(this); // device context for painting

        CRect rcClient; 
        GetClientRect(rcClient);
        
        CPoint upperLeft,lowerRight;
        upperLeft = rcClient.TopLeft();
        lowerRight = rcClient.BottomRight();
        
        CRect recGraph(upperLeft.x,upperLeft.y,lowerRight.x,lowerRight.y);
        CPen penBlack(PS_SOLID,2,RGB(0,0,0));
        dc.SelectObject(&penBlack);
        CBrush brGreen(RGB(92,200,92));
        dc.SelectObject(&brGreen);
        recGraph.DeflateRect(5,5);
        dc.RoundRect(recGraph,CPoint(15,15));

        CDialog::OnPaint();
    }
}




Hope that helps
But is that the code for drawing only on the click of a button or my drawings are always going to stay there?
closed account (z05DSL3A)
No, this is just the correct place to put your drawing code. As Bazzy said, You would have a variable in your view class such as

static bool draw = false;

You can then wrap your drawing code in an if clause:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CTestMfcDlgDlg::OnPaint()
{
    (IsIconic())
    {
        ...
    }
    else
    {
        if(draw)
        {        
            CPaintDC dc(this); // device context for painting
            ...
        }
        CDialog::OnPaint();
    }
}


Then finally your event handler for the button click would be somthing like:

1
2
3
4
5
6
7
8
void CTestMfcDlgDlg::OnBnClickedButton1()
{
    // TODO: Add your control notification handler code here

    draw = !draw;  //Toggle drawing on button click
    this->Invalidate(true); 

}



Topic archived. No new replies allowed.