I am currently using a UILabel to display multiple lines of text. The line break most is set to NSLineBreakByWordWrapping so the the label automatically inserts line breaks. How can I detect where those line breaks are? Basically, I want to return a string with each \n break included.
Here is my work around for detecting line breaks inside UILabel
- (int)getLengthForString:(NSString *)str fromFrame:(CGRect)frame withFont:(UIFont *)font
{
int length = 1;
int lastSpace = 1;
NSString *cutText = [str substringToIndex:length];
CGSize textSize = [cutText sizeWithFont:font constrainedToSize:CGSizeMake(frame.size.width, frame.size.height + 500)];
while (textSize.height <= frame.size.height)
{
NSRange range = NSMakeRange (length, 1);
if ([[str substringWithRange:range] isEqualToString:#" "])
{
lastSpace = length;
}
length++;
cutText = [str substringToIndex:length];
textSize = [cutText sizeWithFont:font constrainedToSize:CGSizeMake(frame.size.width, frame.size.height + 500)];
}
return lastSpace;
}
-(NSString*) getLinebreaksWithString:(NSString*)labelText forWidth:(CGFloat)lblWidth forPoint:(CGPoint)point
{
//Create Label
UILabel *label = [[UILabel alloc] init];
label.text = labelText;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
//Set frame according to string
CGSize size = [label.text sizeWithFont:label.font
constrainedToSize:CGSizeMake(lblWidth, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
[label setFrame:CGRectMake(point.x , point.y , size.width , size.height)];
//Add Label in current view
[self.view addSubview:label];
//Count the total number of lines for the Label which has NSLineBreakByWordWrapping line break mode
int numLines = (int)(label.frame.size.height/label.font.leading);
//Getting and dumping lines from text set on the Label
NSMutableArray *allLines = [[NSMutableArray alloc] init];
BOOL shouldLoop = YES;
while (shouldLoop)
{
//Getting length of the string from rect with font
int length = [self getLengthForString:labelText fromFrame:CGRectMake(point.x, point.y, size.width, size.height/numLines) withFont:label.font] + 1;
[allLines addObject:[labelText substringToIndex:length]];
labelText = [labelText substringFromIndex:length];
if(labelText.length<length)
shouldLoop = NO;
}
[allLines addObject:labelText];
//NSLog(#"\n\n%#\n",allLines);
return [allLines componentsJoinedByString:#"\n"];
}
How to use
NSString *labelText = #"We are what our thoughts have made us; so take care about what you think. Words are secondary. Thoughts live; they travel far.";
NSString *stringWithLineBreakChar = [self getLinebreaksWithString:labelText forWidth:200 forPoint:CGPointMake(15, 15)];
NSLog(#"\n\n%#",stringWithLineBreakChar);
You just need to set parameters required by getLinebreaksWithString and you will get a string with each "\n" break included.
OutPut
Here is my solution to this problem.
I iterate through all whitespace characters in the provided string, set substrings (from beginning to current whitespace) to my label and check the difference in the height. When the height changes I insert a new line character (simulates a line break).
func wordWrapFormattedStringFor(string: String) -> String {
// Save label state
let labelHidden = label.isHidden
let labelText = label.text
// Set text to current label and size it to fit the content
label.isHidden = true
label.text = string
label.sizeToFit()
label.layoutIfNeeded()
// Prepare array with indexes of all whitespace characters
let whitespaceIndexes: [String.Index] = {
let whitespacesSet = CharacterSet.whitespacesAndNewlines
var indexes: [String.Index] = []
string.unicodeScalars.enumerated().forEach { i, unicodeScalar in
if whitespacesSet.contains(unicodeScalar) {
let index = string.index(string.startIndex, offsetBy: i)
indexes.append(index)
}
}
// We want also the index after the last character so that when we make substrings later we include also the last word
// This index can only be used for substring to this index (not from or including, since it points to \0)
let index = string.index(string.startIndex, offsetBy: string.characters.count)
indexes.append(index)
return indexes
}()
var reformattedString = ""
let boundingSize = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let attributes = [NSFontAttributeName: font]
var runningHeight: CGFloat?
var previousIndex: String.Index?
whitespaceIndexes.forEach { index in
let string = string.substring(to: index)
let stringHeight = string.boundingRect(with: boundingSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).height
var newString: String = {
if let previousIndex = previousIndex {
return string.substring(from: previousIndex)
} else {
return string
}
}()
if runningHeight == nil {
runningHeight = stringHeight
}
if runningHeight! < stringHeight {
// Check that we can create a new index with offset of 1 and that newString does not contain only a whitespace (for example if there are multiple consecutive whitespaces)
if newString.characters.count > 1 {
let splitIndex = newString.index(newString.startIndex, offsetBy: 1)
// Insert a new line between the whitespace and rest of the string
newString.insert("\n", at: splitIndex)
}
}
reformattedString += newString
runningHeight = stringHeight
previousIndex = index
}
// Restore label
label.text = labelText
label.isHidden = labelHidden
return reformattedString
}
Image - Comparison of output string with label text.
This solution worked really well in my case, hope it helps.
Related
I'm implementing a little app with Xamarin Forms for a web page, the thing is that in this web is a linear chart with multiple entries and if the user clicks on a point of the line shows info about that point, as you can see in the picture:
Web Line Chart
After some work, I could create a more or less similar line chart using the OxyPlot.Xamarin.Forms plugin with multiple entries which shows the points
My App Line Chart
This is my code:
OnPropertyChanged("GraphModel");
var model = new PlotModel
{
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.BottomCenter,
LegendOrientation = LegendOrientation.Horizontal,
LegendBorderThickness = 0
};
model.PlotType = PlotType.XY;
model.InvalidatePlot(false);
Dictionary<string, List<Prices>> values = HistoricData[Selected.ProductId];
int colorIndex = 0;
List<string> x_names = new List<string>();
foreach (var item in values.Keys)
{
if (item.ToUpper() == Selected.ProductName) { SelectedIndex = colorIndex; }
var lineSeries = new LineSeries()
{
Title = item,
MarkerType = MarkerType.Circle,
};
lineSeries.MarkerResolution = 3;
lineSeries.MarkerFill = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerStroke = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerSize = 3;
var points = new List<DataPoint>();
lineSeries.Color = OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
foreach (var price in values[item])
{
points.Add(new DataPoint(price.Week+price.Year, price.Price));
}
if (ButtonsVisibility.Count == 0)
{
lineSeries.IsVisible = (Selected.ProductName == item.ToUpper()) ? true : false;
}
else
{
lineSeries.IsVisible = ButtonsVisibility[colorIndex];
}
lineSeries.ItemsSource = points;
lineSeries.MarkerType = OxyPlot.MarkerType.Circle;
model.Series.Add(lineSeries);
colorIndex++;
}
NumButtons = colorIndex;
LinearAxis yaxis = new LinearAxis();
yaxis.Position = AxisPosition.Left;
yaxis.MajorGridlineStyle = LineStyle.Dot;
model.Axes.Add(yaxis);
LineChart = model;
OnPropertyChanged("GraphModel");
return LineChart;
My doubt is which property I should work with and show at least the value of a concrete point, I have seen the property OnTouchStarted but is only for all the LineSeries and not for a single point. I read in some articles that OxyPlot.Xamarin.Forms include a tracker. I added this line in my code:
lineSeries.TrackerFormatString = "X={2},\nY={4}";
Is supposed to show the x and y values on click but doesn't show anything, any suggestion?
Should show something like that: Tracker info on point
From the following example: Tracker Example
Updated Code
public PlotModel GetLineChart()
{
OnPropertyChanged("GraphModel");
var model = new PlotModel
{
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.BottomCenter,
LegendOrientation = LegendOrientation.Horizontal,
LegendBorderThickness = 0
};
model.PlotType = PlotType.XY;
model.InvalidatePlot(false);
Dictionary<string, List<Prices>> values = HistoricData[Selected.ProductId];
int colorIndex = 0;
List<string> x_names = new List<string>();
foreach (var item in values.Keys)
{
if (item.ToUpper() == Selected.ProductName) { SelectedIndex = colorIndex; }
var lineSeries = new LineSeries()
{
Title = item,
MarkerType = MarkerType.Circle,
CanTrackerInterpolatePoints = false
};
lineSeries.MarkerResolution = 3;
lineSeries.MarkerFill = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerStroke = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerSize = 3;
var points = new List<DataPoint>();
lineSeries.Color = OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
foreach (var price in values[item])
{
points.Add(new DataPoint(price.Week+price.Year, price.Price));
}
if (ButtonsVisibility.Count == 0)
{
lineSeries.IsVisible = (Selected.ProductName == item.ToUpper()) ? true : false;
}
else
{
lineSeries.IsVisible = ButtonsVisibility[colorIndex];
}
lineSeries.ItemsSource = points;
lineSeries.MarkerType = OxyPlot.MarkerType.Circle;
lineSeries.TrackerFormatString = "X={2},\nY={4}";
lineSeries.TextColor = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
model.Series.Add(lineSeries);
colorIndex++;
}
NumButtons = colorIndex;
LinearAxis yaxis = new LinearAxis();
yaxis.Position = AxisPosition.Left;
//yaxis.StringFormat = "X={2},\nY={4}";
yaxis.MajorGridlineStyle = LineStyle.Dot;
model.Axes.Add(yaxis);
LineChart = model;
OnPropertyChanged("GraphModel");
return LineChart;
}
}
protected async override void OnAppearing()
{
await _viewModel.LinearViewModel.GetSubCategoryHistoricWeekPrices(App.ViewModel.LoginViewModel.SesionToken, FROM_DATE, TO_DATE);
Plot.Model = _viewModel.LinearViewModel.GetLineChart();
PlotController controller = new PlotController();
controller.UnbindAll();
controller.BindTouchDown(PlotCommands.PointsOnlyTrackTouch);
Plot.Controller = controller;
AddButtons();
}
Xaml Declaration for plot view:
<oxy:PlotView
Grid.Row="2"
Grid.RowSpan="2"
Grid.ColumnSpan="4"
x:Name="Plot" />
Your problem lies with following line.
lineSeries.TrackerKey = "X={2},\nY={4}";
When you use series.TrackerKey, you are specifying that you are using a CustomTracker, which in this case you are not. Custom trackers would be useful if you need to use different trackers for each series in the model.
In you case, you should remove that line and use only TrackerFormatString.
lineSeries.TrackerFormatString = "X={2},\nY={4}";
This would show the tooltip using the format string parameters, where {2} signifies X Value and {4} signifies Y. For your information, following are place holders.
{0} = Title of Series
{1} = Title of X-Axis
{2} = X Value
{3} = Title of Y-Axis
{4} = Y Value
If you need to include additional/custom information in your tool, your Data Point needs to be include that information. This where IDataPointProvider interface becomes handy. You could create a Custom DataPoint by implementing the interface and then you could include the same information in your tooltip as well.
Update Based On Comments
Additionally, to include "Touch", you can specify TouchDown in the PlotController. You can do so by defining the PlotController in your viewModel as following.
public PlotController CustomPlotController{get;set;}
You can define the property as follows.
CustomPlotController = new PlotController();
CustomPlotController.UnbindAll();
CustomPlotController.BindTouchDown(PlotCommands.PointsOnlyTrackTouch);
And in your Xaml
<oxy:Plot Controller="{Binding CustomPlotController}"......
I was wondering about programmatically adding a NSTableView inside a NSStackView using Swift 3/MacOS Sierra.
The idea would be to have say 2 NSTextFields aligned via the centerY axis in the .leading gravity space, then a tableview in the .center gravity space, then 2 more NSTextFields aligned via the centerY axis in the .trailing gravity space. The stack view would span the width of the NSView -- like a header.
Is this a good idea or should I avoid doing this? It has been very difficult to get it to look correct -- the table always has too large of a width despite adding constraints to try to pin it to a fixed width.
Any insight would be appreciated. I'm new to programming MacOS.
Thanks,
Here is the output in Interface Builder:
output of the headerview
Here is the code of the NSView I'm using:
The view controller is elsewhere but I'm not really having problems with the view controller -- it's displaying the data in the table correctly. It's just the sizing/positioning of the tableview (which I'm trying to do in the NSView via the NSStackView) is always wrong. It should have a width of 650 but instead has a width of 907 and I get the same error all the time in the debug console:
2017-09-12 17:43:36.041062-0500 RaceProgram[795:36958] [Layout] Detected missing constraints for < RacingProgram.RaceImportViewHeader: 0x6000001ccd50 >. It cannot be placed because there are not enough constraints to fully define the size and origin. Add the missing constraints, or set translatesAutoresizingMaskIntoConstraints=YES and constraints will be generated for you. If this view is laid out manually on macOS 10.12 and later, you may choose to not call [super layout] from your override. Set a breakpoint on DETECTED_MISSING_CONSTRAINTS to debug. This error will only be logged once.
import Cocoa
#IBDesignable
class RaceImportViewHeader: NSView {
// MARK: Properties
private var raceQualificationsTableView:NSTableView
private var raceImportHeaderStackView:NSStackView
private var raceNumberTitle: NSTextField
private var raceNumberValue: NSTextField
public var raceQualificationsTableRowHeight: CGFloat
#IBInspectable var borderColor:NSColor = .black
#IBInspectable var backgroundColor:NSColor = .lightGray
enum InitMethod {
case Coder(NSCoder)
case Frame(CGRect)
}
override convenience init(frame: CGRect) {
self.init(.Frame(frame))!
}
required convenience init?(coder: NSCoder) {
self.init(.Coder(coder))
}
private init?(_ initMethod: InitMethod) {
// Group together the initializers for this view class
raceQualificationsTableView = NSTableView()
raceImportHeaderStackView = NSStackView()
raceNumberTitle = NSTextField()
raceNumberValue = NSTextField()
raceQualificationsTableRowHeight = 17.0 // Initialize the row height for raceQualifications
switch initMethod {
case let .Coder(coder): super.init(coder: coder)
case let .Frame(frame): super.init(frame: frame)
}
self.translatesAutoresizingMaskIntoConstraints = false
drawUI()
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let viewSize: NSRect = self.frame
let newRect = NSRect(x: 0, y: 0, width: viewSize.width, height: viewSize.height)
// Outline the Header --> Only for layout debug purposes
let path = NSBezierPath(rect: newRect)
backgroundColor.setFill()
path.fill()
borderColor.setStroke() // Set the stroke color
path.stroke() // Fill the stroke or border of the rectangle
}
// MARK: UI Construction
func drawUI() {
let viewFrame = self.frame // with respect to the super class
let viewBounds = self.bounds // with respect to the view
// MARK: Race Number Setup
func addRaceNumberTitle(startingPositionX: CGFloat) {
// This configures label for race Number
let width:CGFloat = 60.0 //Arbitrary at the moment
let height:CGFloat = 40.0
let leftPadding:CGFloat = 2.5 // The super view (frame)is the NSView in this case
let topPadding:CGFloat = (viewBounds.height - height)/2
let raceNumberTitleNSRect = NSRect(x: leftPadding + startingPositionX, y: viewBounds.height - height - topPadding, width: width, height: height)
//Swift.print("The raceNumberTitleNSRect title NSRect is \(raceNumberTitleNSRect)")
raceNumberTitle = NSTextField(frame: raceNumberTitleNSRect)
raceNumberTitle.stringValue = "Race\nNumber"
raceNumberTitle.maximumNumberOfLines = 2
raceNumberTitle.isEditable = false
raceNumberTitle.isBordered = false
raceNumberTitle.alignment = .center
raceNumberTitle.backgroundColor = .clear
raceNumberTitle.sizeToFit()
let updatedHeight = raceNumberTitle.frame.height
let newUpdatedPadding = (viewBounds.height - updatedHeight) / 2
let oldOriginX = raceNumberTitle.frame.origin.x
let newOriginY = viewBounds.height - updatedHeight - newUpdatedPadding
let newOrigin = NSPoint(x: oldOriginX, y: newOriginY)
raceNumberTitle.setFrameOrigin(newOrigin)
//addSubview(raceNumberTitle) // Add to view
raceImportHeaderStackView.addView(raceNumberTitle, in: .leading)
}
func addRaceNumberValue(startingPositionX: CGFloat) {
// This configures value label for race number
let width:CGFloat = 20.0 //Arbitrary at the moment
let height:CGFloat = 40.0
let leftPadding:CGFloat = 5.0 // The super view (frame)is the NSView in this case
let topPadding:CGFloat = (viewBounds.height - height)/2
let raceNumberInRect = NSRect(x: startingPositionX + leftPadding, y: viewBounds.height - height - topPadding, width: width, height: height)
Swift.print("The raceNumberInRect title NSRect is \(raceNumberInRect)")
raceNumberValue = NSTextField(frame: raceNumberInRect)
raceNumberValue.identifier = "raceNumber"
raceNumberValue.placeholderString = "1"
raceNumberValue.font = NSFont(name: "Impact", size: 20.0)
raceNumberValue.maximumNumberOfLines = 1
raceNumberValue.isEditable = false
raceNumberValue.isBordered = true
raceNumberValue.alignment = .center
raceNumberValue.backgroundColor = .clear
raceNumberValue.sizeToFit()
let updatedHeight = raceNumberValue.frame.height
let oldOriginX = raceNumberValue.frame.origin.x
let newUpdatedPadding = (viewBounds.height - updatedHeight) / 2
let newOriginY = viewBounds.height - updatedHeight - newUpdatedPadding
let newOrigin = NSPoint(x: oldOriginX, y: newOriginY)
raceNumberValue.setFrameOrigin(newOrigin)
//addSubview(raceNumberValue) // Add to view
raceImportHeaderStackView.addView(raceNumberValue, in: .leading)
}
// MARK: Race Qualifications Table Setup
func addRaceQualificationsTable(startingPositionX: CGFloat) {
// Padding variables
let leftPadding:CGFloat = 5.0
let topPadding:CGFloat = 5.0
// Table Properties
let width:CGFloat = 650.0
let height:CGFloat = 40
let tableRect = CGRect(x: startingPositionX + leftPadding, y: viewBounds.height - height - topPadding, width: width, height: height)
//let insetForTableView:CGFloat = 1.0
//let scrollRect = CGRect(x: tableRect.origin.x-insetForTableView, y: tableRect.origin.y-insetForTableView, width: tableRect.width+2*insetForTableView, height: tableRect.height+2*insetForTableView)
let tableNSSize = NSSize(width: tableRect.width, height: tableRect.height)
let scrollNSRect = NSScrollView.frameSize(forContentSize: tableNSSize, horizontalScrollerClass: nil, verticalScrollerClass: nil, borderType: .bezelBorder, controlSize: .regular, scrollerStyle: .legacy)
Swift.print("tableRect \(tableRect)")
Swift.print("scrollNSRect \(scrollNSRect)")
//Swift.print("scrollRect \(scrollRect)")
let scrollViewOrigin:CGPoint = tableRect.origin
let scrollViewNSSize:CGSize = scrollNSRect
let scrollRect = NSRect(origin: scrollViewOrigin, size: scrollViewNSSize)
Swift.print("scrollRect \(scrollRect)")
let tableScrollView = NSScrollView(frame: scrollRect)
raceQualificationsTableView = NSTableView(frame: tableRect)
raceQualificationsTableView.identifier = "raceQualificationsTable" // Setup identifier
raceQualificationsTableView.rowHeight = 20.0
Swift.print("instrinic size \(raceQualificationsTableView.intrinsicContentSize)")
//Swift.print("tableScrollView contentsize \(tableScrollView.contentSize)")
tableScrollView.documentView = raceQualificationsTableView
tableScrollView.autoresizingMask = .viewNotSizable
Swift.print("tableScroll content size \(tableScrollView.contentSize)")
//self.addSubview(tableScrollView)
raceImportHeaderStackView.addView(tableScrollView, in: .center)
}
func configureRaceQualificationsTable(showRaceNumberCol: Bool, showRaceCodeCol: Bool) {
let headerAlignment = NSTextAlignment.center // Easy way to change justification of headers
// MARK: Race Number Column Options
let raceNumberColumn = NSTableColumn(identifier: "raceNumberCol")
raceNumberColumn.title = "Race"
raceNumberColumn.minWidth = 40.0
raceNumberColumn.width = 40.0
raceNumberColumn.headerToolTip = "Race Number from the Imported Card"
raceNumberColumn.headerCell.alignment = headerAlignment
// Note: Word Race is always going to be wider than the race number value
// So size to Fit is appropriate here.
raceNumberColumn.sizeToFit()
if showRaceNumberCol {
// Option of not adding this to the table
raceQualificationsTableView.addTableColumn(raceNumberColumn)
}
// MARK: Driver Column Options
let breedColumn = NSTableColumn(identifier: "driverCol")
driverColumn.title = "Driver"
driverColumn.minWidth = 10
driverColumn.headerToolTip = "Driver information"
driverColumn.headerCell.alignment = headerAlignment
driverColumn.sizeToFit()
raceQualificationsTableView.addTableColumn(driverColumn)
// MARK: Race Code Column Options
let raceTypeCodeColumn = NSTableColumn(identifier: "raceTypeCodeCol")
raceTypeCodeColumn.title = "Race Code"
raceTypeCodeColumn.minWidth = 40
raceTypeCodeColumn.headerToolTip = "Race Classification Code"
raceTypeCodeColumn.headerCell.alignment = headerAlignment
raceTypeCodeColumn.sizeToFit()
if showRaceCodeCol {
// Option of not adding to the table
raceQualificationsTableView.addTableColumn(raceTypeCodeColumn)
}
// MARK: Race Type Code Description Options
let raceTypeCodeDescColumn = NSTableColumn(identifier: "raceTypeCodeDescCol")
raceTypeCodeDescColumn.title = "Race Desc"
raceTypeCodeDescColumn.minWidth = 50
raceTypeCodeDescColumn.width = 100
raceTypeCodeDescColumn.headerToolTip = "Race Classification Full Description"
raceTypeCodeDescColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(raceTypeCodeDescColumn)
// MARK: Race Restriction Column Options
let raceRestrictionColumn = NSTableColumn(identifier: "raceRestrictionCol")
raceRestrictionColumn.title = "Restrictions"
raceRestrictionColumn.minWidth = 50
raceRestrictionColumn.width = 80
raceRestrictionColumn.headerToolTip = "Race Restrictions"
raceRestrictionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(raceRestrictionColumn)
// MARK: Sex Restriction Column Options
let sexRestrictionColumn = NSTableColumn(identifier: "sexRestrictionCol")
sexRestrictionColumn.title = "Sex"
sexRestrictionColumn.minWidth = 100
sexRestrictionColumn.width = 100
sexRestrictionColumn.headerToolTip = "Sex Restrictions"
sexRestrictionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(sexRestrictionColumn)
// MARK: Age Restriction Column Options
let ageRestrictionColumn = NSTableColumn(identifier: "ageRestrictionCol")
ageRestrictionColumn.title = "Age"
ageRestrictionColumn.minWidth = 100
ageRestrictionColumn.width = 100
ageRestrictionColumn.headerToolTip = "Age Restrictions"
ageRestrictionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(ageRestrictionColumn)
// MARK: Division Column Options
let divisionColumn = NSTableColumn(identifier: "divisionCol")
divisionColumn.title = "Division"
divisionColumn.minWidth = 50
let minDivisionColumnWidth = raceQualificationsTableView.frame.width - raceNumberColumn.width - driverColumn.width - raceTypeCodeColumn.width - raceTypeCodeDescColumn.width - raceRestrictionColumn.width - sexRestrictionColumn.width - ageRestrictionColumn.width
// Calculate the available room for the division column
if (showRaceCodeCol && showRaceNumberCol) {
// This is the minimum case
// No idea why we need the 25.0 manual adjustment
divisionColumn.width = minDivisionColumnWidth - 25.0
} else if (showRaceCodeCol && !showRaceNumberCol) {
// Add back race type code
// No idea why we need to manually adjust 53.5
divisionColumn.width = minDivisionColumnWidth + raceTypeCodeColumn.width - 53.5
} else if (!showRaceCodeCol && showRaceNumberCol) {
// Add back race number col
divisionColumn.width = minDivisionColumnWidth + raceNumberColumn.width
} else {
// Else it's the maximum space
// This code was making the frame too large -- it was increasing the
// the frame size of the column to 670.0 I put a manual reduction of
// 20 to keep the frame size the same. Not sure where this 20 is coming from.
divisionColumn.width = minDivisionColumnWidth + raceNumberColumn.width + raceTypeCodeColumn.width - 20.0
}
//Swift.print("The division column width is \(divisionColumn.width)")
divisionColumn.headerToolTip = "Division -- Unknown what this means"
divisionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(divisionColumn)
//Swift.print("raceQualificationsTableView.frame.width is \( raceQualificationsTableView.frame.width)")
}
// MARK: Race Distance Surface Course Setup
func addRaceDistanceSurfaceCourseTable(startingPositionX: CGFloat) {
// Table Properties
let width:CGFloat = 250.0
let height:CGFloat = 40.0
// Padding variables
let leftPadding:CGFloat = 5.0
let topPosition:CGFloat = (viewBounds.height - ((viewBounds.height - height)/2) - height)
let tableRect = CGRect(x: leftPadding + startingPositionX, y: topPosition, width: width, height: height)
let tableScrollView = NSScrollView(frame: tableRect)
raceDistanceSurfaceCourseTableView = NSTableView(frame: tableRect)
raceDistanceSurfaceCourseTableView.identifier = "raceDistanceSurfaceCourseTable" // Setup identifier
//raceDistanceSurfaceCourseTableView.rowHeight = 20.0
raceDistanceSurfaceCourseTableView.intercellSpacing = NSSize(width: 1.0, height: 1.0)
raceDistanceSurfaceCourseTableView.headerView = ImportRaceTableHeaders()
tableScrollView.documentView = raceDistanceSurfaceCourseTableView
//tableScrollView.hasVerticalScroller = false
//tableScrollView.verticalScroller = nil // Turn off vertical scrolling
//tableScrollView.verticalScrollElasticity = .none
//raceDistanceSurfaceCourseTableView = NSTableViewHeader
//self.addSubview(tableScrollView)
raceImportHeaderStackView.addView(raceDistanceSurfaceCourseTableView, in: .center)
}
// MARK: Construct the fields:
//configureHeaderView()
configureStackView()
addRaceNumberTitle(startingPositionX: 0.0) // Add the race number title
addRaceNumberValue(startingPositionX: raceNumberTitle.frame.origin.x + raceNumberTitle.frame.width) //Add the Race Number value text field
addRaceQualificationsTable(startingPositionX: raceNumberValue.frame.origin.x + raceNumberValue.frame.width)
configureRaceQualificationsTable(showRaceNumberCol: false, showRaceCodeCol: false)
}
// MARK: TableView Functions
func reloadTableViewData(identifier: String) {
Swift.print("Manual reload of data for identifier \(identifier)")
switch identifier {
case "raceQualificationsTable":
raceQualificationsTableView.reloadData()
case "raceDistanceSurfaceCourseTable":
raceDistanceSurfaceCourseTableView.reloadData()
default:
break
}
}
// MARK: Delegate/DataSources Outlets for TableViews
// Race Qualification Table (the header table)
#IBOutlet weak var raceQualificationsDelegate: NSTableViewDelegate? {
get {
return raceQualificationsTableView.delegate
}
set {
raceQualificationsTableView.delegate = newValue
}
}
#IBOutlet weak var raceQualificationsDataSource: NSTableViewDataSource? {
get {
return raceQualificationsTableView.dataSource
}
set {
raceQualificationsTableView.dataSource = newValue
}
}
// Race Distance Surface Course
#IBOutlet weak var raceDistanceSurfaceCourseDelegate: NSTableViewDelegate? {
get {
return raceDistanceSurfaceCourseTableView.delegate
}
set {
raceDistanceSurfaceCourseTableView.delegate = newValue
}
}
#IBOutlet weak var raceDistanceSurfaceCourseDataSource: NSTableViewDataSource? {
get {
return raceDistanceSurfaceCourseTableView.dataSource
}
set {
raceDistanceSurfaceCourseTableView.dataSource = newValue
}
}
// MARK: Label Outlets
#IBOutlet var raceNumber:String? {
get {
return raceNumberValue.stringValue
}
set {
raceNumberValue.stringValue = newValue!
}
}
}
I am trying to number the direct child nodes of the root node in a serial manner (child-1, child-2...).
Here is my method which sets the cell value factory for myColumn:
private void setCellValueFactory() {
myColumn.setPrefWidth(120);
final int[] si_SubsetCount = {
1
};
myColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures < MyDataClass, String > p) - > {
TreeItem < JVCC_PageHeaderInfo > ti_Row = p.getValue();
MyDataClass myDataClass = p.getValue().getValue();
String text;
if (ti_Row.isLeaf()) {
//leaf
} else if (ti_Row.getParent() != null) {
text = "Child-" + si_SubsetCount[0];
si_SubsetCount[0]++;
} else {
si_SubsetCount[0] = 1;
text = "Root";
}
return new ReadOnlyObjectWrapper < > (text);
});
}
But my output is as below:
>Root
>child-4
>leaf
>leaf
>child-8
>leaf
>leaf
I don't understand why the numbering is like 4, 8... instead of 1, 2...
Can someone help me with this.
That is because you can not control, when the CellValueFactorys method for evaluating the value for each row is called. It might get called several times for a single row, which is why your counter does not show the correct value for each line.
A dynamical approach is prefered here. If you should only have to differ between 3 node levels like root/child/leaf, then you could do something like this:
myColumn.setCellValueFactory( ( final CellDataFeatures<String, String> p ) ->
{
final TreeItem<String> value = p.getValue();
String text = "";
if ( value.isLeaf() )
text = "leaf";
else if ( value.getParent() != null )
text = "Child-" + (value.getParent().getChildren().indexOf( value ) + 1);
else
text = "root";
return new ReadOnlyStringWrapper( text );
} );
Since the Children of a TreeItem are stored in an ObservableList, you can just ask them for their index and add 1, since index starts at zero.
I have dynamically created textbox in asp.net. Now i am extracting the values through following code.
string[] sublist = new string[] { };
int[] maxmarkslist = new int[] { };
int i;
for (i = 0; i < Convert.ToInt32(Label15.Text); i++)
{
string sub = "subject" + i;
string marks = "maxmarks" + i;
TextBox subject = (TextBox)PlaceHolder1.FindControl(sub);
TextBox maxmarks = (TextBox)PlaceHolder1.FindControl(marks);
sublist[i] = subject.Text;
maxmarkslist[i] = Convert.ToInt32(maxmarks.Text);
}
But I getting error "Index was outside the bounds of the array" for the below two lines:
sublist[i] = subject.Text;
maxmarkslist[i] = Convert.ToInt32(maxmarks.Text);
When I debugged it, values are coming in subject.Text and maxmarks.Text but not going to array.
Did I define the array in a wrong way?
You define both the arrays as empty arrays. So you will get index out of bound erros if you try to index into those.
Arrays are not dynamically expanding. If you want that, use a collection type and may be later convert to an array.
Try this:
int length = Convert.ToInt32(Label15.Text);
string[] sublist = new string[length-1];
int[] maxmarkslist = new int[length-1];
for (int i = 0; i < length; i++)
{
string sub = "subject" + i;
string marks = "maxmarks" + i;
TextBox subject = (TextBox)PlaceHolder1.FindControl(sub);
TextBox maxmarks = (TextBox)PlaceHolder1.FindControl(marks);
sublist[i] = subject.Text;
maxmarkslist[i] = Convert.ToInt32(maxmarks.Text);
}
Or here is how to do this with a collection (List) type:
int length = Convert.ToInt32(Label15.Text);
List<string> sublist1 = new List<string>();
List<int> maxmarkslist1 = new List<int>();
for (int i = 0; i < Convert.ToInt32(Label15.Text); i++)
{
string sub = "subject" + i;
string marks = "maxmarks" + i;
TextBox subject = (TextBox)PlaceHolder1.FindControl(sub);
TextBox maxmarks = (TextBox)PlaceHolder1.FindControl(marks);
sublist1.Add(subject.Text);
maxmarkslist1.Add(Convert.ToInt32(maxmarks.Text));
}
string[] sublist = sublist1.ToArray();
int[] maxmarkslist = maxmarkslist1.ToArray();
Note with collections you dont have to specify the size upfront. But keep adding items to it as it can expand as needed. But arrays can not do this.
Your string[] sublist = new string[] { }; is a shortcut method where you create and initialize the array. In that you don't have to specify the size, but compiler will count the elements between {} and set the size appropriately. In your case since there are no elements inside {} it will create an empty array.
string[] sublist = new string[100];
int[] maxmarkslist = new int[100];
Put this..replace 100 with the max possible value of your loop...but this is not a good practice...will come back to this thread if i found something better...
I am creating an X++ report, and the requirement is that the user can multi-select on a form and when they click the report menu button the values are pulled in based on the selection.
So far this is easy enough, and I can pull in Str ranges i.e. order numbers, item id's etc, but I want to be able to pull in a date range based on selection.
I have used a method which several MorphX reports use, with use of 3 key methods in X++ reporting;
setQuerySortOrder
setQueryEnableDS
and the main key one which is;
setQueryRange
The code for setQuery Range is as follows;
private void setQueryRange(Common _common)
{
FormDataSource fds;
LogisticsControlTable logisticsTable;
QueryBuildDataSource qbdsLogisticsTable;
QueryBuildRange qbrVanRun;
str rangeVanRun;
QueryBuildRange qbrLogId;
str rangeLogId;
QueryBuildRange qbrExpStartDate;
str rangeExpStartDate;
set vanRunSet = new Set(Types::String);
set logIdSet = new Set(Types::String);
set expStartDate = new Set(Types::Date);
str addRange(str _range, str _value, QueryBuildDataSource _qbds, int _fieldNum, Set _set = null)
{
str ret = _range;
QueryBuildRange qbr;
;
if(_set && _set.in(_Value))
{
return ret;
}
if(strLen(ret) + strLen(_value) + 1 > 255)
{
qbr = _qbds.addRange(_fieldNum);
qbr.value(ret);
ret = '';
}
if(ret)
{
ret += ',';
}
if(_set)
{
_set.add(_value);
}
ret += _value;
return ret;
}
switch(_common.TableId)
{
case tableNum(LogisticsControlTable):
qbdsLogisticsTable = element.query().dataSourceTable(tableNum(LogisticsControlTable));
qbrVanRun = qbdsLogisticsTable.addRange(fieldNum(LogisticsControlTable, APMServiceCenterID));
qbdsLogisticsTable = element.query().dataSourceTable(tableNum(LogisticsControlTable));
qbrLogId = qbdsLogisticsTable.addRange(fieldNum(LogisticsControlTable, LogisticsId));
// qbdsLogisticsTable = element.query().dataSourceTable(tableNum(LogisticsControlTable));
// qbrExpStartDate = qbdsLogisticsTable.addRange(fieldNum(LogisticsControlTable, APMExpDateJobStart));
fds = _common.dataSource();
for(logisticsTable = fds.getFirst(true) ? fds.getFirst(true) : _common;
logisticsTable;
logisticsTable = fds.getNext())
{
rangeVanRun = addrange(rangeVanRun, logisticsTable.APMServiceCenterID, qbdsLogisticsTable, fieldNum(LogisticsControlTable, APMServiceCenterID), vanRunSet);
rangeLogID = addrange(rangeLogID, logisticsTable.LogisticsId, qbdsLogisticsTable, fieldNum(LogisticsControlTable, LogisticsId), logIdSet);
// rangeExpStartDate = addrange(rangeExpStartdate, logisticsTable.APMExpDateJobStart, qbdsLogisticsTable, fieldNum(LogisticsControlTable, APMExpDateJobStart), expStartDate);
}
qbrLogId.value(rangeLogID);
qbrVanRun.value(rangeVanRun);
break;
}
}
Use queryValue to format your dates correctly for the query:
set expStartDate = new Set(Types::String);
rangeExpStartDate = addrange(rangeExpStartdate, queryValue(logisticsTable.APMExpDateJobStart), qbdsLogisticsTable, fieldNum(LogisticsControlTable, APMExpDateJobStart), expStartDate);