How to read Visio Shape's text properly - c#

I'm in the middle of trying to transfer VBA code over to C# so that form application I made is standalone. I'm having issues with trying to get the text from a shape that is within a shape(in a group). Here is an example in VBA that works flawless.
Dim text as String
text = groupShape.shapes.item("rectangle").text
This is returning the correct value.
In C# I am using the microsoft.office.interop.visio.dll reference. I've been able to read in shape names just fine so I know I have the application and document objects working fine. In C# the code above looks like:
var text = "";
text = Doc.Shapes.ItemU("groupShape").Shapes.ItemU("rectangle").text
This is not returning the correct value. It is saying that it will return "Object" so I believe its not returning a string but an object.
What can I do to solve this in order to return the text? Thanks!
EDIT:
Here is the actual code I am writing. The vba and the c#
VBA:
Dim tempShape As Shape
Set tempShape = ActiveDocument.Pages(pageName).Shapes.Item("MainTable")
txtJobName.value = tempShape.Shapes.Item("textJobName").Text
c#
IVisio.Shape tempShape = Doc.Pages[Loaded_Page.Name].Shapes.ItemU["MainTable"];
Txt_JobName.Text = tempShape.Shapes.ItemU["textJobName"].Characters.Text;
ANOTHER EDIT:
I've also tried going into the shape data properties by referencing the cell that the text is in and still no luck.
Txt_JobName.Text = tempShape.Shapes.ItemU["textJobName"].CellsSRC[8, 0, 0].ResultStr[0];

First, try using , .Item not .ItemU, those two are different functions. The default one (one that you are using implicitly in VBA) is Item. You may be actually addressing a different shape in c#
Second, .Text should be starting with capital T, the code with small t should not compile. Make sure that .text in lowercase is not an extension method you imported from somewhere, (unrelated to Visio)
Third caveat - to make sure to get text from shape as you see it in UI, it is better to use shape.Characters.Text, because in case shape contains fields, the shape.Textwill return placeholders ('obj') for those places where the field text should go. But this applies to both VBA and C#
Fourth.. groupShape in VBA appears to be a variable name, not shape name. From the code it's not clear what is the name of that group shape. How do you get that one in VBA? May be worth checking.

I'm not very familiar with the Visio type library, but this:
groupShape.shapes.item("rectangle")
Is returning a Shape object. Grab that object reference instead of discarding it with another dot. VBA is being "helpful" here, and happily lets you write late-bound calls that will only be resolved at runtime - C# will only be that permissive with the dynamic keyword, and you typically don't want to code against dynamic - you want to work with strong types:
Dim rectangle As Shape
Set rectangle = groupShape.shapes.item("rectangle")
Dim rectangleText As String
rectangleText = rectangle.Text
The C# / interop code needs to do the same:
var groupShape = (Shape)Doc.Shapes.ItemU("groupShape");
var rectangle = (Shape)groupShape.Shapes.ItemU("rectangle");
var rectangleText = rectangle.Text // assuming Text is defined on the Shape interface
In other words, always be aware of the types you're working with; if a member returns an object and you need it to be a more specific type, you need to cast as appropriate before you can have compile-time validation of the member call, or cast to dynamic if you don't care for compile-time validation and defer them to run-time:
var text = (string)((dynamic)groupShape.Shapes.ItemU("rectangle")).Text
Notice the cast to string; text will be a string. Forget that cast and your code will have to deal with dynamic spreading throughout the execution paths like a cancer and ruining your type safety - and you don't want that.

As always, I come to find out the reason the .Text was returning "Obj" is that the shape did not have any text in it. If there is text, it will return it. I got too caught up into the return, "Obj". I wonder why it doesn't return empty text or null if there is no text in the shape. Anyways, all the other answers on this question are very helpful and they lead me to finding my problem.

Related

Get Property Value From Nested Object List

