2011-07-11 8 views
10

Było już kilka podobnych pytań na temat stackoverflow, ale nie znalazłem odpowiedzi:Jak określić, kiedy kontrola użytkownika jest w pełni załadowana i wyświetlona?

Mam aplikację, która składa się z kilku stron kart. Na jednym z nich ładuję listę kilkudziesięciu kontrolek użytkownika naraz. Obecnie robię to w wydaniu Load i dlatego mam małe opóźnienie przed załadowaniem tej strony. Chcę, aby interfejs był bardziej responsywny i wypełniał listę po pełnym załadowaniu strony. Czy istnieje sposób śledzenia, kiedy kontrola użytkownika w pełni załadowała zawartość?

VisibleChanged też nie pomaga, ponieważ uruchamia się, zanim zostanie pokazane inne sterowanie dziećmi. To powoduje pewne brzydkie efekty wizualne, gdy niektóre kontrolki podrzędne wciąż nie są widoczne, gdy zaczynam ładować listę kontrolną.

EDIT

Żeby było jasne. Mam niektóre elementy podrzędne dla kontenera strony i mam listę niestandardowych elementów sterujących, które próbuję wczytać później. Problem z dwoma podejściami opisanymi w kilku odpowiedziach poniżej polega na tym, że kiedy zaczynam ładować kontrole, nie pozwalają na pokazywanie innych kontrolek podrzędnych na pojemniku i dlatego mam te brzydkie efekty (i robię to z BackgroundWorker, ale mimo to musi współdziałać z głównego wątku, aby dodać kontrole do listy)

+0

[Odpowiedź DRappa] (https://stackoverflow.com/a/6654780/199364), użyta poprawnie, powinna rozwiązać ten problem. Zobacz mój komentarz do tej odpowiedzi, aby uzyskać więcej szczegółów. – ToolmakerSteve

Odpowiedz

6

Aby interfejs był bardziej responsywny, należy wysłać sobie wiadomość (Control.BeginInvoke), wykonać jedną operację, zamieścić sobie inną wiadomość. Za każdym razem, gdy coś zrobisz, następny krok zostanie umieszczony w kolejce po wszystkich wiadomościach użytkownika, więc działania użytkownika zostaną szybko przetworzone.

One naprawdę ładne podejście jest użycie yield return i niech kompilator zadbać o wszystkie logiki zamknięć:

IEnumerable AsyncLoadUI() 
{ 
    var p = new Panel(); 
    Controls.Add(p); 
    yield return null; 

    for(int i = 0; i < 50; ++i) { 
     var txt = new TextBox(); 
     p.Controls.Add(txt); 
     yield return null; 
    } 
} 

override void OnLoad(EventArgs e) 
{ 
    IEnumerator tasks = AsyncLoadUI().GetEnumerator(); 
    MethodInvoker msg = null; 
    msg = delegate { if (tasks.MoveNext()) BeginInvoke(msg); }; 
    msg(); 
} 
+1

Wow, zabrał mi kilka czytań, aby dowiedzieć się, co się tutaj dzieje. Bardzo sprytny, ale również potencjalnie mylący dla przyszłych opiekunów kodu. Byłoby to przesadą, gdybyśmy tylko dodali paczkę textboxów, jak w twoim przykładzie - oczekuję, że OP będzie miał nieco więcej czasochłonnego działania w kodzie Load. –

+0

@Mark: Atrakcyjność tej metody polega na tym, że działa ona również z bardzo złożonym kodem, tak łatwym w użyciu, jak DoEvent, ale nie wymaga zagnieżdżonej pętli zdarzeń, jak to ma miejsce w przypadku DoEvents. –

+0

@Mark Heat, tak ładuję listę niestandardowych elementów sterujących. @Ben Voigt ten urywek działa dobrze, ale nie rozwiązuje całkowicie problemu. Kod, który umieszczam w każdym zadaniu, wymaga czasu. Gdy te zadania są przetwarzane, nie pozwalają na wyświetlanie wszystkich innych elementów podrzędnych, ponieważ każde zadanie powoduje interakcję z wątkiem interfejsu użytkownika. Dzięki za odpowiedź, jej naprawdę twórcze =) – username

1

Jeśli ładowanie tej liście ponosi niewielkie opóźnienie następnie robi to obciążenie wątku UI będzie zawsze dokonać formularz zawieszeniu nr niezależnie od tego, jakie zdarzenie zrobisz - zmieniając to po załadowaniu formularza, sprawi to, że formularz przestanie odpowiadać i będzie widoczny, a nie będzie powodował opóźnienia przed wyświetleniem formularza.

