2012-12-27 15 views
13

Niedawno uaktualniliśmy do VS 2012, któremu towarzyszy uaktualnienie do .NET Framework 4.5. To wprowadza dziwny i nieznośny błąd w połączeniu z kontrolką WinForms MenuStrip. Ten sam błąd występuje również w aplikacjach, w których program .NET Framework 4.0 instalowany jest jako instalator 4.5, oczywiście również aktualizuje części 4.0. W związku z tym doskonale działający wzorzec kodu jest teraz zepsuty właśnie z powodu aktualizacji ramowej.Strange Bug z .NET 4.0/4.5 WinForms MenuStrip Stealing Focus

Opis problemu:
Mam Form1 z MenuStrip. W przypadku jednego z rozwijanych elementów obsługa zdarzeń otwiera inny formularz 2 (niekoniecznie jest to dziecko, tylko inny formularz). Jeśli użytkownik kliknie prawym przyciskiem myszy w nowy Form2 lub jeden z jego elementów podrzędnych, co spowoduje wywołanie funkcji Show() ContextMenuStrip, oryginalny Form1 pojawi się ponownie na pierwszym planie.
Zdarza się to niezależnie od wszystkich poprzednich akcji interfejsu użytkownika w Form2. Można zmienić rozmiar, przenieść, zminimalizować, zmaksymalizować Form2, przełączać się między kontrolkami, wprowadzać tekst itp. W jakiś sposób MenuStrip of Form1 wydaje się pamiętać, że spowodowało to otwarcie Form2 i skupiło się na pierwszym kliknięciu prawym przyciskiem myszy.

Eksperymentowałem z różnymi metodami, ale nie udało mi się znaleźć obejścia problemu. Konstelacja nie jest rzadkością i może mieć wpływ na wiele aplikacji WinForm. Dlatego postanowiłem opublikować go tutaj, ponieważ realistyczne rozwiązanie może być w interesie ogólnym. Byłbym bardzo zadowolony, gdyby ktoś znał obejście lub przynajmniej miał dla mnie jakieś wskazówki.

Udało mi się wydestylować esencję w poniższym kodzie. Powinien być odtwarzalny na dowolnej maszynie z zainstalowanym .NET 4.5 i pojawia się, gdy celowanie wynosi odpowiednio 4,0 i 4,5, ale nie w wersji 3.5 lub niższej.

using System; 
using System.Windows.Forms; 

namespace RightClickProblem { 
    static class Program { 
     [STAThread] 
     static void Main() { 
      // Construct a MenuStrip with one item "Menu" with one dropdown-item "Popup" 
      var mainMenu = new MenuStrip(); 
      var mainItem = new ToolStripMenuItem("Menu"); 
      mainItem.DropDownItems.Add(new ToolStripMenuItem("Popup", null, Popup)); 
      mainMenu.Items.Add(mainItem); 

      // Create form with MenuStrip and Show 
      var form1 = new Form(); 
      form1.Controls.Add(mainMenu); 
      Application.Run(form1); 
     } 

     private static void Popup(object sender, EventArgs e) { 
      // Create a form with a right click handler and show 
      var form2 = new Form(); 
      var contextMenu = new ContextMenuStrip(); 
      contextMenu.Items.Add("Just an item..."); 
      form2.ContextMenuStrip = contextMenu; 
      form2.Show(); 
      // Problem: contextMenu.Show() will give focus to form1 
     } 
    } 
} 

