What I want to accomplish:
I need a checkbox with a title text that dynamically wraps and breaks on multiple lines depending on a dynamic width established by the parent view. I need a solution that I can use in IB and that will display there as it's shown at runtime. I'm using XCode 13.1, working in a XIB-File targetting MacOS.
What I'm doing:
I create an NSButton in IB. In the attributes inspector I set its style to Check, under "Control" I choose Word Wrap for "Line Break" and finally I set a very long text as the title such as Asd Asd Asd lit tle words and many of them asd asd lit tle ones.
What's happening:
When setting up the button as described above (case 1) and shrinking its width, it will be displayed - in IB and at runtime - like this:
When manually adding a line break to the title as suggested in this similar question by pressing Option + Enter (case 2, here after "them") the title starts wrapping correctly and all the other necessary breaks are generated:
However this solution is not applicable for my case since it only works for a static width, but my checkboxes need to adjust their width dynamically as described above.
Without that additional manual line break it's most interesting that apparently the checkbox is already reacting and changing its position according to the new wrapped height of the title while the title text itself is just clipped by the bounds of the control instead of being displayed in a wrapped fashion.
What I'd expect:
I'd expect the title to wrap in case 1. Since it doesn't: Is this a bug or a feature? How can I make case 1 work and get the title to wrap dynamically depending on its length and the width of the button? Do I just need to set another attribute in the inspector I missed so far? Or is there only a programmatic solution?
To answer the question why I don't use an appropriately short label: Don't ask me, I'm just a developer following specs & reqs and unfortunately I don't have a saying on what would be a good length of text here.
The credits for this answer goes to #Willeke's comment: "AppKit doesn't support multiline checkboxes." The interesting behaviour of the checkbox in case 1 suggesting otherwise seems to be just a glitch or bug.
What I ended up with: I opted for a workaround. I'm placing a checkbox-button (with Image Position "Image only" in IB) right beside a multiline-label, putting both in a custom view, adding the necessary constraints. With a few positioning adjustments I know have a solution that looks exactly like a singleline checkbox, that I can copy-paste in IB and that is solved by Autolayout - in IB and at runtime - without any additional code.
Related
I'm new to the Xcode User Interface testing framework. I can successfully manipulate the screen elements, but cannot work out how to produce a meaningful assertion about what text is visible in a scrolling view.
The test I would like to write would go as follows: launch the app, type lots of text into a text view (enough that the first line scrolls out of view), assert that the first line of text is not visible, scroll the view back up to the top, then assert that the first line is now visible. Note that the purpose of this test is to ensure my app has wired things up correctly, not to test Apple's code.
XCUIApplication allows me to type into my NSTextView instance, and also allows me to scroll the associated NSScrollView. But how do I assert whether the first line of text is currently visible? The value attribute on XCUIElement provides the entire text content of the view, whether or not it is currently displayed.
The accessibilityRange(forLine:) and accessibilityString(for:) methods on NSTextView would be ideal, but I can't see how to access them as the UI test only has access to an XCUIElement, not the underlying NSTextView.
Have I missed something, or is there a better way to approach this?
If you set the accessibility identifier in the storyboard or in code for the text view you can get the text view via (assuming you gave it the id "textview1" and the window it's in has the default accessibility identifier of "Window"):
let textview1TextView = app.windows["Window"].textViews["textview1"]
but that won't actually get you what you need.
Instead, set the accessibility identifier of the scrollview and get that:
let scrollview = app.windows["Window"].scrollViews["scrollview1"]
Then use that to get the scrollbars (you should only have one in this case; you can use scrollbars.count to check.
let scrollbars = scrollview.scrollBars
print("scrollbars count: \(scrollbars.count)")
Then you can use the value attribute of the scrollbar to get it's value:
(you're converting a XCUIElemenTypeQueryProvider into an XCUIElement so you can get it's value):
let val = scrollbars.element.value
it will be 0 at the top and a floating point value when scrolled (one line of text in my test code showed a value of {{0.02409638554216868}}.
Documentation that will help you explore further:
XCUIElementTypeQueryProvider
XCUIElementAttributes
Note that you can put a breakpoint in the middle of your test, run it and then use the debugger console to examine things:
(lldb) po scrollbars.element.value
t = 749.66s Find the ScrollBar ▿ Optional<Any>
- some : 0
(lldb) po scrollbars.element.value
t = 758.17s Find the ScrollBar ▿ Optional<Any>
- some : 0.05421686746987952
and while in the debugger you can even interact with your app's window to scroll it manually (which is what I did between typing in those two po calls), or perhaps add text and so on.
OK OP now noted that they're interested in the specific text showing or not rather than the first line in view or not (which is what I previously answered above).
Here's a bit of a hack, but I think it'll work:
Use XCUICoordinate's click(forDuration:, thenDragTo:) method to select the first line of text (use the view frame to calculate coordinates) and then use the typeKey( modifierFlags:) to invoke the edit menu "copy" command. Then use NSPasteboard methods to get the pasteboard contents and check the text.
Here's a quick test I did to validate the approach (selecting the first line of text using XCUICoordinate as noted above is left as an exercise for the reader):
NSPasteboard.general.clearContents()
// stopped at break point on next line and I manually selected the text of the first line of text in my test app and then hit continue in the debugger
textview1TextView.typeKey("c", modifierFlags:.command)
print( NSPasteboard.general.pasteboardItems?.first?.string(forType: NSPasteboard.PasteboardType.string) ?? "none" );
-> "the text of the first line" was printed to the console.
Note that you can scroll the selection off screen so you have to not scroll after doing the select or you won't be getting the answer you want.
If I make button relatively small, it's caption turns to ellipsis.
How to turn off this feature?
Don't let the button go below it's preferred size, then it will never need to elide the text of the button label:
button.setMinSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);
I want to make very small button
You can use any of the below either separately or in combination:
Apply CSS to use a very small font in the button.
Make the label text for the button very short.
Use brian's answer which proposes explicitly setting the ellipse string to empty.
Use a small graphic icon instead of text.
You can use setMinSize as documented above in all cases (if you wish the button not to go below a preferred size truncating or eliding content).
In all cases, if you wish, you can also apply CSS to minimize the padding between the label and button the border.
From your previous comment (I want to use simple captions like "<" and ">"), I think option 2 (Make the label text for the button very short) is what you want.
You may also be interested in Joel's Designing for People Who Have Better Things To Do With Their Lives which would indicate, usability-wise that very small buttons are usually a pretty bad idea.
in your label/button you can use the textOverrun property to turn off ellipsis.
textOverrun.set(OverrunStyle.CLIP);
this is probably a bit late for you, so i am putting it here for lone wanderers digging up this question.
It puts ... because there's no room for the text. You can use bigger buttons or a smaller font but if you really want the dots gone use button.setEllipsisString(""); , but then you just get truncated text.
I have a NSTouchBar with an item whose view is a NSSegmentedControl that has 4 items - A, B, C, D. For a person with sight, it makes sense, but for a person who uses VoiceOver, just saying "A", "B", "C" or "D" isn't a great experience - they may not understand what exactly does that do, so I'd like to change the accessibility titles to "Select A", "Perform B", "Open C", "Show D".
AFAIK, the NSSegmentedControl doesn't have any methods on setting custom accessibility titles, so I've dug deeper - it seems that it creates a subview for each of the segments (NSSegmentItemView, but it's not important) - so I've tried:
setting the isAccessibilityElement to true on the segmented control itself
for each of the subviews, I've set it to true as well, and I've tried setting accessibility title, label, ... Pretty much anything that would help.
Unfortunately I can set anything I want on these and VoiceOver will ignore it. The issue is complicated by the fact that the Accessibility Inspector doesn't work on the Touch Bar window...
Does anyone have any ideas?
I was trying to do something similar (reimplement accessibility on a custom modification of NSSegmentedControl), and I was mostly successful by overriding some of NSAccessibilityProtocol methods.
In your case, you could try overriding accessibilityChildren to return custom elements that provide different labels.
NSSegmentedControl will pick up the accessibilityDescription of the image. So the solution I used was to ensure all my segmented controls have images, and for each one, if necessary, make a copy of the image, set the accessibilityDescription explicitly, and reset the image. Some of my segments used single character text as well, so I created images of the characters instead and used them.
Ugly, but better than any other solution I found.
I'm just in the process of changing the SalesInvoice Report. One thing I'm trying to do is show the label of some items not left of the item value but above it instead.
Seems easy enough: just change the LabelPosition (for example from CustInvoiceJour_InvoiceId) setting from "left" to "above" and voila: the label has vanished. It is just shown nowhere at all.
Strange. I would have expected the label to show up, well, above the content. Not to vanish.
Am I missing something (there is no label height to set), or is this functionality broken?
I'm not sure if this functionality is broken. But in generated design, these elements will not show the label if it's set to above:
Prolog
PageHeader
Header
Footer
Epilog
ProgrammableSection
If the item you're trying to change is in one of those, then the label position above will, from my experience, not work.
My tip is to create a new control of the text type and then use the same label that the extended data type for the field uses under the "text" property and then adjust the position so it is positioned above the field you're trying to set a label on.
From what I have noticed for a ProgrammableSection the column name labels are not shown only on the first page of the report. The labels are shown on the next pages.
When using custom-draw (NM_CUSTOMDRAW) to draw the entire contents of a ListView SubItem (in Report/Details view), it would be nice to be able to apply the same left and right
padding in my custom paint method that is applied by the control itself for non-custom-drawn items.
Is there a way to programmatically retrieve this padding value? Is it
related to the width of a particular character (" " or "w" or something?) or
is it a fixed value (6px on left and 3px on right or something) or...?
EDIT: To clarify, I want to add the same padding to my NM_CUSTOMDRAWn SubItems that the control adds to items that it draws, and the metric that I'm looking for, for example, is the white space between the beginning of the 2nd column and the word "Siamese" in the following screenshot (Note: screenshot from MSDN added to help explain my question):
(source: microsoft.com)
Note that the word "Siamese" is aligned with the header item ("Breed"). I would like to be able to guarantee the same alignment for custom-drawn items.
use ListView Header message HDM_GETBITMAPMARGIN
see link text
ListView_GetSubItemRect (LVM_GETSUBITEMTECT)
http://msdn.microsoft.com/en-us/library/ms930172.aspx
Despite what the documentation says I suspect LVIR_LABEL returns just the returns the bounding rectangle of the item text, as per ListView_GetItemRect.
(This just kept niggling me as I though I had actually seen an answer somewhere when playing with NM_CUSTOMDRAW).
Edit After Comment 2:
I imagine you have seen NMLVCUSTOMDRAW which if you are willing to use Version 6.0. has rcText. I wouldn't since I use Win2K.
Given what you have found I would go back to the suggestion of using
ListView_GetItemRect to get LVIR_LABEL and compare that with LVIR_BOUNDS and use the difference.
the way for doing this is retrieving the format of the corresponding column with
ListView_GetColumn()
then check the retrieved myLVCOLUMN.mask
LVCOLUMN myLVCOLUMN;
myLVCOLUMN.mask=LVCF_FMT;
ListView_GetColumn(hwnd,nCol,&myLVCOLUMN);
then when we draw the corresponding label belonging to that column
if(myLVCOLUMN.fmt & LVCFMT_CENTER)
DrawText(x,x,x,x, DT_CENTER | DT_WORD_ELLIPSIS );
else if (myLVCOLUMN.fmt & LVCFMT_RIGHT)
DrawText(x,x,x,x, DT_RIGHT | DT_WORD_ELLIPSIS );
else
DrawText(x,x,x,x, DT_LEFT | DT_WORD_ELLIPSIS );
I would assume that GetSystemMetrics() is that you need to look at. I think that SM_CXEDGE and SM_CYEDGE are probably the values you want, but don't quote me on that. ;-)
Can only guess without seeing your output.
A few suggestions: If you are using the DrawTextEx function, have you have experimented with DT_INTERNAL et al?
Are you accidentally putting in a blank image/icon.
Does it look ok in classic screen mode? If so I would look at XP Theme functions to see if some thing is going on.
Late edit after first comment:
I wonder if the size of rectangle matches the space required for the LVN_ENDLABELEDIT edit box around the text so the text doesn't move (or for a focus rectangle)?
I guess you could compare the result of LVM_GETITEMRECT with LVIR_LABEL on the first column and use the difference as your left border.