Jeśli nie ma sposobu na przyspieszenie ładowania listy, prawdopodobnie konieczna będzie zmiana logiki ładowania formularzy, aby zamiast tego wykonać "uniesienie ciężaru" w wątku tła, aby formularz pozostał aktywny, gdy lista jest wypełniana. Powinieneś wiedzieć, że wielowątkowy kod jest trudniejszy do zrozumienia, a gdy zostanie zrobiony niepoprawnie, może powodować błędy, które są przerywane i trudne do debugowania, więc powinieneś zdecydowanie przyspieszyć swój istniejący kod. To powiedziawszy, jeśli nie możesz przyspieszyć ładowania listy, a opóźnienie jest nie do zaakceptowania, nie ma żadnej alternatywy.

Jeśli zdecydujesz się załadować listę asynchronicznie (w wątku w tle), wtedy należy uruchomić wątek tła (zwykle przez BackgroundWorker), który wykonuje ciężką pracę, przygotowując listę elementów do dodania - kiedy to zostało zakończone (lub nie) pole formularza/listy zostało zaktualizowane za pomocą dostarczonej listy pozycji.

Powinieneś być w stanie znaleźć mnóstwo informacji na temat tego, jak to zrobić w Internecie, które omówią to bardziej szczegółowo.

+0

Tak, ładuję je w innym wątku, ale to wciąż wymaga synchronizacji z wątkiem UI, w wyniku czego ładuję je, zanim jakakolwiek inna kontrola podrzędna pokazała – username

+0

@ nazwa użytkownika Nie jestem pewien, czy rozumiem - dlaczego zawracać sobie głowę ładowaniem tej listy w wątku w tle, jeśli synchronizujesz to wywołanie podczas metody ładowania? Być może pomoże ci mały fragment kodu? – Justin

+0

Już ładuję je za pomocą BackgroundWorker. Problemem jest, kiedy zacząć. Kiedy zaczynam od VisibleChanged, wciąż są pewne kontrolki podrzędne, które nie są widoczne. Sterowanie ładowaniem wymaga synchronizacji z wątkiem UI i nie pozwala na natychmiastowe ładowanie innych elementów podrzędnych. – username

2

Spójrz na my solution offered to another. Mają bardzo podobny problem. Poczekaj, aż WSZYSTKO zakończy swoje ładowanie przed wykonaniem określonej czynności, ale nie za każdym razem, gdy formularz musi zostać "aktywowany" lub "pokazany".Polega na dołączeniu do modułu obsługi ładunku twojej najbardziej zewnętrznej kontroli. W Twoim przypadku strona z kartami, ale przykładowe rozwiązanie, które podałem, znajdowało się na poziomie FORMULARZA.

+0

Twoje rozwiązanie jest bardzo eleganckie i proste. +1 – AndHeCodedIt

+0

W przypadku OP ** występują dwa problemy połączone razem **. ** Pierwszy problem ** rozpoczyna wcześnie ładowanie. Odpowiedź DRapp powinna rozwiązać to, ** jeśli ** umieścisz jego kod we właściwym miejscu. ** Nie ** umieszczaj swojego kodu na samej kontrolce użytkownika. Zamiast tego, umieść go na dowolnej kontrolce zawierającej te kontrolki użytkownika. Wtedy nie rozpocznie się, dopóki nie wróci "Load" tego kontenera - w tym czasie wszystkie dzieci również zostaną załadowane. – ToolmakerSteve

+0

** Drugi problem ** polega na tym, że w kodzie jest coś "wolnego" do ładowania kontroli użytkownika. Ta logika powinna zostać przeniesiona z wątku UI. Użyj dowolnej techniki do uruchomienia zadania w tle, ale ** nie czekaj na wynik ** z głównego wątku. Zamiast tego użyj polecenia "BeginInvoke" na sterowaniu zawierającym, aby wykonać końcowy krok wyświetlania. ** Co najważniejsze, niezależnie od tego, co jest wolne ** (np. Ładowanie pliku obrazu i przygotowanie - ale nie dodawanie do wyświetlenia - bitmapy z niego) musi być zrobione ** ZANIM **, że 'BeginInvoke', a ty wciąż jesteś na wątku tła. – ToolmakerSteve

Powiązane problemy