EDIT: Spędziłem trochę czasu przechodzeniu kodu źródłowego .NET Framework i znaleźć przyczynę być bardzo prawdopodobne w System.Windows.Forms.ToolStripManager. Tam firma Microsoft używa filtra wiadomości do śledzenia aktywacji okna, która jest w jakiś sposób nieprawidłowo zaimplementowana w MenuStrip.
W międzyczasie odkryłem również, że Microsoft już rozwiązał ten problem w poprawce (zobacz http://support.microsoft.com/kb/2769674) i mam nadzieję, że znajdzie się w przyszłej aktualizacji programu .NET Framework 4.5.

Niestety, trudno jest wymusić stosowanie tej poprawki na wszystkich komputerach klienckich. Dlatego nadal bardzo cenne byłoby obejście tego rozwiązania. Ja nie były w stanie tak daleko, aby znaleźć praktyczne rozwiązanie ...

EDIT # 2: Oryginalny numer KB jest dla Win8 ale jest podobny poprawce dla Win7 & Vista pod KB 2756203. Jeśli ktoś może go użyć , znalazłem poprawkę do pobrania tutaj: http://thehotfixshare.net/board/index.php?autocom=downloads&showfile=15569. Przetestowaliśmy to i naprawdę naprawia problem. Jeśli wkrótce nie znajdziemy rozwiązania, przejdziemy do poprawki.

EDIT # 3: Uwagi do przyjętego rozwiązania proponowane przez spajce
Oczywiście wywołanie show() na dowolny ContextMenu przekona oryginalny MenuStrip zapomnieć o jego roszczenia ostrości. Można to zrobić tak, aby manekin ContextMenu nie był wyświetlany na ekranie. Znalazłem najkrótszą i najbardziej łatwy do wdrożenia sposób wstawić następujący fragment konstruktora w jakiejkolwiek formie za:

public partial class Form1 : Form { 
    public Form1() { 
     InitializeComponent(); 

     using (var dummyMenu = new ContextMenuStrip()) { 
      dummyMenu.Items.Add(new ToolStripMenuItem()); 
      dummyMenu.Show(Point.Empty); 
     } 
    } 
} 

więc każda forma, która otwiera czyści uszkodzony stan systemu ToolStripMenu. Równie dobrze można umieścić ten kod w statycznej metodzie, takiej jak FormHelper.FixToolStripState() lub umieść go w OnCreateControl (...) szablonu formularza i dziedziczy z niego wszystkie formularze (co i tak szczęśliwie robimy).

+3

Bardzo ładna repro. Poważnie wątpię, że dostaniesz odpowiedź, zajęcia z pasków narzędziowych to poważny problem. Zabrałbym to do pomocy technicznej Microsoft. –

+0

Patrząc na opis poprawki, jest to dokładnie odwrotny problem. Nie usuwa wystarczająco szybko filtra wiadomości. Kliknij lewym przyciskiem myszy formularz, aby zobaczyć problem. W menedżerze paska narzędzi jest kod, który ma usunąć filtr, gdy inne okno jest aktywowane, co nie dzieje się. Mam nadzieję, że ta poprawka sprawi, że wkrótce pojawi się aktualizacja, jest to dość poważny błąd. –

+0

Zasadnicza przyczyna jest trudna do rozwiązania, ponieważ różne elementy składowe są bardzo rozproszone w kodzie źródłowym. Ponieważ nie widziałem sposobu, aby naprawić kod ramy tak czy inaczej, po prostu zatrzymałem się w pewnym momencie. Byłem zaskoczony, że podstawowa implementacja jest trochę brudna (moim zdaniem), ale nie dziwię się, że ten kod jest bardzo podatny na takie błędy. – markus987

Odpowiedz

1

to jest moje rozwiązanie :)

static class Program 
    { 
     /// <summary> 
     /// The main entry point for the application. 
     /// </summary> 
     [STAThread] 
     static void Main() 
     { 
      // Construct a MenuStrip with one item "Menu" with one dropdown-item "Popup" 
      var mainMenu = new MenuStrip(); 
      var mainItem = new ToolStripMenuItem("Menu"); 
      mainItem.DropDownItems.Add(new ToolStripMenuItem("Popup", null, Popup)); 
      mainMenu.Items.Add(mainItem); 

      // Create form with MenuStrip and Show 
      var form1 = new Form(); 
      form1.Controls.Add(mainMenu); 
      Application.Run(form1); 
     } 

     private static void Popup(object sender, EventArgs e) 
     { 
      // Create a form with a right click handler and show 
      var form2 = new Form(); 
      var contextMenu = new ContextMenuStrip(); 
      contextMenu.Items.Add("Just an item..."); 
      var loc = form2.Location; //<---- get the location 
      var p2 = new Point(loc.X, loc.Y); //<---- get the point of form 
      form2.ContextMenuStrip = contextMenu; 
      form2.ContextMenuStrip.Show(form2, p2); //<---- just add this code. 
      form2.Show(); 
      // Problem: contextMenu.Show() will give focus to form1 
     } 
    } 
+0

O dziwo, to naprawdę działa! Zaktualizowałem moje pytanie powyżej, aby nadać mu ogólniejszy charakter (czy powinienem edytować odpowiedź bezpośrednio?). Tylko drobna uwaga: ponieważ Point jest strukturą, to i tak jest kopiowana, więc nie ma potrzeby loc/p2. – markus987

+0

Przyznaję, że jestem tylko początkującym, więc sztuczka którą podałem jest tylko moją propozycją rozwiązania drobnego błędu, który napotkaliśmy, więc moim celem jest "w programowaniu zawsze są czyste kody, aby rozwiązać problem i brudne sposób jest naszą ostatnią bronią "Czytam twoje zaktualizowane pytanie i to świetnie, może to być lepszy sposób :) – spajce

+0

Szczerze mówiąc, nie wierzyłem w pierwsze, że twoje rozwiązanie będzie tak skuteczne. Eksperymentowałem z dodatkowymi wywołaniami samego Show() i odkryłem, że wywołanie programu Show() dwa razy również rozwiązało problem, ale nie widziałem, jak to może być praktyczne, biorąc pod uwagę, że mamy ogromny kod z setkami menu i contextmenus. Twoje proponowane rozwiązanie dało mi właściwe wskazówki. – markus987

0

miałem ten problem zbyt. Nie chciałem zmieniać kodu, ponieważ czułem, że mój program prawidłowo to robi. Znalazłem poprawkę tutaj na stronie Microsoftu:

http://support.microsoft.com/kb/2750147

To robi różnicę, a my po prostu zainstalować go na 2 komputerach użytkowników tego ranka. Nasz informatyk instaluje go na wszystkich komputerach już dziś, gdy został przetestowany i pokazany do działania.

Powiązane problemy