NSAlert beginSheetModalForWindow:completionHandler: - cocoa

While setting up an NSAlert object to be displayed as a modal sheet in Xcode 5.0.2, I hit an interesting surprise.
I was planning on using beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:
As I started to enter it, Xcode autofilled beginSheetModalForWindow:completionHandler: for me (even though I cannot find this in any NSAlert documentation).
I prefer to use completion handlers rather than delegate/selector as a callback mechanism, so I went ahead and tried it. I was pleasantly surprised to find that it worked perfectly.
Three quick questions before I commit to this.
Am I missing something in the documentation?
Is it "safe" to use this feature if it is undocumented? (i.e. will it magically disappear as mysteriously as it appeared?)
I'd rather not hardcode the response values based on what I'm seeing via logging. Does anybody know the "proper" NS...Button constants?

This call is “safe” but it’s 10.9+ only. Here it is from the header file:
#if NS_BLOCKS_AVAILABLE
- (void)beginSheetModalForWindow:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
#endif
It appears they just accidentally left it out of the current docs. The headers are generally considered the “truth” in Cocoa, though—they authoritatively tell you what’s deprecated and what’s new. (Unlike in X11, for instance, where the documentation was declared to be correct over the actual implementations or the headers.)
These are the constants you want to use inside your completionHandler block:
/* These are additional NSModalResponse values used by NSAlert's -runModal and -beginSheetModalForWindow:completionHandler:.
By default, NSAlert return values are position dependent, with this mapping:
first (rightmost) button = NSAlertFirstButtonReturn
second button = NSAlertSecondButtonReturn
third button = NSAlertThirdButtonReturn
buttonPosition 3+x = NSAlertThirdButtonReturn + x
Note that these return values do not apply to an NSAlert created via +alertWithMessageText:defaultButton:alternateButton:otherButton:informativeTextWithFormat:, which instead uses the same return values as NSRunAlertPanel. See NSAlertDefaultReturn, etc. in NSPanel.h
*/
enum {
NSAlertFirstButtonReturn = 1000,
NSAlertSecondButtonReturn = 1001,
NSAlertThirdButtonReturn = 1002
};

Related

How to deselect the contents of a TextField in swift

I have a simple desktop app where a TextField should be focused when the window loads. I have this working, but it's a little annoying that, having loaded the users content into the TextField, the entire contents of the field become selected automatically. The user may want to start editing the content, but they will rarely/never want to replace it all at once (imagine a text editor doing this, to see what I mean).
I see there is an Action for selectAll: but what I want is the opposite Action of selectNone:
I tried passing nil to the selectText method, but that doesn't work:
textField.selectText(nil)
I found a number of answers on StackOverflow that mention a selectedTextRange, but this appears to be outdated, because Xcode 6.3 doesn't recognize this as a valid property on TextField.
Can anyone explain how I do this?
It's been a while since I've dealt with NSTextFields to this level (I work mostly in iOS these days).
After doing a little digging I found this on the net:
NSText* textEditor = [window fieldEditor:YES forObject:textField];
NSRange range = {start, length};
[textEditor setSelectedRange:range];
window is the window containing your field, textField.
This requires the field editor to be managing your field, what can be done simply by previously selecting the whole text of the field using the selectText:sender method.
Here is the final swift code that I got working based on what Duncan C posted:
if let window = NSApplication.sharedApplication().mainWindow {
let textEditor = window.fieldEditor(true, forObject: textField)!
let range = NSRange(0..<0)
textEditor.selectedRange = range
}

Win32 custom message box

