See also for part of my issue: String passing from JavaScript to Qt/C++ object
I use this custom WebPage on my WebView (using setPage), the strings are passed in wrongly. When I disable calling setPage, the strings passed correctly.
I have a C++ object (that inherits from QWidget) that displays an activeX on a webpage (using ActiveQt and QWebView). The ActiveX object displays just fine. I've registered the custom type and used the Qt MetaType system to instantiate the HTML object.
If I want to update a property of that C++ object (not the ActiveX) and call from JavaScript, the string gets a wrong translation or so. If I pass in some string, I only get a string of 1 character on my C++ method. Viewing the memory tells me that the whole string is present (formattted as UTF-32).
If I now don't call the custom WebPage that loads the plugin for me (using setPage on QWebView), I don't get the ActiveX - which is expected - and the strings are passed correctly.
I don't quite understand why this doesn't work. Even with setPage called, I'm talking to the C++ object via JavaScript/QtWebKit bridge, not to the ActiveX component.
I'm on Windows 7, 64 bit, using Qt 4.7.4.
Some code (C++):
class Device : public QWidget
{
Q_OBJECT
public:
explicit Device(QWidget* parent = 0);
Device(const Device& other);
Device& operator=(const Device& other);
~Device();
QString name() const;
void setName(QString);
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
signals:
void nameChanged(QString);
private:
QAxWidget* ax;
};
Q_DECLARE_METATYPE(Device);
Device::Device(QWidget* parent/* = 0*/)
: QWidget(parent)
{
ax = new QAxWidget(this);
ax->setControl("{...}");
}
QString Device::name() const
{
return _name;
}
void Device::setName(QString name)
{
if (_name != name) {
_name = name;
emit nameChanged(name);
}
}
// Needed for ActiveQt objects to work
class MapPage : public QWebPage
{
Q_OBJECT
protected:
QObject* createPlugin(const QString& classId, const QUrl& url, const QStringList& paramNames, const QStringList& paramValues);
public:
MapPage(QObject* parent = 0);
};
// ============================================================================
// Needed for ActiveQt objects to work
MapPage::MapPage(QObject* parent/* = 0*/)
: QWebPage(parent)
{
qRegisterMetaType<Device>();
settings()->setAttribute(QWebSettings::PluginsEnabled, true);
}
QObject* MapPage::createPlugin(const QString& classId, const QUrl& url, const QStringList& paramNames, const QStringList& paramValues)
{
// Create widget using QUiLoader, which means widgets don't need to be registered with meta object system (only for gui objects!).
QUiLoader loader;
QObject* result = 0;//loader.createWidget(classId, view());
// If loading failed, use the Qt Metatype system; expect objects derived from QObject
if (!result) {
//result = static_cast<QWidget*>(QMetaType::construct(QMetaType::type(classId.toLatin1())));
int id = QMetaType::type("Device");
void* classPtr = QMetaType::construct(id);
result = static_cast<QWidget*>(classPtr);
}
return result;
}
and then on my window
qRegisterMetaType<Device>();
mapPage = new MapPage(this);
ui.webView->setPage(mapPage); // Needed for ActiveQt objects to work - makes strings broken
...
ui.webView->setHtml(src);
Narrowed it down to an issue probably related to JavaScript/HTML.
//device.name = 'test'; // works
var str = 'test';
previewCameo.view = str; // works
but when I get the name via a HTML drag-n-drop event (using e.dataTransfer), it fails
var name = e.dataTransfer.getData('name');
alert(name); // shows correct name
device.name = name; // fails <------
device.name = e.dataTransfer.getData('name'); // fails <------
So retrieval of the dataTransfer data fails for some reason. Why?
On Linux I do get a string back, but its formatted like t[]e[]s[]t[]. Seems like an encoding issue.
Related
I'm making a simple 2D game for Android using the Unity3D game engine. I created all the levels and everything but I'm stuck at making the game over/retry menu. So far I've been using new scenes as a game over menu. I used this simple script:
#pragma strict
var level = Application.LoadLevel;
function OnCollisionEnter(Collision : Collision)
{
if(Collision.collider.tag == "Player")
{
Application.LoadLevel("GameOver");
}
}
And this as a 'menu':
#pragma strict
var myGUISkin : GUISkin;
var btnTexture : Texture;
function OnGUI() {
GUI.skin = myGUISkin;
if (GUI.Button(Rect(Screen.width/2-60,Screen.height/2+30,100,40),"Retry"))
Application.LoadLevel("Easy1");
if (GUI.Button(Rect(Screen.width/2-90,Screen.height/2+100,170,40),"Main Menu"))
Application.LoadLevel("MainMenu");
}
The problem stands at the part where I have to create over 200 game over scenes, obstacles (the objects that kill the player) and recreate the same script over 200 times for each level. Is there any other way to make this faster and less painful?
Edit : If possible,please when you suggest your ideas,use javascript only,I don't understand C#,not even a little bit.I know Im asking too much but it realy confuses me.
Thank you.
There are several different solutions, but I would recommend using PlayerPrefs. This has the extra benefit of persisting even when the application is closed and then re-opened.
In your Awake() function of your Main Menu class, you can get the current level and store it in a static string of your Main Menu class. If it is the player's 1st time, use the name for level 1.
Something like this:
static string currentLevelName;
void Awake()
{
currentLevelName = PlayerPrefs.GetString("CurrentLevel");
if (currentLevelName == defaultValue)
{
currentLevelName = "Level1"
}
}
Then, modify your button to do this instead:
if (GUI.Button(Rect(Screen.width/2-60,Screen.height/2+30,100,40),"Retry"))
Application.LoadLevel(currentLevelName);
Whenever the player advances to the next level, set the string in PlayerPrefs to the new level name:
PlayerPrefs.SetString("CurrentLevel", Application.loadedLevelName);
You can create a class with static properties. For example (in c#)
public class GameOverInput
{
public static string name;
public static string retryLevel;
//all the info you need
}
Then you can easily read the input in your game over scene (only one is needed)
public class GameOverMenu : MonoBehavior
{
void Start()
{
Debug.Log("You were killed by " + GameOverInput.name);
Application.LoadLevel(GameOverInput.retryLevel);
}
}
And you set this info just before loading the game over scene
if (Collision.collider.tag == "Player")
{
GameOverInput.name = "Baddie";
Application.LoadLevel("GameOver");
}
Another option would be to make something like a singleton LevelManager MonoBehavior and add it to an object named "Level Manager". Use the DontDestroyOnLoad function to make the object persist even when you load another level.
class LevelManager : MonoBehavior
{
static LevelManager _instance;
public string currentLevelName;
public string killedBy;
function Awake ()
{
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject); // Make sure we never have more than 1 Level Manager.
}
}
LevelManager Instance
{
get
{
if (_instance == null)
{
GameObject levelManagerObject = GameObject.Find("Level Manager");
_instance = levelManagerObject.GetComponent<LevelManager>();
}
return _instance
}
}
}
Then, from the main menu class, you can always access the Level Manager like so:
Debug.Log("Killed by " + LevelManager.Instance.killedBy);
or
LevelManager.Instance.currentLevelName = Application.loadedLevelName;
I'm creating my own IntelliSense Presenter, since Visual Studio2012 support change theme, so I want my background color of the presenter can be auto-changed when the theme been changed. Is there a way to track the theme changes event, or get the current color theme of the Visual Studio?
Yes, this is possible. I had to solve a similiar issue with one of my extensions...
The current theme is stored in the Windows Registry; so I implemented the following utility class.
public enum VsTheme
{
Unknown = 0,
Light,
Dark,
Blue
}
public class ThemeUtil
{
private static readonly IDictionary<string, VsTheme> Themes = new Dictionary<string, VsTheme>()
{
{ "de3dbbcd-f642-433c-8353-8f1df4370aba", VsTheme.Light },
{ "1ded0138-47ce-435e-84ef-9ec1f439b749", VsTheme.Dark },
{ "a4d6a176-b948-4b29-8c66-53c97a1ed7d0", VsTheme.Blue }
};
public static VsTheme GetCurrentTheme()
{
string themeId = GetThemeId();
if (string.IsNullOrWhiteSpace(themeId) == false)
{
VsTheme theme;
if (Themes.TryGetValue(themeId, out theme))
{
return theme;
}
}
return VsTheme.Unknown;
}
public static string GetThemeId()
{
const string CategoryName = "General";
const string ThemePropertyName = "CurrentTheme";
string keyName = string.Format(#"Software\Microsoft\VisualStudio\11.0\{0}", CategoryName);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
return (string)key.GetValue(ThemePropertyName, string.Empty);
}
}
return null;
}
}
Okay; this just helps to figur out the current settings... listening for the theme changed notification is a bit trickier. After your package is loaded, you must obtain an IVsShell instance via the DTE; once you have this object you can utilize the AdviceBroadcastMessages method to subscribe for event notifications. You have to provide an object whose type implements the IVsBroadcastMessageEvents interface...
I don´t want to post the whole implementation, but the following lines might illustrate the key scenario...
class VsBroadcastMessageEvents : IVsBroadcastMessageEvent
{
int IVsBroadcastMessageEvent.OnBroadcastMessage(uint msg, IntPtr wParam, IntPtr lParam)
{
const uint WM_SYSCOLORCHANGE = 0x15;
if (msg == WM_SYSCOLORCHANGE)
{
// obtain current theme from the Registry and update any UI...
}
}
}
Consider implementing IDisposable on that type as well, in order to be able to unsubscribe from the event source, when the package gets unloaded.
This is how I subscribe for event notifications...
class ShellService
{
private readonly IVsShell shell;
private bool advised;
public ShellService(IVsShell shellInstance)
{
this.shell = shellInstance;
}
public void AdviseBroadcastMessages(IVsBroadcastMessageEvents broadcastMessageEvents, out uint cookie)
{
cookie = 0;
try
{
int r = this.shell.AdviseBroadcastMessages(broadcastMessageEvents, out cookie);
this.advised = (r == VSConstants.S_OK);
}
catch (COMException) { }
catch (InvalidComObjectException) { }
}
public void UnadviseBroadcastMessages(uint cookie)
{
...
}
}
Keep the value of the cookie parameter; you´ll need it to successfully unsubscribe.
Hope that helps (-:
Just wanted to put an update just in case anyone else comes along.. #Matze and #Frank are totally right.. However in VS 2015.. they added a easy way to detect the theme change. So you need to include PlatformUI an dyou get a super easy event
using Microsoft.VisualStudio.PlatformUI;
....
//Then you get an event
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
You should make sure your control is disposable so you can unsubscribe from the event...
BONUS!
It also give you easy access to the colors.. even if the user has changed them from the default .. so you can do stuff like this in when set your colors
var defaultBackground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
var defaultForeground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey);
For VS 2015 this has changed, the solution #Matze has still works but you need to update the GetThemeId() function to check for the version and if it's 14.0 (VS2015) look in a different place in the registry. The way the value is stored has changed also, it's still a string but now contains other values seperated by a '*'. The theme guid is the last value in the list.
if (version == "14.0")
{
string keyName = string.Format(#"Software\Microsoft\VisualStudio\{0}\ApplicationPrivateSettings\Microsoft\VisualStudio", version);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
var keyText = (string)key.GetValue("ColorTheme", string.Empty);
if (!string.IsNullOrEmpty(keyText))
{
var keyTextValues = keyText.Split('*');
if (keyTextValues.Length > 2)
{
return keyTextValues[2];
}
}
}
}
return null;
}
This was the code present in book CLR via C# 3e (Microsoft)
But it is giving error during runtime
TypeLoadException was unhandled Could not load type 'MarshalByRefType'
from assembly 'CLRviaCSharp, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null'.
Except the method 'Main', all code is of the book mentioned above.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Runtime.Remoting;
namespace CLRviaCSharp
{
class Program
{
static void Main(string[] args)
{
Marshalling();
}
private static void Marshalling()
{
// Get a reference to the AppDomain that that calling thread is executing in
AppDomain adCallingThreadDomain = Thread.GetDomain();
// Every AppDomain is assigned a friendly string name (helpful for debugging)
// Get this AppDomain’s friendly string name and display it
String callingDomainName = adCallingThreadDomain.FriendlyName;
Console.WriteLine("Default AppDomain’s friendly name={0}", callingDomainName);
// Get & display the assembly in our AppDomain that contains the ‘Main’ method
String exeAssembly = Assembly.GetEntryAssembly().FullName;
Console.WriteLine("Main assembly={0}", exeAssembly);
// Define a local variable that can refer to an AppDomain
AppDomain ad2 = null;
// *** DEMO 1: Cross-AppDomain Communication using Marshal-by-Reference ***
Console.WriteLine("{0}Demo #1", Environment.NewLine);
// Create new AppDomain (security & configuration match current AppDomain)
ad2 = AppDomain.CreateDomain("AD #2", null, null);
MarshalByRefType mbrt = null;
// Load our assembly into the new AppDomain, construct an object, marshal
// it back to our AD (we really get a reference to a proxy)
mbrt = (MarshalByRefType)
ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");
Console.WriteLine("Type={0}", mbrt.GetType()); // The CLR lies about the type
// Prove that we got a reference to a proxy object
Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbrt));
// This looks like we’re calling a method on MarshalByRefType but, we’re not.
// We’re calling a method on the proxy type. The proxy transitions the thread
// to the AppDomain owning the object and calls this method on the real object.
mbrt.SomeMethod();
// Unload the new AppDomain
AppDomain.Unload(ad2);
// mbrt refers to a valid proxy object; the proxy object refers to an invalid AppDomain
try
{
// We’re calling a method on the proxy type. The AD is invalid, exception is thrown
mbrt.SomeMethod();
Console.WriteLine("Successful call.");
}
catch (AppDomainUnloadedException)
{
Console.WriteLine("Failed call.");
}
// *** DEMO 2: Cross-AppDomain Communication using Marshal-by-Value ***
Console.WriteLine("{0}Demo #2", Environment.NewLine);
// Create new AppDomain (security & configuration match current AppDomain)
ad2 = AppDomain.CreateDomain("AD #2", null, null);
// Load our assembly into the new AppDomain, construct an object, marshal
// it back to our AD (we really get a reference to a proxy)
mbrt = (MarshalByRefType)
ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");
// The object’s method returns a COPY of the returned object;
// the object is marshaled by value (not be reference).
MarshalByValType mbvt = mbrt.MethodWithReturn();
// Prove that we did NOT get a reference to a proxy object
Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbvt));
// This looks like we’re calling a method on MarshalByValType and we are.
Console.WriteLine("Returned object created " + mbvt.ToString());
// Unload the new AppDomain
AppDomain.Unload(ad2);
// mbvt refers to valid object; unloading the AppDomain has no impact.
try
{
// We’re calling a method on an object; no exception is thrown
Console.WriteLine("Returned object created " + mbvt.ToString());
Console.WriteLine("Successful call.");
}
catch (AppDomainUnloadedException)
{
Console.WriteLine("Failed call.");
}
// DEMO 3: Cross-AppDomain Communication using non-marshalable type ***
Console.WriteLine("{0}Demo #3", Environment.NewLine);
// Create new AppDomain (security & configuration match current AppDomain)
ad2 = AppDomain.CreateDomain("AD #2", null, null);
// Load our assembly into the new AppDomain, construct an object, marshal
// it back to our AD (we really get a reference to a proxy)
mbrt = (MarshalByRefType)
ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");
// The object’s method returns an non-marshalable object; exception
NonMarshalableType nmt = mbrt.MethodArgAndReturn(callingDomainName);
// We won’t get here...
}
}
// Instances can be marshaled-by-reference across AppDomain boundaries
public sealed class MarshalByRefType : MarshalByRefObject
{
public MarshalByRefType()
{
Console.WriteLine("{0} ctor running in {1}",
this.GetType().ToString(), Thread.GetDomain().FriendlyName);
}
public void SomeMethod()
{
Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
}
public MarshalByValType MethodWithReturn()
{
Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
MarshalByValType t = new MarshalByValType();
return t;
}
public NonMarshalableType MethodArgAndReturn(String callingDomainName)
{
// NOTE: callingDomainName is [Serializable]
Console.WriteLine("Calling from ‘{0}’ to ‘{1}’.",
callingDomainName, Thread.GetDomain().FriendlyName);
NonMarshalableType t = new NonMarshalableType();
return t;
}
}
// Instances can be marshaled-by-value across AppDomain boundaries
[Serializable]
public sealed class MarshalByValType : Object
{
private DateTime m_creationTime = DateTime.Now; // NOTE: DateTime is [Serializable]
public MarshalByValType()
{
Console.WriteLine("{0} ctor running in {1}, Created on {2:D}",
this.GetType().ToString(),
Thread.GetDomain().FriendlyName,
m_creationTime);
}
public override String ToString()
{
return m_creationTime.ToLongDateString();
}
}
// Instances cannot be marshaled across AppDomain boundaries
// [Serializable]
public sealed class NonMarshalableType : Object
{
public NonMarshalableType()
{
Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
}
}
}
When you useAppDomain.CreateInstanceAndUnwrap method, you should pass a type's full name to the typeName parameter.
So, replace
ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");
with
ad2.CreateInstanceAndUnwrap(exeAssembly, typeof(MarshalByRefType).FullName);
I have an action that returns an Excel as a custom FileResult. My solution is based on the ClosedXml library (internaly using OpenXml).
My XlsxResult class uses a read-only .xlsx file on the server as a template. It then passes on the template into a memory stream that gets manipulated and saved back with ClosedXml. In the end the memory stream get written to the response.
This works fine both on Cassini as well as IIS Express but fails when deployed on azure with no error whatsoever. The only effect I am experiencing is the request sent to the server never gets any response. I am still waiting for something to happen after 60 minutes or so...
My action:
[OutputCache(Location= System.Web.UI.OutputCacheLocation.None, Duration=0)]
public FileResult Export(int year, int month, int day) {
var date = new DateTime(year, month, day);
var filename = string.Format("MyTemplate_{0:yyyyMMdd}.xlsx", date);
//return new FilePathResult("~/Content/templates/MyTemplate.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
var result = new XlsxExportTemplatedResult("MyTemplate.xlsx", filename, (workbook) => {
var ws = workbook.Worksheets.Worksheet("My Export Sheet");
ws.Cell("B3").Value = date;
// Using a OpenXML's predefined formats (15 stands for date)
ws.Cell("B3").Style.NumberFormat.NumberFormatId = 15;
ws.Columns().AdjustToContents(); // You can also specify the range of columns to adjust, e.g.
return workbook;
});
return result;
}
My FileResult
public class XlsxExportTemplatedResult : FileResult
{
// default buffer size as defined in BufferedStream type
private const int BufferSize = 0x1000;
public static readonly string TEMPLATE_FOLDER_LOCATION = #"~\Content\templates";
public XlsxExportTemplatedResult(string templateName, string fileDownloadName, Func<XLWorkbook, XLWorkbook> generate)
: base("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
this.TempalteName = templateName;
this.FileDownloadName = fileDownloadName;
this.Generate = generate;
}
public string TempalteName { get; protected set; }
public Func<XLWorkbook, XLWorkbook> Generate { get; protected set; }
protected string templatePath = string.Empty;
public override void ExecuteResult(ControllerContext context) {
templatePath = context.HttpContext.Server.MapPath(System.IO.Path.Combine(TEMPLATE_FOLDER_LOCATION, this.TempalteName));
base.ExecuteResult(context);
}
//http://msdn.microsoft.com/en-us/library/office/ee945362(v=office.11).aspx
protected override void WriteFile(System.Web.HttpResponseBase response) {
FileStream fileStream = new FileStream(templatePath, FileMode.Open, FileAccess.Read);
using (MemoryStream memoryStream = new MemoryStream()) {
CopyStream(fileStream, memoryStream);
using (var workbook = new XLWorkbook(memoryStream)) {
Generate(workbook);
workbook.Save();
}
// At this point, the memory stream contains the modified document.
// grab chunks of data and write to the output stream
Stream outputStream = response.OutputStream;
byte[] buffer = new byte[BufferSize];
while (true) {
int bytesRead = memoryStream.Read(buffer, 0, BufferSize);
if (bytesRead == 0) {
// no more data
break;
}
outputStream.Write(buffer, 0, bytesRead);
}
}
fileStream.Dispose();
}
static private void CopyStream(Stream source, Stream destination) {
byte[] buffer = new byte[BufferSize];
int bytesRead;
do {
bytesRead = source.Read(buffer, 0, buffer.Length);
destination.Write(buffer, 0, bytesRead);
} while (bytesRead != 0);
}
}
So am I missing something (apparently I am).
Please Note:
There are no dlls missing from Azure because I checked using RemoteAccess feature of the Windows Azure Tools 1.7
My export is not a heavy long running task.
when I changed the action to just return a FilePathResult with the template xlsx it worked on azure. But I need to process the file before returning it as u might suspect :-)
Tanks.
UPDATE:
After I logged extensively in my code the execution hangs with no error at the ClosedXml "Save" method call. But still no error. Abstract from the WADLogsTable:
Opening template file from path:
E:\sitesroot\0\Content\templates\MyTemplate.xlsx
Opened template from path:
E:\sitesroot\0\Content\templates\MyTemplate.xlsx just
copied template to editable memory stream. Bytes copied: 15955,
Position: 15955
modified the excel document in memory.
here it hangs when a it calls to workbook.Save(); This is a ClosedXml method call.
I was facing the exact same error situation as you. I can't offer a fix in your specific situation, and I know you switched tracks, but after going through the same frustrating steps you had faced, I'd like to "pave the way" for an answer for you (or others).
Drop into your package manager console in Visual Studio and install Elmah with the MVC goodies (routing):
Install-Package elmah.MVC
Now, in your root web.config, update your Elmah entry. It's likely at the end of the file, looking like this:
<elmah></elmah>
Update that bad boy to allow remote access and set up your log path:
<elmah>
<security allowRemoteAccess="1" />
<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/app_data/elmah" />
</elmah>
Now, push that up to Azure.
Finally, visit your site, force the error then navigate to http://your-site-here.azurewebsites.net/elmah and you'll see the exact cause of the error.
Elmah is so the awesome.
Sheepish confession: The error for me wasn't in the third party code, it turned out to be in my connection string, for which I hadn't set MultipleActiveResultsSets to true. The other fix I had to do was pass my entities in after calling ToList() to one of the internal methods on that library, leaving it as IQueryable borked the method up.
I really need help with this one.
I have written an adding that adds a some text to the subject line of an email on send it also sets the same value in a property on the email through the PropertyAccessor. This works beautifully when the email is created through the Outlook interface.
The problem I have encountered is when a user is working with excel and decides to send the spreadsheet as an attachment from the Excel 2010 -> File -> Save & Send -> Send as Attachment Menu Item.
This will open the outlook 2010 mail editor as you would expect, the user can address the email and press send as normal and the email will be sent as normal. The problem is that the email editor does not close after sending as you would expect.
I am using the Globals.ThisAddIn.Application.ItemSend event to make my changes to the email. I notice also on debug that this event fires a couple of times when sending. The cancel argument of this event is always false during execution.
Finally found something that seems to work
Developing an Inspector Wrapper for Outlook 2010
In case the link goes dead as they are likely to do
Here's the wrapper
namespace OutlookAddIn
{
// Eventhandler used to correctly clean up resources.
// <param name="id">The unique id of the Inspector instance.</param>
internal delegate void InspectorWrapperClosedEventHandler(Guid id);
// The base class for all inspector wrappers.
internal abstract class InspectorWrapper
{
// Event notification for the InspectorWrapper.Closed event.
// This event is raised when an inspector has been closed.
// The unique ID that identifies an inspector window.
protected InspectorWrapper(Inspector inspector)
{
Id = Guid.NewGuid();
Inspector = inspector;
// Register Inspector events here
((InspectorEvents_10_Event) Inspector).Close += InspectorClose;
((InspectorEvents_10_Event) Inspector).Activate += Activate;
(Inspector).Deactivate += Deactivate;
(Inspector).BeforeMaximize += BeforeMaximize;
(Inspector).BeforeMinimize += BeforeMinimize;
(Inspector).BeforeMove += BeforeMove;
(Inspector).BeforeSize += BeforeSize;
(Inspector).PageChange += PageChange;
// Initialize is called to give the derived wrappers.
Initialize();
}
public Guid Id { get; private set; }
// The Outlook Inspector instance.
public Inspector Inspector { get; private set; }
public event InspectorWrapperClosedEventHandler Closed;
// .ctor
// <param name="inspector">The Outlook Inspector instance that should be handled.</param>
// Event handler for the Inspector Close event.
private void InspectorClose()
{
// Call the Close Method - the derived classes can implement cleanup code
// by overriding the Close method.
Close();
// Unregister Inspector events.
((InspectorEvents_10_Event) Inspector).Close -= InspectorClose;
// Clean up resources and do a GC.Collect().
Inspector = null;
GC.Collect();
GC.WaitForPendingFinalizers();
// Raise the Close event.
if (Closed != null)
{
Closed(Id);
}
}
protected virtual void Initialize()
{}
// Method is called when another page of the inspector has been selected.
// <param name="ActivePageName">The active page name by reference.</param>
protected virtual void PageChange(ref string ActivePageName)
{}
// Method is called before the inspector is resized.
// <param name="Cancel">To prevent resizing, set Cancel to true.</param>
protected virtual void BeforeSize(ref bool Cancel)
{}
// Method is called before the inspector is moved around.
// <param name="Cancel">To prevent moving, set Cancel to true.</param>
protected virtual void BeforeMove(ref bool Cancel)
{}
// Method is called before the inspector is minimized.
// <param name="Cancel">To prevent minimizing, set Cancel to true.</param>
protected virtual void BeforeMinimize(ref bool Cancel)
{}
// Method is called before the inspector is maximized.
// <param name="Cancel">To prevent maximizing, set Cancel to true.</param>
protected virtual void BeforeMaximize(ref bool Cancel)
{}
// Method is called when the inspector is deactivated.
protected virtual void Deactivate()
{}
// Method is called when the inspector is activated.
protected virtual void Activate()
{}
// Derived classes can do a cleanup by overriding this method.
protected virtual void Close()
{}
// This factory method returns a specific InspectorWrapper or null if not handled.
// <param name=”inspector”>The Outlook Inspector instance.</param>
// Returns the specific wrapper or null.
public static InspectorWrapper GetWrapperFor(Inspector inspector)
{
// Retrieve the message class by using late binding.
string messageClass = inspector.CurrentItem.GetType().InvokeMember("MessageClass", BindingFlags.GetProperty,
null, inspector.CurrentItem, null);
// Depending on the message class, you can instantiate a
// different wrapper explicitly for a given message class by
// using a switch statement.
switch (messageClass)
{
case "IPM.Contact":
return new ContactItemWrapper(inspector);
case "IPM.Journal":
return new ContactItemWrapper(inspector);
case "IPM.Note":
return new MailItemWrapper(inspector);
case "IPM.Post":
return new PostItemWrapper(inspector);
case "IPM.Task":
return new TaskItemWrapper(inspector);
}
// Or, check if the message class begins with a specific fragment.
if (messageClass.StartsWith("IPM.Contact.X4U"))
{
return new X4UContactItemWrapper(inspector);
}
// Or, check the interface type of the item.
if (inspector.CurrentItem is AppointmentItem)
{
return new AppointmentItemWrapper(inspector);
}
// No wrapper is found.
return null;
}
}
// Derive a wrapper for each message class/item type.
}
and here is an example override
internal class MailItemWrapper : InspectorWrapper
{
// The Object instance behind the Inspector, which is the current item.
public MailItemWrapper(Inspector inspector)
: base(inspector)
{
// Get the item in the current Inspector.
Item = (MailItem) Inspector.CurrentItem;
// Register Item events.
Item.Open += Item_Open;
Item.Write += Item_Write;
}
public MailItem Item { get; private set; }
// This method is called when the item is visible and the UI is initialized.
// <param name="Cancel">When you set this property to true, the Inspector is closed.</param>
private void Item_Open(ref bool Cancel)
{
// TODO: Implement something
}
// This method is called when the item is saved.
// <param name="Cancel">When set to true, the save operation is cancelled.</param>
private void Item_Write(ref bool Cancel)
{
//TODO: Implement something
}
// The Close method is called when the inspector has been closed.
// Do your cleanup tasks here.
// The UI is gone, cannot access it here.
protected override void Close()
{
// Unregister events.
Item.Write -= Item_Write;
Item.Open -= Item_Open;
// Release references to COM objects.
Item = null;
// Set item to null to keep a reference in memory of the garbage collector.
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
and here is the ThisAddin class
public partial class ThisAddIn
{
// Holds a reference to the Application.Inspectors collection.
// Required to get notifications for NewInspector events.
private Inspectors _inspectors;
// A dictionary that holds a reference to the inspectors handled by the add-in.
private Dictionary<Guid, InspectorWrapper> _wrappedInspectors;
private void ThisAddInStartup(object sender, EventArgs e)
{
_wrappedInspectors = new Dictionary<Guid, InspectorWrapper>();
_inspectors = Globals.ThisAddIn.Application.Inspectors;
_inspectors.NewInspector += WrapInspector;
// Also handle existing Inspectors
// (for example, double-clicking a .msg file).
foreach (Inspector inspector in _inspectors)
{
WrapInspector(inspector);
}
}
// Wrap an Inspector, if required, and store it in memory to get events of the wrapped Inspector.
// <param name="inspector">The Outlook Inspector instance.</param>
private void WrapInspector(Inspector inspector)
{
var wrapper = InspectorWrapper.GetWrapperFor(inspector);
if (wrapper != null)
{
// Register the Closed event.
wrapper.Closed += WrapperClosed;
// Remember the inspector in memory.
_wrappedInspectors[wrapper.Id] = wrapper;
}
}
// Method is called when an inspector has been closed.
// Removes reference from memory.
// <param name="id">The unique id of the closed inspector</param>
private void WrapperClosed(Guid id)
{
_wrappedInspectors.Remove(id);
}
private void ThisAddInShutdown(object sender, EventArgs e)
{
// Clean up.
_wrappedInspectors.Clear();
_inspectors.NewInspector -= WrapInspector;
_inspectors = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
Startup += ThisAddInStartup;
Shutdown += ThisAddInShutdown;
}
#endregion
}
There should be enough there to get people out of trouble if the link is down