Krypton-Suite-Legacy-Archive / Krypton-NET-5.470

A update to Component factory's krypton toolkit to support the .NET 4.7 framework.
BSD 3-Clause "New" or "Revised" License
78 stars 20 forks source link

Random KeyNotFoundException while using krypton controls #82

Closed Tape-Worm closed 5 years ago

Tape-Worm commented 5 years ago

I've had this come up several times now, with no real discernible pattern. I have a krypton tree (and a ribbon, and a few other controls) which I use as a file system explorer. When I click on the node to open a file I randomly get the key not found exception. Note that when I click on the file node, the ribbon is updated to enable/disable certain buttons, so given the stack trace and order of operations, I assume the problem is with the ribbon.

I noted that someone posted an issue for the same problem for the original krypton toolkit. This issue is dated August/2018 and still not resolved. Considering the severity of this bug, that's not inspiring me with confidence in these components.

This is the stack trace for the exception:

Error Message: The given key was not present in the dictionary. Exception Type: System.Collections.Generic.KeyNotFoundException Source: mscorlib Target Site: System.ThrowHelper.ThrowKeyNotFoundException Stack trace at Gorgon.Editor.Program.Main(String[] args) in Program.cs:line 195 at Gorgon.UI.GorgonApplication.Run(ApplicationContext context, Func1 idleMethod) in GorgonApplication.cs:line 729 at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at ComponentFactory.Krypton.Toolkit.ViewControl.WndProc(Message& m) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.Control.WmMouseMove(Message& m) at ComponentFactory.Krypton.Toolkit.ViewControl.OnMouseMove(MouseEventArgs e) at ComponentFactory.Krypton.Ribbon.ViewRibbonManager.MouseMove(MouseEventArgs e, Point rawPt) at ComponentFactory.Krypton.Ribbon.ViewDrawRibbonGroupsBorderSynch.ViewGroupFromPoint(Point pt) at System.Collections.Generic.Dictionary2.get_Item(TKey key) at System.ThrowHelper.ThrowKeyNotFoundException()

PWagner1 commented 5 years ago

Hi @Tape-Worm

Are you referring to the extended toolkit code? The file system explorer is not ready yet, as I need to find a better implementation.

Update 1: I have found that this particular control cannot be drag & dropped onto the form, instead it needs to be coded in. However, I am planning to make a more robust UI, similar to the FolderBrowserDialog to replace this control entirely, so this issue should not pop up again.

Please also be aware that the Extended toolkit is a rolling release, so it will never be fully stable.

PWagner1 commented 5 years ago

Hi @Tape-Worm

From the stack trace you've given me, it seems to be looping. Are you able to set a breakpoint on the areas where this is triggered?

I've had a similar issue with my Colour Wheel component in my extended toolkit project. By the way, I'm also upgrading the file system explorer to a more robust design.

PWagner1 commented 5 years ago

I wonder if it's because ComponentFactory.Krypton.Ribbon is not configured to run unsafe code?

Tape-Worm commented 5 years ago

My apologies for not getting back to you sooner.

No, I'm not using any file explorer from your controls (didn't know it existed), this is my own file explorer. I'm just using the krypton tree view to show the files and folders.

As for breakpoint - No I can't, none of that code is mine (with the exception of the Run at the top - which is nothing more than the entry point for the application, and merely calls Application.Run() - a winforms method). This is all happening outside of my code from what I can tell. I can see if I can capture more detailed stack info.

Regarding a loop - No, there shouldn't be a loop (not that I can see here). When I click the node, a property gets set to the current node, and a notification is sent to the form with the ribbon. This notification (a handler for the property changed event) calls a method that enables/disables buttons on the ribbon. At least that's what I can recall, I'll have to dig in further to see if there's more to it, but I'm pretty sure there's no loop.

I'm not sure why unsafe code would cause this issue? Safe assemblies get mixed with unsafe assemblies all the time. If there were issues with that, I think it'd have popped up long before now?

OK, so, last night, I found that if I let my application sit for a few seconds after launch, the error never occurred, but if I clicked on the node nearly right away after launch, it broke. That's likely just coincidence, but I figured it couldn't hurt to put it out there.

I will try to see if I can dig into the exception further and send a more detailed stacktrace later.

PWagner1 commented 5 years ago

Hi @Tape-Worm

Thanks for your reply. A more detailed stacktrace would be good, as if this happens randomly, it would be like searching for a needle in a haystack. Also, are you using any other third party libraries that may be conflicting with each other?

Is there any way of reproducing this bug?

Tape-Worm commented 5 years ago

And of course, I haven't had the issue pop up today. That's the weird thing about it, I can go days and not see anything, and then at other times it exceptions several times throughout the course of a session.

