How to drag and drop rows in NSTableView in Mac - xamarin

I have struck with one point i.e now I have got a list of data using NSTableView but what my requirement is, able to drag and drop that rows from one row position to another row position. please give any suggestion for get out this problem. Thanks in advance.
My sample code

Within your NSTableViewDataSource subclass implement WriteRows, ValidateDrop and AcceptDrop and register which Drag/Drop targets your NSTableView accepts. In this case you are only accepting Drops from within your own NSTableView.
Assign a name that will be used for valid drag operations on this NSTableView:
// Any name can be registered, I find using the class name
// of the items in the datasource is cleaner than a const string
string DragDropType = typeof(Product).FullName;
Register the drag types for your NSTableView:
ProductTable.RegisterForDraggedTypes(new string[] { DragDropType });
Implement drag/drop methods on your NSTableViewDataSource:
public override bool WriteRows(NSTableView tableView, NSIndexSet rowIndexes, NSPasteboard pboard)
{
var data = NSKeyedArchiver.ArchivedDataWithRootObject(rowIndexes);
pboard.DeclareTypes(new string[] { DragDropType }, this);
pboard.SetDataForType(data, DragDropType);
return true;
}
public override NSDragOperation ValidateDrop(NSTableView tableView, NSDraggingInfo info, nint row, NSTableViewDropOperation dropOperation)
{
tableView.SetDropRowDropOperation(row, dropOperation);
return NSDragOperation.Move;
}
public override bool AcceptDrop(NSTableView tableView, NSDraggingInfo info, nint row, NSTableViewDropOperation dropOperation)
{
var rowData = info.DraggingPasteboard.GetDataForType(DragDropType);
if (rowData == null)
return false;
var dataArray = NSKeyedUnarchiver.UnarchiveObject(rowData) as NSIndexSet;
Console.WriteLine($"{dataArray}");
// Move hack for this example... you need to handle the complete NSIndexSet
tableView.BeginUpdates();
var tmpProduct = Products[(int)dataArray.FirstIndex];
Products.RemoveAt((int)dataArray.FirstIndex);
if (Products.Count == row - 1)
Products.Insert((int)row - 1 , tmpProduct);
else
Products.Insert((int)row, tmpProduct);
tableView.ReloadData();
tableView.EndUpdates();
return true;
}

Related

Xamarin.iOS - Multiple XIBs in a single UITableView

I am trying to load different nibs into a single UITableView depending on a variable within a model. I seem to have something that looks logical and doesn't crash, however, only 1 of the xibs is being loaded and displayed.
Controller Method:
private void populateTableData()
{
liveTipsTableView.RegisterNibForCellReuse(UINib.FromName("LiveTipCell_", null), "LiveTipCell_");
liveTipsTableView.RegisterNibForCellReuse(UINib.FromName("NewsCell_", null), "NewsCell_");
setListViewSource();
Refresh();
AddRefreshControl();
Add(liveTipsTableView);
liveTipsTableView.Add(RefreshControl);
}
TableSource Method
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var localTip = _tips[indexPath.Row];
if (localTip.NewsItem)
{
CellIdentifier = new NSString("NewsCell_");
NewsCell_ cell = new NewsCell_();
cell = tableView.DequeueReusableCell("NewsCell_") as NewsCell_;
var views = NSBundle.MainBundle.LoadNib("NewsCell_", cell, null);
cell = ObjCRuntime.Runtime.GetNSObject(views.ValueAt(0)) as NewsCell_;
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
cell.BindDataToCell(localTip);
return cell;
}
else
{
CellIdentifier = new NSString("LiveTipCell_");
LiveTipCell_ cell = new LiveTipCell_();
cell = tableView.DequeueReusableCell("LiveTipCell_") as LiveTipCell_;
var views = NSBundle.MainBundle.LoadNib("LiveTipCell_", cell, null);
cell = ObjCRuntime.Runtime.GetNSObject(views.ValueAt(0)) as LiveTipCell_;
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
cell.BindDataToCell(localTip);
return cell;
}
}
I have 2 separate Xib files and they have their own classes which work to populate the views. They all seem to getting used when debugging, it's just a case of the table view is only displaying 1 of these items.
Thanks in advance and please let me know if you need more information to see what's going on.
This is in objective C, but it shouldn't be too hard to convert.
SO Post on a different question

Xamarin.ios CollectionView Delete and Insert Items

