This ones could run a task and allow the user to cancel it or retry if a timeout reaches and the task is not finished.
Implementation
#include "stdafx.h"
#include "ProgressWnd.h"
#include <thread>
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define IDC_CANCEL 10
#define IDC_TEXT 11
#define IDC_PROGRESS 12
#define IDC_RETRY 13
LPCTSTR szSection = _T("Settings");
LPCTSTR szEntryX = _T("X");
LPCTSTR szEntryY = _T("Y");
CProgressWnd::CProgressWnd()
{
CommonConstruct();
}
CProgressWnd::CProgressWnd(CWnd* pParent, LPCTSTR pszTitle, BOOL bSmooth , BOOL bEnableRetry )
{
CommonConstruct();
m_strTitle = pszTitle;
m_bEnableRetry = bEnableRetry;
Create(pParent, pszTitle, bSmooth);
}
void CProgressWnd::CommonConstruct()
{
m_wRenenableWnd = NULL;
m_nNumTextLines = 4;
m_nPrevPos = 0;
m_nPrevPercent = 0;
m_nStep = 1;
m_nMinValue = 0;
m_nMaxValue = 100;
m_strTitle = _T("Progress");
m_strCancelLabel = _T(" Cancel ");
m_strRetryLabel = _T(" Retry ");
m_bCancelled = FALSE;
m_bModal = FALSE;
m_bEnableRetry = FALSE;
m_bPersistantPosition = TRUE;
}
CProgressWnd::~CProgressWnd()
{
DestroyWindow();
}
BOOL CProgressWnd::Create(CWnd* pParent, LPCTSTR pszTitle, BOOL bSmooth )
{
BOOL bSuccess;
CString csClassName = AfxRegisterWndClass(CS_OWNDC|CS_HREDRAW|CS_VREDRAW,
::LoadCursor(NULL, IDC_APPSTARTING),
CBrush(::GetSysColor(COLOR_BTNFACE)));
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(NONCLIENTMETRICS);
VERIFY(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0));
m_font.CreateFontIndirect(&(ncm.lfMessageFont));
if (!pParent)
pParent = AfxGetMainWnd();
bSuccess = CreateEx(WS_EX_DLGMODALFRAME|WS_EX_TOPMOST,
csClassName,
pszTitle,
WS_POPUP|WS_BORDER|WS_CAPTION,
0,0,
390,130,
pParent->GetSafeHwnd(),
0,
NULL);
if (!bSuccess)
return FALSE;
CRect TempRect(0,0,10,10);
bSuccess = m_Text.Create(_T(""), WS_CHILD|WS_VISIBLE|SS_NOPREFIX|SS_LEFTNOWORDWRAP,
TempRect, this, IDC_TEXT);
if (!bSuccess)
return FALSE;
DWORD dwProgressStyle = WS_CHILD|WS_VISIBLE;
#ifdef PBS_SMOOTH
if (bSmooth)
dwProgressStyle |= PBS_SMOOTH;
#endif
bSuccess = m_wndProgress.Create(dwProgressStyle, TempRect, this, IDC_PROGRESS);
if (!bSuccess)
return FALSE;
bSuccess = m_CancelButton.Create(m_strCancelLabel,
WS_CHILD|WS_VISIBLE|WS_TABSTOP| BS_PUSHBUTTON,
TempRect, this, IDC_CANCEL);
if (!bSuccess)
return FALSE;
if (m_bEnableRetry)
{
bSuccess = m_RetryButton.Create(m_strRetryLabel,
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
TempRect, this, IDC_RETRY);
if (bSuccess)
m_RetryButton.SetFont(&m_font, TRUE);
}
m_CancelButton.SetFont(&m_font, TRUE);
m_Text.SetFont(&m_font, TRUE);
SetWindowSize(m_nNumTextLines, 390);
if (m_bPersistantPosition)
GetPreviousSettings();
else
CenterWindow();
Show();
return TRUE;
}
BOOL CProgressWnd::GoModal(LPCTSTR pszTitle , BOOL bSmooth )
{
CWnd *pMainWnd = AfxGetMainWnd();
if (!::IsWindow(m_hWnd) && !Create(pMainWnd, pszTitle, bSmooth))
return FALSE;
CWnd * wnd = this;
do {
CWnd * parent = wnd->GetParent();
if (!parent || !parent->IsWindowEnabled()) {
m_wRenenableWnd = wnd;
m_wRenenableWnd->EnableWindow(false);
break;
}
wnd = parent;
} while (1);
EnableWindow(TRUE);
m_bModal = TRUE;
return TRUE;
}
void CProgressWnd::SetWindowSize(int nNumTextLines, int nWindowWidth )
{
int nMargin = 10;
CSize EdgeSize(::GetSystemMetrics(SM_CXEDGE), ::GetSystemMetrics(SM_CYEDGE));
CRect TextRect, CancelRect, ProgressRect, RetryRect;
CSize CancelSize;
TextRect.SetRect(nMargin,nMargin, nWindowWidth-2*nMargin, 100+2*nMargin);
CDC* pDC = GetDC();
if (pDC)
{
CFont* pOldFont = pDC->SelectObject(&m_font);
CString str = _T("M");
for (int i = 0; i < nNumTextLines-1; i++)
str += _T("\nM");
pDC->DrawText(str, TextRect, DT_CALCRECT|DT_NOCLIP|DT_NOPREFIX);
TextRect.right = TextRect.left + nWindowWidth;
CancelSize = pDC->GetTextExtent(m_strCancelLabel + _T(" ")) +
CSize(EdgeSize.cx*4, EdgeSize.cy*3);
pDC->SelectObject(pOldFont);
ReleaseDC(pDC);
}
CancelRect.SetRect(TextRect.right-CancelSize.cx, TextRect.bottom+nMargin,
TextRect.right, TextRect.bottom+nMargin + CancelSize.cy);
RetryRect = CancelRect;
RetryRect.OffsetRect(CSize(0, -22));
ProgressRect.SetRect(TextRect.left, CancelRect.top + EdgeSize.cy,
CancelRect.left-nMargin, CancelRect.bottom - EdgeSize.cy);
CSize ClientSize(nMargin + TextRect.Width() + nMargin,
nMargin + TextRect.Height() + nMargin + CancelRect.Height() + nMargin);
CRect WndRect, ClientRect;
GetWindowRect(WndRect); GetClientRect(ClientRect);
WndRect.right = WndRect.left + WndRect.Width()-ClientRect.Width()+ClientSize.cx;
WndRect.bottom = WndRect.top + WndRect.Height()-ClientRect.Height()+ClientSize.cy;
MoveWindow(WndRect);
m_wndProgress.MoveWindow(ProgressRect);
m_CancelButton.MoveWindow(CancelRect);
if (m_bEnableRetry)
{
m_RetryButton.EnableWindow(FALSE);
m_RetryButton.MoveWindow(RetryRect);
TextRect.right -= RetryRect.Width() + 2;
}
m_Text.MoveWindow(TextRect);
}
void CProgressWnd::Clear()
{
SetText(_T(""));
SetPos(0);
m_bCancelled = FALSE;
m_nPrevPos = 0;
if (::IsWindow(GetSafeHwnd()))
UpdateWindow();
}
void CProgressWnd::Hide()
{
if (!::IsWindow(GetSafeHwnd()))
return;
if (IsWindowVisible())
{
ShowWindow(SW_HIDE);
ModifyStyle(WS_VISIBLE, 0);
}
}
void CProgressWnd::Show()
{
if (!::IsWindow(GetSafeHwnd()))
return;
if (!IsWindowVisible())
{
ModifyStyle(0, WS_VISIBLE);
ShowWindow(SW_SHOW);
RedrawWindow(NULL,NULL,RDW_ERASE|RDW_FRAME|RDW_INVALIDATE|RDW_UPDATENOW);
}
}
void CProgressWnd::SetRange(int nLower, int nUpper, int nStep )
{
if (!::IsWindow(GetSafeHwnd()))
return;
#ifdef PBM_SETRANGE32
ASSERT(-0x7FFFFFFF <= nLower && nLower <= 0x7FFFFFFF);
ASSERT(-0x7FFFFFFF <= nUpper && nUpper <= 0x7FFFFFFF);
m_wndProgress.SendMessage(PBM_SETRANGE32, (WPARAM) nLower, (LPARAM) nUpper);
#else
ASSERT(0 <= nLower && nLower <= 65535);
ASSERT(0 <= nUpper && nUpper <= 65535);
m_wndProgress.SetRange(nLower, nUpper);
#endif
m_nMaxValue = nUpper;
m_nMinValue = nLower;
m_nStep = nStep;
m_wndProgress.SetStep(nStep);
}
int CProgressWnd::OffsetPos(int nPos)
{
if (!::IsWindow(GetSafeHwnd()))
return m_nPrevPos;
Show();
return SetPos(m_nPrevPos + nPos);
}
int CProgressWnd::StepIt()
{
if (!::IsWindow(GetSafeHwnd()))
return m_nPrevPos;
Show();
return SetPos(m_nPrevPos + m_nStep);
}
int CProgressWnd::SetStep(int nStep)
{
int nOldStep = m_nStep;
m_nStep = nStep;
if (!::IsWindow(GetSafeHwnd()))
return nOldStep;
return m_wndProgress.SetStep(nStep);
}
int CProgressWnd::SetPos(int nPos)
{
#ifdef PBM_SETRANGE32
ASSERT(-0x7FFFFFFF <= nPos && nPos <= 0x7FFFFFFF);
#else
ASSERT(0 <= nPos && nPos <= 65535);
#endif
if (!::IsWindow(GetSafeHwnd()))
return m_nPrevPos;
Show();
CString strTitle;
int nPercentage;
m_nPrevPos = nPos;
if (m_nMaxValue > m_nMinValue)
nPercentage = (int) (((nPos - m_nMinValue)*100.0)/(m_nMaxValue - m_nMinValue) + 0.5);
else
nPercentage = 0;
if (nPercentage != m_nPrevPercent)
{
m_nPrevPercent = nPercentage;
strTitle.Format(_T("%s [%d%%]"),m_strTitle,nPercentage);
SetWindowText(strTitle);
}
return m_wndProgress.SetPos(nPos);
}
void CProgressWnd::SetText(LPCTSTR fmt, ...)
{
if (!::IsWindow(GetSafeHwnd()))
return;
va_list args;
va_start(args, fmt);
_vstprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
m_Text.SetWindowText(buffer);
}
BEGIN_MESSAGE_MAP(CProgressWnd, CWnd)
ON_WM_ERASEBKGND()
ON_BN_CLICKED(IDC_CANCEL, OnCancel)
ON_BN_CLICKED(IDC_RETRY, OnRetry)
END_MESSAGE_MAP()
BOOL CProgressWnd::OnEraseBkgnd(CDC* pDC)
{
CBrush backBrush(GetSysColor(COLOR_BTNFACE));
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect);
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOldBrush);
return TRUE;
}
void CProgressWnd::OnRetry()
{
}
void CProgressWnd::OnCancel()
{
if (m_bPersistantPosition)
SaveCurrentSettings();
m_bCancelled = TRUE;
Hide();
if (m_bModal)
PostMessage(WM_CLOSE);
CWnd *pWnd = AfxGetMainWnd();
if (pWnd && ::IsWindow(pWnd->m_hWnd))
pWnd->SetForegroundWindow();
}
BOOL CProgressWnd::DestroyWindow()
{
if (m_bPersistantPosition)
SaveCurrentSettings();
if (m_bModal)
{
m_bModal = FALSE;
CWnd *pMainWnd = AfxGetMainWnd();
if (m_wRenenableWnd)
m_wRenenableWnd->EnableWindow(TRUE);
}
return CWnd::DestroyWindow();
}
void CProgressWnd::PeekAndPump(BOOL bCancelOnESCkey )
{
if (m_bModal && ::GetFocus() != m_hWnd)
SetFocus();
MSG msg;
while (!m_bCancelled && ::PeekMessage(&msg, NULL,0,0,PM_NOREMOVE))
{
if (bCancelOnESCkey && (msg.message == WM_CHAR) && (msg.wParam == VK_ESCAPE))
OnCancel();
if (m_bModal && (msg.message == WM_LBUTTONUP))
{
CRect rect;
m_CancelButton.GetWindowRect(rect);
if (rect.PtInRect(msg.pt))
OnCancel();
m_RetryButton.GetWindowRect(rect);
if (rect.PtInRect(msg.pt))
OnRetry();
}
if (!AfxGetApp()->PumpMessage())
{
::PostQuitMessage(0);
return;
}
}
}
void CProgressWnd::GetPreviousSettings()
{
int x = AfxGetApp()->GetProfileInt(szSection, szEntryX, -1);
int y = AfxGetApp()->GetProfileInt(szSection, szEntryY, -1);
if (x >= 0 && x < GetSystemMetrics(SM_CXSCREEN) &&
y >= 0 && y < GetSystemMetrics(SM_CYSCREEN))
{
SetWindowPos(NULL, x,y, 0,0, SWP_NOSIZE|SWP_NOZORDER);
}
else
CenterWindow();
}
void CProgressWnd::SaveCurrentSettings()
{
if (!IsWindow(m_hWnd))
return;
CRect rect;
GetWindowRect(rect);
AfxGetApp()->WriteProfileInt(szSection, szEntryX, rect.left);
AfxGetApp()->WriteProfileInt(szSection, szEntryY, rect.top);
}
CProgressFuncWnd::CProgressFuncWnd()
{
CommonConstruct();
m_bTimeOut = FALSE;
m_startTime = 0;
m_RemainingTime = 0;
bWaitingForUserAction = FALSE;
};
CProgressFuncWnd::CProgressFuncWnd(CWnd* pParent, LPCTSTR pszTitle, CustomProcessFunc myfunc, DWORD dwTimeOut, BOOL bSmooth , BOOL bEnableRetry )
{
CommonConstruct();
bWaitingForUserAction = FALSE;
m_bEnableRetry = bEnableRetry;
m_dwTimeOut = dwTimeOut;
m_strTitle = pszTitle;
m_myfunc = myfunc;
m_nWindowTimer = NULL;
Create(pParent, pszTitle, bSmooth);
}
void CProgressFuncWnd::Show()
{
CProgressWnd::Show();
if (!::IsWindow(GetSafeHwnd()))
return;
if (IsWindowVisible())
{
SetRange(0, m_dwTimeOut);
}
}
BEGIN_MESSAGE_MAP(CProgressFuncWnd, CProgressWnd)
ON_WM_TIMER()
ON_BN_CLICKED(IDC_RETRY, CProgressFuncWnd::OnRetry)
END_MESSAGE_MAP()
void CProgressFuncWnd::OnTimeOut()
{
if (m_bPersistantPosition)
SaveCurrentSettings();
m_bTimeOut = TRUE;
Hide();
if (m_bModal)
PostMessage(WM_CLOSE);
CWnd *pWnd = AfxGetMainWnd();
if (pWnd && ::IsWindow(pWnd->m_hWnd))
pWnd->SetForegroundWindow();
}
void CProgressFuncWnd::LaunchProcess()
{
std::this_thread::sleep_for(std::chrono::milliseconds(30));
m_startTime = GetTickCount();
m_taskResult = std::async(m_myfunc,this, m_dwTimeOut);
}
void CProgressFuncWnd::OnRetry()
{
m_Text.SetWindowText(buffer);
m_RetryButton.EnableWindow(FALSE);
bWaitingForUserAction = FALSE;
RedrawWindow();
LaunchProcess();
}
INT_PTR CProgressFuncWnd::WaitForEnd(DWORD &milis)
{
CString strTitle;
std::future_status status;
if (Cancelled())
{
return IDCANCEL;
}
if (!bWaitingForUserAction)
{
milis = GetTickCount() - m_startTime;
status = m_taskResult.wait_for(std::chrono::milliseconds(0));
if (status == std::future_status::ready)
{
if (m_taskResult.get() == TRUE)
{
RedrawWindow();
strTitle.Format(_T("%s - Operation Completed!"), m_strTitle);
SetWindowText(strTitle);
return IDOK;
}
return IDABORT;
}
if (milis >= m_dwTimeOut)
{
if (!m_bEnableRetry)
{
OnTimeOut();
return IDTIMEOUT;
}
else
{
milis = 0;
SetPos(0);
m_nPrevPos = 0;
m_bTimeOut = TRUE;
strTitle.Format(_T("%s - Operation Failed!"), m_strTitle);
SetWindowText(strTitle);
m_Text.SetWindowText(_T("Operation failed, click 'Retry' to try again or 'Cancel' to exit."));
bWaitingForUserAction = TRUE;
m_RetryButton.EnableWindow(TRUE);
return IDRETRY;
}
}
}
PeekAndPump();
if (!bWaitingForUserAction)
{
m_RemainingTime = m_dwTimeOut - (DWORD)milis;
strTitle.Format(_T("%s [%d%%] Remaining Time: %0.02f seconds"), m_strTitle, m_nPrevPercent, (double)((double)m_RemainingTime / 1000.0f));
SetWindowText(strTitle);
}
return IDRETRY;
}
Interface
#pragma once
#ifndef _INCLUDE_PROGRESSWND_H
#define _INCLUDE_PROGRESSWND_H
#include <future>
#include <functional>
#include <algorithm>
class CProgressWnd;
class CProgressFuncWnd;
class CProgressWnd : public CWnd
{
public:
CProgressWnd();
CProgressWnd(CWnd* pParent, LPCTSTR pszTitle, BOOL bSmooth = FALSE, BOOL bEnableRetry=FALSE);
virtual ~CProgressWnd();
BOOL Create(CWnd* pParent, LPCTSTR pszTitle, BOOL bSmooth = FALSE);
BOOL GoModal(LPCTSTR pszTitle =_T("Progress"), BOOL bSmooth = FALSE);
protected:
void CommonConstruct();
TCHAR buffer[512];
public:
void SetRange(int nLower, int nUpper, int nStep = 1);
int OffsetPos(int nPos);
int StepIt();
int SetStep(int nStep);
virtual int SetPos(int nPos);
void SetText(LPCTSTR fmt, ...);
void Clear();
void Hide();
virtual void Show();
BOOL Cancelled() { return m_bCancelled; }
void SetWindowSize(int nNumTextLines, int nWindowWidth = 390);
void PeekAndPump(BOOL bCancelOnESCkey = TRUE);
protected:
void GetPreviousSettings();
void SaveCurrentSettings();
protected:
CWnd * m_wRenenableWnd;
BOOL m_bEnableRetry;
BOOL m_bCancelled;
BOOL m_bModal;
BOOL m_bPersistantPosition;
int m_nPrevPos, m_nPrevPercent;
int m_nStep;
int m_nMaxValue, m_nMinValue;
int m_nNumTextLines;
CStatic m_Text;
CProgressCtrl m_wndProgress;
CButton m_CancelButton;
CButton m_RetryButton;
CString m_strTitle,
m_strCancelLabel,
m_strRetryLabel;
CFont m_font;
public:
virtual BOOL DestroyWindow();
protected:
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnCancel();
virtual afx_msg void OnRetry();
DECLARE_MESSAGE_MAP()
};
typedef std::function<BOOL(CProgressFuncWnd *pWnd, DWORD dwTimeOut)> CustomProcessFunc;
class CProgressFuncWnd : public CProgressWnd
{
protected:
BOOL bWaitingForUserAction;
DWORD m_startTime;
std::future<BOOL> m_taskResult;
BOOL m_bTimeOut;
UINT_PTR m_nWindowTimer;
DWORD m_dwTimeOut;
DWORD m_RemainingTime;
CustomProcessFunc m_myfunc;
void OnTimeOut();
public:
CProgressFuncWnd();
virtual void Show();
void LaunchProcess();
virtual afx_msg void OnRetry();
INT_PTR WaitForEnd(DWORD &milis);
CProgressFuncWnd(CWnd* pParent, LPCTSTR pszTitle, CustomProcessFunc myfunc,DWORD dwTimeOut,BOOL bSmooth, BOOL bEnableRetry = TRUE);
BOOL TimeOut() { return (m_bTimeOut && !m_bEnableRetry); }
BOOL WaitingUserRetry() {return bWaitingForUserAction; };
DECLARE_MESSAGE_MAP()
};
#endif
Usage example:
void COnLineDevices::OnBnClickedOk()
{
CProgressFuncWnd wndProgress(this, _T("Connecting to device"), std::bind(&COnLineDevices::MyCustomFunc,this, std::placeholders::_1, std::placeholders::_2),5000,FALSE,TRUE);
wndProgress.SetText(_T("Sending data to %s..."),_T("Device 01"));
wndProgress.GoModal();
wndProgress.LaunchProcess();
DWORD milis = 0;
INT_PTR res = IDRETRY;
while ((res=wndProgress.WaitForEnd(milis))==IDRETRY)
{
}
switch (res)
{
case IDCANCEL:
AfxMessageBox(_T("User aborted operation"), MB_ICONSTOP);
break;
case IDTIMEOUT:
AfxMessageBox(_T("Time out reached for operation!"), MB_ICONEXCLAMATION);
break;
case IDOK:
AfxMessageBox(_T("Operation completed on time!"),MB_ICONINFORMATION);
break;
case IDABORT:
AfxMessageBox(_T("Operation TERMINATED ERRONEUSLY!"), MB_ICONSTOP);
break;
}
CDialogEx::OnOK();
}
Custom function running on another thread, as we use std::function we could bind any function to this class easily without reimplementing different dialogs for each one.
BOOL COnLineDevices::MyCustomFunc(CProgressFuncWnd *pWnd,DWORD dwTimeOut)
{
if (!pWnd->WaitingUserRetry())
{
for (int i = 0; i < dwTimeOut; i++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (pWnd->TimeOut() || pWnd->Cancelled() || pWnd->WaitingUserRetry())
{
return FALSE;
}
pWnd->StepIt();
}
}
else
{
return TRUE;
}
return TRUE;
}
|