Disable horizontal scroll bar in list view - c++

I have a list view where I want to disable the horizontal scroll bar.
Basically, I know none of my data will exceed the width of the single column, but if enough entries get added to the list view, the vertical scroll bar pops up, reducing the width available, making the horizontal scroll bar pop up.
I was thinking about some how catching a message right before the vertical scroll bar gets added, and then re-sizing the column to make enough room, but I don't know what message I would need to catch to do this.
EDIT:
Does anyone know if there is a message sent after an item in a list view is deleted? LVN_ITEMCHANGED appeared to only be sent after an item is added. And LVN_DELETEITEM only before an item is deleted.

You could send the message: LVM_SETCOLUMNWIDTH to the listview with cx param set to LVSCW_AUTOSIZE_USEHEADER

Well I worked out one solution.
There is a bug though, if I only remove one item it doesn't resize the column.
case LVN_DELETEITEM:
{
LPNMLISTVIEW listView = (LPNMLISTVIEW) lParam;
// After an item is deleted,
// if there is not a vertical scroll bar and GWL_USERDATA is TRUE,
// resize the column back to normal.
if (!(GetWindowLong(listView->hdr.hwndFrom, GWL_STYLE) & WS_VSCROLL) &&
GetWindowLong(listView->hdr.hwndFrom, GWL_USERDATA) == TRUE)
{
const int ColWidth = ListView_GetColumnWidth(listView->hdr.hwndFrom, 0);
ListView_SetColumnWidth(listView->hdr.hwndFrom, 0, ColWidth + GetSystemMetrics(SM_CXVSCROLL));
SetWindowLong(listView->hdr.hwndFrom, GWL_USERDATA, FALSE);
}
break;
}
case LVN_ITEMCHANGED:
{
LPNMLISTVIEW listView = (LPNMLISTVIEW) lParam;
// After an item is added, if there is a horizontal scrollbar,
// resize the column and set GWL_USERDATA to TRUE.
if (GetWindowLong(listView->hdr.hwndFrom, GWL_STYLE) & WS_HSCROLL)
{
const int ColWidth = ListView_GetColumnWidth(listView->hdr.hwndFrom, 0);
ListView_SetColumnWidth(listView->hdr.hwndFrom, 0, ColWidth - GetSystemMetrics(SM_CXVSCROLL));
SetWindowLong(listView->hdr.hwndFrom, GWL_USERDATA, TRUE);
}
break;
}
I'd still love to see a better solution, but this works for now.

Related

Don't allow the tooltip to pop on CMFCRibbonBar

