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.
Related
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).
I've created a custom editor pop up bar. However the selection I pick keeps resetting each time I select a different game object.
Can someone please take a look over my code to see where it keeps getting reset and how I can stop this from happening? The data I input stays there, it's more a convenience thing that I want option I select to stay open when I come back to the editor window.
string[] analytic_Options = {"None", "Button Press"};
private int analytic_Index = 0;
public override void OnInspectorGUI()
{
GUILayout.BeginVertical();
GUILayout.Space(10);
analytic_Index = EditorGUILayout.Popup(analytic_Index, analytic_Options);
UpdateEditorGUI();
GUILayout.Space(10);
GUILayout.EndVertical();
}
void UpdateEditorGUI()
{
switch(analytic_Index)
{
case 0: // No analytics selected
break;
case 1: // Button analytic info
string name_HolderString = EditorGUILayout.TextField("Event Category", target_Object.button_NameString);
if(target_Object.button_NameString != name_HolderString)
{
button_EventNameString = name_HolderString;
target_Object.button_NameString = name_HolderString;
}
break;
}
However the selection I pick keeps resetting each time I select a
different game object.
I guess you are referring to analytic_Index. Everytime your windows (or inspector) is closed, all non serialized properties will be lost. Here an exhaustive description of how serialization works in Unity.
In a few words, for what concern your specific case. Every member variable of Editor class (or EditorWindow) won't be serialized, so it will be lost when the window is closed (or switching to play mode). Generally the SerializedProperties belong to a particular asset (MonoBehavior, ScriptableObject,..), so you can put such a value on the particular asset you are visualizing through your EditorWindow (or Editor).
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 have succesfuly created custom single view editor in my VSPackage. One of many things I had to cope with was reacting to situation when edited file was changed outside Visual Studio - "standard" editors in Visual Studio display dialog with options like "yes", "yes to all" (reload content) etc., so if more files had changed, only one dialog is displayed.
However, the only thing I can do in my VSPackage so far is display custom dialog when the file had changed. It is not pretty - when file edited in my editor changed along with some others, there will be two completely different dialogs displayed to the user.
So the question is - is there any way how to invoke "standard" Visual Studio "file changed outside VS" dialog for my file?
Sounds like you are using the IVSFileChangeEx interface.
This blog post might be almost what you're looking for. Normally this is used for checking to see if a file can be edited, or reloaded and will provide a file dialog prompt to (check-out or reload).
This uses the IVsQueryEditQuerySave2 interface. You probably want to call DeclareReloadableFile, which will "States that a file will be reloaded if it changes on disk."
private bool CanEditFile()
{
// --- Check the status of the recursion guard
if (_GettingCheckoutStatus) return false;
try
{
_GettingCheckoutStatus = true;
IVsQueryEditQuerySave2 queryEditQuerySave =
(IVsQueryEditQuerySave2)GetService(typeof(SVsQueryEditQuerySave));
// ---Now call the QueryEdit method to find the edit status of this file
string[] documents = { _FileName };
uint result;
uint outFlags;
int hr = queryEditQuerySave.QueryEditFiles(
0, // Flags
1, // Number of elements in the array
documents, // Files to edit
null, // Input flags
null, // Input array of VSQEQS_FILE_ATTRIBUTE_DATA
out result, // result of the checkout
out outFlags // Additional flags
);
if (ErrorHandler.Succeeded(hr) && (result ==
(uint)tagVSQueryEditResult.QER_EditOK))
{
return true;
}
}
finally
{
_GettingCheckoutStatus = false;
}
return false;
}
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.