I am not sure how to even title or ask this question, so apologies for any confusion.
I need to get the value of the Id from the first list in Model.Content.GetPropertyValue("SlidePanel"). I've tried many, many, many thing with my troubles usually being that "You can't do this because it's an Object". In the image below, I want to get the Id Value of "1092" as a String.
-----EDIT-------
I was able to get the value with the code below. Casted the list, grabbed the first list since it would always be that option (I wrapped it in an if, but removed it in this example), then I was able to specify the property I needed and converted as needed.
If I sound like I don't speak this language fluently, it's because I'm still fresh to development. Thanks for everyone who helped.
dynamic slidePanelObject = Model.Content.GetProperty("SlidePanel").Value;
List<object> slidePanelCast = ((IEnumerable<object>)slidePanelObject).Cast<object>().ToList();
dynamic slidePanelFirst = slidePanelCast.First();
var slidePanelId = slidePanelFirst.Id;
string slidePanelString = slidePanelId.ToString();
Model.Content.GetPropertyValue is probably returning an System.Object, so you'll need to cast your temp var to a List<T> type of some kind before you can access it like a list.
Without knowing all the types involved, here's some code that you could modify:
var temp = Model.Content.GetPropertyValue("SlidePanel") as List<TYourType>;

How to specify order of debugger visualizers in Visual Studio

I've been working on a debugger visualizer for Visual Studio for some time and while the actual visualizer works fine. The problem is that it always places itself at the top of the visualizer list when examining a variable which really annoys some of the users who rather have Text as the top one (since the top one is also default when opening VS).
I can't find any support for this on DialogDebuggerVisualizer or DebuggerVisualizerAttribute which were my first thoughts so I've been scouring SO/MSDN/Google for information on how to affect the sort order of the visualizers (preferably to put mine last in the list) but to no avail.
Below is how I register my visualizer, it then just shows a form based on the value that is being visualized.
using Microsoft.VisualStudio.DebuggerVisualizers;
[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(Shorthand.VSAddins.JsonVisualizer.JsonVisualizer),
typeof(VisualizerObjectSource),
Target = typeof(string),
Description = "Json Visualizer")]
namespace Shorthand.VSAddins.JsonVisualizer
{
public class JsonVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var json = objectProvider.GetObject() as string;
var form = new VisualizerForm { Json = json };
windowService.ShowDialog(form);
}
}
}
Does anyone know if it is possible to affect the order of the visualizers or should I just let it be?
I don't think there is a solution. But there is a workaround:
Define your own Text Visualizer and put appropriate DebuggerVisualizer attribute before the attribute of your JsonVisualizer. The result will be that string will be readable by default and Json Visualizer can be chosen. A window with a multi-line textbox is not too much work.
It is probably not even necessary to write visualizer. It should be possible to use internal one but I don't know its name (Which class is used for "Text Visualizer"?).
It will always appear first, by design. The under the hood cast has found the best match for the variable it is reflecting on.
however, you could do either of two things. You could make the visualizer only appear when the sting contains ':'
Or you could use reflection to reorder the visualisers by adding them to the end of the collection in the order you want, then removing the originals from the collection.
For the latter you will most likely have to change the collection from readonly to writable. Via reflection.
There is no reliable source to draw on other than your will to succeed.
I guess that VS 'under the hood' can distinguish between type of string and type of xml quite easily, but Xml is just a string too, so a key question here would be, how does VS tell the difference between the two?
Could you dissect the VS XML visualizer to see how it works (even if you have to use reflector on the DLL to do it, you might get to see the method that works it out)

Creating and populating an excel sheet with late-binding

