I am trying to see if I can use EnvDTE from a console program to set the button text on a selected button in the Forms designer in Visual Studio. So far, I have code that will find VS, get a DTE reference, get a Project reference inside the solution, and access a huge number of Project properties in the solution. I can also get a reference to the Forms designer tab/window so that I can print out its caption, for example.
Console.WriteLine ($"Found form window {formwin.Caption}");
Console.WriteLine( $"Formwin count is {formwin.Collection.Count}");
But this is where I get stuck. I have a button selected on a blank form, and would like to find the magic syntax to reference something like formwin.Controls[0].Text = "Hello world.";
or something. But I can't figure out how to see the controls (the form and the button) on the surface of the designer and so can't set the text property of the button. And I can't find any documentation on doing this kind of thing, short of a reference to record a macro to get some hints. (There's an extension to download and install and try, but I don't know yet if it will work for stuff like this. Most macro recorders do text only.)
Could anyone shed some light on the syntax or where I might find examples or documentation on how to do this? Thank you.
UPDATE 1: Some code to show some progress because of TnTinMn's help. If I make the invalid cast, I can see all the properties that I expect to see for Forms objects in the text editor. But at runtime, it fails.
The opening line of the loop raises an InvalidCastException because it's invalid to cast a System.__ComObject to a System.ComponentModel.ComponentCollection.
IDesignerHost foo = (IDesignerHost) formwin.Object;
IContainer container = foo.Container;
// almost works but foreach throws InvalidCastException at runtime below
foreach (IComponent component in container.Components) {
Console.WriteLine (component.GetType ().Name + " : " + component.Site.Name);
}
UPDATE 2: More code to help the helpers... I'll clean it up after an answer is found.
For now, I'm a little bit further than described in the original post above. I have a __ComObject in my hand and can cast it in the text editor and can see all the Controls and Properties that I want to see and use. BUT... at runtime I get InvalidCastExceptions that say I can't cast __ComObjects to Forms or ComponentCollections or whatever.
Here are two example exceptions, followed by my current console app code. My solution has 1 project in it (ConsoleApp1), which contains 1 Forms1.cs file and 1 Program.cs file.
Error Message 1:
Item caption is Error List ...
Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.__ComObject' to type 'System.ComponentModel.ComponentCollection'.
at System.StubHelpers.InterfaceMarshaler.ConvertToManaged(IntPtr pUnk, IntPtr itfMT, IntPtr classMT, Int32 flags)
at System.ComponentModel.IContainer.get_Components()
at ConsoleApp1.Program.Main(String[] args) in E:\Projects\DteProject1Solution\ConsoleApp1\Program.cs:line 116
Press any key to continue . . .
Error Message 2:
Item caption is Stack Trace Explorer - Stack Trace
Unhandled Exception: System.InvalidCastException: Unable to cast COM object of type 'System.__ComObject' to class type 'System.Windows.Forms.Form'. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.
at ConsoleApp1.Program.Main(String[] args) in E:\Projects\DteProject1Solution\ConsoleApp1\Program.cs:line 107
Press any key to continue . . .
Here is my current almost-working code, with a trace of its output. I wonder about the formwin
variable - I think it's the Form1.cs window, but formwin.Collection.Count
is 13, which are all the usual VStudio windows (editor windows, solution explorer, error list, toolbox, and so on). Maybe formwin.Collection points back to the collection that holds it?
Output of my code:
E:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.exe
E:\Projects\DteProject1Solution\ConsoleApp1\Program.cs
Active doc name is 'Program.cs'
Count of doc.windows is 1
Looping over dte.Solution.Projects:
Project name is ConsoleApp1
Project name is Miscellaneous Files
Number of projects in solution: 2
Finding the ConsoleApp1 project:
Project name is ConsoleApp1
Found project ConsoleApp1
Myproject has a ProjectItem count of 4
Project Type is System.__ComObject
DTE.Windows.Count is 14
Looping over dte.Windows to find 'Form1.cs':
Window is Form1.cs [Design]
Found form? dteclientarea? window Form1.cs [Design]
Window count of formwin.Collection.Count is 14
Looping over formwin.Collection: (obvious VStudio windows)
Item caption is Form1.cs [Design]
Item caption is Program.cs
Item caption is Junk.txt
Item caption is DteProject1
Item caption is Toolbox
Item caption is Output
Item caption is Solution Explorer
Item caption is Properties
Item caption is Locals
Item caption is Call Stack
Item caption is Diagnostic Tools
Item caption is Disassembly
Item caption is Error List ...
Item caption is Stack Trace Explorer - Stack Trace
Unhandled Exception: System.InvalidCastException: Unable to cast COM object of type 'System.__ComObject' to class type 'System.Windows.Forms.Form'. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.
at ConsoleApp1.Program.Main(String[] args) in E:\Projects\DteProject1Solution\ConsoleApp1\Program.cs:line 107
Press any key to continue . . .
And here is the code itself.
class Program
{
static void Main (string[] args) {
void Dprint (string msg) {
Console.WriteLine (msg);
}
var allDTEs = Ide.GetAllDte ();
var dte = allDTEs[0];
Console.WriteLine (dte.FileName);
Console.WriteLine (dte.ActiveWindow.Document.FullName);
var doc = dte.ActiveWindow.Document;
Dprint ($"Active doc name is '{doc.Name}'");
var n = doc.Windows.Count;
Dprint ($"Count of doc.windows is {n}");
Dprint ("Looping over dte.Solution.Projects:");
Projects projectlist = dte.Solution.Projects;
foreach (Project proj in projectlist) {
Dprint ($" Project name is {proj.Name}");
}
Dprint ($"Number of projects in solution: {projectlist.Count}");
Dprint ("Finding the ConsoleApp1 project:");
// This fails: projectlist(idx) error: Method name expected.
//var foundproject = 0;
//for (int idx = 1; idx <= projectlist.Count; idx++) {
// Project proj = projectlist (idx);
// if (proj.Name == "ConsoleApp1") {
// foundproject = idx;
// }
//}
Project myproj = null;
foreach (Project proj in projectlist) {
Dprint ($" Project name is {proj.Name}");
if (proj.Name == "ConsoleApp1") {
myproj = proj as Project;
break;
}
}
Dprint ($"Found project {myproj.Name}");
var n2 = myproj.ProjectItems.Count;
Dprint ($"Myproject has a ProjectItem count of {n2}");
foreach (Property prop in myproj.Properties) {
//Write ($" Property is {prop.Name}"); // 20 or 30 of them print
if (prop.Name == "ProjectType") {
// Myproject type is System.__ComObject
Dprint ($" Project Type is {myproj.Properties.Item ("ProjectType")}");
}
}
Dprint ($"DTE.Windows.Count is {dte.Windows.Count}");
Window formwin = null;
Dprint ($"Looping over dte.Windows to find 'Form1.cs':");
foreach (Window win in dte.Windows) {
Dprint ($" Window is {win.Caption}");
if (win.Caption.Contains ("Form1")) {
formwin = win as Window;
break;
}
}
if (formwin == null) {
Dprint ($"Form window is null.");
return;
}
Dprint ($"Found form? dteclientarea? window {formwin.Caption}");
Dprint ($"Window count of formwin.Collection.Count is {formwin.Collection.Count}");
Dprint ($"Looping over formwin.Collection: (obvious VStudio windows)");
foreach (Window item in formwin.Collection) {
Dprint ($" Item caption is {item.Caption}");
}
// foo becomes a __ComObject, theform becomes null
IDesignerHost foo = (IDesignerHost) formwin.Object;
Form theform = (Form) foo.RootComponent;
if (theform != null) {
Dprint ($"Code name of theform.Name is {theform.Name}");
}
IContainer container = foo.Container;
foreach (IComponent component in container.Components) {
Console.WriteLine (component.GetType ().Name + " : " + component.Site.Name);
}
}
Thank you again to those who are trying to help.