I need to access a specific property inside a COM object (the iTunes COM Library). You can access this property with the dynamic view of the Visual Studio debugger.
I tried to get this property using Reflection but I don't get any private properties or fields back.
I can access all the Properties that I also see in the debugger using this line:
new Microsoft.CSharp.RuntimeBinder.DynamicMetaObjectProviderDebugView(myObject).Items
However, I would rather not use this call because I believe an easier solution exists.
If you have iTunes installed this would be a simple example of what I'm trying to achieve:
iTunesAppClass app;
if (Process.GetProcessesByName("iTunes").Any())
{
app = new iTunesAppClass();
}
else
{
return;
}
foreach (IITPlaylist playlist in app.LibrarySource.Playlists)
{
// This does not work. There is no "Parent".
//var parent = playlist.Parent;
Type playListType = playlist.GetType();
// both contain 0 results
var fields = playListType.GetFields(BindingFlags.NonPublic);
var properties = playListType.GetFields(BindingFlags.NonPublic);
// works but only during runtime
//var parent2 = new Microsoft.CSharp.RuntimeBinder.DynamicMetaObjectProviderDebugView(playlist).Items[4];
}
I have a certain folder with a couple of view classes (XAML files).
Right now i am instantiating these by code:
engineRoomView = new EngineRoomView()
{
DataContext = new ProcessViewModel()
};
and then further down:
item = new TabItem();
item.Contents = engineRoomView;
item.Name = "Engine Room";
views.Add(item);
What I want to achieve is some kind of dynamic code for creating one instance of each view in that particular folder without knowing about them during programming.
If a developer adds another xaml file to that folder. Then this gets created in run-time.
Something imaginary like:
Foreach(file in folder)
{
magicInstance = createInstanceFromFile(file);
MainViewModel.addView(magicInstance);
}
Is this possible?
If I understand you correctly, this could be archived with the build in Xaml Reader. The Xaml Reader can read a xaml file and will generate the objects based on the xaml.
Have a look here:
Loading XAML at runtime?
It sounds like you have a "Parent View" that you want to automatically attach a child view for each file in the same folder.
If the classes in each folder have a namespace consistent with the folder structure, this code should allow you to create a list of an instances of each class in the same folder as an example instance that inherit from a base class (could modify easily for interface also).
static class NamespaceHelper
{
public static List<Type> FindTypesInSameNamespaceAs(object instance)
{
string ns = instance.GetType().Namespace;
Type instanceType = instance.GetType();
List<Type> results = instance.GetType().Assembly.GetTypes().Where(tt => tt.Namespace == ns &&
tt != instanceType).ToList();
return results;
}
public static List<T> InstantiateTypesInSameNamespaceAs<T>(object instance)
{
List<T> instances = new List<T>();
foreach (Type t in FindTypesInSameNamespaceAs(instance))
{
if (t.IsSubclassOf(typeof(T)))
{
T i =(T) Activator.CreateInstance(t);
instances.Add(i);
}
}
return instances;
}
}
Just call NamespaceHelper.InstantiateTypesInSameNamespaceAs<YourBaseViewType>(instanceOfParentViewInSameFolder), loop through the results, and add them to your Parent.
Foreach(ViewBase v in NamespaceHelper.InstantiateTypesInSameNamespaceAs<ViewBase>(this))
{
MainViewModel.addView(v);
}
I'm trying to find out how to read/write to the extended file properties in C#
e.g. Comment, Bit Rate, Date Accessed, Category etc that you can see in Windows explorer.
Any ideas how to do this?
EDIT: I'll mainly be reading/writing to video files (AVI/DIVX/...)
For those of not crazy about VB, here it is in c#:
Note, you have to add a reference to Microsoft Shell Controls and Automation from the COM tab of the References dialog.
public static void Main(string[] args)
{
List<string> arrHeaders = new List<string>();
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder;
objFolder = shell.NameSpace(#"C:\temp\testprop");
for( int i = 0; i < short.MaxValue; i++ )
{
string header = objFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header))
break;
arrHeaders.Add(header);
}
foreach(Shell32.FolderItem2 item in objFolder.Items())
{
for (int i = 0; i < arrHeaders.Count; i++)
{
Console.WriteLine(
$"{i}\t{arrHeaders[i]}: {objFolder.GetDetailsOf(item, i)}");
}
}
}
Solution 2016
Add following NuGet packages to your project:
Microsoft.WindowsAPICodePack-Shell by Microsoft
Microsoft.WindowsAPICodePack-Core by Microsoft
Read and Write Properties
using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;
string filePath = #"C:\temp\example.docx";
var file = ShellFile.FromFilePath(filePath);
// Read and Write:
string[] oldAuthors = file.Properties.System.Author.Value;
string oldTitle = file.Properties.System.Title.Value;
file.Properties.System.Author.Value = new string[] { "Author #1", "Author #2" };
file.Properties.System.Title.Value = "Example Title";
// Alternate way to Write:
ShellPropertyWriter propertyWriter = file.Properties.GetPropertyWriter();
propertyWriter.WriteProperty(SystemProperties.System.Author, new string[] { "Author" });
propertyWriter.Close();
Important:
The file must be a valid one, created by the specific assigned software. Every file type has specific extended file properties and not all of them are writable.
If you right-click a file on desktop and cannot edit a property, you wont be able to edit it in code too.
Example:
Create txt file on desktop, rename its extension to docx. You can't
edit its Author or Title property.
Open it with Word, edit and save
it. Now you can.
So just make sure to use some try catch
Further Topic:
Microsoft Docs: Implementing Property Handlers
There's a CodeProject article for an ID3 reader. And a thread at kixtart.org that has more information for other properties. Basically, you need to call the GetDetailsOf() method on the folder shell object for shell32.dll.
This sample in VB.NET reads all extended properties:
Sub Main()
Dim arrHeaders(35)
Dim shell As New Shell32.Shell
Dim objFolder As Shell32.Folder
objFolder = shell.NameSpace("C:\tmp")
For i = 0 To 34
arrHeaders(i) = objFolder.GetDetailsOf(objFolder.Items, i)
Next
For Each strFileName In objfolder.Items
For i = 0 To 34
Console.WriteLine(i & vbTab & arrHeaders(i) & ": " & objfolder.GetDetailsOf(strFileName, i))
Next
Next
End Sub
You have to add a reference to Microsoft Shell Controls and Automation from the COM tab of the References dialog.
Thank you guys for this thread! It helped me when I wanted to figure out an exe's file version. However, I needed to figure out the last bit myself of what is called Extended Properties.
If you open properties of an exe (or dll) file in Windows Explorer, you get a Version tab, and a view of Extended Properties of that file. I wanted to access one of those values.
The solution to this is the property indexer FolderItem.ExtendedProperty and if you drop all spaces in the property's name, you'll get the value. E.g. File Version goes FileVersion, and there you have it.
Hope this helps anyone else, just thought I'd add this info to this thread. Cheers!
GetDetailsOf() Method - Retrieves details about an item in a folder. For example, its size, type, or the time of its last modification. File Properties may vary based on the Windows-OS version.
List<string> arrHeaders = new List<string>();
Shell shell = new ShellClass();
Folder rFolder = shell.NameSpace(_rootPath);
FolderItem rFiles = rFolder.ParseName(filename);
for (int i = 0; i < short.MaxValue; i++)
{
string value = rFolder.GetDetailsOf(rFiles, i).Trim();
arrHeaders.Add(value);
}
Jerker's answer is little simpler. Here's sample code which works from MS:
var folder = new Shell().NameSpace(folderPath);
foreach (FolderItem2 item in folder.Items())
{
var company = item.ExtendedProperty("Company");
var author = item.ExtendedProperty("Author");
// Etc.
}
For those who can't reference shell32 statically, you can invoke it dynamically like this:
var shellAppType = Type.GetTypeFromProgID("Shell.Application");
dynamic shellApp = Activator.CreateInstance(shellAppType);
var folder = shellApp.NameSpace(folderPath);
foreach (var item in folder.Items())
{
var company = item.ExtendedProperty("Company");
var author = item.ExtendedProperty("Author");
// Etc.
}
After looking at a number of solutions on this thread and elsewhere
the following code was put together. This is only to read a property.
I could not get the
Shell32.FolderItem2.ExtendedProperty function to work, it is supposed
to take a string value and return the correct value and type for that
property... this was always null for me and developer reference resources were very thin.
The WindowsApiCodePack seems
to have been abandoned by Microsoft which brings us the code below.
Use:
string propertyValue = GetExtendedFileProperty("c:\\temp\\FileNameYouWant.ext","PropertyYouWant");
Will return you the value of the extended property you want as a
string for the given file and property name.
Only loops until it found the specified property - not until
all properties are discovered like some sample code
Will work on Windows versions like Windows server 2008 where you will get the error "Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.Shell'" if just trying to create the Shell32 Object normally.
public static string GetExtendedFileProperty(string filePath, string propertyName)
{
string value = string.Empty;
string baseFolder = Path.GetDirectoryName(filePath);
string fileName = Path.GetFileName(filePath);
//Method to load and execute the Shell object for Windows server 8 environment otherwise you get "Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.Shell'"
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object shell = Activator.CreateInstance(shellAppType);
Shell32.Folder shellFolder = (Shell32.Folder)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { baseFolder });
//Parsename will find the specific file I'm looking for in the Shell32.Folder object
Shell32.FolderItem folderitem = shellFolder.ParseName(fileName);
if (folderitem != null)
{
for (int i = 0; i < short.MaxValue; i++)
{
//Get the property name for property index i
string property = shellFolder.GetDetailsOf(null, i);
//Will be empty when all possible properties has been looped through, break out of loop
if (String.IsNullOrEmpty(property)) break;
//Skip to next property if this is not the specified property
if (property != propertyName) continue;
//Read value of property
value = shellFolder.GetDetailsOf(folderitem, i);
}
}
//returns string.Empty if no value was found for the specified property
return value;
}
Here is a solution for reading - not writing - the extended properties based on what I found on this page and at help with shell32 objects.
To be clear this is a hack. It looks like this code will still run on Windows 10 but will hit on some empty properties. Previous version of Windows should use:
var i = 0;
while (true)
{
...
if (String.IsNullOrEmpty(header)) break;
...
i++;
On Windows 10 we assume that there are about 320 properties to read and simply skip the empty entries:
private Dictionary<string, string> GetExtendedProperties(string filePath)
{
var directory = Path.GetDirectoryName(filePath);
var shell = new Shell32.Shell();
var shellFolder = shell.NameSpace(directory);
var fileName = Path.GetFileName(filePath);
var folderitem = shellFolder.ParseName(fileName);
var dictionary = new Dictionary<string, string>();
var i = -1;
while (++i < 320)
{
var header = shellFolder.GetDetailsOf(null, i);
if (String.IsNullOrEmpty(header)) continue;
var value = shellFolder.GetDetailsOf(folderitem, i);
if (!dictionary.ContainsKey(header)) dictionary.Add(header, value);
Console.WriteLine(header +": " + value);
}
Marshal.ReleaseComObject(shell);
Marshal.ReleaseComObject(shellFolder);
return dictionary;
}
As mentioned you need to reference the Com assembly Interop.Shell32.
If you get an STA related exception, you will find the solution here:
Exception when using Shell32 to get File extended properties
I have no idea what those properties names would be like on a foreign system and couldn't find information about which localizable constants to use in order to access the dictionary. I also found that not all the properties from the Properties dialog were present in the dictionary returned.
BTW this is terribly slow and - at least on Windows 10 - parsing dates in the string retrieved would be a challenge so using this seems to be a bad idea to start with.
On Windows 10 you should definitely use the Windows.Storage library which contains the SystemPhotoProperties, SystemMusicProperties etc.
https://learn.microsoft.com/en-us/windows/uwp/files/quickstart-getting-file-properties
And finally, I posted a much better solution that uses WindowsAPICodePack there
I'm not sure what types of files you are trying to write the properties for but taglib-sharp is an excellent open source tagging library that wraps up all this functionality nicely. It has a lot of built in support for most of the popular media file types but also allows you to do more advanced tagging with pretty much any file.
EDIT: I've updated the link to taglib sharp. The old link no longer worked.
EDIT: Updated the link once again per kzu's comment.
What is the best way to access Microsoft Access Database object's Properties (like in CurrentDb.Properties) from C# in Visual Studio 2010?
(Not essential: In fact I want to get rid of Replication in a few dozen databases "on demand". Replication is not in use for a few years, and it was OK for MS Access prior to 2013. Access 2013 rejects databases with this feature.)
You can iterate over and modify the properties in the Access database by using the Access DAO object library.
The following code iterates over the properties in the database as well over the different containers and its properties as well over the Documents and its properties in the Databases container. The output is written to the Debug Output window.
After that I pick a Property from different property collections and change it's value. Do note that using the indexer on the collection will throw an exception if the property doesn't exist.
Make sure you have a reference to the Primary Interop Assembly for Microsoft Office 12.0 Access database engine Object Library (your version migth vary) so that you can have the following in your using statements:
using Microsoft.Office.Interop.Access.Dao;
Your method would go like this:
// Open a database
var dbe = new DBEngine();
var db = dbe.OpenDatabase(#"C:\full\path\to\your\db\scratch.accdb");
// Show database properties
DumpProperties(db.Properties);
// show all containers
foreach (Container c in db.Containers)
{
Debug.WriteLine("{0}:{1}", c.Name, c.Owner);
DumpProperties(c.Properties);
}
//Show documents and properties for a specific container
foreach (Document d in db.Containers["Databases"].Documents)
{
Debug.WriteLine("--------- " + d.Name);
DumpProperties(d.Properties);
}
// set a property on the Database
Property prop = db.
Properties["NavPane Width"];
prop.Value = 300;
// set a property on the UserDefined document
Property userdefProp = db
.Containers["Databases"]
.Documents["UserDefined"]
.Properties["ReplicateProject"];
userdefProp.Value = true;
Property dumper helper
private static void DumpProperties(Properties props)
{
foreach (Property p in props)
{
object val = null;
try
{
val = (object)p.Value;
}
catch (Exception e)
{
val = e.Message;
}
Debug.WriteLine(
"{0} ({2}) = {1}",
p.Name,
val,
(DataTypeEnum) p.Type);
}
}
I used this to overcome an exception being thrown on dynamic types (as the Value property turns out to be)
I have an event like this:
private void btnStartAnalysis_Click(object sender, EventArgs e)
{
SqlConnectionStringBuilder objConnectionString = new SqlConnectionStringBuilder();
objConnectionString.DataSource = txtHost.Text;
objConnectionString.UserID = txtUsername.Text;
objConnectionString.Password = txtPassword.Text;
objConnectionString.InitialCatalog = Convert.ToString(cmbDatabases.SelectedValue);
string[] arrArgs = { objConnectionString.ConnectionString };
//Checks for the selectedItem in the cmbOpearions dropdown and make call to appropriate functions.
string assemblyName = cmbOperations.SelectedValue.ToString();
Assembly assembly = Assembly.LoadFrom(assemblyName);
Type localType = assembly.GetType("PrimaryKeyChecker.PrimaryKeyChecker");
IMFDBAnalyserPlugin analyser = (IMFDBAnalyserPlugin) Activator.CreateInstance(localType);
string response = analyser.RunAnalysis(objConnectionString.ConnectionString);
//show the response of the the function call
txtPluginResponse.Text = response;
}
I want this line to be dynamic:
Type localType = assembly.GetType("PrimaryKeyChecker.PrimaryKeyChecker");
where PrimaryKeyChecker is a namespace and another PrimaryKeyChecker is the class.
But I want to create other namespaces and classes, so is there any way to call them dynamically and load them in the combobox like this.
public void SetOperationDropDown()
{
cmbOperations.DataSource = PluginManager.GetAllPlugins();
if(cmbOperations.Items.Count > 0)
{
cmbOperations.SelectedItem = cmbOperations.Items[0];
}
}
You've almost answered your own question! Assuming you have a list of plugins, configured in a config file or whatnot, then your PluginManager can load up the Types from the assembly using code similar to:
Type analyserType = typeof(IMFDBAnalyserPlugin);
foreach(Type t in assembly.GetTypes()) {
if(t.IsSubtypeOf(analyserType) {
plugins.Add((IMFDBAnalyserPlugin) Activator.CreateInstance(t));
}
}
If you do not have a list of plugins, then you can either scan a directory and do the same thing as above. You could also consider using a plugin framework architecture like MEF and it does a lot of that work for you and discovers the assemblies and plugins at runtime.
I think the answer of Tom can help you populate a list of plugins. Bind them to the combobox where you put the text / description to the Type name and bind the value of combo-items to the actual Type declaration. And you asked for the event to be "Dynamic"...Do you probably mean generic??? Then i would advice to refactor the code in the click_event to a private method, to be able to call it from other "places" as well. Then in the click_event you retrieve the selected Plugin Type from the currently selected item a provide this in the generic function call to RunAnalysis like this:
private void btnStartAnalysis_Click(object sender, EventArgs e)
{
if(cmbOperations.SelectedItem != null)
RunAnalysis<cmbOperations.SelectedItem.Value>();
}
private void RunAnalysis<T>()
{
//Checks for the selectedItem in the cmbOpearions dropdown and make call to appropriate functions.
//string assemblyName = cmbOperations.SelectedValue.ToString();
//Assembly assembly = Assembly.LoadFrom(assemblyName);
//Type localType = assembly.GetType("PrimaryKeyChecker.PrimaryKeyChecker");
IMFDBAnalyserPlugin analyser =
(IMFDBAnalyserPlugin) Activator.CreateInstance(T);
string response = analyser.RunAnalysis(objConnectionString.ConnectionString);
//show the response of the the function call
txtPluginResponse.Text = response;
}
Another way could be to just use a parameter for the Type currently selected. Hope this helps you out or to bring you to new ideas towards a solution.