I am currently developing an iphone app with xamarin in visual studio. I'm a beginner..
I use a CollectionView. This has a list and includes 8 items with different ID's in the CollectionViewSource.cs:
ProductCategoryList.Add (new ProductCategorys () {Categoryid = 1, ProductCategoryName = "testname", ProductCategoryImage = UIImage.FromFile ( "productcat / maincat / testpic.jpg")});
Now I would like, if an item is pressed, delete All Items and refill it.
In the ProductCategoryCollectionDelegate.cs i have:
Public override Boolean ShouldSelectItem (UICollectionView collectionView, NSIndexPath indexPath)
For the delete and insert I have found these 2 methods
Public void DeleteItems (NSIndexPath [] indexPaths)
Public void InsertItems (NSIndexPath [] indexPaths)
(The xamarin Documentation is very scarce)
How can I use this 2 metodes to completely empty the list and add new items?
thanks for help
I've taken a TableView instead of the CollectionView.
With this function it is possible to directly delete items in the Tablesource class:
public override void CommitEditingStyle(UITableView tableView,
UITableViewCellEditingStyle editingStyle,
Foundation.NSIndexPath indexPath)
{
switch (editingStyle)
{
case UITableViewCellEditingStyle.Delete:
AppDelegate.DAUtil.setBasketItemStatusByBasketID(
basketitems[indexPath.Row].ZBASKETID, "2");
// remove the item from the underlying data source
basketitems.RemoveAt(indexPath.Row);
// delete the row from the table
tableView.DeleteRows(new NSIndexPath[] { indexPath },
UITableViewRowAnimation.Fade);
break;
case UITableViewCellEditingStyle.None:
Console.WriteLine("CommitEditingStyle:None called");
break;
}
}
public override bool CanEditRow(UITableView tableView,
NSIndexPath indexPath)
{
return true;
// return false if you wish to disable editing
// for a specific indexPath or for all rows
}
The Source of the Table is a List. Alternatively, you can reload the TableSource with:
tablename.ReloadData();

Drag and drop function swift OSX

This is a bit complex but I hope that someone can help me.
I am trying to build a drag and drop function for my OSX application.
This is how it is looking at the moment.
So there is just a single textfield which the user can drag and drop around the view. It is simple enough with just one textfield but if there are several textfields it is getting complicated and I don't know how to approach.
This is what I currently have:
#IBOutlet weak var test: NSTextField!
#IBAction override func mouseDragged(theEvent: NSEvent) {
NSCursor.closedHandCursor().set()
var event_location = theEvent.locationInWindow
test.frame.origin.x = event_location.x - 192
test.frame.origin.y = event_location.y
}
Test is the name of my NSTextField. I know the name of it so it is simple to move it arround. But if the user adds more textfields (see on the left pane) then I don't know how to address this textfield because I have no name of it (like "test" for the first input).
I am adding the textfields via this code:
let input = NSTextField(frame: CGRectMake(width, height, 100, 22))
self.MainView.addSubview(input)
How can I determine which textfield (if there are multiple on the view) was selected and then move the appropriate via drag and drop?
The drag and drop is working for that single static textfield
I have prepared a sample app, so consider this:
https://github.com/melifaro-/DraggableNSTextFieldSample
The idea is to introduce SelectableTextField which inherits NSTextField. SelectableTextField provides facility for subscription of interested listener on text field selection event. It has didSelectCallback block variable, where you need to set you handling code. Something like this:
textField.didSelectCallback = { (textField) in
//this peace of code will be performed once mouse down event
//was detected on the text field
self.currentTextField = textField
}
By using mentioned callback mechanism, once text field selected, we can store it in currentTextField variable. So that when mouseDragged function of ViewController is called we are aware of currentTextField and we can handle it appropriatelly. In case of sample app we need adjust currentTextField origin according drag event shift. Hope it became better now.
P.S. NSTextField is opened for inheriting from it, so you can freely use our SelectableTextField everywhere where you use NSTextField, including Interface Builder.
EDIT
I have checked out your sample. Unfortuantly I am not able to commit /create pull request into you repository, so find my suggestion here:
override func viewDidLoad() {
super.viewDidLoad()
didButtonSelectCallback = { (button) in
if let currentButton = self.currentButton {
currentButton.highlighted = !currentButton.highlighted
if currentButton == button {
self.currentButton = nil
} else {
self.currentButton = button
}
} else {
self.currentButton = button
}
button.highlighted = !button.highlighted
}
addButtonAtRandomePlace()
addButtonAtRandomePlace()
didButtonSelectCallback(button: addButtonAtRandomePlace())
}
override func mouseDragged(theEvent: NSEvent) {
guard let button = currentButton else {
return
}
NSCursor.closedHandCursor().set()
button.frame.origin.x += theEvent.deltaX
button.frame.origin.y -= theEvent.deltaY
}
private func addButtonAtRandomePlace() -> SelectableButton {
let viewWidth = self.view.bounds.size.width
let viewHeight = self.view.bounds.size.height
let x = CGFloat(rand() % Int32((viewWidth - ButtonWidth)))
let y = CGFloat(rand() % Int32((viewHeight - ButtonHeight)))
let button = SelectableButton(frame: CGRectMake(x, y, ButtonWidth, ButtonHeight))
button.setButtonType(NSButtonType.ToggleButton)
button.alignment = NSCenterTextAlignment
button.bezelStyle = NSBezelStyle.RoundedBezelStyle
button.didSelectCallback = didButtonSelectCallback
self.view.addSubview(button)
return button
}

