Updated to try and make the question more readable, following comments below
Scenario:
user clicks reply in outlook 365 this opens a mailItem within the main window of outlook. we are attempting to add text to the body of this mailItem.
Currently my method can add text to the to and cc field of the imbedded mailItem but not the body. It will add text to the body, To, Cc, and Subject of a modal mailItem, with this modal open it will even add text to the mailItem's body that it would not work with previously.
When only the main window is open, the focusedClass is not present, so it hits the SendMessage method.
Question:
How do you add text to the body of this mailItem? When only the main Outlook window is open and the MailItem is imbedded in the main window.
So far I have looked at Spy++ and the Z order always changes so I cannot find a logical way to get a child window to work with, plus the structure for the mailItem is different while in the main window and modal.
My current solution uses GetWindowThreadProcessId and GetCurrentThreadId to get the focused window, then the GetFocusClass to find the class, with this class obtained we finally call the below to insert the text:
if (focusedClass.ToLower() == "_wwg")
InsertTextToEmailBody(text);
else
SendMessage(focused, EM_REPLACESEL, 0, newText);
private void InsertTextToEmailBody(string text)
{
var outlookApplication = GetOulookApplication();
var activityInspector = outlookApplication.ActiveInspector();
var currentItem = activityInspector?.CurrentItem;
if (currentItem == null)
{
_log.Error("InsertTextToEmailBody could not find CurrentItem and cannot inject into email body");
return;
}
var myInspector = ((MailItem) currentItem).GetInspector;
var wdDoc = (Document) myInspector.WordEditor;
var currentSelection = wdDoc.Application.Selection;
if (currentSelection.Range?.Text?.Length > 0)
{
//The user has a selected range of text, replace that with transcription
currentSelection.Range.Text = text;
return;
}
// Store the user's current Overtype selection
var userOvertype = wdDoc.Application.Options.Overtype;
// Make sure Overtype is turned off.
if (wdDoc.Application.Options.Overtype) wdDoc.Application.Options.Overtype = false;
// Test to see if selection is an insertion point.
if (currentSelection.Type == WdSelectionType.wdSelectionIP)
currentSelection.TypeText(text);
else if (currentSelection.Type == WdSelectionType.wdSelectionNormal)
{
// Move to start of selection.
if (wdDoc.Application.Options.ReplaceSelection)
{
object direction = WdCollapseDirection.wdCollapseStart;
currentSelection.Collapse(ref direction);
}
currentSelection.TypeText(text);
}
// Restore the user's Overtype selection
wdDoc.Application.Options.Overtype = userOvertype;
}
Not sure why you'd want to use GetFocus() etc. to find the HWND of the Word editor - the focus can be anywhere in the Inspector (e.g. Subject or To edit box), plus clicking on the ribbon can take the focus away from the Word editor even if it was there to begin with.
What works for me is the following:
QI/cast your Inspector object (e.g. retrieved from from Application.ActiveInspector) to IOleWindow. Call IOleWindows.GetWindow() to get the top level HWND of the inspector.
Call EnumChildWindows with a callback that calls GetClassName and compares it with "_WwG". Note that in Office 12 and older the class name is "_WwF".
I also not sure what is wrong with your code that uses Word Object Model to insert text. It looks perfectly fine to me (without actually debugging it), and it would be the preferable solution since it would work out-of-proc since SendMessage(EM_REPLACESEL) requires that both the caller and the target window are in the same process (since it takes a pointer to a text buffer).
Related
Looking a bit of advise or direction in regards to VSTO Ribbons with Outlook in C#.
So far I’ve built an Outlook 2010 Ribbon (using TabMail), this Ribbon opens a WinForms window which allows my users to select contacts from a Custom Built Address Book from a SQL database, via a DataGridView.
The users basically select from a datagridview who they want to email, which then gets added to a toLine list.
Application app = new Microsoft.Office.Interop.Outlook.Application();
Mail item item = app.CreateItem((OlItemType.olMailItem));
item.To = toLine;
Item.Display();
This.close();
The downside of using this approach is the user has to build their To List before they actually compose their email.
I’m now trying to make use of TabMailNewMessage. This should allow the user to compose their email, then click the Ribbon icon within the new message and add to their To List from there.
I’ve got the icon showing okay in the TabMailNewMessage, and I’ve got it to open a 2nd Win Form [currently as a test].
I’m a little unsure how to add to the To List of an already open existing mailItem.
At present all I have on the 2nd Win Form is a button, can someone explain how I can click that button and simply add someone to the To List [of this already composed email]. (I don’t have any code behind the button click as I’m not sure what to do)
I also need to make sure that it doesn’t send the email, but simply adds the user to the To List.
Currently using Office 2010, and VS 2013 (with C#).
Hopefully I’m making some sense here.
Thanks
EDIT:
Not sure if its a simple as
Application app = Globals.ThisAddIn.Application;
MailItem mi = (Outlook.MailItem)app.ActiveInspector().CurrentItem;
Mi.Recipients.Add(“joe#email.com”);
This.Close();
The MailItem.Recipients.Add method allows creating a new recipient in the Recipients collection. The name of the recipient can be a string representing the display name, the alias, or the full SMTP email address of the recipient.
using System.Runtime.InteropServices;
// ...
private bool AddRecipients(Outlook.MailItem mail)
{
bool retValue = false;
Outlook.Recipients recipients = null;
Outlook.Recipient recipientTo = null;
Outlook.Recipient recipientCC = null;
Outlook.Recipient recipientBCC = null;
try
{
recipients = mail.Recipients;
// first, we remove all the recipients of the e-mail
while(recipients.Count != 0)
{
recipients.Remove(1);
}
// now we add new recipietns to the e-mail
recipientTo = recipients.Add("Eugene Astafiev");
recipientTo.Type = (int)Outlook.OlMailRecipientType.olTo;
recipientCC = recipients.Add("Somebody");
recipientCC.Type = (int)Outlook.OlMailRecipientType.olCC;
recipientBCC = recipients.Add("eugene.astafiev#somedomain.com");
recipientBCC.Type = (int)Outlook.OlMailRecipientType.olBCC;
retValue = recipients.ResolveAll();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
finally
{
if (recipientBCC != null) Marshal.ReleaseComObject(recipientBCC);
if (recipientCC != null) Marshal.ReleaseComObject(recipientCC);
if (recipientTo != null) Marshal.ReleaseComObject(recipientTo);
if (recipients != null) Marshal.ReleaseComObject(recipients);
}
return retValue;
}
You may find the following articles helpful:
How To: Fill TO,CC and BCC fields in Outlook programmatically
How To: Create and send an Outlook message programmatically
Also, I'd suggest developing an Outlook form region which can be displayed on the same window instead of using a standalone window, see Create Outlook form regions for more information.
Yes, it is as simple as using Application.ActiveInspector.CurrentItem. But you can do better than that - your ribbon button event handler takes IRibbonControl object as a parameter. Cast the IRibbonControl.Context property to Inspector or Explorer (depending on where the button is hosted).
Also keep in mind that in a COM addin there is no reason to create a new instance of the Outlook.Application object (it will be crippled with the security prompts anyway) - use Globals.ThisAddIn.Application
I use watin, because I need to open some websites in the background for which the user needs to support Javascript. I don't know if WatiN is the best for this job, but at the moment it takes very long until Internet Explorer gets visible. I need to disable to popping up of Internet Explorer while using WatiN. User doesn't need to see the opening of sites. Is it possible while using WatiN to visit a website without showing it the user or should I use another alternative which supports JS on client side?
My code at the moment;
public static void visitURL()
{
IE iehandler = new IE("http://www.isjavascriptenabled.com");
if (iehandler.ContainsText("Yes"))
Console.WriteLine("js on");
else
Console.WriteLine("js off");
}
The WatIn.Core.IE class has a Visible property, you can initialize the object like that:
new WatiN.Core.IE() { Visible = true }
This way the IE will just blink on the screen when it's created, and then it will get hidden. You can later control the visibility of the IE with the ShowWindow method of WatiN.Core.IE class - I mean you can show it on the screen if you need, or you can hide again.
I use exactly that trick (of hiding IE) for writing UnitTests (using https://github.com/o2platform/FluentSharp_Fork.WatiN) that run in an hidden IE window
For example here is how I create a helper class (with an configurable hidden value)
public IE_TeamMentor(string webRoot, string path_XmlLibraries, Uri siteUri, bool startHidden)
{
this.ie = "Test_IE_TeamMentor".popupWindow(1000,700,startHidden).add_IE();
this.path_XmlLibraries = path_XmlLibraries;
this.webRoot = webRoot;
this.siteUri = siteUri;
}
which is then consumed by this test:
[Test] public void View_Markdown_Article__Edit__Save()
{
var article = tmProxy.editor_Assert() // assert the editor user (or the calls below will fail due to security demands)
.library_New_Article_New() // create new article
.assert_Not_Null();
var ieTeamMentor = this.new_IE_TeamMentor_Hidden();
var ie = ieTeamMentor.ie;
ieTeamMentor.login_Default_Admin_Account("/article/{0}".format(article.Metadata.Id)); // Login as admin and redirect to article page
var original_Content = ie.element("guidanceItem").innerText().assert_Not_Null(); // get reference to current content
ie.assert_Has_Link("Markdown Editor")
.link ("Markdown Editor").click(); // open markdown editor page
ie.wait_For_Element_InnerHtml("Content").assert_Not_Null()
.element ("Content").innerHtml()
.assert_Is(original_Content); // confirm content matches what was on the view page
var new_Content = "This is the new content of this article".add_5_RandomLetters(); // new 'test content'
ie.element("Content").to_Field().value(new_Content); // put new content in markdown editor
ie.button("Save").click(); // save
ie.wait_For_Element_InnerHtml("guidanceItem").assert_Not_Null()
.element ("guidanceItem").innerHtml()
.assert_Is("<P>{0}</P>".format(new_Content)); // confirm that 'test content' was saved ok (and was markdown transformed)
ieTeamMentor.close();
}
Here are a number of posts that might help you to understand how I use it:
https://github.com/TeamMentor/Dev/tree/master/Source_Code/TM_UnitTests/TeamMentor.UnitTests.QA/TeamMentor_QA_IE
http://blog.diniscruz.com/2014/07/how-to-debug-cassini-hosted-website-and.html
http://blog.diniscruz.com/2014/07/using-watin-and-embedded-cassini-to-run.html
http://blog.diniscruz.com/search/label/WatiN
I follow the steps in this page http://blogs.msdn.com/b/visualstudio/archive/2009/12/09/building-and-publishing-an-extension-for-visual-studio-2010.aspx
I create a TextAdornment project and a search box. I wan to do some different in this page. I want to query a link , get a list in the WPF user control and then write the info into the editor back. so the question is I do not know how to write the text back into the editor in seachbox(WPF user control)?
I searched a lot, and get a way to use the code look like this:
IVsTextManager txtMgr = (IVsTextManager)GetService(typeof(SVsTextManager));
IVsTextView vTextView = null;
int mustHaveFocus = 1;
txtMgr.GetActiveView(mustHaveFocus, null, out vTextView);
IVsUserData userData = vTextView as IVsUserData;
if (userData == null)
{
return null;
}
else
{
IWpfTextViewHost viewHost;
object holder;
Guid guidViewHost = DefGuidList.guidIWpfTextViewHost;
userData.GetData(ref guidViewHost, out holder);
viewHost = (IWpfTextViewHost)holder;
return viewHost;
}
However, the method "GetService" also said not found. I think the reason is this method is for VSPackage. and it is not suitable for Adornment project.
Please help to point how the write the text back into the editor from WPF user control. Thanks!
======================================================================================
Solution:
when creating the SearchBox(WPF User Control), pass through the IWpfTextView to WPF control.and then,it is possible to use this in SearchBox.xaml.cs. Also need to be aware to use the Dispatcher function to keep the UI thread is the active one.
Dispatcher.Invoke(new Action(() =>
{
ITextEdit edit = _view.TextBuffer.CreateEdit();
ITextSnapshot snapshot = edit.Snapshot;
int position = snapshot.GetText().IndexOf("gist:");
edit.Delete(position, 5);
edit.Insert(position, "billmo");
edit.Apply();
}));
The code you have there is if you're in a package and you are trying to figure out what view is currently active...it's overkill for what you're trying to do.
Assuming you started from the TextAdornment template, the adornment object is given an IWpfTextView in the constructor. (If not, you probably have an implementation of IWpfTextCreationListener.TextViewCreated which got it and you need to thread it through.) The IWpfTextView derives ITextView which has a property ITextBuffer. From here, you can call CreateEdit() and edit text from there.
I am building a PowerPoint 2010 C# add-in using Visual Studio 2010. One of the functions of the add-in is to add a shape to the current slide. Once the shape is added to the slide though, I need to prevent it from being copied. That is where I am running into issues. I have looked at all the application level events and am not seeing any sort of beforeCopy or beforePaste type of events.
The only option I can think of right now is to add a keydown event listener to listen for "ctrl+c" and block that if my shape is selected and then create a custom right click menu (not even sure if I can yet) to remove the "Copy" option if my shape is selected. There has to be simpler option though.
Anyone have any ideas how I would prevent a user from copying a shape?
The commands executed by built-in ribbon buttons Microsoft Office can be disabled or re-routed. Microsoft calls this "Repurposing", an introduction can be found here.
So another approach could be to "repurpose" the built-in Copy button with something like this. (Needs to be returned by GetCustomUI to customize the ribbon, see the link above.) This modifies the action executed by the Copy button and the callback method that determines whether the button is enabled or not.
<command idMso="Copy" onAction="copyAction" getEnabled="copyEnabled" />
Implement copyAction to return cancelDefault = true when your shape is selected so it will not be copied.
Implement copyEnabled to return false if your shape is selected. Remember to invalidate the button on selection change events.
Actually, one of both approaches should be sufficient. I guess onAction is easier to implement.
Just to close the loop on this, I am sharing my work-around in the hopes that someone else who has this issue will not waste as much time as I have on this. I ended up just using the SlideSelectionChanged and WindowSelectionChange events and a dictionary to delete my objects that have been coppied.
First, when my shape is added to the stage I add a new entry into the dictionary containing the shape name (in my case it was actually a group of shapes) and its ID.
itemIDDictionary.Add(myGroup.Name, myGroup.Id);
WindowSelectionChange is a fairly simple check. It just looks to see if the newly selected item is in the dictionary already. If it is, it then checks to see if the ID matches. If not, it deletes the item. This works because when you copy and paste an item, the newly pasted item is automatically selected on the slide.
public void itemSelectionChange(PowerPoint.Selection SelectedItem)
{
try
{
if (Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsKey(SelectedItem.ShapeRange.Name))
{
for (int shapeIDCount = 0; shapeIDCount < Globals.Ribbons.Ribbon2.itemIDDictionary.Count; shapeIDCount++)
{
if (!Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsValue(SelectedItem.ShapeRange[1].Id))
{
SelectedItem.Delete();
MessageBox.Show("You can not copy the browser object.\nAdd a new one using the ribbon bar");
}
}
}
}
catch {}
SlideSelectionChanged is just a little bit more complicated as I have to loop through all the shapes on the slide.
try
{
if (SldRange.Count > 0)
{
var showWarning = false;
for (int slideCount = 1; slideCount <= SldRange.Count; slideCount++)
{
int shapeCount = 1;
while (shapeCount <= SldRange[slideCount].Shapes.Count)
{
if (Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsKey(SldRange[slideCount].Shapes[shapeCount].Name))
{
if (!Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsValue(SldRange[slideCount].Shapes[shapeCount].Id))
{
SldRange[slideCount].Shapes[shapeCount].Delete();
showWarning = true;
}
else
{
shapeCount++;
}
}
else
{
shapeCount++;
}
}
}
if(showWarning == true)
{
MessageBox.Show("You can not copy the browser object.\nAdd a new one using the ribbon bar");
}
}
}
catch { }
As I said in my initial post, I am sure there is a cleaner way to do this. I just couldn't find one to save my life.
You can't have your cake and eat it too, apparently.
I'm currently using the System.Windows.Forms.WebBrowser in my application. The program currently depends on using the GetElementsByTagName function. I use it to gather up all the elements of a certain type (either "input"s or "textarea"s), so I can sort through them and return the value of a specific one. This is the code for that function (my WebBrowser is named web1):
// returns the value from a element.
public String FetchValue(String strTagType, String strName)
{
HtmlElementCollection elems;
HtmlDocument page = web1.Document.Window.Frames[1].Document;
elems = page.GetElementsByTagName(strTagType);
foreach (HtmlElement elem in elems)
{
if (elem.GetAttribute("name") == strName ||
elem.GetAttribute("ref") == strName)
{
if (elem.GetAttribute("value") != null)
{
return elem.GetAttribute("value");
}
}
}
return null;
}
(points to note: the webpage I need to pull from is in a frame, and depending on circumstances, the element's identifying name will be either in the name or the ref attribute)
All of that works like a dream with the System.Windows.Forms.WebBrowser.
But what it is unable to do, is redirect the opening of a new window to remain in the application. Anything that opens in a new window shoots to the user's default browser, thus losing the session. This functionality can be easily fixed with the NewWindow2 event, which System.Windows.Forms.WebBrowser doesn't have.
Now forgive me for being stunned at its absence. I have but recently ditched VB6 and moved on to C# (yes VB6, apparently I am employed under a rock), and in VB6, the WebBrowser possessed both the GetElementsByTagName function and the NewWindow2 event.
The AxSHDocVw.WebBrowser has a NewWindow2 event. It would be more than happy to help me route my new windows to where I need them. The code to do this in THAT WebBrowser is (frmNewWindow being a simple form containing only another WebBrowser called web2 (Dock set to Fill)):
private void web1_NewWindow2(
object sender,
AxSHDocVw.DWebBrowserEvents2_NewWindow2Event e)
{
frmNewWindow frmNW = new frmNewWindow();
e.ppDisp = frmNW.web2.Application;
frmNW.web2.RegisterAsBrowser = true;
frmNW.Visible = true;
}
I am unable to produce on my own a way to replicate that function with the underwhelming regular NewWindow event.
I am also unable to figure out how to replicate the FetchValue function I detailed above using the AxSHDocVw.WebBrowser. It appears to go about things in a totally different way and all my knowledge of how to do things is useless.
I know I'm a sick, twisted man for this bizarre fantasy of using these two things in a single application. But can you find it in your heart to help this foolish idealist?
I could no longer rely on the workaround, and had to abandon System.Windows.Forms.WebBrowser. I needed NewWindow2.
I eventually figured out how to accomplish what I needed with the AxWebBrowser. My original post was asking for either a solution for NewWindow2 on the System.Windows.Forms.WebBrowser, or an AxWebBrowser replacement for .GetElementsByTagName. The replacement requires about 4x as much code, but gets the job done. I thought it would be prudent to post my solution, for later Googlers with the same quandary. (also in case there's a better way to have done this)
IHTMLDocument2 webpage = (IHTMLDocument2)webbrowser.Document;
IHTMLFramesCollection2 allframes = webpage.frames;
IHTMLWindow2 targetframe = (IHTMLWindow2)allframes.item("name of target frame");
webpage = (IHTMLDocument2)targetframe.document;
IHTMLElementCollection elements = webpage.all.tags("target tagtype");
foreach (IHTMLElement element in elements)
{
if (elem.getAttribute("name") == strTargetElementName)
{
return element.getAttribute("value");
}
}
The webbrowser.Document is cast into an IHTMLDocument2, then the IHTMLDocument2's frames are put into a IHTMLFramesCollection2, then I cast the specific desired frame into an IHTMLWindow2 (you can choose frame by index # or name), then I cast the frame's .Document member into an IHTMLDocument2 (the originally used one, for convenience sake). From there, the IHTMLDocument2's .all.tags() method is functionally identical to the old WebBrowser.Document.GetElementsByTagName() method, except it requires an IHTMLElementCollection versus an HTMLElementCollection. Then, you can foreach the collection, the individual elements needing to be IHTMLElement, and use .getAttribute to retrieve the attributes. Note that the g is lowercase.
The WebBrowser control can handle the NewWindow event so that new popup windows will be opened in the WebBrowser.
private void webBrowser1_NewWindow(object sender, CancelEventArgs e)
{
// navigate current window to the url
webBrowser1.Navigate(webBrowser1.StatusText);
// cancel the new window opening
e.Cancel = true;
}
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/361b6655-3145-4371-b92c-051c223518f2/
The only solution to this I have seen was a good few years ago now, called csExWb2, now on Google code here.
It gives you an ExWebBrowser control, but with full-on access to all the interfaces and events offered by IE. I used it to get deep and dirty control of elements in a winforms-hosted html editor.
It may be a bit of a leap jumping straight into that, mind.