WPF context menus and other popups appearing in top left of window

Scott St 21 Reputation points
2020-10-29T18:10:46.023+00:00

I have a WPF application that has a portion of it (in a tab) where I have a Canvas where I place some custom controls that act is either a "node" or a "flow line" between nodes. They can be clicked to select, dragged about or use arrow keys to move them. They also have context menus. Everything seems to be working correctly, except at some point sometime all the context menus on the nodes when they are right clicked appear in the top left of the screen; on multiple monitors it is always the left screen even when the app is running on another. The quickest way to trigger this bad behavior is to select multiple nodes (10+) and move them multiple time (5+). It doesn't seem to matter if you use the keyboard or the mouse to move them.
Once in this bad state , the right click is slow to respond and the context menu appears in the top left. i even left the app sit idle for an hour and it was still wacked.
Closing that tab obviously destroys all those visuals. Then reopening it and everything works fine again.
I've check the visual tree when it is wacky and everything looks good; I even managed to catch the PopUpRoot and look at the context menu once and it appeared just fine, but disappears from the visual tree too quickly (even when break in debugger) to really dig into it.
When wacky and I break the app, it is basically idle, and no worker/thread pool threads doing anything and the UI thread shows as just sitting in App.Run ... presumably waiting on a keyboard or mouse hardware event &/or other Windows message to process.

I speculate that an exception is happening in WPF so it isn't getting a proper location (Point?) for the context menu so it ends up being at 0,0. I figure it is an exception because of how long it takes for the context menu to appear in the wrong place. Therefore I've tried debugging into the Framework (4.6.1) following the MS documentation and it gets symbols and I downloaded both versions of the source and have tried point at either, but a can't get VS to show me source for almost anything. Only a few places in the call stack will it prompt for or show source, everything else just shows the disassembly view.

This is a proprietary application so I can't give source (which is quiet large) and Since I have no idea of the cause I'm not even sure how to attempt to replicate the problem is code I could share.

Does anyone have any idea how to approach diagnosing this, or any ideas on what could be failing?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,761 questions
{count} votes

3 answers