As for libraries, nothing special. Yours is the only UI library I'm using.

As for anything outside of the usual .NET 4.7.1 assemblies, I'm using (from nuget):

Beyond that I'm referencing my own assemblies in this solution. There really shouldn't be anything interfering with the operation of Krypton. I really try and keep things as simple as possible in order to avoid situations like this.

In the stack trace I see this method before the indexer on the dictionary is accessed: ViewGroupFromPoint(Point pt)

Is there anything in there that might be trying to access an internal dictionary before it's populated? I can't imagine that you're using background jobs in the library, so I have my doubts that's what's happening.

I'm going to try right now to crash it. I'll report back if I find something.

Tape-Worm commented 5 years ago

So, after trying, and failing to reproduce the problem many many many times (50+), I decided to get off my ass and look at the code.

So, I tracked down where the stack trace is pointing at for the exception (in ViewDrawRibbonGroupsBorderSynch.cs):

 // There can only be groups showing for the currently selected tab
 if (Ribbon.SelectedTab != null)
 {
    // Get the scroll port for this tab
   ViewLayoutRibbonScrollPort viewScrollPort = _tabToView[Ribbon.SelectedTab];

The weird thing is that this seems to be in response to a mouse over event... yet my mouse is nowhere near the ribbon when this error is triggered.

However, I do think that I may be doing something that might be at least a partial cause of the exception.

A problem I have with the Krypton Ribbon is that I have no way of merging in tabs, groups, etc... from another ribbon (for contrast, the developer express ribbon does this quite well). So, I wrote my own functionality to achieve this. Probably incorrectly.

When I open up an image in my app, my image editor plug in loads, and creates the view for the image editor, and merges the image editor ribbon into the main one.

It is entirely possible that while this is happening, and somehow a mouse move is happening, that things might be in flight and the system is kind of in an in-between state. Of course, this is all conjecture at this point, and I have no real way to test out my theory with any level accuracy (as evidenced by my trying and failing to repro the exception).

So my theory was correct, and I was able to consistently repro the issue. My merging code seems to be reason this is happening. Although I don't understand why yet. Whatever is causing it to break, it happens outside of my control (I can't breakpoint it except at the Application.Run level. By then, my main form is long dead). But, if I comment out my code to merge the ribbons: RibbonMerger.Merge(e.Ribbon); Then the error no longer happens when I mouse over the ribbon immediately after opening my image.

So that's how it happened, now I gotta figure out how to fix it.

One thing that you might want to consider doing is to put a check to ensure that the tab exists in the dictionary before proceeding. If it doesn't, then early exit. That would at least avoid the exception I'm seeing. I'm not sure what the side effect of this would be however.

Tape-Worm commented 5 years ago

And finally, I managed to fix it.

So, in my merging code I was doing this:

            string selectedContext = TargetRibbon.SelectedContext;
            KryptonRibbonTab selectedTab = TargetRibbon.SelectedTab;

            MergeTabs(ribbon, TargetRibbon);
            MergeContexts(ribbon, TargetRibbon);

            // Restore the seleted tab.
            TargetRibbon.SelectedContext = selectedContext;
            TargetRibbon.SelectedTab = selectedTab;            

Which is all well and good, but after digging through the Krypton code, I noticed there was a layout method that updated the internal tab dictionary on the ribbon, I traced this back to a public method called CheckPerformLayout().

So, by adding these lines:

            // Ensure that the layout is refreshed.
            TargetRibbon.CheckPerformLayout();
            ribbon.CheckPerformLayout();

After the merge methods, the dictionary was updated and the exception went away.

So this is definitely my fault, and certainly can be worked around. You may close this issue if you wish. But that said, I'd still probably do the ContainsKey check in the ViewGroupFromPoint() method. Especially since it looks like the ContainsKey is performed almost on every other method in that class.

PWagner1 commented 5 years ago

It may have been that your application was running other events first, before faulting on this particular line. That would explain the randomness of the error, but I'll check out the ViewGroupFromPoint() method, and see if the check can be done once.

PWagner1 commented 5 years ago

Hi @Tape-Worm

Was this a issue affecting ribbon tabs or was this fixed?

Tape-Worm commented 5 years ago

It was affecting the ribbon on some level, I don't know if it was specifically the tabs or not. But it was due to my merging/unmerging code and the layout of the ribbon not being updated correctly (my fault).

I haven't had it pop up since I fixed my merging code, so I think it's safe to assume it's fixed on my end. The suggestion I left was regarding the dictionary that wasn't being tested for missing keys in that specific method, but other methods in the same class were doing the check prior to accessing the dictionary. Seemed inconsistent to me.

Anyway, it's OK if you close this issue.