I've been looking at the microsoft support page Binding for Office automation servers with Visual C# .NET to try to create an Excel Worksheet, populate it with values in a datatable, and save it to the machine.
I have an implementation that uses early-binding and simply loops through the items, but I don't know how you would achieve this with late-binding, and I need to be able to embed the Interop types to make the application version independent in regard to MS Office.
How would I add the rows from a datatable to a new Excel Worksheet using late-binding?
I would recommend writing an interface and abstracting the data population step, and the excel step. That way you can have a system that implements early binding with excel to do things, and then an engine that uses this interface to populate the excel sheet. Step 2 would be to write a second implementation of the interface using Late Binding rather than early binding. Then you just substitute the second implementation for the first in your code when you create the interface.
In the code, you would only create 1 object, the interface itself. When creating it though, you can assign it as any other class/implementation that implements that interface...here's an example from my own code:
ISpreadsheetControl SSInterface;
if (conditionCheck())
SSInterface = new ExcelImplementer();
else
SSInterface = new OpenOfficeImplementer();
I Only ever use the 1 object, SSInterface, when placing data or changing page settings, etc etc...whatever else I implemented...but it can do so in two different manners based on which Class I assigned to the interface at load time.
As for the specifics and details on "how to"...I find the second example in the link you provided to be very helpful indeed. Its all about Type and Invoke. The difficulty will be keeping track of what you are working with at any given time. That is one of the things that will make it harder to work with, and a good reason to extract the early binding implementation first. That way you can see all the method names you will need and their parameter lists when writing the second.
I also want to add this: The very simple and short answer to your question is "Do it exactly the same way you already are" You just change 'how' you are calling the method that is populating the data...and all the rest of the excel interop implementation along with it.
UPDATE
I think this might do what you are looking for, although its messy enough that I would recommend putting it (both operations actually, one can call the other) into its own separate method, somewhere.
//Get a range object that contains the cell.
Parameters = new Object[2];
Parameters[0] = iRow + 1;
Parameters[1] = iCol;
objRange_Late = objSheet_Late.GetType().InvokeMember( "Cells",
BindingFlags.GetProperty, null, objSheet_Late, Parameters );
//Write value in cell
Parameters = new Object[1];
Parameters[0] = row[col.ColumnName];
objRange_Late.GetType().InvokeMember( "Value", BindingFlags.SetProperty,
null, objRange_Late, Parameters );
I have to admit, I don't have an implementation that I can test this on right now, but according to the things I know about it, that should work. If "Cells" doesn't work, I would also the same code with "Range"...I dont actually know if that one takes numerical input or not.
link to Cells property description (msdn)
you might also want to explore that whole system a bit, it can help you find some of the things you might be looking for.
Tested Managed to successfully create and test the above code, it works perfectly.

Referring To Named Elements Created Programmatically?

I have a RichTextBox created programmatically with the following code:
RichTextBox RT = new RichTextBox();
RT.Name = "asdf";
RT.Text = "blah";
TableLayoutPanel.Controls.Add(RT,0,0);
Now let's say I want to modify the text of RT, and it's name is "asdf", Visual Studio won't allow me to write asdf.Text = "haha" because asdf doesn't exist yet.
How can I grab "asdf" specifically and set its text? Because this RichTextBox is in a specific cell, can I grab it based on its cell coordinates?
You should be able to get a reference to it via the TableLayoutPanel.Controls property, which returns a TableLayoutControlCollection. That class provides two ways to locate a control by name: the Item property and the Find method. The Item property returns a control with the specified name, whereas the Find method returns a collection of controls. In both cases you would need to cast from a Control to a RichTextBox.
var rt = (RichTextBox)myTableLayoutPanel.Controls.Item["asdf"];
// or
var rts = myTableLayoutPanel.Controls.Find("asdf", false);
foreach (var rt in rts)
// (RichTextBox)rt ...
EDIT: be sure to check that the result is not null before using it in case the control is not found.
Well... you did instantiate the RichTextBox and have a reference that you can use; it's called "RT" in your example.
Now, likely you've done this in a method so it was locally scoped and is no longer available when you want it. So you save that reference somehow by assigning it to some member you can access. If you have a lot of them and want to differentiate by name somehow, you might stick it into a Dictionary<string, RichTextBox>, for example. Or you could put it in some static variable; there are numerous options, each with their own pros and cons.
The one thing you probably don't want to do is walk the control tree looking for the control with the name you specified. But you could also do that, if you really wanted to.