Multiple Custom Cell's in a ListView (Cross Platform)

Currently with ListView's I've only found that you can create a template for cells, which makes each cell look exactly the same. You can't have multiple custom cells in the listview. There are work-arounds like hiding the content in the cell depending on the content, but this seems pretty hacky.
The reason I want to use a listview over a tableview is because we plan on doing inserts, deletions, dynamically showing certain cells, and listview's can be binded to a data source.
Create your own ViewCell which overrides binding context change method. When the binding changes set the ViewCell's view to one that matches the type of view model and also set the height of the cell. Below is a quick sample that should give you an idea how to accomplish it.
public class DataTemplateCell1 : ViewCell
{
protected override void OnBindingContextChanged()
{
var vm1 = this.BindingContext as ViewModel1;
if (vm1 != null)
{
this.View = new View1() { HeightRequest = 40 };
this.Height = this.View.HeightRequest;
return;
}
var vm2 = this.BindingContext as ViewModel2;
if (vm2 != null)
{
this.View = new View2() { HeightRequest = 80 };
this.Height = this.View.HeightRequest;
return;
}
base.OnBindingContextChanged();
}
}

Making an NSTableView behave like a WPF StackPanel

I'm trying to implement a vertical StackPanel equivalent in my MonoMac project and am having trouble figuring it out. I am not using interface builder but creating the controls programmatically (this is a restriction). I tried using a CollectionView but all items in that control are sizing to the same height.
Looking around the internet, it seems NSTableView is the way to go, but I'm not sure how to do it, especially with C# while using a MonoMac target. CollectionView was somewhat more straightforward with the CollectionViewItem.ItemPrototype allowing me to create the views I want to render. But with an NSTableView, it seems like I can only specify a data source that returns the NSObjects I want to display. How do I grab this data and then bind them to the view I want to stick in there?
I would prefer C# code but I'm at a stage where I'll accept any and all help!
I was finally able to get it working. Here is some code for anyone who wants to try it out. Basically, we need to write NSTableViewDelegates for the required functions. This implementation also doesn't cache the controls or anything. The Cocoa API documentation mentioned using an identifier to reuse the control, or something, but the identifier field is get-only in MonoMac.
I also ended up implementing my NSTableViewDelegate functions in my data-source itself which I am sure is not kosher at all, but I'm not sure what the best practice is.
Here's the data source class:
class MyTableViewDataSource : NSTableViewDataSource
{
private NSObject[] _data;
// I'm coming from an NSCollectionView, so my data is already in this format
public MyTableViewDataSource(NSObject[] data)
{
_data = data;
}
public override int GetRowCount(NSTableView tableView)
{
return _data.Length;
}
#region NSTableViewDelegate Methods
public NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, int row)
{
// MyViewClass extends NSView
MyViewClass result = tableView.MakeView("MyView", this) as MyViewClass;
if (result == null)
{
result = new MyViewClass(_data[row]);
result.Frame = new RectangleF(0, 0, tableView.Frame.Width, 100); // height doesn't matter since that is managed by GetRowHeight
result.NeedsDisplay = true;
// result.Identifier = "MyView"; // this line doesn't work because Identifier only has a getter
}
return result;
}
public float GetRowHeight(NSTableView tableView, int row)
{
float height = FigureOutHeightFromData(_data[row]); // run whatever algorithm you need to get the row's height
return height;
}
#endregion
}
And here's the snippet that programmatically creates the table:
var tableView = new NSTableView();
var dataSource = new MyTableViewDataSource();
tableView.DataSource = dataSource;
tableView.HeaderView = null; // get rid of header row
tableView.GetViewForItem = dataSource.GetViewForItem;
tableView.GetRowHeight = dataSource.GetRowHeight;
AddSubView(tableView);
So, it is not a perfect StackPanel because one needs to manually calculate row heights, but it's better than nothing.

Resources