I want to make a custom message box. What I want to customize is the button's text.
MessageBoxW(
NULL,
L"Target folder already exists. Do you want to overwrite the folder?",
L"No title",
MB_YESNOCANCEL | MB_ICONQUESTION
);
I'd like to just change the buttons text to Overwrite, Skip, Cancel.
What's the most simple way?
I have to make this as having same look and feel with Windows default messagebox.
As said by others, a typical way is to create a dialog resource and have a completely independent dialog, which GUI you need to design in the way that it looks like standard dialog (to meet your request for feel and look). If you want to accept text messages, you might probably need to add code which resizes the window appropriately.
Still, there is another option for those who feel like diving into advanced things. While MessageBox API does not offer much for fint tuning, you still have SetWindowsHookEx in your hands. Having registgered the hook, you can intercept standard MessageBox window procedure and subclass it in the way you like.
Typical things include:
changing button text
adding more controls
adding timed automatic close
Hooking standard window can do all of those.
UPD. Hey, I realized I have some code with SetWindowsHookEx to share: http://alax.info/blog/127
You could create an own dialog. Or you could use a window hook as described in this article.
An archived version of the article can be found on web.archive.com.
Make a dialog resource (with a GUI editor, or by hand) and call DialogBox on it. There's no way to alter MessageBox behaviour, other than what's supported by its arguments.
That said, your message box can very well use stock Yes/No options.
The task dialog functionality introduced in Vista does exactly what you want and follows the prevailing system theme. However, if you have to support XP, then this will be of little comfort to you.
I know this question is old, but I just stumbled upon it now.
I would like to expand the other answers in regards to using a TaskDialog instead of a MessageBox. Here's a concise example of using a TaskDialog to do precisely what was asked; change the button's texts:
const TASKDIALOG_BUTTON buttons[] = { {IDYES, L"Overwrite"}, {IDNO, L"Skip"}, {IDCANCEL, L"Cancel"} };
TASKDIALOGCONFIG taskDialogConfig = {
.cbSize = sizeof(TASKDIALOGCONFIG),
.pszMainIcon = TD_WARNING_ICON, // TaskDialog does not support a question icon; see below
.pButtons = buttons,
.cButtons = ARRAYSIZE(buttons),
.pszWindowTitle = L"No title",
.pszContent = L"Target folder already exists. Do you want to overwrite the folder?"
};
TaskDialogIndirect(&taskDialogConfig, NULL, NULL, NULL);
Some noteworthy things:
You need to use TaskDialogIndirect, not the basic TaskDialog function
when not specifying a parent window, the icon specified in pszMainIcon is displayed in the title bar as well
There is no equivalent to the MessageBox's MB_ICONQUESTION, quoting a quote from this forumpost: Don't use the question mark icon to ask questions. Again, use the question mark icon only for Help entry points. There is no need to ask questions using the question mark icon anyway—it's sufficient to present a main instruction as a question.
checking which button was selected would have to be done by passing a pointer to an int as the second argument of TaskDialogIndirect and checking its value on return (the documentation should be pretty clear)
Here is a small open source library that allows you to customize Message Boxes. Developed by Hans Ditrich.
I have successfully used it in another POC that allows embedding a custom icon in such MessageBox that can be called even from a Console application.
I should also point to the Task Dialog. Here is an example of using it:
int nButtonPressed = 0;
TaskDialog(NULL, hInst,
MAKEINTRESOURCE(IDS_APPLICATION_TITLE),
MAKEINTRESOURCE(IDS_DOSOMETHING),
MAKEINTRESOURCE(IDS_SOMECONTENT),
TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON,
TD_WARNING_ICON,
&nButtonPressed);
if (IDOK == nButtonPressed)
{
// OK button pressed
}
else if (IDCANCEL == nButtonPressed)
{
// Cancel pressed
}

Can an NSStatusItem be shrunk to fit?

