Visual Studio Extension: Get the path of the current selected file in the Solution Explorer - visual-studio-extensions

I'm trying to create my first extension for visual studio and so far I've been following this tutorial to get me started (http://www.diaryofaninja.com/blog/2014/02/18/who-said-building-visual-studio-extensions-was-hard).
Now I have a custom menu item appearing when I click on a file in the solution explorer.
What I need now for my small project is to get the path of the file selected in the solution explorer but I can't understand how can I do that.
Any help?
---------------------------- EDIT ------------------------------
As matze said, the answer is in the link I posted. I just didn't notice it when I wrote it.
In the meanwhile I also found another possible answer in this thread: How to get the details of the selected item in solution explorer using vs package
where I found this code:
foreach (UIHierarchyItem selItem in selectedItems)
{
ProjectItem prjItem = selItem.Object as ProjectItem;
string filePath = prjItem.Properties.Item("FullPath").Value.ToString();
//System.Windows.Forms.MessageBox.Show(selItem.Name + filePath);
return filePath;
}
So, here are two ways to get the path to the selected file(s) :)

For future reference:
//In your async method load the DTE
var dte2 = await ServiceProvider.GetGlobalServiceAsync(typeof(SDTE)) as DTE2;
var selectedItems = dte2.SelectItems;
if(selectedItems.MultiSelect || selectedItems.Count > 1){ //Use either/or
for(short i = 1; i <= selectedItems.Count; i++){
//Get selected item
var selectedItem = selectedItems[i];
//Get associated project item (selectedItem.ProjectItem
//If selectedItem is a project, then selectedItem.ProjectItem will be null,
//and selectedItem.Project will not be null.
var projectItem = selectedItem.ProjectItem;
//Get project for ProjectItem
var project = projectItem.ContainingProject;
// Or get project object if selectedItem is a project
var sproject = selectedItem.Project;
//Is selectedItem a physical folder?
var isFolder = projectItem.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder;
//Else, get item's folder
var itemFolder = new FileInfo(projectItem.Properties.Item("FullPath").ToString()).Directory;
//Find config file
var configFiles itemFolder.GetFiles("web.config");
var configfile = configFiles.length > 0 ? configFiles[0] : null;
//Turn config file into ProjectItem object
var configItem = dte2.solution.FindProjectItem(configFile.FullName);
}
}
I hope someone finds this helpful...

The article you mentioned already contains a solution for that.
Look for the menuCommand_BeforeQueryStatus method in the sample code. It uses the IsSingleProjectItemSelection method to obtain an IVsHierarchy object representing the project as well as the id of the selected item. It seems that you can safely cast the hierarchy to IVsProject and use it´s GetMkDocument function to query the item´s fullpath...
IVsHierarchy hierarchy = null;
uint itemid = VSConstants.VSITEMID_NIL;
if (IsSingleProjectItemSelection(out hierarchy, out itemid))
{
IVsProject project;
if ((project = hierarchy as IVsProject) != null)
{
string itemFullPath = null;
project.GetMkDocument(itemid, out itemFullPath);
}
}
I don´t want to copy the entire code from the article into this answer, but it might be of interest how the IsSingleProjectItemSelection function obtains the selected item; so I just add some notes instead which may guide into the right direction... The method uses the GetCurrentSelection method of the global IVsMonitorSelection service to query to the current selected item.

Related

writing a library class properly

This is my very first library class that i am writing and i feel like i need to load up on that topic but cannot find the best sources. I have a web forms project that uploads a pdf and creates a qrcode for it and places it in the document. I need to create a library but don't know where to start or the exact structure. Every method it's own subclass in the library class? or can i have them all in one and what is a professional way of going about this.
This is party of my web forms application that i need to create a library for:
void UpdateStudentSubmissionGrid()
{
var usr = StudentListStep2.SelectedItem as User;
var lib = AssignmentListStep2.SelectedItem as Library;
if (usr == null || lib == null) return;
using (var dc = new DocMgmtDataContext())
{
var subs =
(from doc in dc.Documents
where doc.OwnedByUserID == usr.ID && doc.LibraryID == lib.ID
select new {DocID = doc.ID, Assignment = doc.Library.Name, Submitted = doc.UploadDT})
.OrderByDescending(c => c.Submitted)
.ToList();
StudentSubmissionGrid.DataSource = subs;
}
}
How do i start with this method?
By the looks of things you are using this function for a single webpage. You can call that function from any event i.e. user hits submit button. On the. Click the button and it will create a onclick event. Call the code from inside there UpdateStudentSubmissionGrid(); Just make sure the function is not nested inside another event or function. Webforms is already a class, you are just placing a function within the class.

Reading all components from folder and subfolder

