Visio: catch switching window/document event - c#

I developed an add in for Visio and I'm having some trouble with catching
certain events. I have a custom ribbon for my add in and what I already can do,
is enabled or disabled (show/hide) it based on the document.
But now I would like to have multiple documents open and check when switching to
another document if the ribbon should be enabled or disabled. However I can not seem to find the right event code for this. I tried the following codes but with no luck:
- PageChanged
- WindowChanged
- BeforeWindowPageTurn
- WindowTurnedToPage
So concretely what I would like to do is catch the event when one document loses focus and another one gets the focus.

You could listen to the Application.WindowActivated event and then in the handler, inspect the ActiveDocument or the window.Document property (in the eventargs) to decide whether it is a document that you're interested in. You might also want to listen to DocumentCreated and DocumentOpened (on Application) and only start listening to the window event once you know that the application might be handling one of your documents.

Related

Struggling with WPF internal methods

UPDATE: So, I have a solution to my immediate problem. I haven't succeeded in making "my own" TreeView class. But, the reason I wanted to do that was because controls based on ButtonBase don't function in a Popup from a TreeView, and with the help of #MarkFeldman, I have found a solution that comes at it from a different angle.
The problem is that the MouseDown and MouseUp events bubble, and that bubbling crosses the logical tree boundary between the Popup and its owner. So, when you click on something hosted inside the Popup, the TreeViewItem and TreeView that ultimately own the Popup get to hear about it. This then triggers code inside the TreeView that checks, "Do I have focus?", and if not, helpfully sets focus back to itself -- but being a separate logical tree, the Popup has its own focus context, and so this effectively steals focus from the Button control while it is in the middle of processing a click. The Button responds to this by ignoring the click.
This erroneous handling in the TreeView only happens when MouseDown and MouseUp events reach it. What if there were a way to prevent it from seeing those events in the first place? Well, if you intercept the PreviewMouseDown and PreviewMouseUp events and mark them Handled, then the framework doesn't generate MouseDown and MouseUp events to begin with.
Looking at the Reference Source, it looks like ButtonBase's click handling is tied up in a couple of protected methods:
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Primitives/ButtonBase.cs,414
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Primitives/ButtonBase.cs,478
This means you can call them from your own subclasses! So, instead of making "my own" TreeView where all controls behave properly, instead I can make "my own" CheckBox that works properly in a Popup from a TreeView. Since all of the actual click handling is directly accessible, and the events it normally responds to use the same EventArgs type as the Preview events, and on top of it the default handling takes care of marking the events as Handled, the entire implementation boils down to this:
public class CheckBoxThatWorks : CheckBox
{
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) => base.OnMouseLeftButtonDown(e);
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e) => base.OnMouseLeftButtonUp(e);
}
Nice!
ORIGINAL QUESTION:
I need to make a clone of the TreeView WPF class -- a copy of the control that runs out of my own code. (There is a bug in the control and Microsoft doesn't seem to deem it high-enough priority to fix, and it makes it completely impossible to host buttons (including check boxes and radio buttons) within pop-ups shown from TreeViewItems. link)
I am running into serious difficulties with the amount of internal shenanigans Microsoft has undertaken in the implementation of base WPF controls. A couple of issues I've bumped into:
Controls have a property HandlesScrolling that allows them to take over basic scrolling handling from ScrollViewer. But, it's marked internal, making it seemingly impossible for me to have a control of my own that does its own handling of scrolling from keyboard input. I was going to try having my TreeView handle keyboard scrolling in OnPreviewKeyDown instead of OnKeyDown, so that it can prevent KeyDown events from being raised for the keys it intercepts. I haven't gotten far enough to know what caveats there might be about this.
The Visual States system allows you to declare what styles should be applied when different states are entered, but actually entering states seems to be tied up in the virtual method ChangeVisualState on the Control type. All controls that want to switch between visual states can override this method and inspect their state to determine which Visual State should be shown. Oh wait. They can't because the method is internal! Apparently only Microsoft gets to create controls that set their own visual states??
Are there any strategies I can use to work around these limitations, or am I just completely out of luck?

Cannot read held state of key in WPF inside mouse event handler - Inconsistent behaviour in Windows guest on VMWare Fusion for Mac

This issue is caused by an inconsistent setting in vmware fusion - the secondary mouse function of MacOS (ie, right click) was being triggered by the control-left click, in spite of being configured in vmware fusion settings to send control through as a separate key.
I am leaving this here as it outlines the diagnostic steps taken to isolate the issue.
This behaviour might be present in other virtualisation solutions on MacOS.
This is not a duplicate - I have tried the approaches suggested - They Do Not Work. Genuine help is required please.
I am trying to implement a multiple select function in a WPF canvas, and need to be able to detect when either control key is held while clicking on an item.
I have already tried using the documented and accepted methods of getting the key (Keyboard.IsKeyDown and Keyboard.Modifiers) and although they are supposed to work, they do not in this case.
I cannot place a key handler in the main window of the application as this solution is implemented in a component, and as such, I do not have access to the main window.
I cannot create a keyboard OnKeyDown override, as it does not register the event. I do not know why this is, but suffice to say, I have tried all the methods I can find after an extensive search on Stack Overflow, and am currently out of ideas.
In the current logic, clicking an item in my canvas will clear any selected items already. If the control key is held, I will not clear selected items.
The structure of my application is as follows:
Main Window -> Dock Panel -> ScrollViewer -> Main Custom Component (extends Canvas) -> ChildCanvas objects (multiple instances, all also extend Canvas).
The component will not have any reference back to the main window - this is absolutely not negotiable.
The component must not require a reference to the main window to be passed in to it.
The component cannot rely on any logic, properties, methods or other functional code being placed in the MainWindow - it must be totally reusable.
Multiple instances of a custom object (ChildCanvas) which itself derives from Canvas are placed onto a base custom object (ExtendedCanvas) which also extends Canvas.
I am detecting the mouse click in a mouse event handler within my custom item.
Although I can in theory handle the key event and set a bool flag when the key goes down and clear it when the key goes up, this is not a reliable way to do things as it would require the focus to be on whichever component is handling the key.
All the examples I have looked at require a specific event handler for the key press, but I cannot apply that approach in this case as it simply does not seem to register the key press, even if the component where the event handler is implemented has focus at the time.
I would like to find a way to check if a key is held down in the mouse click handler if this is possible in WPF.
EDIT: I have tried using the Keyboard.Modifiers approach, but this returns no result.
EDIT: I have also tried Keyboard.IsKeyDown(Key.LeftCtrl), which does not return any values.
EDIT: I have tried to handle the key event globally by registering on the EventManager as shown. What I have found is that key up and key down events are triggered in quick succession. The first event has the property e.IsRepeat as false, and all subsequent event occurrences have e.IsRepeat set as true. Using these events, I have set a bool which is set to true in the first keyDown event is e.IsRepeat is false (the first initial key press), and set to false in the keyUp event if e.IsRepeat is false (the keyUp event fire when the key is physically released).
This does not work - clicking the item on the canvas when the key is held altered the state of the bool setting it to false.
This behaviour is totally inconsistent with what Microsoft document for the way the keyboard handling should work.
EventManager.RegisterClassHandler(typeof(Control),
Keyboard.KeyDownEvent,new KeyEventHandler(keyDown),true);
I can call Keyboard.IsKeyToggled(Key.LeftCtrl) which does show the toggled state, but it changes with every key press, and does not show me whether the key is held down at the time of the mouse click.
public class ExtendedCanvas:Canvas {
public ExtendedCanvas() {
MouseUp += thisMouseUp;
}
void thisMouseUp(object sender,MouseButtonEventArgs e) {
if ((Keyboard.Modifiers & ModifierKeys.Control) > 0) {
//This does not work - no key is registered, as for some reason, Keyboard.Modifiers does not register that the key is held.
}
//If CONTROL is held down
if (!ControlKeyHeldDown) {
ClearSelectedItems();
}
IsSelected = true;
}
}
The inconsistent behaviour is the result of a VMWare option.
This VM is being run on MacOS and in spite of VMWare Fusion being configured to send the Control key straight through, Control Click was still triggering the secondary mouse button (ie, right click in this case).
The solution is:
1) Shut down the VM.
2) In VMWare Fusion Preferences -> Keyboard And Mouse -> Mouse Shortcuts, ENABLE (Check) the "Secondary Button" option if it is not checked.
3) Close the preferences window to save the settings.
4) Close VMWare Fusion
5) Reboot the host system
6) In VMWare Fusion Preferences -> Keyboard And Mouse -> Mouse Shortcuts, DISABLE (UnCheck) the "Secondary Button" option.
7) Start the VM, and confirm that Control Click no longer triggers the secondary click.
This option had been configured to send Control straight through prior to upgrading VMWare Fusion from 8 to 10.
It appears that although the Secondary Function checkbox was unchecked, the property was set to true, and it was behaving inconsistently from the way the settings suggested it should.