Sort by: Most helpful
  1. Scott St 21 Reputation points
    2020-10-30T17:35:57.053+00:00

    Why am I limited to 1000 characters in a reply???
    I'll have to break up what I was going to say into multiple replies ... I have no idea how one would post any code with such a small limit.

    The work area where the nodes and flow lines are placed is a Canvas in a ScrollViewer.
    In the XAML:
    The ScrollViewer has InputBindings like this:
    <KeyBinding Gesture="Down"
    Command="{Binding Move}"
    CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Gesture}" />
    There is a DataTemplate that defines what a node is displayed like.
    <DataTemplate>
    <local:NodeIcon Focusable="True"
    Icon="{Binding Icon}"
    IsModified="{Binding HasBeenModified}"
    IsSelected="{Binding IsSelected}"
    Location="{Binding Location}"
    ...
    >
    <local:NodeIcon.ContextMenu>
    <ContextMenu>
    ...
    </ContextMenu>
    </local:NodeIcon.ContextMenu>
    </local:NodeIcon>
    </DataTemplate>

    NodeIcon is a custom control based on Canvas that basically shows a central icon surrounded by a small constellation of little icons that are shown or hidden based on other properties, like IsModified, and defines a bunch of dependency properties for them all as well as the Location and other things.

    The view model's ICommand for the Move binding calls into a method named MoveExecute.
    MoveExecute casts the parameter received as object to a KeyGesture and then passes it to a subroutine named MoveSelection.
    MoveSelection goes through the list of node view models and collects those where the IsSelected property is true.
    Then it adjusts their Location property (a Point) based on the KeyGesture.
    Ex. if the Gesture is the Down one show above, it adds something like 30 to the Y of the Location.

    The NodeIcon.Location dependency property's changed method uses a helper method to walk up the visual tree to find the ContentControl that the data template for the node is actually expanded into, then it uses Canvas.SetTop and Canvas.SetLeft on that content control using the Point that was received via the binding. That causes the node to move to the new location.

    There may be a lot of code in this application, but there really isn't much going on for moving the nodes around on the screen. And they move just fine ... never had a problem with that. It is just that right after moving a bunch of nodes several times the context menus on the nodes that were moved as well as the context menu on the Canvas (white space between nodes) and other popup type things within the same area of the visual tree, all start showing up in the wrong place.

    Most often the context menus show up in the top left corner of screen 1, but occasionally they will display some distance directly to the right of where they should, and even more rarely will display with the bottom cropped off (the frame and some items are just not there) almost as if it were sticking off screen even though it is nowhere near the bottom of the screen, and context menus normally can only stick off screen if they are taller than the entire screen.


  2. Scott St 21 Reputation points
    2020-11-04T14:09:25.07+00:00

    I started to attempt building a demo app that would reproduce the problem but ended up just taking the existing app and commenting out code here and there until the issue disappeared, then narrowed in on that. I discovered that if the creation of flow lines did nothing, then no issues, so the cause must be in them. Digging deeper I isolated it to the code that runs when they are moved. Digging into that I found that if I cleared the entire shape and rebuilt it at every move, then the problem was gone too ... but performance was terrible on a large diagram with lots of flow lines being affect by changes/movements.
    This is the code directly from the flow line, which inherits from Shape:

    protected override Geometry DefiningGeometry
    {
        get
        {
            // If nodes have different relative position than prior flow line
            if( m_FlowLineResult != m_LocationEnd - m_LocationStart )
            {
                // Then (re)build the flow line
                m_FlowLineShape.Clear();
                BuildCurveFlowLine();
                m_FlowLineResult = m_LocationEnd - m_LocationStart;
                m_FlowLineStart = m_LocationStart;
                // Make sure it isn't moved (i.e. at the location just built)
                this.RenderTransform = null;
            }
            // If flow line starts at different location
            else if( m_FlowLineStart != m_LocationStart )
            {
                // Move the existing flow line to the new location
                this.RenderTransform = new TranslateTransform( m_LocationStart.X - m_FlowLineStart.X, m_LocationStart.Y - m_FlowLineStart.Y );
            }
            else
            {
                // Make sure it isn't moved (i.e. moved back to original location)
                this.RenderTransform = null;
            }
    
            return ( m_FlowLineShape );
        }
    }
    

    I forgot it, but the movement code was put in place exactly for the performance issues I described above. I'm not sure why a RenderTransform was used, but they are supposed to be fast. The thing is, if I change line 19 which sets the RenderTransform to this more extensive code:

    if( this.RenderTransform is TranslateTransform tt )
    {
        // Relocate an already moved flow line to the new location
        tt.X = m_LocationStart.X - m_FlowLineStart.X;
        tt.Y = m_LocationStart.Y - m_FlowLineStart.Y;
    }
    else
    {
        // Move the existing flow line to the new location
        this.RenderTransform = new TranslateTransform( m_LocationStart.X - m_FlowLineStart.X, m_LocationStart.Y - m_FlowLineStart.Y );
    }
    

    Then the problem seems to go away, or at least as we've been able to reproduce it. I can't say whether a more extensive/complicated/larger change to my diagrams might still cause the problem. If not, then I'm still at a loss as to explain how changing the RenderTransform repeatedly on multiple Shapes on a Canvas somehow breaks the location of ContextMenus. i can only speculate the WPF is somehow caching the transform reference somewhere to use to apply on the mouse click to hit test the item clicked and gets an exception because the transform is gone (replaced) which explains the severe lag from the right click to the appearance of the ContextMenu, and that the wrong location is also because it can't really locate the Shape to place it either.

    In any case, I find it amazing that code run by the UI thread to get the Shape's Geometry can break WPF by setting its own RenderTransform. I HOPE my change has actually fixed the problem not just made it harder to recreate.


  3. Scott St 21 Reputation points
    2020-11-06T17:20:22.183+00:00

    The description of the problem was fairly extensive ... Context Menus on controls on a Canvas appear it totally the wrong place.
    The controls exhibiting the problem were in my case custom but seeming could be anything; mine were showing a little cluster of Images. My "node" controls.
    The seeming solution implies that setting the RenderTransform in the DefiningGeometery override of custom Shape control to a new instance of a TranslateTransform on each call. There were my "flow line" controls.
    The trigger for the problem is changing the offset (X,Y translation) used on many of the Shapes several times in a row.
    In my case I could reproduce the problem with 12 nodes each with a flow line connecting to a 13th node, and then using the arrow keys to change the location of all the objects in their view model which was bound to custom dependency properties which set attached properties for the nodes using Canvas.SetTop and Canvas.SetLeft but in the flow lines set a field with the value that was used in the TranslateTransform. I typically had to click an arrow key 5 to 10 times to trigger the problem.
    I have a 25MB MP4 video showing the issue, but I have no idea how to share that here.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.