Good day everyone.
I'm writing a revit API that can
1. list all .rfa files' name from another computer,
2. single click file name to download to a custom directory at my local computer and
3. drag file name to place the component into revit.
I've finished the first two but when I drag and drop the file name to revit, a prompt shows up and says
"Value cannot be null. Parameter name: dropData".
I'm using Visual Studio 2019, winForm, .NET Framework v4.5.2, revit 2017 dll and revit 2017.
I copy the code from here , using these codes.
public class LoadedFamilyDropHandler : IDropHandler
{
public void Execute(UIDocument document, object data)
{
ElementId familySymbolId = (ElementId)data;
FamilySymbol symbol = document.Document.GetElement(familySymbolId) as FamilySymbol;
if(symbol != null)
{
document.PromptForFamilyInstancePlacement(symbol);
}
}
}
private void listView1_MouseMove(object sender, MouseEventArgs e)
{
if(System.Windows.Forms.Control.MouseButtons == MouseButtons.Left)
{
if(System.Windows.Forms.Control.MouseButtons == MouseButtons.Left)
{
ListViewItem selectedItem = this.listView1.SelectedItems.Cast<ListViewItem>().FirstOrDefault<ListViewItem>();
if(selectedItem != null)
{
LoadedFamilyDropHandler myhandler = new LoadedFamilyDropHandler();
UIApplication.DoDragDrop(selectedItem.Tag, myhandler);
}
}
}
}
My custom directory path is D:\DownloadedFromVault\ which contains all .rfa files downloaded from another computer.
This is the code when I click the file name and download it from the list view.
client.DownloadFile(server + path + "\\" + listView1.SelectedItems[0].Text, localFilePath + path + "\\" + listView1.SelectedItems[0].Text);
I guess that API cannot find the file because I store them in another directory ,or maybe the file name doesn't connect to the file downloaded.
I don't know how exactly drag and drop functions. Hope someone can help me with this and give me some insights.
I would suggest debugging carefully step by step and determining the exact line of code throwing that exception.
That might help understand what 'dropData' argument has a null value causing the problem.
Hello, Jeremy. Thanks for the input.
I've found out that selectedItem has the file name but selectedItem.Tag is null. I've checked that selectedItem.Tag is Object and that's what dropData in doDrapDrop(dropData,IHandler) need. How can I give value to selectedItem.Tag?
I referenced Revit 2019 SDK and wrote selectedItem.Tag = selectedItem.Text; since it wrote item.Tag = familySymbol.Id;. I thought the tag only needed the file name. When I dragged file name into revit, the error prompt didn't appear anymore. Instead, my cursor turned into prohibit sign. In debug mode, it seems like LoadedFamilyDropHandler did not get executed. I couldn't place components into revit. Any ideas on what went wrong?
Tag is a generic container you can put anything into. It is generally more advantageous to bind the collection to the control than manually adding items such as ListViewItem (do some research on DataBinding). If you create your own non API object containing members such as ElementId, String (things that can tolerate an existance outside of API context). You can put that into a collection (populated from your collector) bind it to the ListView, override .ToString on your object (as a starting point to display text of item in ListView). Then you can use SelectedItem.YourProperty as you like (you are not constrained by members of ListViewItem). Additionally you can then pass the entire item through as dropData and use it as you like during the Execute method of IDropHandler interface. You avoided a null reference exception but you'll get null later if you try to cast a string (filename) to element id.
When you show your window did you use .Show (should not be using .ShowDialog).
I say use ElementId outside of context (which you can) but you have to be careful it relates to the correct document (active document may change during a session). I would probably use UniqueIds for this out of context purpose actually since worksharing can change ElementIds (rationalisation during synchronisation).
Thank you for your reply. I really appreciate everyone's help.
Sorry if this is a basic question. I don't know what to google to solve my problem.
I changed .showDialog() to .show() and the prohibit sign disappeared. I can now drag components to revit but nothing appears.
Right now, I'm adding these codes to my code and I deleted selectedItem.Tag = selectedItem.Text; 'cause I thought that item.tag stored all ListViewItem tag's value so that selectedItem.Tag can get its value.
FYI, I have a treeView which contains directory path and listView which contains files in the directory. UpdateLoadedFamilies() is prior to both of them. I don't know if there's a order problem.
private static Form1 s_form;
public static Form1 GetTheForm(Document document)
{
if(s_form == null)
{
s_form = new Form1(document);
}
s_form.UpdateLoadedFamilies();
return s_form;
}
private void UpdateLoadedFamilies()
{
ListView.ListViewItemCollection collection = listView1.Items;
collection.Clear();
FilteredElementCollector collector = new FilteredElementCollector(m_document);
collector.OfClass(typeof(FamilySymbol));
foreach(FamilySymbol familySymbol in collector.Cast<FamilySymbol>())
{
ListViewItem item = new ListViewItem();
item.Tag = familySymbol.Id;
item.Text = familySymbol.Family.Name + "::" + familySymbol.Name;
item.ToolTipText = "Drag to place instances of " + item.Text + " in the active document.";
collection.Add(item);
}
}
I have these questions.
1. selectedItem.Tag is null. It didn't get the item.tag's value which type is object {Autodesk.Revit.DB.ElementId}.
2. The items are from revit's own library, not from my custom rfa file directory. I wonder how can I change it to my custom directory path.
Maybe there is some tutorials I can learn from. I don't know where to find them.
At what stage is .Tag null (just before exiting UpdateLoadedFamilies or within the drop handler), step through and see because at one stage you are definitely taking items from a collector and populating the tag with their ElementId. It may be that SelectedItems is empty at the point you are dragging but that is something you can only see through stepping. Also I'm not familiar with sample but most drag drop operations have three phases MouseButtonDown, MouseDrag, MouseButtonUp.
Ordinarily (outside of API when writing desktop applications) there are DragDrop events on the source and distination controls to take advantage of, within the API you ony have source events plus DoDragDrop function.
Also please confirm if you are using Forms or WPF.
1. I'm using winForm.
2. I've checked that selectedItem.Tag is null at the start.
ListViewItem selectedItem = this.listView1.SelectedItems.Cast<ListViewItem>().FirstOrDefault<ListViewItem>();
I think it's because UpdateLoadedFamilies doesn't get files at my custom directory(D:\DownloadedFromVault\).
I've checked that they get values from Revit's own library which I don't understand how. That's why selectedItem does not have .tag value. I don't think any item in my listView have .Tag value either. I don't know which variable to change to make UpdateLoadedFamilies update files' path at D:\DownloadedFromVault\ though.
3. In sample, it only has listView_MouseMove. It does not have MouseButtonDown, MouseDrag, MouseButtonUp.
I also have listView_MouseClick which is to click the file name and download it from another computer. I've removed it and it still worked like it was before. So, I don't think it's affecting this.
You should step through the original sample located below to see if you have a similar issue if not then by comparison you may see where it is going wrong:
SDK...\Samples\UIAPI\CS\DragAndDrop\DragAndDrop.cs
You'll see from below code that the library paths are being taken from what you have set in your Revit options:
private void UpdateFamilyFileList()
{
// Visit each Revit library looking for Furniture families
IDictionary<String, String> libraryPaths = m_document.Application.GetLibraryPaths();
foreach (String libraryPath in libraryPaths.Values)
{
foreach (String directory in System.IO.Directory.EnumerateDirectories(libraryPath, "*Furniture", SearchOption.AllDirectories))
{
foreach (String familyFile in System.IO.Directory.EnumerateFiles(directory, "*.rfa", SearchOption.AllDirectories))
{
// Add each Furniture family to the listbox
String fileName = Path.GetFileName(familyFile);
FamilyListBoxMember member = new FamilyListBoxMember(familyFile, fileName);
listBox1.Items.Add(member);
}
}
}
}
It is extracting items from folders named 'Furniture' in these library paths.
I'm really sorry. It's been too long now and I'm still in the bind here.
I really can't find out what went wrong with my code.
No matter what I do, UpdateLoadedFamilies still gets the files from revit's own library instead of my file path.
The item.Tag's value won't pass to selectedItem.Tag since they reference different files.
I don't have listBox so I exclude UpdateFamilyFileList.
Here's my code. Hope someone know how to fix this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace BIMconnectVault
{
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
public class Class1 : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
try
{
Form1 form = Form1.GetTheForm(commandData.View.Document);
form.Show();
form.BringToFront();
return Result.Succeeded;
}
catch (Exception ex)
{
message = ex.Message;
return Result.Failed;
}
}
}
}
using System;
using System.IO;
using System.Net;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Configuration;
using System.Collections.Specialized;
using Autodesk.Revit.UI;
using System.Diagnostics;
using System.Data.SqlClient;
using Autodesk.Revit.DB;
namespace BIMconnectVault
{
public partial class Form1 : System.Windows.Forms.Form
{
private ExternalCommandData commandData;
private string server;
private string path;
private Document m_document;
private static Form1 s_form;
public static Form1 GetTheForm(Document document)
{
if (s_form == null)
{
s_form = new Form1(document);
}
s_form.UpdateLoadedFamilies();
return s_form;
}
private void UpdateLoadedFamilies()
{
ListView.ListViewItemCollection collection = listView1.Items;
collection.Clear();
FilteredElementCollector collector = new FilteredElementCollector(m_document);
collector.OfClass(typeof(FamilySymbol));
foreach(FamilySymbol familySymbol in collector.Cast<FamilySymbol>())
{
ListViewItem item = new ListViewItem();
item.Tag = familySymbol.Id;
item.Text = familySymbol.Family.Name + "::" + familySymbol.Name;
item.ToolTipText = "Drag to place instances of " + item.Text + " in the active document.";
collection.Add(item);
}
}
public Form1(Document document)
{
InitializeComponent();
m_document = document;
Open_Remote_Connection("IP", "vaultname", "password");
DirectoryInfo directoryInfo = new DirectoryInfo("directorypath");
if (directoryInfo.Exists)
{
BuildTree(directoryInfo, treeView1.Nodes);
}
listView1.Click += new System.EventHandler(ListView1_Click);
}
private void Open_Remote_Connection(string strComputer, string strUserName, string strPassword)
{
System.Diagnostics.ProcessStartInfo ProcessStartInfo = new System.Diagnostics.ProcessStartInfo();
ProcessStartInfo.FileName = "net";
ProcessStartInfo.Arguments = "use \\\\" + strComputer + "\\c$ /USER:" + strUserName + " " + strPassword;
ProcessStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
System.Diagnostics.Process.Start(ProcessStartInfo);
System.Threading.Thread.Sleep(2000);
}
private void BuildTree(DirectoryInfo directoryInfo, TreeNodeCollection addInMe)
{
TreeNode curNode = addInMe.Add(directoryInfo.Name);
foreach (DirectoryInfo subdir in directoryInfo.GetDirectories())
{
BuildTree(subdir, curNode.Nodes);
}
}
void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
server = "\\\\IP\\";
path = treeView1.SelectedNode.FullPath;
string serverPath = server + path;
DirectoryInfo directoryInfo = new DirectoryInfo(serverPath);
listView1.Clear();
foreach (FileInfo file in directoryInfo.GetFiles())
{
listView1.Items.Add(file.Name);
}
}
void ListView1_Click(Object sender, EventArgs e)
{
string localFilePath = "D:\\DownloadFromVault\\";
UIApplication app = commandData.Application;
Document document = app.ActiveUIDocument.Document;
using (var client = new WebClient())
{
if (!File.Exists(localFilePath + path + "\\" + listView1.SelectedItems[0].Text))
{
if (!Directory.Exists(localFilePath + path))
{
Directory.CreateDirectory(localFilePath + path);
}
client.DownloadFile(server + path + "\\" + listView1.SelectedItems[0].Text, localFilePath + path + "\\" + listView1.SelectedItems[0].Text);
string query = "query";
SQLtoCSV(query, listView1.SelectedItems[0].Text);
MessageBox.Show("Download successfully.");
}
else
{
MessageBox.Show("File already Exists.");
}
}
}
private void SQLtoCSV(string query, string Filename)
{
string localFilePath = "localFilePath ";
string csvFileName = Filename.Replace(".rfa", ".csv");
string connectionString = "connectionString";
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
SqlCommand cmd = new SqlCommand(query, conn);
SqlDataReader dr = cmd.ExecuteReader();
if (!Directory.Exists(localFilePath + path))
{
Directory.CreateDirectory(localFilePath + path);
}
using (System.IO.StreamWriter fs = new System.IO.StreamWriter(localFilePath + path + "\\" + csvFileName, false, Encoding.UTF8))
{
for (int i = 0; i < dr.FieldCount; i++)
{
string name = dr.GetName(i);
if (name.Contains(","))
{
name = "\"" + name + "\"";
}
fs.Write(name + ",");
}
fs.WriteLine();
while (dr.Read())
{
for (int i = 0; i < dr.FieldCount; i++)
{
string value = dr[i].ToString();
if (value.Contains(","))
{
value = "\"" + value + "\"";
}
fs.Write(value + ",");
}
fs.WriteLine();
}
fs.Close();
}
}
public class LoadedFamilyDropHandler : IDropHandler
{
public void Execute(UIDocument document, object data)
{
ElementId familySymbolId = (ElementId)data;
FamilySymbol symbol = document.Document.GetElement(familySymbolId) as FamilySymbol;
if(symbol != null)
{
document.PromptForFamilyInstancePlacement(symbol);
}
}
}
private void listView1_MouseMove(object sender, MouseEventArgs e)
{
if(System.Windows.Forms.Control.MouseButtons == MouseButtons.Left)
{
ListViewItem selectedItem = this.listView1.SelectedItems.Cast<ListViewItem>().FirstOrDefault<ListViewItem>();
if(selectedItem != null)
{
LoadedFamilyDropHandler myhandler = new LoadedFamilyDropHandler();
UIApplication.DoDragDrop(selectedItem.Tag, myhandler);
}
}
}
}
}
Seems odd, do you have directory or location called "directorypath" would have expected UNC path for IO.DirectoryInfo within your Form1 constructor, by consequence is BuildTree being run?
Why would Revit know where to look for something perhaps by a setting or by default in absence of one? Have you eliminated Document.Application.GetLibraryPaths() everywhere?
I think you should still go back to original sample and change Document.Application.GetLibraryPaths within UpdateFamilyFileList to another local location to understand that before moving onto more complex things with remote server locations etc.
Hello, everyone.
Because I stuck here for too long, I decided to change the method to place the component.
I used a button and external event to load and place rfa files right now.
I'll come back to this question in the future.
Thanks for everyone's help.
Can't find what you're looking for? Ask the community or share your knowledge.