Resources in net standard library project? - xamarin

I am intending on using a javascript sensor fusion library in a Xamarin app - and potentially also in watch projects. In my net standard library I have:
The Ahrs file contains:
public class Ahrs
{
static ScriptEngine engine = new ScriptEngine();
static FileScriptSource fileScript = new FileScriptSource("./index.js"); // <-- invalid file path
public static double Run()
{ // file notfound
engine.Evaluate(fileScript);
return engine.CallGlobalFunction<double>("update", 0, 0, 0, 1, 1, 1);
}
}
... where I attempt to call a method update inside my js file with Jurassic
Is there some way I can add the index.js file to my net standard library and run it from the Ahrs file all inside net standard library?

Seems to work for me if I make the path "index.js" rather than "./index.js"
public class Ahrs
{
static ScriptEngine engine = new ScriptEngine();
static FileScriptSource fileScript = new FileScriptSource("index.js"); // <-- valid file path
public static double Run()
{ // file found
engine.Evaluate(fileScript);
return engine.CallGlobalFunction<double>("update", 0, 0, 0, 1, 1, 1);
}
}
I also made the javascript file's properties:
Build Action : Embedded resource
Copy to Output Directory : Copy always

Related

Localization with SharedResources not working in .NET Core 2.1

I've been fighting with this problem for hours... and I can't find what it is...
I'm just trying to localize the _Layout.cshtml file. Both the IStringLocalizer and the IHtmlLocalizer do not seem to find the Resource files.
I've followed and searched for:
https://github.com/MormonJesus69420/SharedResourcesExample
.Net Core Data Annotations - localization with shared resources
https://stackoverflow.com/search?q=shared+resources+.net+core
https://andrewlock.net/adding-localisation-to-an-asp-net-core-application/
There's something silly that I may be overlooking.
Here's my startup.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using EduPlaTools.Data;
using EduPlaTools.Models;
using EduPlaTools.Services;
using System.Globalization;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Pomelo.EntityFrameworkCore.MySql;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Microsoft.AspNetCore.HttpOverrides;
namespace EduPlaTools
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// This is for string translation!
// Adds Localization Services (StringLocalizer, HtmlLocalizer, etc.)
// the opts.ResourcesPath = is the path in which the resources are found.
// In our case the folder is named Resources!
// There's specific and neutral resources. (Specific en-US). (Neutral: es)
/**
* If no ResourcesPath is specified, the view's resources will be expected to be next to the views.
* If ResourcesPath were set to "resources", then view resources would be expected to be ina Resource directory,
* in a path speicifc to their veiw (Resources/Views/Home/About.en.resx, for example).
*
* */
services.AddLocalization(opts => opts.ResourcesPath = "Resources");
// services.AddBContext
// There are subtle differences between the original and the modified version.
services.AddDbContextPool<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("MySQLConnection"),
mysqlOptions =>
{
mysqlOptions.ServerVersion(new Version(8, 0, 12), ServerType.MySql); // replace with your Server Version and Type
}
));
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, options => options.ResourcesPath = "Resources")
.AddDataAnnotationsLocalization();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This may be dangerous and is not recommended
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
.Database.Migrate();
}
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// These must line up with the ending of the .resx files.
// Example: SharedResources.en.resx, SharedResources.es.rex
// If you want to add specific, then do it like:
// new CultureInfo("en-US")
List<CultureInfo> supportedCultures = new List<CultureInfo>
{
new CultureInfo("es"),
new CultureInfo("en"),
new CultureInfo("es-ES"),
new CultureInfo("en-US")
};
// Registers the localization, and changes the localization per request.
app.UseRequestLocalization(new RequestLocalizationOptions
{
// We give the default support of Spanish.
DefaultRequestCulture = new RequestCulture("es"),
// Format numbers, dates, etc.
SupportedCultures = supportedCultures,
// The strings that we have localized
SupportedUICultures = supportedCultures
});
// This will seed the databse:
SeedDatabase.Initialize(app.ApplicationServices);
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Here's how I'm trying to call it inside the _Layout.cshtml:
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
#inject IStringLocalizer<SharedResources> SharedLocalizer
#inject IHtmlLocalizer<SharedResources> _localizer;
#SharedLocalizer["Menu_Home"]
Here's the directory structure:
Here are the contents of SharedResources.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EduPlaTools
{
/**
* This is a dummy class that is needed so Localization works.
* Now in .NET Core Localization works as a service, and implementsw
* naming conventions (AT the file level). Therefore, if the files do not
* implement the correct name, there's going to be problems.
*
* See an example, here:
* https://github.com/SteinTheRuler/ASP.NET-Core-Localization/blob/master/Resources/SharedResources.cs
*
* This is a workaround to create a Resource File that can be read by the entire
* application. It's left in blank so the convention over configuration
* picks it up.
*
* */
public class SharedResources
{
}
}
Here are the contents of the resx files:
I've also tried renaming them to no avail.. (Tried Resources.es.rex, Resources.rex)
I tried setting breakpoints to see how it behaved. It of course, didn't find the Resource files. I then compared it with Mormon's repo by recalling an inexistent key. I compared it with my output, but Mormon's repo doesn't display the "SearchedLocation" (Was it introduced in a later .NET Core version?)
Mormon's Repo:
My repo:
I know this may be something silly... But it's been close to 4 hours, and I can't stop since I have a LOT to do!!
Any ideas?
if you want to implement localization with shared resource, you have to create your own culture localizer class:
public class CultureLocalizer
{
private readonly IStringLocalizer _localizer;
public CultureLocalizer(IStringLocalizerFactory factory)
{
var type = typeof(ViewResource);
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
_localizer = factory.Create("ViewResource", assemblyName.Name);
}
// if we have formatted string we can provide arguments
// e.g.: #Localizer.Text("Hello {0}", User.Name)
public LocalizedString Text(string key, params string[] arguments)
{
return arguments == null
? _localizer[key]
: _localizer[key, arguments];
}
}
then register it is startup:
services.AddSingleton<CultureLocalizer>();
and modify view locaization settings :
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization(o=>o.ResourcesPath = "Resources")
in your views you have to inject the culture localizer class before using it.
those are initial settings for view localization with shared resource, you need to configure localization settings for DataAnnotation, ModelBinding and Identity error messages as well.
these articles could help for starting:
Developing multicultural web application with ASP.NET Core 2.1 Razor Pages:
http://www.ziyad.info/en/articles/10-Developing_Multicultural_Web_Application
it includes step by step tutorial for localizing using shared resources, additionally, this article is about localizing Identity error messages :
http://ziyad.info/en/articles/20-Localizing_Identity_Error_Messages
I wanted to add an answer which further develops Laz's solution. Just in case someone wants to have individual localized views.
Back in Startup.cs, you have:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization(o=>o.ResourcesPath = "Resources")
Technically, you are indicating MVC to look in the "Resources" folder as the main path, and then follow the convention to look for localized resource files.
Therefore
In case you want to localize the Login.cshtml view found in Views/Account/Login.chsmtl, you have to create the resource file in: Resources/Views/Account/Login.en.resx
You would then need to add the following either in the view directly Login.cshtml or in the _ViewImports.cshtml to reference it to all the views:
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
After that, in your code you can do:
Localizer["My_Resource_file_key"]
And you'll have it translated.
Here are some illustrations:
An update to the previous answers. Due to the recent breaking change in .NET Core 3 (https://github.com/dotnet/docs/issues/16964), the accepted answer will only work if the resource lives directly in the resource folder.
I have created a workaround to use shared resources in views (same applies to controllers, data annotations, services, whatever you need...).
First you need to create an empty class for your resources. This one has to live under YourApp.Resources namespace. then create your resources named same as your class (in my example I have Views.cs in the namespace MyApp.Resources.Shared and Views.resx).
Then here is the helper class to load the shared resources:
public class SharedViewLocalizer
{
private readonly IStringLocalizer _localizer;
public SharedViewLocalizer(IStringLocalizerFactory factory)
{
var assemblyName = new AssemblyName(typeof(Resources.Shared.Views).GetTypeInfo().Assembly.FullName);
localizer = factory.Create("Shared.Views", assemblyName.Name);
}
public string this[string key] => _localizer[key];
public string this[string key, params object[] arguments] => _localizer[key, arguments];
}
You have to register is in the Startup.Configure:
services.AddSingleton<SharedViewLocalizer>();
I suppose you use
services.AddLocalization(options => options.ResourcesPath = "Resources");
to setup default resources location.
And then in your view you use it as follows:
#inject IViewLocalizer _localizer
#inject SharedViewLocalizer _sharedLocalizer
#_localizer["View spacific resource"] // Resource from Resources/Views/ControllerName/ViewName.resx
#_sharedLocalizer["Shared resource"] // Resource from Resources/Shared/Views.resx
#_sharedLocalizer["Also supports {0} number of arguments", "unlimited"]
Same principle can be applied to DataAnnotations where we can use the built-in method in Startup.Configure:
services.AddMvc()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
var assemblyName = new AssemblyName(typeof(DataAnnotations).GetTypeInfo().Assembly.FullName);
return factory.Create("Shared.DataAnnotations", assemblyName.Name
};
})
.AddViewLocalization();
Again, I'm expecting my resources to live in the namespace Resources.Shared and have an empty class called DataAnnotations created.
Hope this helps to overcome the current breaking change problems.

