I'm new to sharepoint development and I'm trying to modify the behaviour of the Sharepoint ribbon. As you all know, the ribbon is such that when something else gains focus(e.g a list item), the ribbon automatically switches to an appropriate tab or tab group(e.g the List tools tab group).
I'd like to disable this constant switching of tabs and make the browse tab to always be the active tab, unless the user explicitly clicks on another tab.
I've tried doing the following in the Page_Load() of a Usercontrol, but it only works once, when the page is initially loaded. What am I doing wrong? More importantly, how could I do it right, if at all?
Basically, I'm hoping someone could point me to the event that's fired when the context changes and the ribbon switches, and how I could hook up to this event and force the ribbon to switch back to the browse tab.
protected void Page_Load()
{
string showBrowseTabScript = string.Empty;
showBrowseTabScript = #"
function ShowBrowseTab() {
var ribbon = SP.Ribbon.PageManager.get_instance().get_ribbon();
SelectRibbonTab(""Ribbon.Read"", true);
}
SP.SOD.executeOrDelayUntilScriptLoaded(function() {
var pm = SP.Ribbon.PageManager.get_instance();
pm.add_ribbonInited(function() {
ShowBrowseTab();
});
var ribbon = null;
try
{
ribbon = pm.get_ribbon();
}
catch (e) { }
if (!ribbon) {
if (typeof(_ribbonStartInit) == ""function"")
_ribbonStartInit(_ribbon.initialTabId, false, null);
}
else {
ShowBrowseTab();
}
},
""sp.ribbon.js"");
";
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "BrowseTabScript", showBrowseTabScript, true);
}
Here is my solution to the problem, in case anyone is interested.
Taking Ken Henderson's suggestion into consideration, I was able to achieve what I've been trying to do, although I achieved this by modifying the code of the SP.Ribbon.js and SP.Ribbon.debug.js files. I'm using the SP.Ribbon.debug.js to show my solution below, since it is not as cryptic as the SP.Ribbon.js.
Basically, I use the code below to trick the ribbon into thinking that the User is on a different tab and has clicked on the "Browse" tab. You will notice that I set the old tab information in the code. It will still work without me doing this, but I did it just in case the ribbon needs that information for something else I'm not aware of. This code, in combination with the Page_Load() function I posted in the first post, cause the ribbon to behave just like I needed it to.
SP.Ribbon.PageManager.prototype = {
executeRootCommand: function (commandId, properties, commandInfo, root) {
ULSMg8: ;
var $v_0;
if (!SP.ScriptUtility.isNullOrUndefined(commandInfo) && commandId !== 'RibbonEvent' && (commandId !== 'CommandContextChanged' || (!SP.ScriptUtility.isNullOrUndefined(properties) && properties['ChangedByUser']))) {
// My changes to SP.Ribbon
if (properties["ChangedByUser"] === false) {
properties["ChangedByUser"] = true;
var $NewContextId = properties["NewContextId"];
var $NewContextCommand = properties["NewContextCommand"];
properties["OldContextId"] = $NewContextId;
properties["OldContextCommand"] = $NewContextCommand;
properties["NewContextId"] = "Ribbon.Read";
properties["NewContextCommand"] = "ReadTab";
SelectRibbonTab("Ribbon.Read", true);
}
// End of changes to SP.Ribbon
// the rest of the code has been ommitted for clarity
return $v_0;
}
}
To the best of my knowledge SharePoint doesn't expose any events to detect when the ribbon tabs update (either tabs adding/removing or which is active). At least I was unable to find any a few weeks ago when I was trying to detect when tabs were added/removed (I didn't care which was active just the number/width of them).
(Sorry for the lack of details, the SharePoint dev environment at the office is unavailable at the moment so I can't look up the details very easily.)
There are two possiblities for solving this problem (each has risks/problems):
Override JS Functionality
Figure out what JavaScript function is being called when the user clicks on an item that updates the ribbon. You might be able to replace that function with your own that provides the behavior you want. This would be similar in concept to a custom master page that scrolls on the window and has to change the behavior of the width sizing. I'm unable to verify the details at the moment but it looks like the function is called SingleItemSelect in core.js.
This could be a problem if you have exceptions to when to override this behavior and if MS changes anything in the future you're implementation may break and/or need to be updated.
Add your own event handler
In your JavaScript code try to find an appropriate DOM event to attach an event handler to in the ribbon to detect when MS's code changes the ribbon. There is a good chance given the limitations of the DOM events that there will not be an event to attach a handler to. You may end up adding a function that is called periodically (polling loop/timer) that detects ribbon tab changes and resets the active one.
Honestly this will not work well since there will be flickering as MS's ribbon code changes the active tab and your's changes it back. Additionally you'll need to detect when the user clicks on a tab so that you don't undo their changes.
Wrap up
Honestly I would push back and get this requirement changed so that the ribbon behavior works the way MS designed and not try to fight it. If the ribbon showing up when the user clicks on an item is really an issue then I would propose to the client that instead of forcing the Browse tab as active to add an additional link in the ribbon area somewhere that allows the ribbon (or at least the part that expands over the title area) to be toggled as hidden/shown independently of what MS's JavaScript is doing to the ribbon.
Related
I have a VSTO add-in that I developed for Word 2010 and I'm currently in the process of upgrading to Office 2016. One of the things I do is, in the ribbon xml, disable the save as command (<command idMso="FileSaveAs" getEnabled="IsFileSaveAsEnabled" ...>) with certain documents. However, it appears that Microsoft have added a new Save As tab to the backstage view in Word 2016 that I can't dynamically disable.
The disabling of the save as command effects the Save As button if it's added to the quick access toolbar, but it does not effect the tab on the back stage view. I've tried changing other things on this tab and it appears to ignore any changes I attempt to make.
I'm developing in VS 2017 and automatically migrated the project to Office 2016.
If I add
<backstage>
...
<tab idMso="TabSave" getEnabled="IsFileSaveEnabled" />
...
</backstage>
to my ribbon.xml the IsFileSaveEnabled isn't invoked.
public bool IsFileSaveEnabled(IRibbonControl control)
{
return false; // Not the actual implementation, but you get the idea.
}
In fact even setting the enabled attribute to false does nothing, however this
<backstage>
...
<tab idMso="TabSave" visible="false" />
...
</backstage>
does actually hide the tab. Though this is no good because this will happen for all documents, but I want it to be conditional.
So is it just not possible to disable this tab the way I want to or is there something new I need to do? I can't really find anything else on the web about this.
I'm going to answer my own question here as I thought of a work around. Instead of trying to interact with the built in save as tab I'm just going to permanently hide it and add my own copy of this tab that I create from scratch to look and behave exactly like the built in tab. Bit more work, but can't think of any other way of doing this.
If the visibility should be conditional then the setting needs to be dynamic. That means it requires a callback rather than a static setting:
<tab idMso="TabSave" getVisible="procedureName" />
This procedure needs to be in the Ribbon1.cs (or whatever the class is called in your VSTO project) and the logic for making the control visible (or not) - same as IsFileSaveEnabled. The callback will trigger when the Ribbon loads the first time, and any time the control (or entire Ribbon) is invalidated (Ribbon.Invalidate / InvalidateControl(controlID)). This would usually be done in an event such as DocumentOpen, DocumentClose, DocumentChange, etc.
I'm assuming you already have procedures to initialize a Ribbon object in your code (GetCustomUI and Ribbon_Load).
Here's a simple example I have in a test project, that toggles the visibility of a Group:
private Office.IRibbonUI ribbon; //initialized via Ribbon's load event
bool bGetVisible = false;
//triggered by clicking a Ribbon control
public void ShowFontGroup_Click(Office.IRibbonControl ctl)
{
bGetVisible = true;
ribbon.Invalidate(); //triggers all "get" callbacks in the Ribbon
}
//callback triggered by invalidating the Ribbon
public bool GroupFont_GetVisible(Office.IRibbonControl ctl)
{
return bGetVisible;
}
I don't have Word 2016 so I can't verify this, but you could probably intercept the save event and abort when it's a Save As.
// this is for a document-level add-in; do this in your startup method
BeforeSave += new SaveEventHandler(ThisDocument_BeforeSave);
private void ThisDocument_BeforeSave(object sender, SaveEventArgs e)
{
e.Cancel = e.ShowSaveAsDialog;
return;
}
I do something similar in my add-in and it works well.
So, I've finally had to deal with this annoying issue. It seems that it's a known "bug" and there doesn't seem to be a great work-around. I was wondering what seems to be the best work around for this.
A little bit of info. In my form region I have a Winform control and a WPF control. The user can't do certain key combinations on the Winform control (Ctrl-A to select all, Delete key deletes email instead of highlighted text), but everything works fine on the WPF control.
I've tried adding the Winform control to the WPF control using a WindowsFormHost, but that made it worse as it wouldn't register the backspace key after that. I tried capturing the delete event for the email, but I can't get the .BeforeDelete to trigger. Same for the Explorer.BeforeItemCut event. Currently I'm trying to capture the WndProc event to re-direct the key events, but it seems like there should be a better/easier way.
Not sure how to continue from here. Any help in direction is welcomed. Below is my how I'm trying to capture email delete event.
Outlook.MailItem _selEmail;
// This does get triggered
private void Explorer_SelectionChange()
{
var actExplorer = this.Application.ActiveExplorer();
if(this.Application.ActiveExplorer().Selection.Count > 0)
{
var selObject = actExplorer.Selection[1];
if(selObject is Outlook.MailItem)
{
_selEmail = selObject as Outlook.MailItem;
_selEmail.BeforeEmailDelete -=
new Outlook.ItemEvents_10_BeforeDeleteEventHandler(Email_BeforeDelete);
_selEmail.BeforeEmailDelete +=
new Outlook.ItemEvents_10_BeforeDeleteEventHandler(Email_BeforeDelete);
}
}
}
// Haven't gotten this to trigger. The Console.Write("") is there
// only for a breakpoint;
private void Email_BeforeDelete(object sender, ref bool cancel)
{
Console.WriteLine("");
}
First of all, I'd suggest breaking the chain of property and method calls and declare each property or method call on a separate line of code. Thus, you will be able to release underlying COM objects inplace. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. You can read more about that in the Systematically Releasing Objects article in MSDN.
Try to turn off the Single key reading using the space bar option in Outlook which allows you to move quickly through your messages in the Reading Pane. The space bar scrolls the current item. At the end of the item, it jumps to the next unread item in your message list.
Finally, using WPF controls on Outlook forms produce a well-known issue. Outlook has a habit of swallowing various keys and not sending them along to your code or form region. The spacebar, tab, backspace keys are among those affected when the keys are pressed in the reading pane. You can find a similar forum thread.
I know that the dialog(showMessage) is a closed API and that you can not force a click event on Dialog with any web-based technologies such as jQuery or Javascript. The instance of the window within the browser is single threaded and locks the thread until the dialog receives an event. This I understand.
What I am trying to do is simulate a click event pragmatically for Test Case purposes. I am using the Telerik testing framework to run these Test Cases in C# .NET 4.5 environment.
So is it possible to simulate this click event? It is testing the behavior of one our buttons that when clicked the user must confirm they are leaving the page without saving changes.
Thanks to all in advance!
I am not familiar with Telerik's testing tools, but as far as i know the only way to "issue" such a click would be with a ui macro that automated mouse motions and actually clicked the screen at a particular location.
That said, you may be able to solve your problem by using a mocked method. Rather than directly calling window.prompt, instead define your own prompt function along the lines of:
debug = true; //remove or set to false when not testing
var myPrompt = function(){
if(debug){
return "Greetings, Program";
} else {
return prompt("Please enter your greeting:","Greeting");
}
}
You can naturally set this up for other types of message box, so long as you keep the type they return in mind.
Windows Form Application – Manipulating input-elements in WinForm WebBrowser
Although I am familiar with HttpWebResponse/HttpWebRequest to login to a website, I was trying it now via using the mshtml library and found some weird behavior and I would like to see if someone else might be able to help me out here..
I have an HTML login page with a java backend with a Username field, a Password field and a Button.
The logic is very basic, I have a built a winform app with a built in webbrowser.
At the Document_Completed event I use the following code to enter my settings and to click the button.
private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowser.Url.ToString() == #"MyWebPage/signin")
{
HTMLDocument hdc = new HTMLDocumentClass();
hdc = (HTMLDocument)webBrowser.Document.DomDocument;
IHTMLElement elb = hdc.getElementById("login_button");
IHTMLInputElement elu = (IHTMLInputElement)hdc.getElementById("username");
IHTMLInputElement elp = (IHTMLInputElement)hdc.getElementById("password");
try
{
elu.value = "MyID";
elp.value = "MyPwd";
elb.click();
}
catch { }
}
}
Apart for this code being very quick and without error handling, it should do the trick and it does, partially..
There are two scenario's:
I launch the tool, it loads the webpage.
The tool populates the UserID field and the Password field correctly
The tool fails to click the button
I click the button manually, I am logged in, I click logout, I am back at login page
I immediatly logged in again, the tool enters the information
The tool immediatly clicks the button as well.
Is there anyone who might be able to explain me why this happens and how I could get around this with the current setup (hence not using HttpWebRequest). I don't see the difference between loading the page at startup or being redirected after logout, but apparently there is a difference in there or I am doing something wrong.
Any feedback on this matter is very much appreciated.
Thanks,
Kevin
EDIT:
I added a Button to my Windows Form that bas the same backend Code as below in order to click the button on the webpage, this works perfectly.
I triggered clicking this button in the webBrowser_Completed event but it doesn't work.
For some reason, everything I add to the webBrowser_DocumentCompleted event does not allow me to trigger the click event for the button in my WebBrowser control. Once that entire event has completed, if I then try to trigger it it works but I would like to automate this.. Any advice?
This might be a long shot and not the most elegant workaround but how about letting a backgroundworker run for a second in your DocumentCompleted event that then triggers the button that you clicked from it's seperate thread. This might just get this automated.
As this will run from a different thread, keep in mind that you might have to invoke certain controls so this might be another downside to this workaround..
If this doesn't work then, as Regfor previously suggested, Watin.org can help you out.
how about this :
HtmlElement button = webBrowser.HtmlDocument.GetElementById("login_button");
button.InvokeMember("click");
it works in my program.
I have a frustrating problem. Here's a simplified version of what I'm doing:
A UserControl in c# contains a toolbar and an embedded WebBrowser object. The toolbar contains an "Edit" button, which when clicked sets the webbrowser control in design mode. Another button, "Cancel", turns off design mode.
Pseudocode (very simplified):
public void SetDesignMode(bool dm) {
IHTMLDocument2 doc = webBrowser.Document as IHTMLDocument2;
if (dm) doc.designMode = "On";
else doc.designMode = "Off";
_designMode = dm;
ReloadDocument(); // setting designmode clears the document element, so it must be reloaded
}
public void OnLoadCompleted() {
IHTMLDocument2 doc = webBrowser.Document as IHTMLDocument2;
if (!_documentLoaded) {
if (_designMode) doc.designMode = "On";
else doc.designMode = "Off";
ReloadDocument();
_documentLoaded = true;
}
}
public void ReloadDocument() {
_documentLoaded = false;
// code that navigates to the document
}
The problem:
If I click on the displayed web page, and then on the "Edit" button, the WebBrowser control will not become editable. The mouse pointer when hoovering over pictures/links show the web browser navigation mouse pointers, not the editing ones. If I click in the text, the caret won't display.
Debugging reveals that the designMode property on the document is actually set to "On" in this situation, but the control is behaving as if it is set to "Off".
If I don't click in the web page before clicking the "Edit" button, everything works as expected.
Elaboration:
If I click the "Cancel" button when the control is in design mode, I get the corresponding (mis)behaviour, if the document have been clicked in.
Simply clicking on "Edit", then "Cancel", then "Edit" etc. without ever clicking in the document works fine (the mouseover test shows the proper mouse pointers, and I get link navigation or editing depending on the design mode if I click a link in the displayed document).
I've tried various techniques to make sure that another control gets focus before I change the designMode property, but it doesn't make any difference. I've searched MSDN and half of the known internet and haven't found any mention of this kind of problem. Flipping the designMode property like this seems to be quite unusal.
One more tidbit of information: I'm setting up document events by advising the document with a sink implemented by the usercontrol. I doubt that this should have any bearing on the problem, but I've included it here for the sake of being complete. Update: Disabling this doesn't change anything regarding the problem.
Does anybody recognize this problem?
Update:
I've worked around the problem by re-creating the web browser control in SetDesignMode(). It's an ugly solution, but it works and does actually look ok. I'm very interested in any feedback on this problem, though. I believe it is a bug in MSHTML.
I'm not quite sure if we had exactly the same problem, but I suppose my solution should work for you as well.
The basic issue seems to be that x64 reset the designMode attribute, as noted in this article. In my case, I set it to "On" after instantiating the webbrowser, but in the DocumentCompleted event, it was "Inherit" again. Setting it back to "On" in DocumentCompleted makes it editable, but clears the document. Setting the DocumentText again restarts the whole doom loop.
So one solution I found was to refrain from setting the DocumentText, instead I created an empty document, then set the body's (which at this point is no longer null) InnerHtml property:
doc.designMode = "On"; // enable editing
// designMode change resets the document, create it anew
webBrowser1.Document.Write("<html><body></body></html>")
webBrowser1.Document.Body.InnerHtml = "myDocumentText"
Obviously, this works only if you have the text ready, and not if you're navigating to an URL. However, there is another solution which worked for me, which seems easier and safer. I found it in this answer by LaughingJohn. I guess the first line depends on your application, you had the IHTMLDocument directly in webBrowser1.Document.
doc = webBrowser1.Document.DomDocument as IHTMLDocument2;
if (doc != null && doc.body != null)
((HtmlBody)doc.body).contentEditable = "true";
It sounds to me like the WebBrowser gets the focus when you click on it and somehow holds on to it. Try this: click on the WebBrowser, then press the Tab key on the keyboard (which should move the focus off the WebBrowser) and then see if you can click on your buttons.
If you can, then try attaching a handler to the Button.MouseEnter event and call ((Button)sender).Foucs() in it to focus the button programmatically.