I have CMFCRibbonBar control. I need to create my custom tooltip. My tooltip derives from CMFCToolTipCtrl and works quite well. But...
When I hover a ribbon button, tooltip shows up. That's great. But when I move the mouse out of the button, tooltip is closed. That is not what I want. I just need to be able to move the mouse on the tooltip and click the link that is on the tooltip. Imagine this is some kind of interactive tooltip. What can I do to achieve that?
OK, I've done something that is useful, but the outcome is not satisfying 100%.
So, first of all, create your own tooltip, inheriting from CMfcToolTipCtrl.
The idea is that:
- user may want to interact with your tooltip, or not. So we have to create some smart way from closing and showing the tooltip.
- We can assume, that when user hovers the tooltip with mouse, then he wants to interact.
Unfortunately whenever user moves the mouse from the ribbon button, the tooltip dissapears. But sometimes we can catch MouseMove inside it. But it's rather rare. So, we have to get the moment, when tooltip is closed by a system.
There is such a message that we can add to message map:
ON_NOTIFY_REFLECT(TTN_POP, &CAsInteractiveToolTip::OnPop)
Now, our OnPop will look like that (I am using pImpl idiom):
void CAsInteractiveToolTip::OnPop(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_pImpl->m_forceClose)
{
CMFCToolTipCtrl::OnPop(pNMHDR, pResult);
m_pImpl->m_forceOpened = false;
m_pImpl->m_forceClose = false;
m_pImpl->StopForceOpenTimer();
}
else
{
m_pImpl->StartForceOpenTimer();
}
*pResult = 0;
}
Now, what's happening here is:
- when tooltip is being closed, check if it's force closed by our code. If not, it means that it's closed by system. In such case, we have to give the user a chance to hover the mouse over our tooltip. So, we have to show the tooltip again (force it to show). This is done in timer method. StartForceOpenTimer is simple method that starts the timer:
void StartForceOpenTimer()
{
if (!m_forceOpenTimerActive)
{
m_self.SetTimer(IDT_FORCE_OPEN_TIMER, 100, (TIMERPROC)NULL);
m_forceOpenTimerActive = true;
}
}
Now, the magic starts in timer method:
void CAsInteractiveToolTip::OnForceTimer()
{
static DWORD waitForUserStartTime = 0;
static bool waitingForUserReaction = false;
if (!waitingForUserReaction)
{
//open and give the user chance to mouse move over it within 0.5 seconds
SetWindowPos(&wndTopMost, -1, -1, -1, -1, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
waitForUserStartTime = GetTickCount();
waitingForUserReaction = true;
return;
}
if (GetTickCount() - waitForUserStartTime > 500)
{
m_pImpl->StopForceOpenTimer();
m_pImpl->m_forceClose = true;
waitingForUserReaction = false;
m_pImpl->PopToolTip();
return;
}
if (m_pImpl->m_doForceOpen)
{
m_pImpl->StopForceOpenTimer();
waitingForUserReaction = false;
m_pImpl->m_forceOpened = true;
}
}
Overall idea is:
- force to show the tooltip
- wait about 0.5 second for a user to hover the mouse
- if user hovers the mouse over tooltip window, we can assume that he wants to interact. So we can leave the window opened.
- if user doens't interact with the window within 0.5 second, just close the tooltip again.
Now, PopToolTip method just starts another timer with interval of 100 ms.
And here is the other part of the magic:
void CAsInteractiveToolTip::OnPopTimer()
{
m_pImpl->StopForceOpenTimer();
KillTimer(IDT_POP_TIMER);
//Pop();
m_pImpl->m_forceClose = true;
m_pImpl->m_hdr.idFrom = 2;
m_pImpl->m_hdr.hwndFrom = GetSafeHwnd();
m_pImpl->m_hdr.code = (int)TTN_POP; //4294966774
GetParent()->SendMessage(WM_NOTIFY, 1, (LPARAM)&m_pImpl->m_hdr);
//GetParent()->SendMessage(WM_NOTIFY, 2, (LPARAM)&m_pImpl->m_hdr);
ShowWindow(SW_HIDE);
}
Now, this method should just pop (hide) the tooltip. But for some reason in my case calling Pop() method does nothing. So I would have to send WM_NOTIFY message with appropriate parameters (they are taken from my debug observations).
Now, OnPop will start again, but this time m_forceClose is set to true, so the tooltip will not show again (the first timer will not run).
Now the third part of the magic - Mouse Move. Just add it to your message map:
ON_WM_MOUSEMOVE()
And the method:
void CAsInteractiveToolTip::OnMouseMove(UINT nFlags, CPoint point)
{
m_pImpl->m_doForceOpen = true; //let the first timer know, that user wants to interact
CMFCToolTipCtrl::OnMouseMove(nFlags, point);
}
And you can just hide the tooltip when user clicks on it. Just:
void CAsInteractiveToolTip::OnLButtonDown(UINT nFlags, CPoint point)
{
m_pImpl->m_forceClose = true;
m_pImpl->PopToolTip();
}
This is not the ideal solution, but it somehow works. If anyone has any suggestions, I will be happy to hear them :)

Customising a dynamic layout at runtime based on control visibility

I have a multi-purpose CDialog that supports resizing. It can display content in 3 variations.
Variation 1:
Variation 2:
Variation 3:
The dialogue controls are using the dynamic layout settings from the resource editor.
Variation 1 is fine and need no changes.
Variation 2 does not display the combo and date button. As a result I would like the "Text will ..." label to be down at the bottom and the "edit" box to be taller.
Variation 3 has a similar issue where the date button should move to the bottom and the edit box be taller.
Can this be achieved by changing the dynamic layout in code?
Update
I tried this in OnInitDialog:
if (!m_bShowWeekCombo)
{
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
CRect rctNote;
m_staticInfo.GetWindowRect(rctNote);
ScreenToClient(rctNote);
m_staticInfo.MoveWindow(rctCombo.left, rctCombo.top, rctNote.Width(), rctNote.Height());
}
At first I thought it was working:
The note is now at the bottom. But as soon as I resize the window:
The note has reverted to the original position.
I know I have this answer to a similar issue but do I really have to re-build the whole layout?
Update 2
if (!m_bShowWeekCombo)
{
CRect rctEdit;
m_editText.GetWindowRect(rctEdit);
ScreenToClient(rctEdit);
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
CRect rctNote;
m_staticInfo.GetWindowRect(rctNote);
ScreenToClient(rctNote);
//m_staticInfo.MoveWindow(rctCombo.left, rctCombo.top, rctNote.Width(), rctNote.Height());
m_staticInfo.SetWindowPos(NULL, rctCombo.left, rctCombo.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
m_editText.SetWindowPos(NULL, 0, 0, rctEdit.Width(), rctEdit.Height() + (rctCombo.top - rctNote.top),
SWP_NOMOVE | SWP_NOZORDER);
if (m_pDynamicLayout)
{
if (!m_pDynamicLayout->HasItem(m_staticInfo.m_hWnd))
{
m_pDynamicLayout->AddItem(m_staticInfo.m_hWnd,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
}
else
{
TRACE(L"item already has dynamic move/size\n");
}
if (!m_pDynamicLayout->HasItem(m_editText.m_hWnd))
{
m_pDynamicLayout->AddItem(m_editText.m_hWnd,
CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
else
{
TRACE(L"item already has dynamic move/size\n");
}
}
}
When I try the above the control width is the original width, even though the dialog had restored to wider dialog width.
CMFCDynamicLayout reads the dialog resource, it stores the coordinates for the child controls as well as their dynamic resize/move properties.
This is all done in CDialog::OnInitDialog. If you move the child control, example, m_staticInfo then CMFCDynamicLayout doesn't know you moved/resized the control. So upon the next dialog resize request, CMFCDynamicLayout uses the old values.
You can add dynamic resize/move for all controls expcept m_staticInfo and other controls which you intend to move manually. Then add m_staticInfo separately:
BOOL CMyDialog::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
m_staticInfo.SetWindowPos(NULL, rctCombo.left, rctCombo.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
if(m_pDynamicLayout)
{
if(!m_pDynamicLayout->HasItem(m_staticInfo.m_hWnd))
{
m_pDynamicLayout->AddItem(m_staticInfo.m_hWnd,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeNone());
}
else
{
TRACE(L"item already has dynamic move/size\n");
AfxDebugBreak(0);
}
}
return 1;
}
Internally, MFC calls LoadDynamicLayoutResource(m_lpszTemplateName) to initialize dynamic size/move. But documentation says not to use this method directly.
Clarification
If you are using a dialog that supports resizing then you must remember to calculate the new width and height when you move the control to the new position. You would then use one of the appropriate Size calls. For example:
// The EDIT control height now needs increasing
iNewEditHeight = rctButton.top - iTextMarginY - rctEdit.top;
m_editText.SetWindowPos(nullptr, 0, 0, iNewWidth, iNewEditHeight, SWP_NOMOVE | SWP_NOZORDER);
It is up to you to workout how you want your control initially re-sized.
Then, in OnInitDialog I called a new method:
void CEditTextDlg::SetupDynamicLayout()
{
if (m_pDynamicLayout != nullptr)
{
m_pDynamicLayout->AddItem(IDC_BUTTON_INSERT_DATE,
CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
m_pDynamicLayout->AddItem(IDC_STATIC_INFO,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
m_pDynamicLayout->AddItem(IDC_EDIT_TEXT,
CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
}
If you don't set the width correctly when using SetWindowPos and only use SizeNone() it will not resize correctly.

CTabCtrl ItemAction & ItemState

I’ve created my own CXTabCtrl that extends CTabCtrl and override the DrawItem Function.
During the phase of rewriting the DrawItem Function, I wasn’t able to differentiate between this two states of CTabCtrl Item:
CTabCtrl item is selected and have focus.
CTabctrl item is selected but doesn’t have focus.
By focus I mean the Focus rectangle is not drawing. Here are two images that will help identify the two states:
Here’s the DrawItem current code, in which I can detect the selected states, but still Unable to detect the focus states.
Here’s a part of the DrawItem current code, in which I can detect the selected states, but still Unable to detect the focus states.
void CXtabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
BOOL bFontSuccess = FALSE;
CFont* def_font = NULL;
CFont font_italic;
TC_ITEM tci;
CRect rect(lpDrawItemStruct->rcItem);
wchar_t szTabText[256];
wmemset(szTabText,_T('\0'),256);
RECT rectComplet;
GetClientRect(&rectComplet);
CBrush brtmp(ColorCategoryBackgroundTop);
int nbItem = GetItemCount();
tci.mask = TCIF_TEXT;
tci.pszText = szTabText;
tci.cchTextMax = sizeof(szTabText) -1;
GetItem(lpDrawItemStruct->itemID, &tci);
BOOL bSelect = (lpDrawItemStruct->itemState & ODS_SELECTED) &&
(lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE));
BOOL bfocus = (lpDrawItemStruct->itemState & ODS_FOCUS) &&
(lpDrawItemStruct->itemAction & (ODA_FOCUS | ODA_DRAWENTIRE));
if (bSelect)//Draw In a Specific Way
if (bFocus) //Draw In a Specific Way
}
So, I would be grateful if someone can describe the proper way to detect the two states of a CTabCtrl Item “Selected & Focused”, “Selected & But not focused”
For a standard tab control, the UI will not always draw the focus rectangle. To see the focus rectangle, the tab control must have WS_TABSTOP flag.
The focus rectangle will then be visible when user clicks the Tab key to go through the dialog's controls, or when Alt key is pressed and tab control has focus.
The focus rectangle should be drawn automatically for owner draw tab control when applicable. Make sure WS_TABSTOP is set for tab control (in dialog editor, go to tab control's properties and set "Tabstop = true")
BOOL focused = selected && (GetFocus() == this); will always be TRUE when user clicks on the tab control. ODS_NOFOCUSRECT will indicate if focus rectangle is not requested by the UI. See the example below.
Side note, sizeof(szTabText) returns the wrong value for wchar_t. Use _countof(szTabText) or sizeof(szTabText)/sizeof(*szTabText)
void CXtabCtrl::DrawItem(LPDRAWITEMSTRUCT di)
{
CDC* pDC = CDC::FromHandle(di->hDC);
TC_ITEM tci;
wchar_t text[256] = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = _countof(text);
GetItem(di->itemID, &tci);
BOOL selected = di->itemState & ODS_SELECTED;
BOOL focused = selected && (GetFocus() == this);
//The UI may not be drawing focus rectangle, even if we click on the tab
if(di->itemState & ODS_NOFOCUSRECT)
focused = FALSE;
CString str;
if(selected) str += L"SEL ";//indicate selected
if(focused) str += L"FOC ";//indicate focused
CRect rect(di->rcItem);
pDC->TextOut(rect.left, rect.top, str);
}

Message is highlighted in blue in Modal box in VC++

We are using below code to populate the messages in a dialog box, when the message appears the message is highlighted in blue - it's like we selected the message using the mouse. i want it the messages not to select when it appears. Anybody can help me on this issue.
CDialog::OnInitDialog();
CFont *m_pFont = new CFont();
LOGFONT lf;
memset(&lf, 0,sizeof(LOGFONT));
lf.lfHeight = 16;
lf.lfWeight = FW_BOLD;
strncpy_s(lf.lfFaceName,"Arial",6);
m_pFont->CreateFontIndirectA(&lf);
GetDlgItem(IDC_EDIT1)->SetFont(m_pFont,TRUE);
m_message.SetWindowTextA((LPCTSTR)Message);
return TRUE;
The selection will be set to all the text whenever the edit box becomes the selected item. If the edit box is the first in the tab order, or if you tab to it or click on it, all the characters are selected. You can override this behavior by capturing the EN_SETFOCUS event and resetting the selection yourself:
void CMyDlg::OnEnSetfocusEdit1()
{
m_edit1.SetSel(0, 0); // or (-1, -1) to set the selection at the end
}

win32 CTabctrl: Drawing "child" windows in the active tab

I have a resizable dialog that contains a CTabCtrl, the tab control has 4 tabs that when clicked on displays one of four different CTreeCtrls.
I have derived a class from CTabCtrl, which keeps track of its "child" controls like so:
...
class Container: public CTabCtrl {
vector<CWnd*> _children;
....
int Container::AddTab(CWnd* Child) {
CString txt;Child->GetWindowText(txt);
_children.push_back(Child);
int idx = this->InsertItem(this->GetItemCount(), txt, 0);
if(idx == 0) {
CRect c;
this->GetWindowRect(&c);
GetParent()->ScreenToClient(&c);
this->AdjustRect(FALSE, c);
Child->SetWindowPos(&wndTop, c.left, c.top, c.Width(), c.Height(), SWP_SHOWWINDOW);
this->SetCurSel(idx);
} else Child->ShowWindow(SW_HIDE);
return idx;
}
And I attempt to draw the child controls like so:
void Container::OnTabChanging(NMHDR*, LRESULT* pResult) { // hide the changed from tab
int selected = this->GetCurSel();
if(selected != -1)
{
// move old window to bottom of the zorder and hide
_children[selected]->SetWindowPos(&wndBottom, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_HIDEWINDOW);
ASSERT(!_children[selected]->IsWindowVisible());
}
*pResult = 0;
}
// show the child for the tab being changed to
void CNodeContainer::OnTabChanged(NMHDR* pNMHDR, LRESULT* pResult) {
int selected = this->GetCurSel();
ASSERT(selected!=-1);
CRect c;
this->GetWindowRect(&c);
GetParent()->ScreenToClient(&c);
this->AdjustRect(FALSE, c);
_children[selected]->SetWindowPos(&wndTop, c.left, c.top, c.Width(), c.Height(), SWP_SHOWWINDOW|SWP_FRAMECHANGED);
*pResult = 0;
}
However the child controls, whilst they appear, don't always draw correctly, they sort of mix up their content together and only show the right content when i click on them (the actual tree controls).
Is this the best way of drawing and moving windows around in the zorder, what am I missing?
Many thanks
bg
Instead of just changing the z-order of your children, completely hide every child except the top one. I use the same approach in a custom CTabCtrl and it works fine.
Its fixed now - the problem came from the fact that the in the resize code for the tabctrl, I was using movewindow to move the child windows into place - This was changing the zorder of the child windows.
This could solve the problem after your window or tab apears. Try to use
this->RedrawWindow();
In OnTabChanging() function before it returns.