Registering for UIAutomation structure change events

I am working on an application that is monitoring a given application for automation events. Currently, I am working specifically with structure change events on a WPF application that I developed.
public void MonitorStructureChangedEvents(AutomationElement element)
{
Automation.AddStructureChangedEventHandler(element, TreeScope.Subtree, OnStructureChanged);
}
where in this case, element is the root AutomationElement of the Application (its main window). The WPF application in question is just a Window with a grid view and various controls (text boxes, checkboxes, buttons, etc). It is a test app I have developed specifically for testing UIAutomation events.
I am using a Unit Test project to test these events, and I am launching the application in the ClassInitialize decorated method. I do not register for StructureChanged events until the application is launched and I have located it via WMI in my TestMethod. The application is spawned as a new process.
However, upon registering for structure changed events, I receive structure changed events for all the elements in the main window of my application, even though the WPF application is effectively idling. I have buttons in the main window that add and remove controls to test StructureChanged events, and it does work, however I am unsure why when I initially register, all of the elements fire a structure changed event.
Edit: After further testing, I notice that these events are fired as soon as I either click on the application window, or hover over a button. It then fires a structure changed event for every element in the app one time. After it is done, it no longer fires a structure changed event if I hover over a button, or click on the application (even after clicking on another application or the desktop)
Edit2: After further testing, I believe I figured out the cause of the issue, but no solution yet. When I try to TreeWalker.RawViewWalker.GetFirstChild(rootApplicationElement) I receive a null. It appears that the AutomationElement that I am acquiring has no children cached. Once I add the StructureChanged event handler on the element, the TreeWalker method works, and I get a valid element. It seems when I activate the window after this, that's when it realizes that it now has all these new child elements. Is there a way to cache all the descendants of the rootApplicationElement so that before I add the event handler, I can walk the entire subtree?
I was able to solve the problem by using the following method
CacheRequest request = new CacheRequest();
request.TreeScope = TreeScope.Element | TreeScope.Descendants;
using (request.Activate())
{
rootApplicationElement = AutomationElement.RootElement.FindAll(TreeScope.Children,
new PropertyCondition(AutomationElementIdentifiers.ProcessIdProperty, ApplicationInstance.ProcessId))[0];
}
I do not advise that people acquire a root element in this fashion, since if an application has more than one window you will get more than one result, but this was tailored to a specific testing need.