I am working on Tridon 2009 using .NET Templating C# 2.0
I need to read all the components from folders and its subfolder.
If in my code I write:
OrganizationalItem imageFolder =
(OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
I am able to read all the components in subfolder from the place where indicator component is present, but I am not able to read other components present in the folder where indicator is present.
But If I write
OrganizationalItem imageFolder = (OrganizationalItem)m_Engine.GetObject(
comp.OrganizationalItem.OrganizationalItem.Id);
then I am able to read only folder where indicator component is present.
Below is my code.
XmlDocument doc = xBase.createNewXmlDocRoot("ImageLibrary");
XmlElement root = doc.DocumentElement;
Filter filter = new Filter();
Component comp = this.GetComponent();
filter.Conditions["ItemType"] = ItemType.Folder;
filter.Conditions["Recursive"] = "true";
OrganizationalItem imageFolder =
(OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
XmlElement itemList = imageFolder.GetListItems(filter);
foreach (XmlElement itemImg in itemList)
{
filter.Conditions["ItemType"] = ItemType.Component;
filter.Conditions["BasedOnSchema"] = comp.Schema.Id;
OrganizationalItem imgFolder =
(OrganizationalItem)m_Engine.GetObject(itemImg.GetAttribute("ID")
.ToString());
XmlElement imageLibs = imgFolder.GetListItems(filter);
doc = this.createImageNodes(imageLibs, doc, filter, comp);
foreach (XmlElement imglib in imageLibsList)
{
XmlElement imageroot = doc.CreateElement("Image");
XmlElement uploadeddateNode = doc.CreateElement("DateUploaded");
Component imgComp =
(Component)m_Engine.GetObject(imglib.GetAttribute("ID"));
}
}
Please suggest.
I see a lot of superfluous code on your snippet regarding the question "Reading all components from folder and subfolder"
But answering the question itself, when you are doing:
OrganizationalItem imageFolder = (OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
Your are not being able to read components present on that folder, because you have previously set the filter to folders only on the following line:
filter.Conditions["ItemType"] = ItemType.Folder;
Solution:
If you want to retrieve all components on the "indicator component" folder and below, you need to set the filter on your first search as following:
filter.Conditions["Recursive"] = "true";
filter.Conditions["ItemType"] = ItemType.Component;
filter.Conditions["BasedOnSchema"] = comp.Schema.Id;
And perform the search:
OrganizationalItem imageFolder = (OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
XmlElement itemList = imageFolder.GetListItems(filter);
Pretty basic stuff. Try to avoid using Filter class, since it was deprecated in 2009, and use GetListItems as much as possible as fetching lists is ALWAYS faster.
public class GetComponentsInSameFolder : ITemplate
{
public void Transform(Engine engine, Package package)
{
TemplatingLogger log = TemplatingLogger.GetLogger(GetType());
if (package.GetByName(Package.ComponentName) == null)
{
log.Info("This template should only be used with Component Templates. Could not find component in package, exiting");
return;
}
var c = (Component)engine.GetObject(package.GetByName(Package.ComponentName));
var container = (Folder)c.OrganizationalItem;
var filter = new OrganizationalItemItemsFilter(engine.GetSession()) { ItemTypes = new[] { ItemType.Component } };
// Always faster to use GetListItems if we only need limited elements
foreach (XmlNode node in container.GetListItems(filter))
{
string componentId = node.Attributes["ID"].Value;
string componentTitle = node.Attributes["Title"].Value;
}
// If we need more info, use GetItems instead
foreach (Component component in container.GetItems(filter))
{
// If your filter is messed up, GetItems will return objects that may
// not be a Component, in which case the code will blow up with an
// InvalidCastException. Be careful with filter.ItemTypes[]
Schema componentSchema = component.Schema;
SchemaPurpose purpose = componentSchema.Purpose;
XmlElement content = component.Content;
}
}
}
I'd think you'd want to collect sub folders and recursively call your function for each of them, which seems like what you're trying to achieve.
Is this function called createImageNodes() and where do you set imageLibsList?
It looks like you're treating each item as a folder in your first loop, what about the components?

Tridion 2009 SP1: Broker how to get Binary Url?

I am trying to retrieve the Binary Url of a multimedia component's file that is published as a dynamic Component Presentation.
I can see the Url in the Binaries table within the Broker database but I can't seem to get the binary url using either of the following bits of code:
using SQLBinaryMetaHome:
using (var sqlBinMetaHome = new Com.Tridion.Broker.Binaries.Meta.SQLBinaryMetaHome())
{
int componentItemId = int.Parse(queryStringId.Split('-')[1]);
var binaryMeta = sqlBinMetaHome.FindByPrimaryKey(new TCDURI(publicationId, 16, componentItemId));
if (binaryMeta != null)
{
VideoBinaryUrl = binaryMeta.GetURLPath();
}
else
{
Logger.Log.ErrorFormat("Failed ot load via SQL Binary Meta {0}", queryStringId);
}
}
Using Binary Meta factory:
using (var b = new BinaryMetaFactory())
{
var binaryMeta = b.GetMeta(queryStringId);
if (binaryMeta != null)
{
VideoBinaryUrl = binaryMeta.UrlPath;
}
else
{
Logger.Log.ErrorFormat("Failed to load binary meta {0}", queryStringId);
}
}
I can load the Component Meta data using the ComponentMetaFactory.
Any ideas on why I can't load the Binary Meta? Am I on the right track?
Rob
It looks like your first example is importing (auto-generated) methods from an internal DLL (Tridion.ContentDelivery.Interop.dll). Please don't use those and stick to the ones in the Tridion.ContentDelivery namespace (Tridion.ContentDelivery.dll).
You can find the official documentation for the Content Delivery .NET API in CHM format on SDL Tridion World (click the link, log in to the site and click the link again). From that documentation comes this example:
//create a new BinaryMetaFactory instance:
BinaryMetaFactory binaryMetaFactory = new BinaryMetaFactory();
//find the metadata for the specified binary
BinaryMeta binaryMeta = binaryMetaFactory.GetBinaryMeta("tcm:1-123");
//print the path to the output stream:
if(binaryMeta!=null) {
Response.Write("Path of the binary: " + binaryMeta.UrlPath);
}
//Dispose the BinaryMetaFactory
binaryMetaFactory.Dispose();
The factory class is Tridion.ContentDelivery.Meta.BinaryMetaFactory from Tridion.ContentDelivery.dll. I indeed also can't find a GetBinaryMeta method in that class, so it seems there is a mistake in the code sample. The most likely method that you should use is GetMeta.
Is there a reason you are not using a Binary Link to get a Link object to the specific Variant of the binary you want? Keep in mind that any DCP may render multiple variations of your multimedia component. From the Link object you can then get the URL to the binary.
Look for BinaryLink in the documentation for more details.
Try this:-
BinaryMeta binaryMeta = b.GetBinaryMeta(queryStringId);
if(binaryMeta != null) {
VideoBinaryUrl = binaryMeta.URLPath;
}
I did a SQL Profiler on the code and noticed that it was because I deployed my test app it wasn't calling the broker. Running the code within the actual Tridion Published site did hit the database but it was passing the value "[#def#]" for the variantId column.
I have now got it working with the following code:
IComponentMeta cm = cmf.GetMeta(queryStringId);
if (cm != null)
{
TcmId = queryStringId;
Title = cm.TryGetValue("title");
Summary = cm.TryGetValue("summary");
Product = cm.TryGetValue("product");
if (cm.SchemaId == StreamingContentSchemaId)
{
VideoId = cm.TryGetValue("video_url");
IsVimeo = true;
}
else if (cm.SchemaId == WebcastSchemaId)
{
using (var b = new BinaryMetaFactory())
{
var binaryMeta = b.GetMeta(queryStringId, "tcm:0-" + cm.OwningPublicationId + "-1");
if (binaryMeta != null)
{
VideoBinaryUrl = binaryMeta.UrlPath;
}
else
{
Logger.Log.ErrorFormat("Failed to load binary meta {0}", queryStringId);
}
}
}

How do you change the Repository link on Alfresco Share?

In Alfresco Share, when you click the "Repository" icon in the toolbar, you're taken to:
/share/page/repository
I would like to change this link to take the user to their home folder, example:
/share/page/repository#filter=path|/User%2520Homes/g/gi/gillespie/patrick.j.gillespie
I figured this would be a simple change, however, I'm pulling my hair out trying to figure out how to change the link. Does anyone know what I edit to change that link?
Update: So I can update the link via the share-config-custom.xml file, changing this line:
<item type="link" id="repository">/repository</item>
But I'm not sure how to get the folder path information in there. Does anyone have any ideas?
This was surprisingly difficult, so I figured I'd post what I did in case anyone else has the same problem.
If you're not using hashed home folders, you can provide this functionality by simply updating your share-config.xml or share-config-custom.xml like so:
<item type="link" id="repository">/repository#filter=path|/User%2520Homes/{userid}</item>
However, if you're using hashed home folders, things become a little tricky. Instead of modifying any XML files, you'll instead create a web script to get the user's home folder path and then modify the share template file to replace the links to the repository. Below is a sample web script and the modifications to the template file that are needed in order to do this.
hfp.get.desc.xml
<webscript>
<shortname>Home Folder Path</shortname>
<description>Description here.</description>
<format default="json">argument</format>
<url>/demo/get-home-folder-path</url>
<authentication>user</authentication>
</webscript>
hfp.get.js
This script traverses up the node tree and puts together the folder's path.
var myPerson = person;//used people.getPerson("patrick.j.gillespie") for testing
var currentNode = myPerson.properties.homeFolder;
var myDir = currentNode.properties["{http://www.alfresco.org/model/content/1.0}name"];
var count = 0;
while (count < 100) { // prevent an infinite loop
currentNode = currentNode.parent;
if (currentNode === undefined || currentNode === null) {break;}
if (currentNode.properties === undefined) {break;}
myDir = currentNode.properties["{http://www.alfresco.org/model/content/1.0}name"] + "/" + myDir;
count++;
}
if (count === 100) { //something went wrong
myDir = "";
}
model.homeFolder = myDir;
hfp.get.json.ftl
{
"homeFolder": "${homeFolder}"
}
Finally, you'll need to modify the "alfresco-template.ftl" file, located in "share/WEB-INF/classes/alfresco/templates/org/alfresco/include". Near the bottom of the file, add the code below. It will call the above web script to fetch the home folder path, and then update the Repository links with the home folder link.
<script type="text/javascript">
var callback = {
success: function(res) {
var data = YAHOO.lang.JSON.parse(res.responseText);
var homeFolder = "";
var hfIndex = data.homeFolder.indexOf("/User Homes/");
if (hfIndex !== -1) {
homeFolder = data.homeFolder.substr(hfIndex+12);
}
var repoLinks = $("a[title='Repository']");
for (var ii = 0; ii < repoLinks.length; ii++) {
repoLinks.get(ii).href = "/share/page/repository#filter=path|/User%2520Homes/" + homeFolder;
}
}
};
var sUrl = Alfresco.constants.PROXY_URI + "demo/get-home-folder-path";
var postData = "";
var getData = "";
var request = YAHOO.util.Connect.asyncRequest('GET', sUrl+getData, callback, postData);
</script>
There may possibly be a better way, but I was unable to find it. I'll probably refine it later, and this should mostly just be used as a starting point, but I figured I'd post it in case anyone had a similar problem.
Although not super elegant, I guess you can append parameters or anchors using a Javascript onclick handler on the link. Not quite sure which webscript renders the toolbar, but that one may be a good place to put the customization.

Accessing the object/row being edited in Dynamic Data

I'm modifying the "Edit.aspx" default page template used by ASP.NET Dynamic Data and adding some additional controls. I know that I can find the type of object being edited by looking at DetailsDataSource.GetTable().EntityType, but how can I see the actual object itself? Also, can I change the properties of the object and tell the data context to submit those changes?
Maybe you have found a solution already, however I'd like to share my expresience on this.
It turned out to be a great pita, but I've managed to obtain the editing row. I had to extract the DetailsDataSource WhereParameters and then create a query in runtime.
The code below works for tables with a single primary key. If you have compound keys, I guess, it will require modifications:
Parameter param = null;
foreach(object item in (DetailsDataSource.WhereParameters[0] as DynamicQueryStringParameter).GetWhereParameters(DetailsDataSource)) {
param = (Parameter)item;
break;
}
IQueryable query = DetailsDataSource.GetTable().GetQuery();
ParameterExpression lambdaArgument = Expression.Parameter(query.ElementType, "");
object paramValue = Convert.ChangeType(param.DefaultValue, param.Type);
Expression compareExpr = Expression.Equal(
Expression.Property(lambdaArgument, param.Name),
Expression.Constant(paramValue)
);
Expression lambda = Expression.Lambda(compareExpr, lambdaArgument);
Expression filteredQuery = Expression.Call(typeof(Queryable), "Where", new Type[] { query.ElementType }, query.Expression, lambda);
var WANTED = query.Provider.CreateQuery(filteredQuery).Cast<object>().FirstOrDefault<object>();
If it's a DD object you may be able to use FieldTemplateUserControl.FindFieldTemplate(controlId). Then if you need to you can cast it as an ITextControl to manipulate data.
Otherwise, try using this extension method to find the child control:
public static T FindControl<T>(this Control startingControl, string id) where T : Control
{
T found = startingControl.FindControl(id) as T;
if (found == null)
{
found = FindChildControl<T>(startingControl, id);
}
return found;
}
I found another solution, the other ones did not work.
In my case, I've copied Edit.aspx in /CustomPages/Devices/
Where Devices is the name of the table for which I want this custom behaviour.
Add this in Edit.aspx -> Page_Init()
DetailsDataSource.Selected += entityDataSource_Selected;
Add this in Edit.aspx :
protected void entityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
Device device = e.Results.Cast<Device>().First();
// you have the object/row being edited !
}
Just change Device to your own table name.

Resources