Code Generation for All CLR supported language using a CodeDomSerializer

I am implementing Code Generation for WindowsForm control at Design-Time, using a Custom CodeDomSerializer.
Here is what I have.
A user control i.e. MyControl written
in C#.
MyControl has a property
MyControlProperty of type ObjectXXX
that is accessible publicly.(like
myControl.MyControlProperty).
The type ObjectYYY has a public property
PropertyXXX of type Collection.
The ObjectXXX has a internal field of type ObjectYYY.
The ObjectXXX should be initialized by passing Collection (which
is nothing but ObjectYYY.PropertyXXX).
The code generated should be as given in the code snippet below.
Line1. NamespaceX.NamespaceY.ObjectXXX x = new NamespaceX.NamespaceY.ObjectXXX(NamespaceX.NamespaceY.ObjectYYY.PropertyXXX);
Line2. myControl.MyControlProperty = x;
I succeeded in generating the aforementioned code at Design-Time by writing a custom CodeDomSerializer FOR C# Language.
But, if i use MyControl for developing an application in C++ Language, the DOT operator is serialized for both ScopeResolution and Pointer-To-Member operator.
What I am doing for code in Line1 is,
string fullyQualifiedName = "NamespaceX.NamespaceY.ObjectYYY.PropertyXXX"; // HERE VARIABLE NAME IS HARDCODED WITH TWO TYPES OF OPERATORS
CodeExpression[] parameters = new CodeExpression[] {new CodeVariableReferenceExpression(fullyQualifiedName);};
CodeStatement code = new CodeVariableDeclarationStatement(typeof(ObjectXXX), "objectXXX1", new CodeObjectCreateExpression(new CodeTypeReference(typeof(ObjectXXX)), parameters));
generatedCode.Add(code); //generatedCode has the final code
For Line2,
CodeExpression codeLhs = new CodeVariableReferenceExpression(myControlVariable + "." + "MyControlProperty"); // HERE Pointer-To-Member IS HARDCODED AS DOT
CodeExpression codeRhs = new CodeVariableReferenceExpression("objectXXX1");
CodeAssignStatement codeAssignStmt = new CodeAssignStatement(codeLhs, codeRhs);
generatedCode.Add(codeAssignStmt); //generatedCode has the final code
Obviously the C++ Designer generated code should have '::' operator(and not DOT) for the ScopeResolution and '->' for the Pointer-To-Member resolution. I was not able to figure out how to make the code serialization for any CLR supported language.
How to solve this problem?
-Thanks a bunch
Dattebayo
Thanks for the quick reply.
I found the solution.
What i need was generating code containing property access and generating code for of .NET types.
To generate code that accesses a property, one should use CodePropertyReferenceExpression. This solves my problem with Line2.
To generate code that contains a Type, one should use Code CodeTypeReferenceExpression.
This combined with CodePropertyReferenceExpression solved problem with Line1.
Now, I am able to generate code properly w.r.t. the Language in use.
//For C# The code would be
NamespaceX.NamespaceY.ObjectXXX x = new NamespaceX.NamespaceY.ObjectXXX(NamespaceX.NamespaceY.ObjectYYY.PropertyXXX);
this.myControl.MyControlProperty = x;
//For C++ The code would be
NamespaceX::NamespaceY::ObjectXXX x = new NamespaceX::NamespaceY::ObjectXXX(NamespaceX::NamespaceY::ObjectYYY::PropertyXXX);
this->myControl->MyControlProperty = x;
Not sure if this will help, but have you looked at MyGeneration its a multi language code generator written in .Net. It doesn't use CodeDomSerializer, but it does generate good code - maybe it'll solve your underlying problem without having to re-invent the wheel?

Categories

Resources