How to create a custom .NET ComboBox with the textbox active during edit

I'm trying to implement my own combobox like a lot of folks before me. What I want to accomplish is a combobox that filters and highlights items in the dropdown list while the user is typing in the combo textbox. The behaviour of a regular combobox after you click the arrow button is that the dropdown pops up and the focus stays in the textbox. This way you can start typing right away.
In order to customize the dropdown control you have to implement something from scratch. Most of the implementations that I've come across use either a Form or a ToolStripDropDown to host the custom control. Both are toplevel controls which means that you have to somehow close it yourself if the user clicks somewhere outside the dropdown. ToolStripDropDown does this automatically if AutoClose is true, but also somehow steals the combo textbox the focus on show if it is activated. A Form must be shown using ShowWithoutActivation() in order to prevent it from stealing the focus.
The problem is that the dropdown does never close unless I click somewhere within the dropdown and therefore activate it.
Another twist is that the combobox control is supposted to be hosted in an MFC application instead of a pure WinForms app.
The dropdown never being activated (gaining focus) is the main complication here. Otherwise you could just use the forms Deactivate event to hide it. The way to go here is to add an IMessageFilter to the WinForms application and catch mouse click messages. The message filter then determines whether the click took place outisde of the dropdown and closes it. If you are creating a WinForms application you are done.
Some extra work is necessary if you are for example in a MFC application hosting the control in a MFC window. In that case your IMessageFilter is useless. The reason is that the WinForms Application is never being run and therefore the event pump is never being invoked. Instead the MFC message pump does all the message handling. To solve this issue I've come accross a neat trick to activate the Application message pump in MFC applications.
In MFC applications there is usually an equivalent to the WinForms Application which is CWinApp (or CWinAppEx). The trick is to tap into the PreTranslateMessage method and serve the WinForms Application message pump before (or after) the MFC message pump:
BOOL CWinApp::PreTranslateMessage(MSG* pMsg)
{
if (FilterWindowsFormsMessages(pMsg))
{
return TRUE;
}
return CWinApp::PreTranslateMessage(pMsg);
}
BOOL CWinApp::FilterWindowsFormsMessages(MSG* pMsg)
{
Message message = Message::Create(IntPtr(pMsg->hwnd),
int(pMsg->message),
IntPtr((void*)pMsg->wParam),
IntPtr((void*)pMsg->wParam));
if (Application::FilterMessage(message))
{
return TRUE;
}
return FALSE;
}
This way the registered IMessageFilters are being served and everything works fine.

Designing a cancellable operation in WinForms (Save pending changes)

We are developing a .NET application that uses a few custom controls.
One specific control is a simple "text editor" like control, where some content gets loaded into, based on the user's selection.
In cases where the user modifies the text in this control, and then makes a different selection which would overwrite that content, we would like to have the usual popup saying "Do you want to save pending changes? YES/NO/CANCEL".
I am considering which level of the application should be the one responsible for this: the control itself or the code that uses it?
I have been looking into the existing CancelEventArgs class, and considering using it, however i am not sure if this is a good fit for this specific scenario.
Example code i was thinking of:
When setting the text of the custom control, raise the "BeforeChanged" event. This will be handled and will allow to cancel the operation.
public void SetText(string text)
{
CancelEventArgs args = new CancelEventArgs();
// Raise the BeforeTextChanged event.
BeforeTextChanged(args);
// If the user cancelled the operation - do not modify text.
// For example, user code will check if needs saving, show the popup, etc.
if (args.Cancel)
{
return;
}
}
I don't think you need to handle this with events - it will just complicate matters. This is linear problem and a dialog popup will halt the current thread and give you enough control to handle the user's decision. The event in this scenario is the 'UserSelectionChanged' event. No need for more.
It looks ok in both cases but to keep your logic consistent; In case, you have encapsulated the file loading/contexts loading inside the user control then keep the use events. otherwise, handle it on the target form/control.

Categories

Resources