Skip to content

Instantly share code, notes, and snippets.

@davidruhmann
Created August 3, 2015 21:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidruhmann/d6e837e75311e8f9f9a4 to your computer and use it in GitHub Desktop.
Save davidruhmann/d6e837e75311e8f9f9a4 to your computer and use it in GitHub Desktop.
Visual C++ MFC Message Dialog Class
#include "stdafx.h"
#include "MessageDlg.h"
#include <dcp_colorscheme.h> // GetSchemeBrush
#include <Nursing_MsgAPI_Macros.h> // MSGWRITE*
// Static Value Definitions
#define MIN_HEIGHT 50
#define MIN_WIDTH 50
#define MAX_HEIGHT 500
#define MAX_WIDTH 500
#define MAX_MESSAGE_LINES 5
#define DEF_BUTTON_STYLE WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON
#define DEF_WINDOW_STYLE DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION
// Resource Handle
#ifdef SET_RESOURCE_HANDLE
#undef SET_RESOURCE_HANDLE
#endif
#define SET_RESOURCE_HANDLE AFX_MANAGE_STATE(AfxGetStaticModuleState()); AfxSetResourceHandle(AfxGetInstanceHandle());
BOOL CCCButton::Create(_In_ LPCTSTR sText, _In_ DWORD nStyle, _In_ const RECT& rect, _In_ CWnd* pParent, _In_ UINT nID)
{
SetText(sText);
return CCButton::Create(sText, nStyle, rect, pParent, nID);
}
int CCCButton::GetWindowText(_Out_writes_(nMaxCount) LPTSTR sBuffer, _In_ int nMaxCount) const
{
_tcsncpy_s(sBuffer, nMaxCount, m_sText, _TRUNCATE);
return _tcsnlen(sBuffer, nMaxCount);
}
void CCCButton::GetWindowText(_Out_ CString& sString) const
{
sString = m_sText;
}
void CCCButton::SetText(_In_ LPCTSTR sText)
{
m_sText = sText;
}
void CCCButton::SetWindowText(_In_ LPCTSTR sText) {
SetText(sText);
CCButton::SetWindowText(sText);
}
IMPLEMENT_DYNAMIC(CMessageDlg, CCDialog) // RTTI support
// Message Map Declaration
BEGIN_MESSAGE_MAP(CMessageDlg, CCDialog)
ON_WM_CREATE()
ON_WM_DESTROY()
ON_WM_SIZE()
ON_BN_CLICKED(EControl::eButtonOK, OnOK)
ON_BN_CLICKED(EControl::eButtonCancel, OnCancel)
ON_BN_CLICKED(EControl::eButtonAbort, OnAbort)
ON_BN_CLICKED(EControl::eButtonRetry, OnRetry)
ON_BN_CLICKED(EControl::eButtonIgnore, OnIgnore)
ON_BN_CLICKED(EControl::eButtonYes, OnYes)
ON_BN_CLICKED(EControl::eButtonNo, OnNo)
ON_BN_CLICKED(EControl::eButtonClose, OnClose)
ON_BN_CLICKED(EControl::eButtonHelp, OnHelp)
ON_BN_CLICKED(EControl::eButtonTryAgain, OnTryAgain)
ON_BN_CLICKED(EControl::eButtonContinue, OnContinue)
ON_CONTROL_RANGE(BN_CLICKED, EControl::eButtonCustom, EControl::eButtonCustomEnd, OnCustom)
ON_CONTROL_RANGE(BN_CLICKED, EControl::eButtonOK, EControl::eButtonContinue, OnClickButton)
ON_CONTROL_RANGE(BN_CLICKED, EControl::eButtonCustom, EControl::eButtonCustomEnd, OnClickCustom)
ON_WM_PAINT()
ON_WM_CTLCOLOR()
ON_WM_GETMINMAXINFO()
END_MESSAGE_MAP()
////////////////////////////////////////////////////////////////////////////////
/// \brief Default CMessageDlg Constructor
/// \details Create the Reason Dialog
/// \param[in] sTitle - title to display on the reason dialog (default = NULL)
/// \param[in] sMessage - message to display on the reason dialog (default = NULL)
/// \param[in] eButtons - Reason dialog button type (default = eOKCancel)
/// \param[in] eIcon - Reason dialog message icons (default = eNone)
/// \param[in] pParent - CWnd pointer to parent window (default = NULL)
/// \pre none
/// \post Reason Dialog object is created.
////////////////////////////////////////////////////////////////////////////////
CMessageDlg::CMessageDlg(_In_opt_ LPCTSTR sTitle /*= NULL*/, _In_opt_ LPCTSTR sMessage /*= NULL*/, _In_ EButtons eButtons /*= eOKCancel*/, _In_ EIcon eIcon /*= eNone*/, _In_opt_ CWnd* pParent /*= NULL*/)
: CCDialog(),
m_pParent(pParent),
m_sTitle(sTitle),
m_sMessage(sMessage),
m_MinSize(0, 0),
m_MaxSize(0, 0),
m_eButtons(eButtons),
m_eIcon(eIcon),
m_eNextButtonID(eButtonCustom),
m_eNextControlID(eReservedID), // TODO Test with derived class
m_nWindowStyle(DEF_WINDOW_STYLE),
m_nButtonStyle(DEF_BUTTON_STYLE),
m_ButtonList(),
m_ButtonMap()
{
MSGWRITETRACE(__FUNCTION__);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Default CMessageDlg Destructor
/// \details Deletes all allocated memory.
/// \pre none
/// \post Memory is freed
////////////////////////////////////////////////////////////////////////////////
CMessageDlg::~CMessageDlg()
{
MSGWRITETRACE(__FUNCTION__);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Add a button to the message dialog.
/// \return CButton* = pointer to the newly added button.
/// \param[in] sText
/// \param[in] eButtonID
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
CButton* CMessageDlg::AddButton(_In_opt_ LPCTSTR sText /*= NULL*/, _In_ EControl eButtonID /*= EControl::eButonCustom*/)
{
MSGWRITETRACE(__FUNCTION__);
SetLastError(ERROR_SUCCESS);
// Validate Button ID
if (eInvalidID == eButtonID)
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Invalid Control ID encountered. Task aborted."));
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
// Create New Button
CCCButton* pButton = new CCCButton();
if (NULL == pButton)
{
ASSERT(pButton);
MSGWRITEERRORF(__FUNCTION__, _T("Failed to create new button: %u. Task aborted."), eButtonID);
SetLastError(ERROR_FUNCTION_FAILED);
return NULL;
}
// Get next Control ID
if (eButtonCustom == eButtonID)
{
eButtonID = GetNextButtonID();
MSGWRITEDEBUGF(__FUNCTION__, _T("Custom control, using next available ID: %u"), eButtonID);
}
// Remove Existing Button
if (RemoveButton(eButtonID))
{
MSGWRITEDEBUGF(__FUNCTION__, _T("Existing button with same ID %u was removed."), eButtonID);
}
// Create Window
if (false == CreateButton(*pButton, CString(sText), eButtonID))
{
MSGWRITEERRORF(__FUNCTION__, _T("Failed to add button control: %u. Task aborted."), eButtonID);
SetLastError(ERROR_CREATE_FAILED);
delete pButton;
return NULL;
}
// Add Button
m_ButtonMap.SetAt(eButtonID, pButton);
m_ButtonList.AddTail(eButtonID);
MSGWRITEDEBUGF(__FUNCTION__, _T("Created button: %u"), eButtonID);
return pButton;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Calculate the window growth needed for the control's contents.
/// \remarks The growth is a positive or negative amount from the current size.
/// example: cx = +10 because my control's contents need 10 more units to fit.
/// \return void
/// \param[in,out] size - input current growth needed, update with max growth.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::CalculateContentsGrowth(_Inout_ CSize& size)
{
// Calculate the Message text box width needed vs what it actually has (offset)
CRect rect(0, 0, 0, 0);
m_edtMessage.GetClientRect(rect);
LONG nOffset = (CalculateWidestLineSize(m_edtMessage.GetSafeHwnd()).cx - (rect.Width() - (2 * GetSystemMetrics(SM_CXEDGE))));
size.cx = max(nOffset, size.cx);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Determine the height and width of a string of text.
/// \return CSize = size of the string. Upon failure size of 0 is returned.
/// For extended error details check GetLastError()
/// ERROR_SUCCESS means success
/// ERROR_NO_DATA means the string is empty. (0)
/// ERROR_INVALID_PARAMETER means the hWnd is NULL. (0)
/// ERROR_INVALID_HANDLE means no window handles could be retrieved. (0)
/// ERROR_INVALID_OPERATION means the device context failed. (0)
/// ERROR_FUNCTION_FAILED means the calculation failed on one of the lines. (?)
/// \param[in] hWnd - Handle of the window for calculation context.
/// \param[in] sText = NULL (window text) - String to measure.
/// \param[in] nWidth = 0 (actual text) - Width to measure against. If < 0, use
/// the window's client width. Value only used if > 0.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
CSize CMessageDlg::CalculateTextSize(_In_ HWND hWnd, _In_opt_ LPCTSTR sText /*= NULL*/, _In_opt_ int nWidth /*= 0*/)
{
MSGWRITETRACE(__FUNCTION__);
SetLastError(ERROR_SUCCESS);
// Verify Parameters
if (NULL == hWnd)
{
ASSERT(hWnd);
MSGWRITEERROR(__FUNCTION__, _T("Invalid handle encountered. Task aborted."));
SetLastError(ERROR_INVALID_PARAMETER);
return CSize(0, 0);
}
// Convert to CWnd
CWnd* pWnd = CWnd::FromHandle(hWnd);
if (NULL == pWnd)
{
ASSERT(pWnd);
MSGWRITEERROR(__FUNCTION__, _T("Invalid handle encountered. Task aborted."));
SetLastError(ERROR_INVALID_HANDLE);
return CSize(0, 0);
}
// Retrieve Text
CString sString(sText);
if (NULL == sText || sString.IsEmpty())
{
pWnd->GetWindowText(sString);
if (sString.IsEmpty())
{
MSGWRITEWARN(__FUNCTION__, _T("Nothing to calculate, string is empty. Task aborted."));
SetLastError(ERROR_NO_DATA);
return CSize(0, 0);
}
}
// Retrieve Device Context
CDC* pDC = pWnd->GetDC();
ASSERT(pDC);
if (NULL == pDC)
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to get device context. Task aborted."));
SetLastError(ERROR_INVALID_OPERATION);
return CSize(0, 0);
}
// Save Current Context
int nSavedDC = pDC->SaveDC();
if (0 == nSavedDC)
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to save device context."));
}
// Switch to the control's Font
CFont* myFont = pWnd->GetFont();
CFont* exFont = pDC->SelectObject(myFont);
// Determine the Width to use: Given if > 0, Client if < 0, Text if == 0.
CRect rect(0, 0, nWidth, 0);
if (nWidth < 0)
{
CRect client;
pWnd->GetClientRect(client);
rect.right = client.right;
}
else if (0 == nWidth)
{
rect.right = CalculateWidestLineSize(hWnd, sString).cx;
}
// Calculate the Height
pDC->DrawText(sString, rect, DT_CALCRECT | DT_WORDBREAK);
// Convert to Size
CSize size;
size.cx = rect.Width();
size.cy = rect.Height();
// Retrieve the Text Metrics
TEXTMETRIC tm;
if (0 == pDC->GetTextMetrics(&tm))
{
tm = {0};
MSGWRITEERROR(__FUNCTION__, _T("Failed to retrieve text metrics."));
}
// Add the average width to prevent clipping
size.cx += tm.tmAveCharWidth;
// Add the external leading to prevent clipping
//size.cy += tm.tmExternalLeading;
// Convert Measurement
pDC->LPtoDP(&size);
// Restore the previous Font
pDC->SelectObject(exFont);
// Restore the saved device context
if (0 != nSavedDC)
{
if (0 == pDC->RestoreDC(nSavedDC))
{
MSGWRITEERRORF(__FUNCTION__, _T("Failed to restore the saved device context: %d. May exhibit drawing issues."), nSavedDC);
}
}
// Release the device context
if (0 == pWnd->ReleaseDC(pDC))
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to release device context. Memory leaked."));
}
return size;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Determine the length of the widest line of text.
/// \remarks The height returned is just the height of a single line.
/// \return CSize = size of the widest line. Upon failure size of 0 is returned.
/// For extended error details check GetLastError()
/// ERROR_SUCCESS means success
/// ERROR_NO_DATA means the string is empty. (0)
/// ERROR_INVALID_PARAMETER means the hWnd is NULL. (0)
/// ERROR_INVALID_HANDLE means no window handles could be retrieved. (0)
/// ERROR_INVALID_OPERATION means the device context failed. (0)
/// ERROR_FUNCTION_FAILED means the calculation failed on one of the lines. (?)
/// \param[in] hWnd - Handle of the window for calculation context.
/// \param[in] sText = NULL (window text) - String to measure.
/// \param[in] bSingle = false - Specify if the string is single line (strip CRLF).
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
CSize CMessageDlg::CalculateWidestLineSize(_In_ HWND hWnd, _In_opt_ LPCTSTR sText /*= NULL*/, _In_ bool bSingle /*= false*/)
{
MSGWRITETRACE(__FUNCTION__);
SetLastError(ERROR_SUCCESS);
// Verify Parameters
if (NULL == hWnd)
{
ASSERT(hWnd);
MSGWRITEERROR(__FUNCTION__, _T("Invalid handle encountered. Task aborted."));
SetLastError(ERROR_INVALID_PARAMETER);
return CSize(0, 0);
}
// Convert to CWnd
CWnd* pWnd = CWnd::FromHandle(hWnd);
if (NULL == pWnd)
{
ASSERT(pWnd);
MSGWRITEERROR(__FUNCTION__, _T("Invalid handle encountered. Task aborted."));
SetLastError(ERROR_INVALID_HANDLE);
return CSize(0, 0);
}
// Retrieve Text
CString sString(sText);
if (NULL == sText || sString.IsEmpty())
{
pWnd->GetWindowText(sString);
if (sString.IsEmpty())
{
MSGWRITEWARN(__FUNCTION__, _T("Nothing to calculate, string is empty. Task aborted."));
SetLastError(ERROR_NO_DATA);
return CSize(0, 0);
}
}
// Retrieve Device Context
CDC* pDC = pWnd->GetDC();
ASSERT(pDC);
if (NULL == pDC)
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to get device context. Task aborted."));
SetLastError(ERROR_INVALID_OPERATION);
return CSize(0, 0);
}
// Remove CR LF to get accurate width for non multi line controls
if (bSingle)
{
// CR - Carriage Return (\r)
if (sString.Remove(_T('\r')))
{
MSGWRITEDEBUG(__FUNCTION__, _T("Removed CR from the string."));
}
// LF - Line Feed (\n)
if (sString.Remove(_T('\n')))
{
MSGWRITEDEBUG(__FUNCTION__, _T("Removed LF from the string."));
}
}
// Switch to the control's Font
CFont* myFont = pWnd->GetFont();
CFont* exFont = pDC->SelectObject(myFont);
// Loop Setup
CSize size;
CSize widest;
CString sLine;
// Check the Width of each Line
for (int i = 0; AfxExtractSubString(sLine, sString, i); i++)
{
// Calculate the Text Width
if (0 == GetTextExtentPoint32(pDC->GetSafeHdc(), sLine, sLine.GetLength(), &size))
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to calculate size."));
SetLastError(ERROR_FUNCTION_FAILED);
size = CSize(0, 0);
}
// Remember the Widest
if (size.cx > widest.cx)
{
widest = size;
}
}
// Retrieve the Text Metrics
TEXTMETRIC tm;
if (0 == pDC->GetTextMetrics(&tm))
{
tm = {0};
MSGWRITEERROR(__FUNCTION__, _T("Failed to retrieve text metrics."));
}
// Add the average width to prevent clipping
widest.cx += tm.tmAveCharWidth;
// Verify the Height is large enough according to the metrics
widest.cy = widest.cy < (tm.tmHeight + tm.tmExternalLeading) ? (tm.tmHeight + tm.tmExternalLeading) : widest.cy;
// Convert Measurement
pDC->LPtoDP(&widest);
// Restore the previous Font
pDC->SelectObject(exFont);
if (0 == pWnd->ReleaseDC(pDC))
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to release device context. Memory leaked."));
}
return widest;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Remove added buttons.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::ClearButtons()
{
MSGWRITETRACE(__FUNCTION__);
// Remove Buttons by List
POSITION pos = m_ButtonList.GetHeadPosition();
while (pos)
{
if (false == RemoveButton(m_ButtonList.GetNext(pos)))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to remove button. Possible memory leak."));
}
}
// Remove Buttons by Map (Double Check)
CButton* pValue = NULL;
EControl eKey = EControl::eInvalidID;
pos = m_ButtonMap.GetStartPosition();
while (pos)
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Button list and map are misaligned. Could cause memory leaks!"));
m_ButtonMap.GetNextAssoc(pos, eKey, pValue);
if (false == RemoveButton(eKey))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to remove button. Possible memory leak."));
}
}
}
bool CMessageDlg::CreateButton(_In_ CCCButton& button, _In_ const CString& sText, _In_ EControl eButtonID)
{
SetLastError(ERROR_SUCCESS);
// Validate Button ID
if (eInvalidID == eButtonID)
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Invalid Control ID encountered. Task aborted."));
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Validate Parent
if (FALSE == IsWindow(GetSafeHwnd()))
{
// TODO ENSURE vs ASSERT
button.SetWindowText(sText);
MSGWRITEDEBUG(__FUNCTION__, _T("Dialog not yet initialized. Button not created."));
SetLastError(ERROR_NOT_READY);
return true;
}
// Validate Text
if (sText.IsEmpty())
{
MSGWRITEWARNF(__FUNCTION__, _T("Empty button text encountered for ID: %u"), eButtonID);
}
// Create Window
CRect rect(0, 0, 0, 0);
if (FALSE == button.Create(sText, m_nButtonStyle, rect, this, eButtonID))
{
MSGWRITEERRORF(__FUNCTION__, _T("Failed to create button control: %u. Task aborted."), eButtonID);
SetLastError(ERROR_CREATE_FAILED);
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Create the added buttons.
/// \return bool
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
bool CMessageDlg::CreateButtons()
{
MSGWRITETRACE(__FUNCTION__);
// Create Buttons in the Map
CButton* pValue = NULL;
CCCButton* pButton = NULL;
EControl eKey = EControl::eInvalidID;
POSITION pos = m_ButtonMap.GetStartPosition();
while (pos)
{
m_ButtonMap.GetNextAssoc(pos, eKey, pValue);
pButton = dynamic_cast<CCCButton*>(pValue);
if (NULL == pValue)
{
ASSERT(NULL);
MSGWRITEERROR(__FUNCTION__, _T("Invalid button encountered. Attempting to continue."));
continue;
}
if (IsWindow(pValue->GetSafeHwnd()))
{
MSGWRITEDEBUG(__FUNCTION__, _T("Skipping button since it has already been created."));
continue;
}
CString sText;
pButton->GetWindowText(sText);
CreateButton(*pButton, sText, eKey);
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Called by the framework to exchange and validate dialog data.
/// \remarks Never call this function directly. It is called by the UpdateData
/// member function. Call UpdateData to initialize a dialog box's controls or
/// retrieve data from a dialog box.
/// \return void
/// \param[in] pDX - A pointer to a CDataExchange object.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::DoDataExchange(CDataExchange* pDX)
{
// Inherited Exchange
CCDialog::DoDataExchange(pDX);
}
////////////////////////////////////////////////////////////////////////////////
/// \details This member function handles all interaction with the user while the
/// dialog box is active. This is what makes the dialog box modal; that is, the
/// user cannot interact with other windows until the dialog box is closed.
/// \remarks https://msdn.microsoft.com/en-us/library/619z63f5.aspx
/// \return int = An int value that specifies the value of the nResult parameter
/// that was passed to the CDialog::EndDialog member function, which is used to
/// close the dialog box. The return value is -1 if the function could not create
/// the dialog box, or IDABORT if some other error occurred, in which case the
/// Output window will contain error information from GetLastError.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
INT_PTR CMessageDlg::DoModal()
{
// Set Limits
SetSizeLimits(MIN_WIDTH, MIN_HEIGHT, MAX_WIDTH, MAX_HEIGHT, false);
// Create Window
DWORD dwExStyle = WS_EX_CONTROLPARENT;
DWORD dwStyle = DS_MODALFRAME | m_nWindowStyle;
if (FALSE == CreateModal(dwExStyle, m_sTitle, dwStyle, 0, 0, GetSystemMetrics(SM_CXMIN), GetSystemMetrics(SM_CYMIN), m_pParent))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to create window. Task aborted."));
return EResult::eFailed;
}
// Display Dialog
return CCDialog::DoModal();
}
// TODO Modeless
////////////////////////////////////////////////////////////////////////////////
/// \brief Set the control to focus upon.
/// \remarks Make sure to call this in OnInitDialog and return this result.
/// \return bool = true on success, false on failure.
/// \param[in] hWnd - handle to the control to put focus.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
bool CMessageDlg::FocusOn(_In_ HWND hWnd)
{
MSGWRITETRACE(__FUNCTION__);
CWnd* pWnd = CWnd::FromHandle(hWnd);
if (NULL == pWnd)
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to retrieve window handle. Task aborted"));
return false;
}
GotoDlgCtrl(pWnd);
pWnd->SetFocus();
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Retrieve the next available button ID.
/// \return EControl = button ID upon success, eInvalidID on failure.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
CMessageDlg::EControl CMessageDlg::GetNextButtonID()
{
m_eNextButtonID = m_eNextButtonID >= eButtonCustomEnd ? eInvalidID : static_cast<EControl>(m_eNextButtonID + 1);
MSGWRITEDEBUGF(__FUNCTION__, _T("Next Button ID: %u"), m_eNextButtonID);
return m_eNextButtonID;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Retrieve the next available control ID.
/// \return EControl = control ID upon success, eInvalidID on failure.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
CMessageDlg::EControl CMessageDlg::GetNextControlID()
{
m_eNextControlID = m_eNextControlID >= MAXUINT ? eInvalidID : static_cast<EControl>(m_eNextControlID + 1);
MSGWRITEDEBUGF(__FUNCTION__, _T("Next Control ID: %u"), m_eNextControlID);
return m_eNextControlID;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Retrieves the text metrics for a given window.
/// \return bool = true on success, false on failure.
/// For extended error details check GetLastError()
/// ERROR_SUCCESS means success (true)
/// ERROR_INVALID_PARAMETER means the hWnd is NULL. (false)
/// ERROR_INVALID_HANDLE means no window handles could be retrieved. (false)
/// ERROR_INVALID_OPERATION means the device context failed. (false)
/// ERROR_FUNCTION_FAILED means that the TEXTMETRIC was not retrieved. (false)
/// ERROR_OBJECT_NOT_FOUND means the device context was leaked. (true)
/// \param[in] hWnd - Handle of the window for context. If NULL, use window.
/// \param[in] tm - TEXTMETRIC to store the results. Values set to 0 upon failure.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
bool CMessageDlg::GetTextMetrics(_In_ HWND hWnd, _Out_ TEXTMETRIC& tm)
{
MSGWRITETRACE(__FUNCTION__);
SetLastError(ERROR_SUCCESS);
// Initialize
tm = {0};
// Verify Parameters
if (NULL == hWnd)
{
ASSERT(hWnd);
MSGWRITEERROR(__FUNCTION__, _T("Invalid handle encountered. Task aborted."));
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
// Convert to CWnd
CWnd* pWnd = CWnd::FromHandle(hWnd);
if (NULL == pWnd)
{
ASSERT(pWnd);
MSGWRITEERROR(__FUNCTION__, _T("Invalid handle encountered. Task aborted."));
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
// Retrieve Device Context
CDC* pDC = pWnd->GetDC();
ASSERT(pDC);
if (NULL == pDC)
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to get device context. Task aborted."));
SetLastError(ERROR_INVALID_OPERATION);
return false;
}
// Switch to the control's Font
CFont* myFont = pWnd->GetFont();
CFont* exFont = pDC->SelectObject(myFont);
// Retrieve the Text Metrics
bool bResult(true);
if (0 == pDC->GetTextMetrics(&tm))
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to retrieve text metrics."));
SetLastError(ERROR_FUNCTION_FAILED);
bResult = false;
}
// Restore the previous Font
pDC->SelectObject(exFont);
// Release the device context
if (0 == pWnd->ReleaseDC(pDC))
{
MSGWRITEERROR(__FUNCTION__, _T("Failed to release device context. Memory leaked."));
SetLastError(ERROR_OBJECT_NOT_FOUND);
}
return bResult;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Load the Message Icon based upon the type given.
/// \remarks Attempts to load system icon upon failure of custom icon.
/// \return HICON = handle to the icon if successfully loaded, else NULL.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
HICON CMessageDlg::LoadMessageIcon()
{
MSGWRITETRACE(__FUNCTION__);
// Load Resource Location
SET_RESOURCE_HANDLE;
// Identify Icon
UINT nID = 0U;
LPTSTR sID = NULL;
switch (m_eIcon)
{
case eWarning:
nID = IDI_WARNING_4017;
sID = IDI_WARNING;
break;
case eInfo:
nID = IDI_INFORMATION_5104;
sID = IDI_INFORMATION;
break;
case eError:
nID = IDI_ERROR_6275;
sID = IDI_ERROR;
break;
case eAlert:
nID = IDI_ALERT_6047;
break;
case eQuestion:
sID = IDI_QUESTION;
break;
case eNone:
default:
break;
}
// Load Icon
HICON hIcon = NULL;
if (nID > 0U)
{
hIcon = static_cast<HICON>(LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(nID), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
}
if (NULL == hIcon)
{
MSGWRITEWARN(__FUNCTION__, _T("Failed to load resource icon. Attempting to continue."));
// Load System Icon
if (NULL != sID)
{
hIcon = static_cast<HICON>(LoadImage(NULL, sID, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
}
if (NULL == hIcon)
{
MSGWRITEWARN(__FUNCTION__, _T("Failed to load system icon. Attempting to continue."));
}
}
return hIcon;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Abort button on click event handler ON_BN_CLICKED
/// \remarks Ends the dialog with EResult::eAbort result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnAbort()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eAbort);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Cancel button on click event handler ON_BN_CLICKED, Alt + F4, Escape.
/// \remarks Ends the dialog with EResult::eCancel result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnCancel()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eCancel);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Button on click event handler. BN_CLICKED for control range.
/// \remarks Ends the dialog with nID result.
/// \return void
/// \param[in] nID - Control ID for the button clicked.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnClickButton(_In_ UINT nID)
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(nID);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Custom button on click event handler. BN_CLICKED for control range.
/// \remarks Ends the dialog with nID result.
/// \return void
/// \param[in] nID - Control ID for the button clicked.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnClickCustom(_In_ UINT nID)
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(nID);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Close button on click event handler. ON_BN_CLICKED
/// \remarks Ends the dialog with EResult::eClose result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnClose()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eClose);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Continue button on click event handler. ON_BN_CLICKED
/// \remarks Ends the dialog with EResult::eContinue result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnContinue()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eContinue);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief The framework calls this member function when a child control is about to be drawn.
/// \details Powerchart UI Refresh Color scheme API's are defined in <dcp_colorscheme.h>.
/// To paint the control background as White, call the GetSchemeBrush(gBodyBackgroundBrush) API
/// which returns the Brush to be used to paint the control background with the predetermined color.
/// \remarks ON_WM_CTLCOLOR
/// \return HBRUSH = a handle to the brush that is to be used for painting the control background.
/// \param[in] pDC - Contains a pointer to the display context for the child window. May be temporary.
/// \param[in] pWnd - Contains a pointer to the control asking for the color. May be temporary.
/// \param[in] nCtlColor - Contains a value which specifies the type of control.
/// \pre none
/// \post Color the static controls
////////////////////////////////////////////////////////////////////////////////
HBRUSH CMessageDlg::OnCtlColor(_In_ CDC* pDC, _In_ CWnd* pWnd, _In_ UINT nCtlColor)
{
// Inherited Control Coloring
HBRUSH hbr = CCDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// Control Coloring Overrides
switch (nCtlColor)
{
// Static Controls
case CTLCOLOR_STATIC:
if (NULL != pDC)
{
pDC->SetBkMode(TRANSPARENT);
}
else
{
ASSERT(pDC);
MSGWRITEERRORF(__FUNCTION__, _T("Invalid device context encountered for static control."));
}
hbr = ::GetSchemeBrush(gBodyBackgroundBrush);
break;
default:
break;
}
return hbr;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Dialog on creation event handler.
/// \details The framework calls this member function when an application
/// requests that the window be created by calling the Create member function.
/// \remarks https://msdn.microsoft.com/en-us/library/384x0633.aspx
/// \return int = OnCreate must return 0 to continue the creation of the CWnd object.
/// If the application returns -1, the window will be destroyed.
/// \param[in] lpCreateStruct - Points to a CREATESTRUCT structure that contains
/// information about the CWnd object being created.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
int CMessageDlg::OnCreate(_In_ LPCREATESTRUCT lpCreateStruct)
{
MSGWRITETRACE(__FUNCTION__);
// Inherited Creation
if (CCDialog::OnCreate(lpCreateStruct) == -1)
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to create window from parent. Task aborted."));
EndDialog(EResult::eFailed);
return -1;
}
// Load Resource Location
SET_RESOURCE_HANDLE;
// Invisible Rectangle
CRect rect(0, 0, 0, 0);
// Icon image
if (FALSE == m_imgIcon.Create(NULL, WS_CHILD | WS_VISIBLE | SS_ICON, rect, this, EControl::eIcon))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to create icon control. Task aborted."));
EndDialog(EResult::eFailed);
return -1;
}
// Load Icon
HICON hIcon = LoadMessageIcon();
if (NULL != hIcon)
{
SetMessageIcon(hIcon);
}
// Message text box
if (FALSE == m_edtMessage.Create(WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | ES_WANTRETURN, rect, this, EControl::eMessage))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to create message control. Task aborted."));
EndDialog(EResult::eFailed);
return -1;
}
m_edtMessage.SetWindowText(m_sMessage);
m_edtMessage.HideCaret();
// Remove border from message text box
if (0 == m_edtMessage.ModifyStyleEx(WS_EX_CLIENTEDGE, 0UL))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to modify message control extended style."));
}
// Create buttons added before creation
CreateButtons();
// Load First button text
UINT nStringID = IDS_OK;
EControl nControlID = EControl::eInvalidID;
switch (m_eButtons)
{
case EButtons::eYesNo:
case EButtons::eYesNoCancel:
nStringID = IDS_YES;
nControlID = EControl::eButtonYes;
break;
case EButtons::eRetryCancel:
nStringID = IDS_RETRY;
nControlID = EControl::eButtonRetry;
break;
case EButtons::eCancelTryContinue:
nStringID = IDS_CANCEL;
nControlID = EControl::eButtonCancel;
break;
case EButtons::eAbortRetryIgnore:
nStringID = IDS_ABORT;
nControlID = EControl::eButtonAbort;
break;
case EButtons::eOKCancel:
case EButtons::eOKOnly:
default:
nStringID = IDS_OK;
nControlID = EControl::eButtonOK;
break;
}
CString sText;
if (FALSE == sText.LoadString(nStringID))
{
sText.Empty();
MSGWRITEERRORF(__FUNCTION__, _T("Failed to load first button resource string: %u. Attempting to continue."), nStringID);
}
// First button
if (eInvalidID != nControlID
&& NULL == AddButton(sText, nControlID))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to create first button control. Task aborted."));
EndDialog(EResult::eFailed);
return -1;
}
// Load Second button text
switch (m_eButtons)
{
case EButtons::eYesNo:
case EButtons::eYesNoCancel:
nStringID = IDS_NO;
nControlID = EControl::eButtonNo;
break;
case EButtons::eOKCancel:
case EButtons::eRetryCancel:
nStringID = IDS_CANCEL;
nControlID = EControl::eButtonCancel;
break;
case EButtons::eCancelTryContinue:
nStringID = IDS_TRYAGAIN;
nControlID = EControl::eButtonTryAgain;
break;
case EButtons::eAbortRetryIgnore:
nStringID = IDS_RETRY;
nControlID = EControl::eButtonRetry;
break;
case EButtons::eOKOnly:
default:
nStringID = 0U;
nControlID = EControl::eInvalidID;
break;
}
if (FALSE == sText.LoadString(nStringID))
{
sText.Empty();
MSGWRITEERRORF(__FUNCTION__, _T("Failed to load second button resource string: %u. Attempting to continue."), nStringID);
}
// Second button
if (eInvalidID != nControlID
&& NULL == AddButton(sText, nControlID))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to create second button control. Task aborted."));
EndDialog(EResult::eFailed);
return -1;
}
// Load Third button text
switch (m_eButtons)
{
case EButtons::eYesNoCancel:
nStringID = IDS_CANCEL;
nControlID = EControl::eButtonCancel;
break;
case EButtons::eCancelTryContinue:
nStringID = IDS_CONTINUE;
nControlID = EControl::eButtonContinue;
break;
case EButtons::eAbortRetryIgnore:
nStringID = IDS_IGNORE;
nControlID = EControl::eButtonIgnore;
break;
case EButtons::eOKCancel:
case EButtons::eRetryCancel:
case EButtons::eYesNo:
case EButtons::eOKOnly:
default:
nStringID = 0U;
nControlID = EControl::eInvalidID;
break;
}
if (FALSE == sText.LoadString(nStringID))
{
sText.Empty();
MSGWRITEERRORF(__FUNCTION__, _T("Failed to load third button resource string: %u. Attempting to continue."), nStringID);
}
// Third button
if (eInvalidID != nControlID
&& NULL == AddButton(sText, nControlID))
{
ASSERT(false);
MSGWRITEERROR(__FUNCTION__, _T("Failed to create third button control. Task aborted."));
EndDialog(EResult::eFailed);
return -1;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Custom button on click event handler. BN_CLICKED for control range.
/// \remarks Ends the dialog with nID result.
/// \return void
/// \param[in] nID - Control ID for the button clicked.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnCustom(_In_ UINT nID)
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(nID);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Dialog on destroy event handler.
/// \details The framework calls this member function to inform the CWnd object
/// that it is being destroyed.
/// \remarks https://msdn.microsoft.com/en-us/library/2eahe3wf.aspx
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnDestroy()
{
// Clean up dynamically created controls
m_imgIcon.DestroyWindow();
m_edtMessage.DestroyWindow();
ClearButtons();
CCDialog::OnDestroy();
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Handler for the WM_GETMINMAXINFO message.
/// \details Sent to a window when the size or position of the window is about to
/// change. An application can use this message to override the window's default
/// maximized size and position, or its default minimum or maximum tracking size.
/// \remarks https://msdn.microsoft.com/en-us/library/windows/desktop/ms632626.aspx
/// \remarks Override the Minimum and Maximum tracking size and the Maximum size
/// when the window does not have a maximize button.
/// \param[in,out] lpMMI - A pointer to a MINMAXINFO structure that contains the
/// default maximized position and dimensions, and the default minimum and maximum
/// tracking sizes. An application can override the defaults by setting the members
/// of this structure.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnGetMinMaxInfo(_Inout_ MINMAXINFO* lpMMI)
{
MSGWRITETRACE(__FUNCTION__);
// Inherited Limits
CCDialog::OnGetMinMaxInfo(lpMMI);
// Validate Parameters
if (NULL == lpMMI)
{
ASSERT(lpMMI);
MSGWRITEERROR(__FUNCTION__, _T("Invalid MINMAXINFO reference encountered."));
return;
}
// Override Min and Max Tracking (Drag) Sizes
lpMMI->ptMinTrackSize = m_MinSize;
lpMMI->ptMaxTrackSize = m_MaxSize;
// Override Max Size when no Maximize button shown
if (false == (GetStyle() & WS_MAXIMIZEBOX))
{
lpMMI->ptMaxSize = m_MaxSize;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Help button on click event handler. ON_BN_CLICKED
/// \remarks Ends the dialog with EResult::eHelp result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnHelp()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eHelp);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Ignore button on click event handler ON_BN_CLICKED
/// \remarks Ends the dialog with EResult::eIgnore result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnIgnore()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eIgnore);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief This method is called in response to the WM_INITDIALOG message.
/// \remarks https://msdn.microsoft.com/en-us/library/fwz35s59.aspx
/// \return BOOL = Specifies whether the application has set the input focus to
/// one of the controls in the dialog box. If OnInitDialog returns nonzero,
/// Windows sets the input focus to the default location, the first control in
/// the dialog box. The application can return 0 only if it has explicitly set
/// the input focus to one of the controls in the dialog box.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
BOOL CMessageDlg::OnInitDialog()
{
MSGWRITETRACE(__FUNCTION__);
// Inherited Initialization
CCDialog::OnInitDialog();
// Make first button the default
SetDefID(m_ButtonList.GetHead()); // ASSERTs if list is empty.
// Clear Previous Data
ResetDialog();
// Size Window Appropriately
SizeWindowToContents();
// Beep
UINT nBeep = m_eIcon;
if (nBeep & eAlert)
{
nBeep = MB_ICONWARNING;
}
if (0 == MessageBeep(nBeep))
{
Beep(700, 600);
}
//BOOL bFocus = FocusOn(.GetSafeHwnd()) ? FALSE : TRUE; // TODO update control
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
////////////////////////////////////////////////////////////////////////////////
/// \brief No button on click event handler. ON_BN_CLICKED.
/// \remarks Ends the dialog with EResult::eNo result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnNo()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eNo);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief OK button on click event handler. ON_BN_CLICKED or Enter if default.
/// \remarks Ends the dialog with EResult::eOK result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnOK()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eOK);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Dialog on paint event handler. ON_WM_PAINT
/// \details The framework calls this member function when Windows or an
/// application makes a request to repaint a portion of an application's window.
/// \remarks Powerchart UI Refresh Color scheme API's are defined in <dcp_colorscheme.h>.
/// To paint the Window background as White, call the GetSchemeBrush(gBodyBackgroundBrush) API
/// which returns the Brush to be used to fill the window rectangle with the predetermined color.
/// \remarks The coloring is only done when the window is not iconic "minimized".
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnPaint()
{
// Minimized Window = Iconic
if (FALSE == IsIconic())
{
// Color the Window
CPaintDC dc(this);
::FillRect(dc.GetSafeHdc(), &dc.m_ps.rcPaint, ::GetSchemeBrush(gBodyBackgroundBrush));
// Inherited Painting
CCDialog::OnPaint();
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Retry button on click event handler ON_BN_CLICKED
/// \remarks Ends the dialog with EResult::eRetry result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnRetry()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eRetry);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Dialog on size and move event handler. ON_WM_SIZE
/// \details The framework calls this member function after the window's size has changed.
/// \remarks https://msdn.microsoft.com/en-US/library/h464d4f3.aspx
/// \return void
/// \param[in] nType - Specifies the type of resizing requested.
/// \param[in] cx - Specifies the new width of the client area.
/// \param[in] cy - Specifies the new height of the client area.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnSize(_In_ UINT nType, _In_ int cx, _In_ int cy)
{
// Inherited Sizing
CCDialog::OnSize(nType, cx, cy);
// TODO Honor RTL
// Dimensions:
//
// 0 y Position
// x +--------------------------------------------+ --
// | | | t
// | 0 h Sizing | |
// | w +--------------+ +--------------+ | --
// | | | | | | |
// | | Control | | Control | | |
// | | | | | | |
// | | | | | | |
// | +--------------+ +--------------+ | | --
// | | | | SECTION_YSPACE
// | | | |
// | +--------------+ | | --
// | | | | |
// | | Control | | |
// | | | | |
// | | | | |
// | +--------------+ | | --
// | | | | CONTROL_YSPACE
// | +--------------+ | | --
// | | | | |
// | | Control | | |
// | | | | |
// | | | | |
// | +--------------+ | --
// | | | b
// +--------------------------------------------+ cx --
// cy
//
// |------|---------------------------------|---|
// l r
//
// |---|
// CONTROL_XSPACE
//// Initialize Dimensions
int l = LEFT_SPACE * 2; // Left Padding
int r = RIGHT_SPACE * 2; // Right Padding
int t = TOP_SPACE; // Top Padding
int b = BOTTOM_SPACE; // Bottom Padding
// Position
int x = 0;
int y = 0;
// Size
int w = 0;
int h = 0;
// Minimum
int mx = 0;
int my = 0;
// Temp
int temp = 0;
// TODO Adjust initial x,y based upon scroll bar positions
// TODO Establish Minimums for everything
//////////////////////////////
// Icon and Message Section //
//////////////////////////////
// Icon image
x = l;
y = t;
CRect rect(0, 0, 0, 0);
m_imgIcon.GetWindowRect(rect);
w = rect.Width();
h = rect.Height();
m_imgIcon.MoveWindow(x, y, w, h);
// Horizontal Control Spacer
if (w > 0)
{
x += CONTROL_XSPACE;
}
// Message text box
x += w;
w = cx - r - x; // Use available width
m_edtMessage.GetWindowRect(rect);
m_edtMessage.MoveWindow(x, y, w, rect.Height()); // Update control width for accurate line count
temp = h; // Remember icon height
if (m_edtMessage.GetLineCount() > MAX_MESSAGE_LINES)
{
h = CalculateWidestLineSize(m_edtMessage.GetSafeHwnd()).cy * MAX_MESSAGE_LINES;
m_edtMessage.ModifyStyle(0UL, WS_VSCROLL);
}
else
{
h = CalculateTextSize(m_edtMessage.GetSafeHwnd(), NULL, w).cy;
m_edtMessage.ModifyStyle(WS_VSCROLL, 0UL);
}
h += h > 0 ? (2 * GetSystemMetrics(SM_CYBORDER)) : 0;
m_edtMessage.MoveWindow(x, y, w, h);
// Vertical Section Spacer only if a message exists
if (h > 0)
{
y += SECTION_YSPACE;
// Actual Section Height
h = max(h, temp);
}
// Minimum Height for Message Section
my = y + h;
/////////////////////
// Buttons Section //
/////////////////////
// Size Buttons (Right to Left)
x = cx;
temp = 0;
CSize size(0, 0);
bool bFirst(true);
CButton* pButton = NULL;
POSITION pos = m_ButtonList.GetTailPosition();
while (pos)
{
// Retrieve Button pointer
if (FALSE == m_ButtonMap.Lookup(m_ButtonList.GetPrev(pos), pButton) || NULL == pButton)
{
ASSERT(pButton);
MSGWRITEERROR(__FUNCTION__, _T("Button list and map are misaligned. Could cause memory leaks!"));
continue;
}
// Size Button
size = CalculateWidestLineSize(pButton->GetSafeHwnd(), NULL, true);
w = size.cx + (2 * GetSystemMetrics(SM_CXEDGE));
w = max(BUTTON_WIDTH, w);
h = max(BUTTON_HEIGHT, size.cy);
x -= ((bFirst ? RIGHT_SPACE : CONTROL_XSPACE) + w);
y = cy - b - h;
pButton->MoveWindow(x, y, w, h);
temp = max(cy - y, temp);
bFirst = false;
}
// Vertical Section Spacer only if a button exists
if (false == bFirst)
{
temp += SECTION_YSPACE;
}
// Add Minimums for Buttons
mx = cx - (x - LEFT_SPACE);
//////////////////////
// Children Section //
//////////////////////
// Calculate Child Safe Rectangle
CRect child(l, my, cx - r, cy - temp);
child.right = max(child.left, child.right);
child.bottom = max(child.top, child.bottom);
my += temp; // Add Button Section to Minimum Height
// Size Children
CSize sz = SizeChild(child);
temp = cx - abs(sz.cx - (l + r)); // Calculate Minimum Child Width
mx = max(mx, temp); // Update Minimum Width if greater
my += sz.cy; // Add Used Child Section to Minimum Height
////////////////////////
// Minimum Adjustment //
////////////////////////
// Client to Window
GetWindowRect(rect);
mx += rect.Width() - cx;
my += rect.Height() - cy;
// Save the adjusted minimums
SetSizeLimits(mx, my);
// Update Window with Changes
RedrawWindow();
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Try Again button on click event handler. ON_BN_CLICKED
/// \remarks Ends the dialog with EResult::eTryAgain result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnTryAgain()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eTryAgain);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Yes button on click event handler. ON_BN_CLICKED or if default Enter.
/// \remarks Ends the dialog with EResult::eYes result.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::OnYes()
{
MSGWRITETRACE(__FUNCTION__);
EndDialog(EResult::eYes);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Display a dialog to obtain.
/// \return int = EResult of the dialog.
/// \param[in] dialog - Dialog to display.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
int CMessageDlg::Prompt(_In_ CMessageDlg& dialog)
{
MSGWRITETRACE(__FUNCTION__);
// Prompt with the Dialog
int nResult = dialog.DoModal();
MSGWRITEINFOF(__FUNCTION__, _T("Result = %d"), nResult);
return nResult;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Remove a button by the control ID.
/// \return bool = true on success, false on failure.
/// \param[in] eControlID - button control ID to remove.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
bool CMessageDlg::RemoveButton(_In_ EControl eControlID)
{
MSGWRITETRACE(__FUNCTION__);
SetLastError(ERROR_SUCCESS);
CButton* pButton = NULL;
if (FALSE == m_ButtonMap.Lookup(eControlID, pButton) || NULL == pButton)
{
MSGWRITEDEBUGF(__FUNCTION__, _T("Button does not exist with ID: %u"), eControlID);
SetLastError(ERROR_NOT_FOUND);
return false;
}
// Remove References
POSITION pos = m_ButtonList.Find(eControlID);
if (NULL == pos)
{
MSGWRITEERRORF(__FUNCTION__, _T("Button was not found in the list: %u"), eControlID);
SetLastError(ERROR_NOT_FOUND);
}
m_ButtonList.RemoveAt(pos); // ASSERTs if position is invalid hence why it is not in an else.
m_ButtonMap.SetAt(eControlID, NULL);
if (FALSE == m_ButtonMap.RemoveKey(eControlID))
{
MSGWRITEERRORF(__FUNCTION__, _T("Failed to remove button: %u. Attempting to continue."), eControlID);
}
// Destroy Window
if (FALSE == pButton->DestroyWindow())
{
MSGWRITEERRORF(__FUNCTION__, _T("Failed to destroy button: %u. Attempting to continue."), eControlID);
}
// Delete Button
MSGWRITEDEBUGF(__FUNCTION__, _T("Button deleted: %u"), eControlID);
delete(pButton);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Reset the dialog components.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::ResetDialog()
{
MSGWRITETRACE(__FUNCTION__);
//FocusOn(m_cboReason.GetSafeHwnd()); // TODO Update control
}
void CMessageDlg::SetButtonStyle(_In_ DWORD nStyle)
{
if (MAXDWORD == nStyle)
{
m_nButtonStyle = DEF_BUTTON_STYLE;
}
else
{
m_nButtonStyle = nStyle;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Set the message icon to use.
/// \remarks Do not destroy the icon given until done with the dialog else the
/// icon will disappear from the dialog.
/// \return void
/// \param[in] hIcon - Handle to the icon to show.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::SetMessageIcon(_In_opt_ HICON hIcon)
{
MSGWRITETRACE(__FUNCTION__);
if (NULL == hIcon)
{
MSGWRITEWARN(__FUNCTION__, _T("NULL icon detected."));
}
m_imgIcon.SetIcon(hIcon);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Set the dimension limits for the dialog.
/// \remarks Only positive values will be used a limits.
/// \remarks Negative values will set the bound to the system limit or limitless max(0).
/// \remarks Zero will be ignored and keep the existing limit.
/// \return void
/// \param[in] nMinWidth = 0 - Minimum Suggested Width
/// \param[in] nMinHeight = 0 - Minimum Suggested Height
/// \param[in] nMaxWidth = 0 - Maximum Suggested Width
/// \param[in] nMaxHeight = 0 - Maximum Suggested Height
/// \param[in] bResize = true - Resize the dialog to the contents given the limits.
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::SetSizeLimits(_In_opt_ LONG nMinWidth /*= 0*/, _In_opt_ LONG nMinHeight /*= 0*/, _In_opt_ LONG nMaxWidth /*= 0*/, _In_opt_ LONG nMaxHeight /*= 0*/, bool bResize /*= true*/)
{
MSGWRITETRACE(__FUNCTION__);
// Retrieve System Limits
int nCXMIN = GetSystemMetrics(SM_CXMINTRACK);
int nCXMAX = GetSystemMetrics(SM_CXMAXTRACK);
int nCYMIN = GetSystemMetrics(SM_CYMINTRACK);
int nCYMAX = GetSystemMetrics(SM_CYMAXTRACK);
//// TODO If Minimums are greater than the System Limits, Show Scroll Bars as last resort to fit everything
//nMinWidth > nCXMAX ? ModifyStyle(NULL, WS_HSCROLL) : ModifyStyle(WS_HSCROLL, NULL);
//nMinWidth > nCXMAX ? ModifyStyle(NULL, WS_VSCROLL) : ModifyStyle(WS_VSCROLL, NULL);
// 1. Keep if > 0 and within system limits
// 2. Use System or on system fail, max limitless(0), if < 0 or outside system limits
// 3. Discard(No Change) if == 0
nMinWidth = nMinWidth > 0 ? (nMinWidth < nCXMIN ? nCXMIN : (nCXMAX > 0 ? min(nCXMAX, nMinWidth) : nMinWidth)) : (nMinWidth < 0 ? nCXMIN : m_MinSize.x);
nMaxWidth = nMaxWidth > 0 ? (nMaxWidth < nCXMIN ? nCXMIN : (nCXMAX > 0 ? min(nCXMAX, nMaxWidth) : nMaxWidth)) : (nMaxWidth < 0 ? nCXMAX : m_MaxSize.x);
nMinHeight = nMinHeight > 0 ? (nMinHeight < nCYMIN ? nCYMIN : (nCYMAX > 0 ? min(nCYMAX, nMinHeight) : nMinHeight)) : (nMinHeight < 0 ? nCYMIN : m_MinSize.y);
nMaxHeight = nMaxHeight > 0 ? (nMaxHeight < nCYMIN ? nCYMIN : (nCYMAX > 0 ? min(nCYMAX, nMaxHeight) : nMaxHeight)) : (nMaxHeight < 0 ? nCYMAX : m_MaxSize.y);
// Verify Positive Plane, if negative and not max limitless(0), use minimum.
nMaxWidth = nMaxWidth > 0 ? max(nMinWidth, nMaxWidth) : 0;
nMaxHeight = nMaxHeight > 0 ? max(nMinHeight, nMaxHeight) : 0;
// Save Changes
const CPoint oldMin = m_MinSize;
const CPoint oldMax = m_MaxSize;
m_MinSize.x = nMinWidth;
m_MinSize.y = nMinHeight;
m_MaxSize.x = nMaxWidth;
m_MaxSize.y = nMaxHeight;
// Check for Changes
bool bChanged = false;
if (oldMin.x != m_MinSize.x
|| oldMin.y != m_MinSize.y
|| oldMax.x != m_MaxSize.x
|| oldMax.y != m_MaxSize.y)
{
bChanged = true;
}
// Resize to contents with limits
if (bChanged && bResize)
{
SizeWindowToContents();
}
}
void CMessageDlg::SetWindowStyle(_In_ DWORD nStyle)
{
if (MAXDWORD == nStyle)
{
m_nWindowStyle = DEF_WINDOW_STYLE;
}
else
{
m_nWindowStyle = nStyle;
}
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Size the Child class elements. Called by OnSize()
/// \remarks The parent method should be called and added for return.
/// \return CSize = the actual width and height used from the rect top left.
/// \param[in] rect - Rectangle where it is safe to draw my elements
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
CSize CMessageDlg::SizeChild(_In_ const CRect& rect)
{
return CSize(0, 0);
}
////////////////////////////////////////////////////////////////////////////////
/// \brief Resize the Window to fit the contents within the defined limits.
/// \details Adjust the width based upon the message and reasons and adjust the
/// height based upon the message and comment field.
/// \remarks OnSize must be run first to establish initial window sizes.
/// Just run this no earlier than the OnInitDialog() function.
/// \remarks The window is also repositioned to maintain on screen center.
/// \return void
/// \pre none
/// \post none
////////////////////////////////////////////////////////////////////////////////
void CMessageDlg::SizeWindowToContents()
{
MSGWRITETRACE(__FUNCTION__);
// Calculate Growth Needed
CSize size(INT_MIN, INT_MIN);
CalculateContentsGrowth(size);
// Adjust the Window Size to fit the content
CRect rect(0, 0, 0, 0);
GetWindowRect(rect);
int w = rect.Width() + size.cx;
int h = rect.Height() + size.cy;
// Retrieve limits
int xMin = m_MinSize.x;
int yMin = m_MinSize.y;
int xMax = m_MaxSize.x;
int yMax = m_MaxSize.y;
// Adjust to remain within the space limits
w = xMin > w ? xMin : xMax < xMin ? w : xMax > w ? w : xMax > 0 ? xMax : w;
h = yMin > h ? yMin : yMax < yMin ? h : yMax > h ? h : yMax > 0 ? yMax : h;
// Save as Limits
SetSizeLimits(w, h, 0, 0, false);
// Determine the actual adjustment
size.cx = w - rect.Width();
size.cy = h - rect.Height();
// Adjust the Window Location to maintain center
int x = rect.left - (size.cx / 2);
int y = rect.top - (size.cy / 2);
// Adjust to remain within the location limits
//x = max(0, x);
//y = max(0, y);
// Move the Window
MoveWindow(x, y, w, h);
}
////////////////////////////////////////////////////////////////////////////////
/// Project PVDocMgmt1
///
/// \file MessageDlg.h
///
/// \class CMessageDlg
///
/// \brief Class to create a message dialog.
///
/// \details Creates a message dialog and its controls.
///
////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "Resource.h"
#include <dcp_dialog.h> // CCDialog
#include <dcp_edit.h> // CCEdit
#include <dcp_button.h> // CCButton
#include <dcp_static.h> // CCStatic
#include <Wtsapi32.h> // IDTIMEOUT, IDASYNC
// TODO Options
// TODO Honor NOMB
class CCCButton : public CCButton
{
private:
CString m_sText;
public:
virtual BOOL Create(_In_ LPCTSTR sText, _In_ DWORD nStyle, _In_ const RECT& rect, _In_ CWnd* pParent, _In_ UINT nID) override;
int GetWindowText(_Out_writes_(nMaxCount) LPTSTR lpszStringBuf, _In_ int nMaxCount) const;
void GetWindowText(_Out_ CString& rString) const;
void SetWindowText(_In_ LPCTSTR sText);
protected:
void SetText(_In_ LPCTSTR sText);
};
class CMessageDlg : public CCDialog
{
protected:
//! Dialog Control IDs
enum EControl : UINT
{
eInvalidID = 0U, //!< Control ID must be > 0
eButtonOK = IDOK, //!< OK
eButtonCancel = IDCANCEL, //!< Cancel
eButtonAbort = IDABORT, //!< Abort
eButtonRetry = IDRETRY, //!< Retry
eButtonIgnore = IDIGNORE, //!< Ignore
eButtonYes = IDYES, //!< Yes
eButtonNo = IDNO, //!< No
eButtonClose = IDCLOSE, //!< Close
eButtonHelp = IDHELP, //!< Help
eButtonTryAgain = IDTRYAGAIN, //!< Try Again
eButtonContinue = IDCONTINUE, //!< Continue
eTimerTimeout = IDTIMEOUT, //!< Timeout Timer
eThreadAsync = IDASYNC, //!< Asynchronous Thread
eButtonCustom, //!< Beginning of Custom Button ID Range
eButtonCustomEnd = eButtonCustom + 100U, //!< End of Custom Button ID Range (Limit 100)
eIcon, //!< Message Static Icon
eMessage, //!< Message Text Box
// eReservedID must be listed last
eReservedID //!< Next Control ID
};
public:
//! Dialog Result Values
enum EResult : int
{
eFailed = -1, //!< Dialog Creation Failed
eOK = EControl::eButtonOK, //!< OK
eCancel = EControl::eButtonCancel, //!< Cancel
eAbort = EControl::eButtonAbort, //!< Abort
eRetry = EControl::eButtonRetry, //!< Retry
eIgnore = EControl::eButtonIgnore, //!< Ignore
eYes = EControl::eButtonYes, //!< Yes
eNo = EControl::eButtonNo, //!< No
eClose = EControl::eButtonClose, //!< Close
eHelp = EControl::eButtonHelp, //!< Help
eTryAgain = EControl::eButtonTryAgain, //!< Try Again
eContinue = EControl::eButtonContinue, //!< Continue
eTimeout = IDTIMEOUT, //!< Timeout TODO add timeout functionality
eAsync = IDASYNC, //!< Asynchronous, No Immediate Result TODO add threaded functionality
eReservedCustom = EControl::eButtonCustom, //!< First Possible Custom Button
eReservedCustomEnd = EControl::eButtonCustomEnd, //!< Last Possible Custom Button
// eReservedResult must be listed last
eReservedResult //!< Next Result for Derived Classes
};
//! Dialog Button Types
enum EButtons : DWORD
{
eOKOnly = MB_OK, //!< OK button
eOKCancel = MB_OKCANCEL, //!< OK and Cancel buttons
eAbortRetryIgnore = MB_ABORTRETRYIGNORE, //!< Abort, Retry, and Ignore buttons
eYesNoCancel = MB_YESNOCANCEL, //!< Yes, No, and Cancel buttons
eYesNo = MB_YESNO, //!< Yes and No buttons
eRetryCancel = MB_RETRYCANCEL, //!< Retry and Cancel buttons
eCancelTryContinue = MB_CANCELTRYCONTINUE, //!< Cancel, Try Again, and Continue buttons
eCustom //!< User Defined Buttons
};
//! Dialog Message Icons
enum EIcon : DWORD
{
eInfo = MB_ICONINFORMATION, //!< Blue Circle with White Exclamation Mark
eError = MB_ICONERROR, //!< Red Circle with White X
eWarning = MB_ICONWARNING, //!< Yellow Triangle with Black Exclamation Point
eQuestion = MB_ICONQUESTION, //!< Blue Circle with White Question Mark
eAlert, //!< Red Triangle with White Exclamation Point
eUser = MB_USERICON, //!< User Defined Icon
eNone //!< No Icon
};
private:
DECLARE_DYNAMIC(CMessageDlg) // RTTI support
// Attributes
CWnd* m_pParent; //!< Pointer to Parent Window
CString m_sTitle; //!< Dialog caption
CString m_sMessage; //!< Message to display
CPoint m_MinSize; //!< Dialog Minimum Dimension Limits
CPoint m_MaxSize; //!< Dialog Maximum Dimension Limits
EButtons m_eButtons; //!< Dialog Buttons Type
EIcon m_eIcon; //!< Dialog Message Icon
EControl m_eNextButtonID; //!< Next Available Button ID
EControl m_eNextControlID; //!< Next Available Control ID
DWORD m_nWindowStyle; //!< Window Style to Apply
DWORD m_nButtonStyle; //!< Button Style to Apply
// Controls
CCStatic m_imgIcon; //!< Message Icon Image Control Member
CCEdit m_edtMessage; //!< Message Control Member
// Dynamic Controls
CList<EControl> m_ButtonList; //!< Ordered List of the Buttons
CMap<EControl, EControl, CButton*, CButton*> m_ButtonMap; //!< Map of Buttons
public:
// Constructors
CMessageDlg(_In_opt_ LPCTSTR sTitle = NULL, _In_opt_ LPCTSTR sMessage = NULL, _In_ EButtons eButtons = eOKCancel, _In_ EIcon eIcon = eNone, _In_opt_ CWnd* pParent = NULL);
virtual ~CMessageDlg() override;
// Accessors
void SetMessageIcon(_In_opt_ HICON hIcon);
void SetSizeLimits(_In_opt_ LONG nMinWidth = 0, _In_opt_ LONG nMinHeight = 0, _In_opt_ LONG nMaxWidth = 0, _In_opt_ LONG nMaxHeight = 0, bool bResize = true);
// Public Methods
CButton* AddButton(_In_opt_ LPCTSTR sText = NULL, _In_ EControl eButtonID = EControl::eButtonCustom);
bool RemoveButton(_In_ EControl eControlID);
void SetButtonStyle(_In_ DWORD nStyle = MAXDWORD);
void SetWindowStyle(_In_ DWORD nStyle = MAXDWORD);
// Public Overrides
virtual INT_PTR DoModal() override;
virtual EControl GetNextButtonID();
virtual EControl GetNextControlID();
virtual void ResetDialog();
// Public Static Methods
static int Prompt(_In_ CMessageDlg& dialog);
protected:
// Internal Methods
void ClearButtons();
bool CreateButton(_In_ CCCButton& button, _In_ const CString& sText, _In_ EControl eButtonID);
bool CreateButtons();
bool FocusOn(_In_ HWND hWnd);
HICON LoadMessageIcon();
void SizeWindowToContents();
// Static Methods
static CSize CalculateTextSize(_In_ HWND hWnd, _In_opt_ LPCTSTR sText = NULL, _In_opt_ int nWidth = 0);
static CSize CalculateWidestLineSize(_In_ HWND hWnd, _In_opt_ LPCTSTR sText = NULL, _In_ bool bSingle = false);
static bool GetTextMetrics(_In_ HWND hWnd, _Out_ TEXTMETRIC& tm);
// Internal Overrides
virtual void CalculateContentsGrowth(_Inout_ CSize& size);
virtual void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
virtual BOOL OnInitDialog() override;
virtual CSize SizeChild(_In_ const CRect& rect);
//virtual void OnTimeout();
// Message Map Functions
afx_msg int OnCreate(_In_ LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg void OnSize(_In_ UINT nType, _In_ int cx, _In_ int cy);
afx_msg void OnOK() override;
afx_msg void OnCancel() override;
afx_msg void OnYes();
afx_msg void OnNo();
afx_msg void OnAbort();
afx_msg void OnIgnore();
afx_msg void OnRetry();
afx_msg void OnClose();
afx_msg void OnHelp();
afx_msg void OnTryAgain();
afx_msg void OnContinue();
afx_msg void OnClickButton(_In_ UINT nID);
afx_msg void OnClickCustom(_In_ UINT nID);
afx_msg void OnCustom(_In_ UINT nID);
afx_msg void OnPaint();
afx_msg HBRUSH OnCtlColor(_In_ CDC* pDC, _In_ CWnd* pWnd, _In_ UINT nCtlColor);
afx_msg void OnGetMinMaxInfo(_Inout_ MINMAXINFO* lpMMI);
DECLARE_MESSAGE_MAP()
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment