It has been 10 months since I worked on my app due to a death in the family, just started looking at it again and still not sure how to solve the problem.
The project inquires/help started here:
MFC MDI Collecting control states for the "apply" button routine
Since this is a specific focused question, I didn't want to muck up my other thread, so what I'd like to do is change the documents tab styles after the view is loaded. I know that this can be done because the master repository from Microsoft with all the code examples has a project called VCSamples-master\VCSamples-master\VC2010Samples\MFC\Visual C++ 2008 Feature Pack\TabControl which I have looked at. It dawns on me that even though I can follow its code, the calls are from within the MDI window itself where my issue is I'm trying to do this via a property page dialog using OnApply which changes things.
I was able to do part of this properly with the help of the thread above to the OutputPane successfully because I was able to get the Pane handle and execute. I was told that for the MDI tabs after creation that I need to parse the tabs, count them, and then execute. So my issue here is after I capture the tabs......how to change their styles.
Here is the code as it stands:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
// Update Output Pane Tab Styles (Works 100%)
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.RecalcLayout();
//Get the open file tabs in the MDI
for (POSITION pos = AfxGetApp()->GetFirstDocTemplatePosition(); pos != NULL; )
{
CDocTemplate* pTempl = AfxGetApp()->GetNextDocTemplate(pos);
for (POSITION pos1 = pTempl->GetFirstDocPosition(); pos1 != NULL; )
{
CDocument* pDoc = pTempl->GetNextDoc(pos1);
for (POSITION pos2 = pDoc->GetFirstViewPosition(); pos2 != NULL; )
{
CView* pView = pDoc->GetNextView(pos2);
if (pView->IsKindOf(RUNTIME_CLASS(CTrainView)))
{
// THIS IS WHERE MY ISSUE IS, NOW THAT ALL THE TABS ARE
// CAPTURED, HOW DO I ADDRESS THEM LIKE WHAT IS SHOWN
// ABOVE:
//((CMainFrame*)AfxGetMainWnd())->xxxxxx.yyyyyy.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
}
}
}
}
}
return bResult;
}
If I can figure this last piece out, I'll be basically finished, I just can't seem to find a solution on how to do this via property sheet via OnApply.
Any suggestions or actual code examples I can see to solve my problem?
FYI: No, I haven't had any time to take additional OOP to solve this. I'm hoping someone can provide some guidance so I can move on after getting this sorted.
Thanks,
Chris
EDIT 1:
So I took a closer look at Constantine's suggestion and here is what I came up with:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
// Update Output Pane Tab Styles
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.RecalcLayout();
CMFCTabCtrl& MDI_STYLES = ((CMainFrame*)AfxGetMainWnd())->GetMDITabs();
MDI_STYLES.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
MDI_STYLES.RecalcLayout();
CMDIFrameWndEx* pMainFrame = DYNAMIC_DOWNCAST(CMDIFrameWndEx, GetTopLevelFrame());
pMainFrame->SetFocus();
pMainFrame->RecalcLayout();
}
return bResult;
}
The m_styles_tabs is getting the index value of 0-8 when I select the radio button. The code compiles and runs and I see the index value change when I break on it, but the tabs for the MDI are still not updating. Does the edited code make sense based on the members shown here:
https://learn.microsoft.com/en-us/cpp/mfc/reference/cmfctabctrl-class?view=msvc-170#modifytabstyle
I think this the right direction, am I missing something?
I am trying to make a (new in 10.10) NSSplitViewItem collapse and uncollapse whilst moving its containing window so as to keep the whole thing "in place".
The problem is that I am getting a twitch in the animation (as seen here).
The code where I'm doing the collapsing is this:
func togglePanel(panelID: Int) {
if let splitViewItem = self.splitViewItems[panelID] as? NSSplitViewItem {
// Toggle the collapsed state
NSAnimationContext.runAnimationGroup({ context in
// special case for the left panel
if panelID == 0 {
var windowFrame = self.view.window.frame
let panelWidth = splitViewItem.viewController.view.frame.width
if splitViewItem.collapsed {
windowFrame.origin.x -= panelWidth
windowFrame.size.width += panelWidth
} else {
windowFrame.origin.x += panelWidth
windowFrame.size.width -= panelWidth
}
self.view.window.animator().setFrame(windowFrame, display: true)
}
splitViewItem.animator().collapsed = !splitViewItem.collapsed
}, completionHandler: nil)
}
}
I am aware of the "Don't cross the streams" issue (from session 213, WWDC'13) where a window resizing animation running on the main thread and a core animation collapse animation running on a separate thread interfere with each other. Putting the splitViewItem collapse animation onto the main thread seems like the wrong approach and I've got a nagging feeling there's a much better way of doing this that I'm missing.
Since I am not finding any documentation on the NSSplitViewItems anywhere (yet) I would appreciate any insights on this.
I have the little test project on GitHub here if anyone wants a look.
Update The project mentioned has now been updated with the solution.
Thanks,
Teo
The problem is similar to the "don't cross the streams" issue in that there are two drivers to the animation you've created: (1) the split view item (2) the window, and they're not in sync.
In the example from the '13 Cocoa Animations talk, constraints were setup to result in the correct within-window animation as only the window's frame was animated.
Something similar could be tried here -- only animating the window's frame and not the split view item, but since the item manages the constraints used to (un)collapse, the app can't control exactly how within-window content animates:
Instead the split view item animation could completely drive the animation and use NSWindow's -anchorAttributeForOrientation: to describe how the window's frame is affected.
if let splitViewItem = self.splitViewItems[panelID] as? NSSplitViewItem {
let window = self.view.window
if panelID == 0 {
// The Trailing edge of the window is "anchored", alternatively it could be the Right edge
window.setAnchorAttribute(.Trailing, forOrientation:.Horizontal)
}
splitViewItem.animator().collapsed = !splitViewItem.collapsed
}
For anyone using Objective C and targeting 10.11 El Capitan.
This did the trick for me, didn't need to set AnchorAttributes.
splitViewItem.collapsed = YES;
I have an application in which double clicking over an image view area changes the layout of the image view. Also on single click a dot will be placed on the image.
My problem is, both functionality is working when double clicked.
Of course I know that, when a double click occurs the control first goes to LButtonDown. I don't want the dot functionality to work when double click occurs. I have been working around this for more than a week. Please help.
The easiest way to solve this is to build a finite-state machine for handling mouse clicks.
Basically, this will be a singleton object, which takes input from the mouse click events you're currently using.
It's output will be SingleClickDetected, DoubleClickDetected, ....
Red arrows indicate events which you are reporting to the rest of your application.
Parentheses indicate what event you are reporting.
Of course, this state machine will have to be modified if you have to deal directly with MouseDown and MouseUp events, instead of MouseClick events.
It will be slightly larger, but the idea is basically the same.
EDIT:
From the comments, it looks like Windows doesn't cleanly report single- vs double-clicks, and you need to separate them.
A state-machine for this scenario:
This is probably overkill for what you're trying to do, especially since most, if not all GUI-based programs in the history of everything have never ever used a double-click drag.
It does show the basic idea, and shows how you can extend your state machine to handle different types of button clicks.
Furthermore, if you wanted to, you could handle double-right-clicks, a drag that involves both left and right buttons, or any other scenario you could think of and incorporate into your UI.
I wrote the following code and it works.
UINT TimerId;
int clicks;
VOID CALLBACK TimerProc(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
KillTimer(NULL, TimerId);
if (clicks < 2 && !double_click){
MessageBox(hWnd, L"Show Widget", L"Widget", MB_OK);
}
clicks = 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
UINT uID;
UINT uMouseMsg;
uID = (UINT)wParam;
uMouseMsg = (UINT)lParam;
if (uMouseMsg == WM_LBUTTONDBLCLK){
double_click = true;
MessageBox(hWnd, L"Double click", L"CAPT", MB_OK);
return 0;
}
if (uMouseMsg == WM_LBUTTONDOWN){
double_click = false;
clicks++;
//single click opens context menu
if (clicks == 1){
TimerId = SetTimer(NULL, 0, 500, &TimerProc);
}
return 0;
}
,...
}
Try storing the timestamp of the last LButtonDown; if the time difference between the last timestamp and the timestamp produced in the current event is too short, you can just cancel your operation (but still store the new LButtonDown timestamp)
The only thing you can do is wait for a short amount of time each time you receive click event, and test if in the meantime the equivalent of a double click event doesn't occur before performing the single click response. This may be source to new bugs and unresponsive UI. Maybe try to change the user interaction to toss the problem away.
EDIT: The fact that you are working around this for more than a week is a symptom of bad user interaction design. A "double click" still means two clicks occurs, which means the application naturally should perform the operation of a single click. Check the ui of the apps installed on your desktop to verify this. Have you considered using a different user medium to trigger the UI response? For instance, you can use the right button to put the dot on the image.
#E.T's answer is spot-on. To implement something like that, you really need a timer running along with your message loop.
In my application I wanted to be able to distinguish mouse down/up from double click because I want a double click to not undo a drag operation (imagine selection box with left button drag and double click to zoom to fit).
The following code does this using PreTranslateMessage. I did not add the timer out of laziness. So the UI behaves a bit "funny" if you don't immediately move the mouse after the left button is held down. In my case, this is a minor issue.
BOOL MyWindow::PreTranslateMessage(MSG *pMsg)
{
//From https://msdn.microsoft.com/en-us/library/windows/desktop/ms645606(v=vs.85).aspx
//Double-clicking the left mouse button actually generates a sequence of four messages:
//WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, and WM_LBUTTONUP
//So here's the problem. If an button up message arrives, we can't just
//take it, because it may be followed by a double click message. So we check
//for this.
//Ideally you need a timer - what happens if you don't get any messages
//after the button up or down? But here we get lazy and assume a message
//will come "soon enough".
static bool upMessageStored = false;
static bool dnMessageStored = false;
static MSG upMessage, dnMessage;
static bool processDnNow = false, processUpNow = false;
//This logic sequence absorbs all button up and down messages, storing them to be sent
//if something other than a double click message immediately follows.
if (!(pMsg->message == WM_LBUTTONDBLCLK ||
pMsg->message == WM_LBUTTONDOWN ||
pMsg->message == WM_LBUTTONUP) &&
(upMessageStored || dnMessageStored))
{
//If we receive any message other than double click and we've stored the messages,
//then post them.
Output::Message(L"Stored messages posted.\n");
if (upMessageStored)
{
processUpNow = true;
upMessageStored = false;
this->PostMessage(upMessage.message, upMessage.wParam, upMessage.lParam);
}
if (dnMessageStored)
{
processDnNow = true;
dnMessageStored = false;
this->PostMessage(dnMessage.message, dnMessage.wParam, dnMessage.lParam);
}
return TitlelessWindow::PreTranslateMessage(pMsg);
}
if (pMsg->message == WM_LBUTTONDOWN && !processDnNow)
{
Output::Message(L"WM_LBUTTONDOWN absorbed; message stored\n");
dnMessage = *pMsg;
dnMessageStored = true;
return TRUE; //swallow this message.
}
else if (pMsg->message == WM_LBUTTONUP && !processUpNow)
{
Output::Message(L"WM_LBUTTONUP absorbed; message stored\n");
upMessage = *pMsg;
upMessageStored = true;
return TRUE; //swallow this message.
}
else if (pMsg->message == WM_LBUTTONDBLCLK)
{
Output::Message(L"WM_LBUTTONDBLCLK; stored message discarded\n");
upMessageStored = false;
dnMessageStored = false;
processUpNow = false;
processDnNow = false;
}
//If we get here, we are processing messages normally. Be sure we clear the flags
//for up and down.
processDnNow = false;
processUpNow = false;
return ParentClass::PreTranslateMessage(pMsg);
}
I really liked the finite state machine answer BUT there is a flaw in it.
There is no such thing as "single click time" which you can exceed.
If you look at a the working of your mouse closely you will see that:-
single click not one event but = WM_LBUTTONDOWN, WM_LBUTTONUP which is independent of time in between, the appropriate action will take place anyway
Double-clicking the left mouse button actually generates a sequence of four messages: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, and WM_LBUTTONUP
so you should use the 3rd flag to your advantage
BTW, I am also working on something like this. Thanks!
MFC doc/view architecture, sdi (more precisely multiple top-level windows).
In my view class, I set my "playground" (i.e. logical space) with SetScrollSizes(); Then I want to limit maximum frame window size to that of view's maximum size.
Here is what I'm doing but I think there might be better solution, please advice:
I'm implementing OnGetMinMaxInfo() in my CMainFrame. There I try to get active view's scroll sizes, and set lpMMI->ptMaxTrackSize appropriately. Below is the code:
void CMainFrame::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
// Call base version:
CFrameWndEx::OnGetMinMaxInfo(lpMMI);
// Get active view:
CScrollView *pScrollView = (CScrollView *)GetActiveView();
if (pScrollView && pScrollView->IsKindOf(RUNTIME_CLASS(CMyFckinView)))
{
// Get total size of playground:
CSize sizePlayground = pScrollView->GetTotalSize();
// Test if the size is non-zero, i.e. there is at least one node displayed:
if (sizePlayground.cx && sizePlayground.cy/* && !IsPrintPreview()*/)
{
// Set maximum window size to match our playground size:
CRect rectClient, rectWindow;
pScrollView->GetClientRect(&rectClient);
this->GetWindowRect(&rectWindow);
if (rectWindow.top > -5000 && rectWindow.left > -5000) // Avoid when minimized...
{
lpMMI->ptMaxTrackSize.x = sizePlayground.cx + (rectWindow.Width() - rectClient.Width());
lpMMI->ptMaxTrackSize.y = sizePlayground.cy + (rectWindow.Height() - rectClient.Height());
return;
}
}
}
}
This works but has one problem: When print preview is displayed (standard MFC print preview), I obviously want to allow free window resizing, so I use runtime info GetActiveView()->IsKindOf(...) to determine that active view is really my view, and not print-preview's view (which is CPreviewViewEx). But when I close the print preview, OnGetMinMaxInfo is not called, so I'm unable to adjust frame size according to my view again. As soon as I move the window OnGetMinMaxInfo gets called again and correctly adjusts frame size, but without manually moving the window old size (to which the print preview was sized to) is retained and has ugly artifact.
What can I do? Basically if I could trap the moment when print preview is closed, I could use following trick:
// Trigger the WM_MINMAXINFO message:
CFrameWnd *pFrame = GetParentFrame();
RECT rectWindow;
pFrame->GetWindowRect(&rectWindow);
pFrame->MoveWindow(&rectWindow);
But I don't know how to trap print-preview closure.
What I'm trying to accomplish seems quite standard: who would want to have frame window resized bigger than view's logical size (set by SetScrollSizes())? So there should be some more natural solution maybe?
In your CMyFckinView, handle a message that is reliably sent when the print preview is closed and then post a user message to the mainframe which will trigger your "force minmax" code. Perhaps WM_FOCUS or WM_ACTIVATE?
Im creating a drop down menu and i want to know if there is anyway to implement the following:
I need to keep the sub-menu open for like 1 sec if the user moves the mouse away from the tab he selected. Much likely like in current intel web page www.intel.com , here u hover over menu, but if u take the mouse away from the tab or the sub-menu is opens it takes a few to hide the sub menu.
Im using .mouseover from jquery to show the menu (a div) but i cant find a way to make it stay for a few moments.
Thanks in advance
This may be of service
What is the JavaScript version of sleep()?
If you want to do something in the interim setTimeout() takes the arguments as shown where continue execution is another subroutine. If you just want this one tab to work this way have mouseover call doStuff and set a boolean (e.g. mouseStillIn) to TRUE. When the mouse exits set this boolean to FALSE, call a recursive function everytime mouseStillIn is TRUE.
e.g.
var mouseStillIn : boolean = false;
function MouseIn()
{
mouseStillIn=true;
CheckMouse();
}
function CheckMouse()
{
if(mouseStillIn)
{
setTimeout(CheckMouse, 1000);
}
}
function MouseOut()
{
mouseStillIn=false;
}