How to use/setup TexturePacker2 libgdx

I have a difficulty with texturepacker2 from libgdx. I was trying to create textureAtlas using texturepakcer2 so that I can create animated images. However I could not use
TexturePacker2.process(Input Directory Path", "Output Directory Path", "texture_file");
Because it could not recognize TexturePacker2.
Even thought I import gdx-tool.jar file inside libs and also added libraries through
Project -> Properties -> Java Build Path -> Libraries -> Add jars, it still cannot resolve nor recognize the gdx-tool.jar.
How can I create texture atlas using TexturePakcer2? I heard there is a way to create using nightly-build from libgdx, how can I do it? When I unzip latest nightly-build there were so many jar, but I could only run setup-ui.
There are several ways. I used to take the way of implementing it into my Desktop application. Whenever i start it, the Atlas is generated. (If i changed something in it).
public class Main
{
public static void main(String[] args)
{
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "MyApp";
cfg.useGL20 = true;
cfg.fullscreen = false;
// switch for fullscreen
if (cfg.fullscreen)
{
cfg.width = Toolkit.getDefaultToolkit().getScreenSize().width;
cfg.height = Toolkit.getDefaultToolkit().getScreenSize().height;
}
else
{
cfg.width = 1280;
cfg.height = 720;
}
cfg.addIcon("data/appiconWindows.png", FileType.Internal);
// automatic packing of the textures and images and so on
Settings settings = new Settings();
settings.maxWidth = 2048;
settings.maxHeight = 2048;
settings.paddingX = 0;
settings.paddingY = 0;
TexturePacker2.process(settings, "directory with the files",
"output dir", "name of Atlas"); //third is outputdir
new LwjglApplication(new MainClass(), cfg);
}
}
Dont forget to add the tools lib to the Desktop project. gdx-tools.jar From the nightly or the Stable.
Else you can call it with the console. Like this:
java -cp gdx.jar;extensions/gdx-tools/gdx-tools.jar com.badlogic.gdx.tools.texturepacker.TexturePacker inputDir [outputDir] [packFileName]
Use TexturePacker from com.badlogic.gdx.tools.imagepacker.TexturePacker then create a class as below:
public class TextureSetup {
public static void main(String[] args) {
//TexturePacker; using default settings
TexturePacker.Settings packSettings = new TexturePacker.Settings();
TexturePacker.process(packSettings, "input-folder", "output-folder", "textures.pack");
}
}

cross-AppDomain event issues

I use the following helper class with POS for .Net to get a reference to the hardware in a separate AppDomain (getting around some limitations of requiring <NetFx40_LegacySecurityPolicy enabled="true"/>
public static class PosHelper
{
private static AppDomain _posAppDomain { get; set; }
private static AppDomain PosAppDomain
{
get
{
if (_posAppDomain == null)
{
AppDomainSetup currentAppDomainSetup = AppDomain.CurrentDomain.SetupInformation;
AppDomainSetup newAppDomainSetup = new AppDomainSetup()
{
ApplicationBase = currentAppDomainSetup.ApplicationBase,
LoaderOptimization = currentAppDomainSetup.LoaderOptimization,
ConfigurationFile = currentAppDomainSetup.ConfigurationFile
};
newAppDomainSetup.SetCompatibilitySwitches(new[] { "NetFx40_LegacySecurityPolicy" });
_posAppDomain = AppDomain.CreateDomain("POS Hardware AppDomain", null, newAppDomainSetup);
}
return _posAppDomain;
}
}
public static T GetHardware<T>() where T : PosHardware, new()
{
T hardware = (T)PosAppDomain.CreateInstanceFromAndUnwrap(Assembly.GetAssembly(typeof(T)).Location, typeof(T).FullName);
hardware.FindAndOpenDevice();
return hardware;
}
}
I have a basic class to handle when a POS scanner scans data. In that class I have an event that I want to fire when data is scanned. Here's a snippet:
public class ScannerDevice : PosHardware
{
public event Action<string> DataScanned;
...
_scanner.DataEvent += new DataEventHandler(Scanner_DataEvent);
...
private void Scanner_DataEvent(object sender, DataEventArgs e)
{
ASCIIEncoding encoder = new ASCIIEncoding();
if (DataScanned != null)
DataScanned(encoder.GetString(_scanner.ScanDataLabel));
_scanner.DataEventEnabled = true; // enable for subsequent scans
}
Note that the PosHardware abstract class inherits MarshalByRefObject and is marked [Serializable]
In my main AppDomain I try to use the event like so:
Scanner = PosHelper.GetHardware<ScannerDevice>();
Scanner.DataScanned += m =>
{
Debug.WriteLine(m);
};
When it hits the line trying to add the lambda to the DataScanned event I get this error:
Could not load file or assembly 'MyAssemlyName, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null' or one of its dependencies. The
system cannot find the file specified.
This has to be related to trying to communicate between AppDomains. Not really sure what to do. Do I need to register "MyAssemblyName" in the separate AppDomain used for Pos for .Net?
I use prism, so some modules are loaded at runtime (in a subfolder in my output directory)... including the one in which I use the last code snippet above (Scanner = PosHelper.GetHardware....)
I believe I solved my problem. Since my prism modules are loaded at runtime within a subdirectory I needed to add this to the AppDomain so that the AppDomain could find the assemblies in the subdirectories folder.:
PrivateBinPath = #"Modules"
http://msdn.microsoft.com/en-us/library/system.appdomainsetup.privatebinpath.aspx
Edit
This only partially solved my problem. I also had to override InitializeLifetimeService() and return null so that my MarshalByRefObject's would not be disposed while the program is running (I believe the default timeout is 5 minutes).
Also , this now works:
Scanner.DataScanned += m =>
{
Debug.WriteLine(m);
}
but when I try something like this
Scanner.DataScanned += m =>
{
DoSomething(m);
}
Where DoSomething is not in a Serializable and MarshalByRefObject class, it craps out since all classes that are used in the communication between AppDomain's need to have those. So where I'm at now is looking at using WCF named pipes to pass data around... and other similar solutions.

Dynamics AX 2009: Batch Trouble with AsciiIO class

I have a custom class, we'll call it FileProcessUpload and it extends RunBaseBatch. It more or less creates a CSV file and then uploads it to an FTP server. When the class is run manually, everything works fine. However, when submitted as a Batch Job, there is an error in the infolog stating "AsciiIO object not initialized".
Probably the most important thing to note here is that this Batch Job is being delegated to a different AOS.
Here is a cropped down version of the offending code:
void CreateFiles()
{
#File
AsciiIO asciiio;
FileIOPermission permission;
ATable aTable;
str outputFile;
str directory;
;
directory = #'C:\Uploads';
ouptutFile = directory + #'\output.csv';
if (!WinAPI::folderExists(directory))
{
WinAPI::createDirectory(directory);
}
// Try to assert the appropriate file access mode
permission = new FileIOPermission(outputFile, #io_write);
permission.assert();
// Try to open the file for writing
asciiio = new AsciiIO(outputFile, #io_write);
if (asciiio != null)
{
while select aTable
{
// Write the necessary lines into the file
asciiio.write(aTable.field1 + ',' + aTable.field2);
}
}
else
{
error('Could not create file: ' + outputFile);
}
// Close file and release permission assertion
asciiio = null;
CodeAccessPermission::revertAssert();
}
Does the service user that Ax is running under have permissions to read/write the file?
You are using the WinAPI class, but should you be using WinAPIServer class instead? You may be executing on the server of course.
Do you need to add to your class the following public boolean runsImpersonated() { return false; } and run this class on a client?
Good luck
Edit: Executing your code via the server static void mainOnServer(Args args) method signature is commonly used (see PurchFormLetter class for it's usage) to make sure that you execute on the server. It is called from static void main(Args args)
Use file path and file name instead of str as directory and name
If runbasebatch then should put pack/uppack filePath and fileName and put it into currentVersion control at classdeclaration.
If you move/delete/encrytion/read file, using system.io.file /system.io.stream, or streamreader, or system.net.ftpwebrequest, and system.net.ftpwebresponse, remember to run on server static void method for this...
Any file format I have done, txt, .csv, .gpg, I move around file easily in/out ax to other server, no problem to just write a file inside of AX by fellowing the above rule..

How do I write to the Visual Studio Output Window in My Custom Tool?

I am writing a custom tool and I currently have it doing what I want as far as functionality. I would like to be able to write to Visual Studio if something goes wrong. (Incorrectly formatted code or whatever).
Are there any standards for this? Right now I basically can force the tool to fail and Visual Studio puts in a warning that it has done so. I'd like a category in the Output window with any resulting messages I want to send. I could also live with a more descriptive task/warning in the Error list window.
Output Window
To write to the "General" output window in Visual Studio, you need to do the following:
IVsOutputWindow outWindow = Package.GetGlobalService( typeof( SVsOutputWindow ) ) as IVsOutputWindow;
Guid generalPaneGuid = VSConstants.GUID_OutWindowGeneralPane; // P.S. There's also the GUID_OutWindowDebugPane available.
IVsOutputWindowPane generalPane;
outWindow.GetPane( ref generalPaneGuid , out generalPane );
generalPane.OutputString( "Hello World!" );
generalPane.Activate(); // Brings this pane into view
If, however, you want to write to a custom window, this is what you need to do:
IVsOutputWindow outWindow = Package.GetGlobalService( typeof( SVsOutputWindow ) ) as IVsOutputWindow;
// Use e.g. Tools -> Create GUID to make a stable, but unique GUID for your pane.
// Also, in a real project, this should probably be a static constant, and not a local variable
Guid customGuid = new Guid("0F44E2D1-F5FA-4d2d-AB30-22BE8ECD9789");
string customTitle = "Custom Window Title";
outWindow.CreatePane( ref customGuid, customTitle, 1, 1 );
IVsOutputWindowPane customPane;
outWindow.GetPane( ref customGuid, out customPane);
customPane.OutputString( "Hello, Custom World!" );
customPane.Activate(); // Brings this pane into view
Details on IVsOutputWindow and IVsOutputWindowPane can be found on MSDN.
Error List
For adding items to the error list, the IVsSingleFileGenerator has a method call void Generate(...) which has a parameter of the type IVsGeneratorProgress. This interface has a method void GeneratorError() which lets you report errors and warnings to the Visual Studio error list.
public class MyCodeGenerator : IVsSingleFileGenerator
{
...
public void Generate( string inputFilePath, string inputFileContents, string defaultNamespace, out IntPtr outputFileContents, out int output, IVsGeneratorProgress generateProgress )
{
...
generateProgress.GeneratorError( false, 0, "An error occured", 2, 4);
...
}
...
}
The details of GeneratorError() can be found on MSDN.
There is another way using Marshal.GetActiveObject to grab a running DTE2 instance.
First reference EnvDTE and envdte80. This currently works in VisualStudio 2012, I haven't tried the others yet.
using System;
using System.Runtime.InteropServices;
using EnvDTE;
using EnvDTE80;
internal class VsOutputLogger
{
private static Lazy<Action<string>> _Logger = new Lazy<Action<string>>( () => GetWindow().OutputString );
private static Action<string> Logger
{
get { return _Logger.Value; }
}
public static void SetLogger( Action<string> logger )
{
_Logger = new Lazy<Action<string>>( () => logger );
}
public static void Write( string format, params object[] args)
{
var message = string.Format( format, args );
Write( message );
}
public static void Write( string message )
{
Logger( message + Environment.NewLine );
}
private static OutputWindowPane GetWindow()
{
var dte = (DTE2) Marshal.GetActiveObject( "VisualStudio.DTE" );
return dte.ToolWindows.OutputWindow.ActivePane;
}
}
If you want anything to appear in the Output window, it has to come from stdout. To do this, your app needs to be linked as a "console" app. Set the /SUBSYSTEM:CONSOLE flag in the project's property page, under Linker/System set the SubSystem property to CONSOLE.
Once you have your output in the window, if you include the text "Error:" it will appear as an error, or if you set "Warning:" it will appear as a warning. If your error text begins with a path/filename, followed by a line number in parenthesis, the IDE will recognize it as a "clickable" error, and navigate you automatically to the faulting line.
This is demonstrated in the following helper class from a Microsoft sample project:
https://github.com/microsoft/VSSDK-Extensibility-Samples/blob/df10d37b863feeff6e8fcaa6f4d172f602a882c5/Reference_Services/C%23/Reference.Services/HelperFunctions.cs#L28
The code is as follows:
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.Samples.VisualStudio.Services
{
/// <summary>
/// This class is used to expose some utility functions used in this project.
/// </summary>
internal static class HelperFunctions
{
/// <summary>
/// This function is used to write a string on the Output window of Visual Studio.
/// </summary>
/// <param name="provider">The service provider to query for SVsOutputWindow</param>
/// <param name="text">The text to write</param>
internal static void WriteOnOutputWindow(IServiceProvider provider, string text)
{
// At first write the text on the debug output.
Debug.WriteLine(text);
// Check if we have a provider
if (null == provider)
{
// If there is no provider we can not do anything; exit now.
Debug.WriteLine("No service provider passed to WriteOnOutputWindow.");
return;
}
// Now get the SVsOutputWindow service from the service provider.
IVsOutputWindow outputWindow = provider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
if (null == outputWindow)
{
// If the provider doesn't expose the service there is nothing we can do.
// Write a message on the debug output and exit.
Debug.WriteLine("Can not get the SVsOutputWindow service.");
return;
}
// We can not write on the Output window itself, but only on one of its panes.
// Here we try to use the "General" pane.
Guid guidGeneral = Microsoft.VisualStudio.VSConstants.GUID_OutWindowGeneralPane;
IVsOutputWindowPane windowPane;
if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidGeneral, out windowPane)) ||
(null == windowPane))
{
if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.CreatePane(ref guidGeneral, "General", 1, 0)))
{
// Nothing to do here, just debug output and exit
Debug.WriteLine("Failed to create the Output window pane.");
return;
}
if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidGeneral, out windowPane)) ||
(null == windowPane))
{
// Again, there is nothing we can do to recover from this error, so write on the
// debug output and exit.
Debug.WriteLine("Failed to get the Output window pane.");
return;
}
if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.Activate()))
{
Debug.WriteLine("Failed to activate the Output window pane.");
return;
}
}
// Finally we can write on the window pane.
if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.OutputString(text)))
{
Debug.WriteLine("Failed to write on the Output window pane.");
}
}
}
}
You can use the Debug and/or Trace classes. There is some information here:
http://msdn.microsoft.com/en-us/library/bs4c1wda(VS.71).aspx
Best of luck.
use System.Diagnostics.Debugger.Message

Resources