I have a variable length NSStatusItem which I'd like to stay visible whenever possible, even if that means showing only some of the content, but when my item is wide enough to run into an application's menu bar, it is hidden entirely. Is there a way to tell when this happens so that I can shrink the view to fit available space?
I've experimented with a custom view, overriding all the viewWill* methods, the frame setters, and the display methods, and periodically checking whether the containing window has moved or become hidden. I can't find any way to tell when my item is too long.
This depends on whether your status item application can detect the number of menu items in the OS X menu bar. A quick search through apple documentation shows that there are no public APIs provided by Apple for the purpose of doing this. To my knowledge, there are no private ones available too.
So I would recommend instead that you make your status item small by default and expanded when clicked by the user.
Edit:
Actually look at the discussion here: a really clever way to detect if your status item is being hidden. So once you have detected that it is being hidden, you can shrink it so that it reappears.
Here's a complete working example based on the discussion that hollow7 referenced:
self.statusItem.title = #"Message that will be truncated as necessary.";
while (self.statusItem.title.length > 0) {
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenAboveWindow, (CGWindowID)self.statusItemWindow.windowNumber);
if (CFArrayGetCount(windowList) > 1) {
CFRelease(windowList);
self.statusItem.title = [self.statusItem.title substringToIndex:self.statusItem.title.length - 1];
} else {
CFRelease(windowList);
break;
}
}
A tricky part that remains is getting the NSStatusItem window. So far, I've found two methods for obtaining it.
1 - There's a private method called _window. You can utilize it like this:
self.statusItemWindow = [self.statusItem performSelector:#selector(_window)];
2 - This is a bit more complicated but I think this is more likely to pass Apple's static analysis for private method usage in the Mac App Store:
Set the target and action of the NSStatusItem to a method you control, like this:
self.statusItem.target = self;
self.statusItem.action = #selector(itemClicked:);
Then access the window in the invoked method:
- (void)itemClicked:(id)sender {
self.statusItemWindow = [[NSApp currentEvent] window];
}

Big problems with MFC/WinAPI

I need to create a SDI form with a formview that has two tabs, which encapsulate multiple dialogs as the tab content. But the form has to have a colored background.
And things like these makes me hate programming.
First, I tried CTabControl, via resource editor, tried different things, but the undocumented behavior and the quirks with no answers led me into a roadblock.
After many hours of searching, I found that there is a control called property sheet, which is actually what I need.
Some more searching later, I found that property sheet can even be actually embedded onto CFormView like so: http://www.codeguru.com/Cpp/controls/propertysheet/article.php/c591
And that the dialog classes derived from CPropertyPage can be directly added as pages via AddPage method of CPropertySheet.
Great! Not quite so... Some of the controls didn't worked, and were not created, ran into weird asserts. Turns out the DS_CONTROL style was missing from the dialogs. Found it completely accidentaly on Link, no word about that on MSDN!!!! Property page must have: DS_3DLOOK | DS_CONTROL | WS_CHILD | WS_TABSTOP, and can have: DS_SHELLFONT | DS_LOCALEDIT | WS_CLIPCHILDREN styles! Not any other, which are created by default with resource editor. Sweet, super hidden information for software developers!
The quote in comments on that page: "OMG. That's where that behavior came from...
It turns out that the PlaySound API relied on that behavior when playing sounds on 64bit machines." by Larry Osterman, who as I understand works for Microsoft for 20 years, got me laughing out loud.
Anyway, fixed that, the dialog-controls(CPropertyPages) are created as expected now, and that part looks something remotely promising, but the next part with color is dead end again!
Normally you override WM_CTLCOLOR, check for control ID or hwnd and supply the necessary brush to set the color you need. Not quite so with CPropertySheet, the whole top row stays gray! For CTabCtrl it somehow works, for CPropertySheet it doesn't.
Why? Seems that the CPropertySheet is kinda embedded inside CTabControl or something, because if I override WM_ERASEBKGND, only the internal part changes the color.
Now it seems that there is a GetTabControl() method in the CPropertySheet, that returns the actual CTabCtrl* of the CPropertySheet. But since it's constructed internally, I can't find how to override it's WM_CTLCOLOR message processing.
There seems to be a way to subclass the windowproc, but after multiple tries I can't find any good source on how to do it. SubclassWindow doc on MSDN says: "The window must not already be attached to an MFC object when this function is called."?! What's that?
I tried creating a custom CCustomTabCtrl class based on CTabCtrl via MFC wizard, created an instance of it, called SubclassWindow from one of the CCustomPropertySheet handlers to override the internal CTabCtrl, but nothing works, mystical crashes deep inside MFC.
Tried setting WindowLong with GCL_HBRBACKGROUND for the internal CTabCtrl, nothing changed.
And worst of all, I can't find any sort of useful documentation or tutorials on the topic.
Most I can find is how to ownerdraw the tab control, but this is seriously wrong on so many ways, I want a standard control behavior minus background color, I don't want to support different color schemes, windows versions, IAccesible interfaces and all this stuff, and none of the ownerdraw samples I've seen can get even 10% of all the standard control behavior right. I have no illusion that I will create something better, I wont with the resources at hand.
I stumbled upon this thread, and I can't agree with the author more: http://arstechnica.com/civis/viewtopic.php?f=20&t=169886&sid=aad002424e80121e514548d428cf09c6 owner draw controls are undocumented PITA, that are impossible to do right, and there is NULL information on MSDN to help.
So is there anything I have missed or haven't tried yet? How to change the top strip background color of the CPropertySheet? Anyone?
Your only option is to ownerdraw the tab control. It's not that hard. Well, it is frustrating because MFC doesn't tell you how to make the necessary Win32 calls.
In your CPropertySheet-derived class, overwrite OnInitDialog() and add:
GetTabControl()->ModifyStyle(0,TCS_OWNERDRAWFIXED);
This puts your CPropertySheet-derived class in charge of drawing the tab control. Add a handler for WM_DRAWITEM (OnDrawItem) and change backgroundColor and textColor to match whatever colors you wanted. Code for OnDrawItem follows:
void CPropSht::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if (ODT_TAB != lpDrawItemStruct->CtlType)
{
CPropertySheet::OnDrawItem(nIDCtl, lpDrawItemStruct);
return;
}
// prepare to draw the tab control
COLORREF backgroundColor = RGB(0,255,0);
COLORREF textColor = RGB(0,0,255);
CTabCtrl *c_Tab = GetTabControl();
// Get the current tab item text.
TCHAR buffer[256] = {0};
TC_ITEM tcItem;
tcItem.pszText = buffer;
tcItem.cchTextMax = 256;
tcItem.mask = TCIF_TEXT;
if (!c_Tab->GetItem(c_Tab->GetCurSel(), &tcItem )) return;
// draw it
CDC aDC;
aDC.Attach(lpDrawItemStruct->hDC);
int nSavedDC = aDC.SaveDC();
CBrush newBrush;
newBrush.CreateSolidBrush(backgroundColor);
aDC.SelectObject(&newBrush);
aDC.FillRect(&lpDrawItemStruct->rcItem, &newBrush);
aDC.SetBkMode(TRANSPARENT);
aDC.SetTextColor(textColor);
aDC.DrawText(tcItem.pszText, &lpDrawItemStruct->rcItem, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
aDC.RestoreDC(nSavedDC);
aDC.Detach();
}
Thank you for this solution but...
The above solution works well with one tab, but when you have multiple tabs it seems rename the wrong tabs. I needed to change the if statement for GetItem to:
if (!c_Tab->GetItem(lpDrawItemStruct->itemID, &tcItem )) return;
Needed lpDrawItemStruct->itemID to get the tabs named correctly

EN_PROTECTED and RichEdit

The EN_PROTECTED notify message is sent to the parent of a rich edit control when there is an attempt to change "protected" text. This works for me and I've tried it with both richedit20 and richedit50. Any change to this protected text immediately triggers the EN_PROTECTED message. (Its a little complicated to set it up, but I've done that correctly.)
However, the documentation says if the parent returns non zero in response to the EN_PROTECTED message, it will prevent the protected text from being changed. This is not working for me.
I created a simple test dialog test app, using MFC - add the richedit control via the dialog edit, added a call to AfxInitRichEdit in the app initialization, added some code to the dialog to put text in the control (SetWindowText), selected the second word, applied the CFE_PROTECTED effects and then added a handler for EN_PROTECTED, in the handler i just set *pResult = TRUE.
When i ran the app it all worked fine; in that i was not able to modify the protected word but i could modify the rest of the text.
Unfortunately that really doesn't lead us to a conclusive reason as to why your code doesn't work - from what i can see it appears correct. Could be the version of the RichEdit20 dll i suppose - mine is 5.31.23.1224
(To Ruddy: code sample below if it reveals anything)
I eventually just did a PostMessage(hwnd,EM_UNDO...) from within the EN_PROTECTED handler and that is what I had to do to get this working for me. Returning TRUE never accomplished anything, though I know the handler was being hit and only for protected text. (the ODS function below is OutputDebugString).
But I've seen multiple examples on the web (most of them MFC however or sometimes DELPHI or something), where just returning TRUE in the EN_PROTECTED handler is said to prevent the change.
Actually, my Rich Edit Control was within a dialog but was being created with CreateWindowEx,so I tried intializing it through the RC file instead but it made no difference. (Some of the stuff I'm doing is kind of old school admittedly - sorry about that.) But actually I tried anything and everything to make EN_PROTECTED work like its documented and nothing worked - bizarre.
Oh well, EM_UNDO from within the EN_PROTECTED handler works, so guess I'll stick with that.
Original code (with the added EM_UNDO call):
case WM_NOTIFY: {
NM_UPDOWN* nm = (NM_UPDOWN*)lParam;
if ((nm->hdr.code == UDN_DELTAPOS) && (nm->hdr.idFrom == ID_UPD_ERR)) {
int e = nm->iPos + nm->iDelta;
SetWindowText(xml2->hStatMsg[1],xml2->ErrMsg(1,e));
SetWindowText(xml2->hStatMsg[2],xml2->ErrMsg(2,e));
}
else if (wParam == ID_EDIT_A) {
if (((LPNMHDR)lParam)->code == EN_PROTECTED) {
ODS("EN_PROTECTED", (int)((ENPROTECTED*)lParam)->msg);
PostMessage(xml2->hImgXml2,EM_UNDO,0,0);
return TRUE;
}
if (((LPNMHDR)lParam)->code == EN_SELCHANGE) {
anchors_adjsel(xml2->hImgXml2);
}
}
}
break;

Resources