I am attempting to launch AutoCAD 2013 from an existing C# desktop application, use database values to draw a diagram, and then save the drawing to the database as a variable bit array (varbyte). I've been having good success with all except getting coordinates into an existing AutoCAD editor window.
I've tried NETLOAD but I can't put a list of 55+ AutoCAD Database Circle objects through the command line. Attempting to pass objects through COM Interop has been met with failure and confusion over the 32-bit versus 64-bit status of AutoCAD and my application. While this site has been helpful, http://through-the-interface.typepad.com/ , I haven't been able to get what I want done.
Any suggestions would be greatly appreciated. Thanks.
EDIT:
I took Locke's advice and I added functions to the .NET DLL to draw single items based on simple arguments (like X, Y coordinates, radius, label, etc.). Here is what the method signature looks like:
[CommandMethod("DrawSmallCircle", CommandFlags.Session)]
public static void DrawSmallCircle(double x, double y, double aRadius, string aGuage, string aLabel, string aTitle)
After netloading the .dll which hosts the above method (and it's containing class), I use SendCommand on the instantiated interop AcadApplication like so:
acApp.ActiveDocument.SendCommand("DrawSmallCircle " +
circ.circle.Center.X.ToString() + ", " +
circ.circle.Center.Y.ToString() + ", " +
circ.circle.Radius.ToString() + ", " +
circ.guage + ", " +
circ.label + ", " +
circ.title + " "
);
Unfortunately the error I get in response is simply "Common Language Runtime detected an invalid program."
I'm not sure if registering a command in my .dll will do any good because, allegedly, "Command methods can't take arguments, but lispFunctions can."
http://forums.autodesk.com/t5/NET/CommandMethod-with-custom-params/td-p/2572973
Okay, so you've run into the Interop/In-Process automation issue. Traditionally, AutoCAD doesn't want to let out of process modules talk to in-process modules, at least in the context of sending parameters back and forth. The formal approach which involves registering the in-process COM interface is challenging to get behaving correctly, especially in the context of x64 bit. I've still yet to see it behave consistently across multiple computers, therefore I tend to default to the following approach.
You are correct in that methods flagged with the [CommandMethod] flag cannot take arguments, so they'll obviously need to be voids. The trick to sending it parameter context at runtime is to include parameter prompts in the defined method itself. Think of it as you're developing the command to be invoked by the user inside AutoCAD, and they are prompted for each piece of data before the command can proceed. Much like other native AutoCAD commands, the parameter data can be sent alongside the call to the command in a single string.
Example:
(Command "._CIRCLE" "0,0" "5") <-- Draws a circle at 0,0 with a radius of 5.
So your command call could end up looking something like this:
(Command "DRAWDBCIRCLE" "2.3,56.12", "7" "Gauge" "Label" "Title")
In-Process Code
[CommandMethod("DRAWDBCIRCLE")]
public void DrawDbCircle()
{
var acDb = HostApplicationServices.WorkingDatabase;
var acEd = Application.DocumentManager.MdiActiveDocument.Editor;
using (var acTrans = acDb.TransactionManager.StartOpenCloseTransaction())
{
var bt = (BlockTable)acTrans.GetObject(acDb.BlockTableId, OpenMode.ForWrite);
var btr = (BlockTableRecord)acTrans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
Point3d centerPoint;
double radius;
string gauge, label, title;
// Prompt for the centerpoint
var pointResult = acEd.GetPoint("ENTER CIRCLE ORIGIN: \n");
centerPoint = pointResult.Value;
// Prompt for the radius
var doubleResult = acEd.GetDouble("ENTER CIRCLE RADIUS: \n");
radius = doubleResult.Value;
// Prompt for the strings
var stringResult = acEd.GetString("ENTER CIRCLE GAUGE: \n");
gauge = stringResult.StringResult;
stringResult = acEd.GetString("ENTER CIRCLE LABEL: \n");
label = stringResult.StringResult;
stringResult = acEd.GetString("ENTER CIRCLE TITLE: \n");
title = stringResult.StringResult;
// Create the circle
var circ = new Circle(centerPoint, Vector3d.ZAxis, radius);
// <-- Add code for dealing with strings -->
btr.AppendEntity(circ);
acTrans.AddNewlyCreatedDBObject(circ, true);
acTrans.Commit();
}
}
Interop Code
private AcadApplication acApp;
private AcadDocument acDoc;
private void btnRun_Click(object sender, EventArgs e)
{
if (acApp == null) return;
acDoc = acApp.ActiveDocument;
foreach (DataRow row in circleTable.Rows)
DrawDatabaseCircle(row);
}
private void DrawDatabaseCircle(DataRow circRow)
{
var cmdFormat = string.Format("\"{0},{1}\" \"{2}\" \"{3}\" \"{4}\" \"{5}\"", circRow.ItemArray);
acDoc.SendCommand(string.Format("(Command \"DRAWDBCIRCLE\" {0})\n", cmdFormat));
}
Obviously this is more or less pseudo-code. I assume a number of things here, like the AcadApplication and AcadDocument fields being set correctly, that the dll containing the defined command method has been properly netloaded, and that the database circles are coming out of a DataTable. Error-handling would be needed in the commandmethod for checking the parameters, and it would make sense to enclose the SendCommand method in a try/catch.
This technique really only works when you have data types that can be represented by strings, so it won't cover every situation. It's definitely worth trying to get the COM registered interface working long-term for more robust communication between in/out processes.
Related
i have opened solidworks assembly (swDocumentTypes_e.swDocASSEMBLY) using C# and i have iterated through all the features in order to get all the Sketchs called 'ISO/XXX' under each part of the assembly, here is the code
public void openFile(string skeletonFilePath)
{
object[] Features = null;
int i = 0;
string FeatType = null;[1]
string FeatTypeName = null;
if (string.IsNullOrWhiteSpace(skeletonFilePath)) { return; }
ModelDoc2 model = _sldWorks.OpenDoc("C:PATH/fileName.SLDASM", (int)swDocumentTypes_e.swDocASSEMBLY);
Feature swFeat = default(Feature);
SelectionMgr swSelMgr = default(SelectionMgr);
swSelMgr = (SelectionMgr)model.SelectionManager;
swFeat = (Feature)model.FirstFeature();
while ((swFeat != null))
{
FeatType = swFeat.Name;
FeatTypeName = swFeat.GetTypeName2();
if ((FeatTypeName == "Reference")
{
Debug.Print(" Name of feature: " + swFeat.Name);
Debug.Print(" Type of feature: " + swFeat.GetTypeName2());
}
swFeat = (Feature)swFeat.GetNextFeature();
}
}
the problem:
each time i try to extract the items under the feature (of one part) i got an exception, i have to tried these ways:
swFeat.GetDefinition() // i've got null exception
swFeat.GetSpecificFeature2() // i've got dynamic value which i don't know the class i need to cast with
var childs = (Object[])swFeatSupport.GetChildren(); // i've got only to constraints under the part
example of project
Your code is only iterating over top level features. You can use IFeature::GetFirstSubFeature() and IFeature::GetNextSubFeature to get sub feature. Make this function recursive so it will iterate over all features, regardless of how many levels deep they are. Another layer you need to consider is the components - you need to iterate over components in an assembly first if you need feature data in the context of individual parts.
Here's an example from the Solidworks API documentation. Its poorly written (IMO) but it will guide you in the right direction.
I want to show ellipse in c#. My codes is fine when it running in R but i get message from c# like this :
"Object is static; operation not allowed (Exception from HRESULT: 0x8004000B (OLE_E_STATIC))"
here this my codes :
df.rconn.Evaluate("library(cluster)")
df.rconn.Evaluate("library(rrcov)")
public void setScatter(int xAxis, int yAxis, int zAxis, List<string> variable)
{
// plot from R
//to show outlier with method : classic & robust Mve
this.comboBoxXAxis.SelectedIndex = xAxis;
this.comboBoxYAxis.SelectedIndex = yAxis;
dataform.rconn.EvaluateNoReturn("x<-X[," + xAxis + "] ");
dataform.rconn.EvaluateNoReturn("y<-X[," + yAxis + "] ");
dataform.rconn.EvaluateNoReturn("shape <- cov(X)");
dataform.rconn.EvaluateNoReturn("center<- colMeans(X)");
dataform.rconn.EvaluateNoReturn("d2.95 <- qchisq(0.95, df = 2)");
//dataform.rconn.EvaluateNoReturn("gr<- grid(lty=3,col='lightgray', equilogs = 'TRUE')");
//dataform.rconn.Evaluate("mtext('with classical (red) and robust (blue)')");
dataform.rconn.EvaluateNoReturn("plot(x,y, main='Draw Ellipse ', pch=19,col='black', type='p')");
dataform.rconn.EvaluateNoReturn("elp<- unname(ellipsoidPoints(shape, d2.95,center))");
dataform.rconn.Evaluate(" lines(elp, col='red' , lty=7 , lwd=2)");
//dataform.rconn.EvaluateNoReturn("lines(e)");
//dataform.rconn.EvaluateNoReturn("lines(ellipsoidPoints(mve#cov, d2 = d2.95, loc=mve#center), col='blue', lty='7' , lwd='2') ");
axGraphicsDevice2.RemoveFromConnector();
}
in any code that I comment always got the same error. I don't know why this problem happen. Any idea how to show that ellipse ? Thank you very much because you have helped me in completing my thesis.
Some missing contextual information, but going by what you provide a guess is that your "rconn" (statconnector?) is not happy when operations refresh a graphic device (lines, mtext, etc.).
As I needed to test some R graphic stuff from c# (with R.NET) for my own purposes, I used your code as a basis and propose it as a workaround for you to try. You'll find a sample console application that works, using R.NET, on GitHub under:
https://github.com/jmp75/rdotnet-support/tree/master/samples/DrawEllipse
compiled/run from VS2013, Windows7 64 bits, .NET framework 4.5.
I noticed I needed optionally to use dev.hold and dev.flush to get the intuitive refreshes.
e.Evaluate("dev.hold()");
e.Evaluate("mtext('with classical (red) and robust (blue)')");
e.Evaluate("dev.flush()");
Hope this helps.
I'm trying to use .NET localization in a portable class library for a Xamarin iOS/Android project. I've followed the steps at:
How to use localization in C#
And have code which looks as follows:
string sText = strings.enter_movie_name;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr");
sText = strings.enter_movie_name;
lblEnterMovieName.Text = sText;
I've also tried:
ResourceManager resman = new ResourceManager(typeof(MyApplication.strings));
string sText = resman.GetString("enter_movie_name", new CultureInfo("fr"));
I've created strings.resx with enter_movie_name equal to "Enter movie name:" and strings.fr.resx equal to "Entre la movie name:"
However, sText always ends up as "Enter movie name:". I can't seem to get the "Entre la movie name:" version out.
I also saw the post at Cross-platform Localization but couldn't work it out. I also don't want to use the iOS specific version at Localization on Xamarin.iOS as I'd like to be able to get at my strings from a platform independent library.
Can anyone point out where I'm going wrong?
I've created a bit of an ugly solution, but it works. What I've done is made a folder called 'strings' in my Portable Class Library (PCL) and inside that created files called:
strings.resx
strings_fr.resx
strings_ja_JP.resx
I've set all of these as Embedded Resource with custom tool as ResXFileCodeGenerator. This means I have a separate resource DLL for each language.
In iOS I can then get the locale by calling:
string sLocale = NSLocale.CurrentLocale.LocaleIdentifier;
I would guess there's an Android equivalent using Locale but I don't know what it is.
This gives me a string like "ja_JP" or "fr_FR" or "en_GB" (note they're underscores, not dashes). I then use this with the following static class I created to retrieve an appropriate resource manager and get the string from it.
If given a locale aa_BB it first looks for strings_aa_BB, then for strings_aa, then for strings.
public static class Localise
{
private const string STRINGS_ROOT = "MyPCL.strings.strings";
public static string GetString(string sID, string sLocale)
{
string sResource = STRINGS_ROOT + "_" + sLocale;
Type type = Type.GetType(sResource);
if (type == null)
{
if (sLocale.Length > 2) {
sResource = STRINGS_ROOT + "_" + sLocale.Substring(0, 2); // Use first two letters of region code
type = Type.GetType(sResource);
}
}
if (type == null) {
sResource = STRINGS_ROOT;
type = Type.GetType(sResource);
if (type == null)
{
System.Diagnostics.Debug.WriteLine("No strings resource file when looking for " + sID + " in " + sLocale);
return null; // This shouldn't ever happen in theory
}
}
ResourceManager resman = new ResourceManager(type);
return resman.GetString(sID);
}
}
An example of how this would be used (referring to the above code) would be:
string sText = Localise.GetString("enter_movie_name", sLocale);
lblEnterMovieName.Text = sText;
A significant downside of this is that all views will need to have their text set programatically, but does have the upside that the translations can be done once and then reused on many platforms. They also remain separate from the main code in their own DLLs and therefore can be recompiled individually if necessary.
I created a similar solution to the accepted answer but using txt files instead of Resx and nuget ready to go: https://github.com/xleon/I18N-Portable. Blog post here.
Other improvements are:
"anyKey".Translate(); // from anywhere
Automatic detection of the current culture
Get a list of supported translations: List<PortableLanguage> languages = I18N.Current.Languages;
Support for Data Binding in Mvvm frameworks / Xamarin.Forms
etc
My goal is to make a open source YouTube player that can be controlled via global media keys.
The global key issue I got it covered but the communication between the YouTube player and my Windows Forms application just doesn't work for some reason.
So far this is what I have:
private AxShockwaveFlashObjects.AxShockwaveFlash player;
player.movie = "http://youtube.googleapis.com/v/9bZkp7q19f0"
...
private void playBtn_Click(object sender, EventArgs e)
{
player.CallFunction("<invoke name=\"playVideo\" returntype=\"xml\"></invoke>");
}
Unfortunately this returns:
"Error HRESULT E_FAIL has been returned from a call to a COM component."
What am I missing? Should I load a different URL?
The documentation states that YouTube player uses ExternalInterface class to control it from JavaScript or AS3 so it should work with c#.
UPDATED:
Method used to embed the player: http://www.youtube.com/watch?v=kg-z8JfOIKw
Also tried to use the JavaScript-API in the WebBrowser control but no luck (player just didn't respond to JavaScript commands, tried even to set WebBrowser.url to a working demo, all that I succeeded is to get the onYouTubePlayerReady() to fire using the simple embedded object version )
I think there might be some security issues that I'm overseeing, don't know.
UPDATE 2:
fond solution, see my answer below.
It sounds like your trying to use Adobe Flash as your interface; then pass certain variables back into C#.
An example would be this:
In Flash; create a button... Actionscript:
on (press) {
fscommand("Yo","dude");
}
Then Visual Studio you just need to add the COM object reference: Shockwave Flash Object
Then set the embed to true;
Then inside Visual Studio you should be able to go to Properties; find fscommand. The fscommand will allow you to physically connect the value from the Flash movie.
AxShockwaveFlashObjects._IShockwaveFlashEvents_FSCommandEvent
That collects; then just use e.command and e.arg for example to have the collected item do something.
Then add this to the EventHandler;
lbl_Result.Text="The "+e.args.ToString()+" "+e.command.ToString()+" was clicked";
And boom it's transmitting it's data from Flash into Visual Studio. No need for any crazy difficult sockets.
On a side note; if you have Flash inside Visual Studio the key is to ensure it's "embed is set to true." That will hold all the path references within the Flash Object; to avoid any miscalling to incorrect paths.
I'm not sure if that is the answer your seeking; or answers your question. But without more details on your goal / error. I can't assist you.
Hope this helps. The first portion should actually show you the best way to embed your Shockwave into Visual Studio.
Make sure you add the correct reference:
Inside your project open 'Solution Explorer'
Right-Click to 'Add Reference'
Go to 'COM Object'
Find Proper object;
COM Objects:
Shockwave ActiveX
Flash Accessibility
Flash Broker
Shockwave Flash
Hope that helps.
It sounds like you aren't embedding it correctly; so you can make the call to it. If I'm slightly mistaken; or is this what you meant:
If your having difficulty Ryk had a post awhile back; with a method to embed YouTube videos:
<% MyYoutubeUtils.ShowEmebddedVideo("<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/gtNlQodFMi8&hl=en&fs=1"></param><param name="allowFullScreen" value="true"></param><embed src="http://www.youtube.com/v/gtNlQodFMi8&hl=en&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" width="425" height="344"></embed></object>") %>
Or...
public static string ShowEmbeddedVideo(string youtubeObject)
{
var xdoc = XDocument.Parse(youtubeObject);
var returnObject = string.Format("<object type=\"{0}\" data=\{1}\"><param name=\"movie\" value=\"{1}\" />",
xdoc.Root.Element("embed").Attribute("type").Value,
xdoc.Root.Element("embed").Attribute("src").Value);
return returnObject;
}
Which you can find the thread here: https://stackoverflow.com/questions/2547101/purify-embedding-youtube-videos-method-in-c-sharp
I do apologize if my post appears fragmented; but I couldn't tell if it was the reference, the variable, the method, or embed that was causing you difficulties. Truly hope this helps; or give me more details and I'll tweak my response accordingly.
C# to ActionScript Communication:
import flash.external.ExternalInterface;
ExternalInterface.addCallback("loadAndPlayVideo", null, loadAndPlayVideo);
function loadAndPlayVideo(uri:String):void
{
videoPlayer.contentPath = uri;
}
Then in C#; add an instance of the ActiveX control and add the content into a Constructor.
private AxShockwaveFlash flashPlayer;
public FLVPlayer ()
{
// Add Error Handling; to condense I left out.
flashPlayer.LoadMovie(0, Application.StartupPath + "\\player.swf");
}
fileDialog = new OpenFileDialog();
fileDialog.Filter = "*.flv|*.flv";
fileDialog.Title = "Select a Flash Video File...";
fileDialog.Multiselect = false;
fileDialog.RestoreDirectory = true;
if (fileDialog.ShowDialog() == DialogResult.OK)
{
flashPlayer.CallFunction("<invoke" + " name=\"loadAndPlayVideo\" returntype=\"xml"> <arguements><string>" + fileDialog.FileName + "</string></arguements></invoke>");
}
ActionScript Communication to C#:
import flash.external.ExternalInterface;
ExternalInterface.call("ResizePlayer", videoPlayer.metadata.width, videoPlayer.metadata.height);
flashPlayer.FlashCall += new _IShockwaveFlashEvents_FlashCallEventHandler(flashPlayer_FlashCall);
Then the XML should appear:
<invoke name="ResizePlayer" returntype="xml">
<arguements>
<number> 320 </number>
<number> 240 </number>
</arguments>
</invoke>
Then parse the XML in the event handler and invoke the C# function locally.
XmlDocument document = new XmlDocument();
document.LoadXML(e.request);
XmlNodeList list = document.GetElementsByTagName("arguements");
ResizePlayer(Convert.ToInt32(list[0].FirstChild.InnerText), Convert.ToInt32(list[0].ChildNodes[1].InnerText));
Now they are both passing data back and forth. That is a basic example; but by utilizing the ActionScript Communication you shouldn't have any issues utilizing the native API.
Hope that is more helpful. You can expand on that idea by a utility class for reuse. Obviously the above code has some limitations; but hopefully it points you in the right direction. Was that direction you were attempting to go? Or did I still miss the point?
Create a new Flash Movie; in ActionScript 3. Then on the initial first frame; apply the below:
Security.allowDomain("www.youtube.com");
var my_player:Object;
var my_loader:Loader = new Loader();
my_loader.load(new URLRequest("http://www.youtube.com/apiplayer?version=3"))
my_loader.contentLoaderInfo.addEventListener(Event.INIT, onLoaderInit);
function onLoaderInit(e:Event):void{
addChild(my_loader);
my_player = my_loader.content;
my_player.addEventListener("onReady", onPlayerReady);
}
function onPlayerReady(e:Event):void{
my_player.setSize(640,360);
my_player.loadVideoById("_OBlgSz8sSM",0);
}
So what exactly is that script doing? It is utilizing the native API and using ActionScript Communication. So below I'll break down each line.
Security.allowDomain("www.youtube.com");
Without that line YouTube won't interact with the object.
var my_player:Object;
You can't just load a movie into the movie; so we will create a variable Object. You have to load a special .swf that will contain access to those codes. The below; does just that. So you can access the API.
var my_loader:Loader = new Loader();
my_loader.load(new URLRequest("http://www.youtube.com/apiplayer?version=3"));
We now reference the Google API per their documentation.
my_loader.contentLoaderInfo.addEventListener(Event.INIT, onLoaderInit);
But in order to actually work with our object; we need to wait for it to be fully initialized. So the Event Listener will wait; so we know when we can pass commands to it.
The onLoaderInit function will be triggered upon initialization. Then it's first task will be my_loader to display the list so that the video appears.
The addChild(my_loader); is what will load one; the my_player = my_loader.content; will store a reference for easy access to the object.
Though it has been initialized; you have to wait even further... You use my_player.addEventListener("onReady", onPlayerReady); to wait and listen for those custom events. Which will allow a later function to handle.
Now the player is ready for basic configuration;
function onPlayerReady(e:Event):void{
my_player.setSize(640,360);
}
The above function starts very basic manipulation. Then the last line my_player.loadVideoById("_OBlgSz8sSM",0); is referencing the particular video.
Then on your stage; you could create two buttons and apply:
play_btn.addEventListener(MouseEvent.CLICK, playVid);
function playVid(e:MouseEvent):void {
my_player.playVideo();
}
pause_btn.addEventListener(MouseEvent.CLICK, pauseVid);
function pauseVid(e:MouseEvent):void {
my_player.pauseVideo();
}
Which would give you a play and pause functionality. Some additional items you could use our:
loadVideoById()
cueVideoById()
playVideo()
pauseVideo()
stopVideo()
mute()
unMute()
Keep in mind those can't be used or called until it has been fully initialized. But using that; with the earlier method should allow you to layout the goal and actually pass variables between the two for manipulation.
Hopefully that helps.
I'd start by making sure that javascript can talk to your flash app.
make sure you have: allowScriptAccess="sameDomain" set in the embed (from http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/external/ExternalInterface.html#includeExamplesSummary).
you should validate that html->flash works; then C->html; and gradually work up to C->you-tube-component. you have a lot of potential points of failure between C and the you-tube-component right now and it's hard to address all of them at the same time.
After a lot of tries and head-hammering, I've found a solution:
Seems that the Error HRESULT E_FAIL... happens when the flash dosen't understand the requested flash call. Also for the youtube external api to work, the js api needs to be enabled:
player.movie = "http://www.youtube.com/v/VIDEO_ID?version=3&enablejsapi=1"
As I said in the question the whole program is open source, so you will find the full code at bitbucket. Any advice, suggestions or collaborators are highly appreciated.
The complete solution:
Here is the complete guide for embedding and interacting with the YouTube player or any other flash object.
After following the video tutorial
, set the flash player's FlashCall event to the function that will handle the flash->c# interaction (in my example it's YTplayer_FlashCall )
the generated `InitializeComponent()` should be:
...
this.YTplayer = new AxShockwaveFlashObjects.AxShockwaveFlash();
this.YTplayer.Name = "YTplayer";
this.YTplayer.Enabled = true;
this.YTplayer.OcxState = ((System.Windows.Forms.AxHost.State)(resources.GetObject("YTplayer.OcxState")));
this.YTplayer.FlashCall += new AxShockwaveFlashObjects._IShockwaveFlashEvents_FlashCallEventHandler(this.YTplayer_FlashCall);
...
the FlashCall event handler
private void YTplayer_FlashCall(object sender, AxShockwaveFlashObjects._IShockwaveFlashEvents_FlashCallEvent e)
{
Console.Write("YTplayer_FlashCall: raw: "+e.request.ToString()+"\r\n");
// message is in xml format so we need to parse it
XmlDocument document = new XmlDocument();
document.LoadXml(e.request);
// get attributes to see which command flash is trying to call
XmlAttributeCollection attributes = document.FirstChild.Attributes;
String command = attributes.Item(0).InnerText;
// get parameters
XmlNodeList list = document.GetElementsByTagName("arguments");
List<string> listS = new List<string>();
foreach (XmlNode l in list){
listS.Add(l.InnerText);
}
Console.Write("YTplayer_FlashCall: \"" + command.ToString() + "(" + string.Join(",", listS) + ")\r\n");
// Interpret command
switch (command)
{
case "onYouTubePlayerReady": YTready(listS[0]); break;
case "YTStateChange": YTStateChange(listS[0]); break;
case "YTError": YTStateError(listS[0]); break;
default: Console.Write("YTplayer_FlashCall: (unknownCommand)\r\n"); break;
}
}
this will resolve the flash->c# communication
calling the flash external functions (c#->flash):
private string YTplayer_CallFlash(string ytFunction){
string flashXMLrequest = "";
string response="";
string flashFunction="";
List<string> flashFunctionArgs = new List<string>();
Regex func2xml = new Regex(#"([a-z][a-z0-9]*)(\(([^)]*)\))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
Match fmatch = func2xml.Match(ytFunction);
if(fmatch.Captures.Count != 1){
Console.Write("bad function request string");
return "";
}
flashFunction=fmatch.Groups[1].Value.ToString();
flashXMLrequest = "<invoke name=\"" + flashFunction + "\" returntype=\"xml\">";
if (fmatch.Groups[3].Value.Length > 0)
{
flashFunctionArgs = pars*emphasized text*eDelimitedString(fmatch.Groups[3].Value);
if (flashFunctionArgs.Count > 0)
{
flashXMLrequest += "<arguments><string>";
flashXMLrequest += string.Join("</string><string>", flashFunctionArgs);
flashXMLrequest += "</string></arguments>";
}
}
flashXMLrequest += "</invoke>";
try
{
Console.Write("YTplayer_CallFlash: \"" + flashXMLrequest + "\"\r\n");
response = YTplayer.CallFunction(flashXMLrequest);
Console.Write("YTplayer_CallFlash_response: \"" + response + "\"\r\n");
}
catch
{
Console.Write("YTplayer_CallFlash: error \"" + flashXMLrequest + "\"\r\n");
}
return response;
}
private static List<string> parseDelimitedString (string arguments, char delim = ',')
{
bool inQuotes = false;
bool inNonQuotes = false;
int whiteSpaceCount = 0;
List<string> strings = new List<string>();
StringBuilder sb = new StringBuilder();
foreach (char c in arguments)
{
if (c == '\'' || c == '"')
{
if (!inQuotes)
inQuotes = true;
else
inQuotes = false;
whiteSpaceCount = 0;
}else if (c == delim)
{
if (!inQuotes)
{
if (whiteSpaceCount > 0 && inQuotes)
{
sb.Remove(sb.Length - whiteSpaceCount, whiteSpaceCount);
inNonQuotes = false;
}
strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString());
sb.Remove(0, sb.Length);
}
else
{
sb.Append(c);
}
whiteSpaceCount = 0;
}
else if (char.IsWhiteSpace(c))
{
if (inNonQuotes || inQuotes)
{
sb.Append(c);
whiteSpaceCount++;
}
}
else
{
if (!inQuotes) inNonQuotes = true;
sb.Append(c);
whiteSpaceCount = 0;
}
}
strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString());
return strings;
}
adding Youtube event handlers:
private void YTready(string playerID)
{
YTState = true;
//start eventHandlers
YTplayer_CallFlash("addEventListener(\"onStateChange\",\"YTStateChange\")");
YTplayer_CallFlash("addEventListener(\"onError\",\"YTError\")");
}
private void YTStateChange(string YTplayState)
{
switch (int.Parse(YTplayState))
{
case -1: playState = false; break; //not started yet
case 1: playState = true; break; //playing
case 2: playState = false; break; //paused
//case 3: ; break; //buffering
case 0: playState = false; if (!loopFile) mediaNext(); else YTplayer_CallFlash("seekTo(0)"); break; //ended
}
}
private void YTStateError(string error)
{
Console.Write("YTplayer_error: "+error+"\r\n");
}
usage ex:
YTplayer_CallFlash("playVideo()");
YTplayer_CallFlash("pauseVideo()");
YTplayer_CallFlash("loadVideoById(KuNQgln6TL0)");
string currentVideoId = YTplayer_CallFlash("getPlaylist()");
string currentDuration = YTplayer_CallFlash("getDuration()");
The functions YTplayer_CallFlash, YTplayer_FlashCall should work for any flash-C# communication with minor adjustments like the YTplayer_CallFlash's switch (command).
This stumped me for a number of hours.
Just add enable JS to your URL:
http://www.youtube.com/v/9bZkp7q19f0?version=3&enablejsapi=1
CallFunction works fine for me now! Also remove unrequired space in the call.
I have a sample program from Aforge library. It uses a kind of logging system (I assume it is like a StringBuilder or something...).
In the samples, here and there I see something like:
IImageProcessingLog _log = new ImageProcessingLog();
//some code
_log.AddMessage("Image size: " + _bitmap.Width + " x " + _bitmap.Height);
//more codes and usage of `_log`
Clearly this is some sort of string. Later I want to dump all this data into a TextBox. I tried to do _log.ToString() but it just returns the object name.
Any idea how can I use this log feature?
Thanks
The ImageProcessingLog class has a property called Messages.
Messages is of type List<string>. So, to get all logged messages
simply iterate the elements of the messages list.
TextBox tbMessages = ...;
ImageProcessingLog log = new ImageProcessingLog();
log.AddMessage(...);
foreach(string msg in log.Messages)
{
tbMessages.Text += msg;
}
Unfortunately the IImageProcessingLog interface does not have such a property.
A possible workaround would be to create an adapter class/interface which wraps the
ImageProcessingLog class.