[C#] Automatyczne odświeżanie listy bez migania


(Max Graczyk) #1

Witam,

Chcę zrobić w jednym takim programie w C#, aby lista ciągle się odświeżała. Lecz niestety ona ciągle miga i nie da się nad nią zapanować. Zawsze wraca ona "do góry", jak przewijam na dół.

Po prostu wybieram komponent Timer, ustawiam "tykanie" na 1 milisekundę i ustawiam, żeby był włączony.

W zdarzeniu timer1_Tick wklejam kod odświeżającą listę, uruchamiam program i widzę tą migającą listę (wszystko napisane powyżej).

Może ktoś podpowie, jak zrobić tak, żeby lista ciągle się odświeżała BEZ MIGANIA?


(Ryan) #2

Po pierwsze: rozdzielczość i dokładność to dwie różne rzeczy. To, że możesz Timer ustawić w milisekundach nie znaczy, że z zadaną dokładnością będzie wywoływany. Zasadniczo już ustawiając na 50ms nie masz gwarancji, że się wywoła 20 razy na sekundę. 1 milisekunda to w ogóle totalne nieporozumienie, bo Timer nie służy do wzbudzania tak częstych eventów (najlepiej na nim ustawiać zdarzenia, które się mają wywoływać co kilka-kilkanaście sekund do kilku minut). Tak więc zacznijmy od tego, że Timer o takiej częstotliwości nie ma sensu.

Druga rzecz - jak komponent ma wiedzieć co ma być widoczne po zmianie danych w liście, jeśli potencjalnie nowy wpis może być gdzieś w miejscu, które obecnie widać? Ma przenieść poza widoczny fragment element u góry czy na dole? Przesuwać coś? Takimi rzeczami musisz sterować samemu - zapamiętaj pozycję listy i po dodaniu/usunięciu elementu, przywróć starą pozycję (i zaznaczenie - bo co jeśli zaznaczony był element,który usunąłeś?). Tak, trzeba to zrobić ręcznie, bo komponent nie zgadnie Twoich intencji.

Wreszcie - komponent nie wspiera podwójnego buforowania i będzie migał. Aby to zamaskować musisz zrobić dwie rzeczy. Pierwsza rzecz: generować pełną listę elementów listy nie na właściwościach komponentu, a poza nim, po czym przypisać zbudowaną listę do komponentu. Musisz to robić na głównym wątku aplikacji niestety. Druga rzecz: nie generować zmian wiele razy na sekundę. Po co?


(Max Graczyk) #3

Słuchaj, części nie rozumiem, ponieważ jestem początkującym w języku C#. Ale ja robię program do monitorowania procesów i robię sobie listę procesów, i żeby tej listy ciągle nie odświeżać, chcę zrobić automatyczne odświeżanie listy. Mogę zrobić "tykanie" na 1000 milisekund (1 sekunda) i może to coś zdziała, ale dalej lista będzie migać.

A jak mam to zrobić? Czy jesteś w stanie pokazać, co trzeba w kodzie programu zmienić, żeby to osiągnąć?


(Ryan) #4

Ok, to jak teraz wiem co chcesz zrobić, to sugerowane rozwiązanie jest trochę inne. Tu jest sugerowany sposób aktualizowania listy:

private void UpdateList()

{

	Process[] ProcessList = Process.GetProcesses();


	// Add new

	foreach (Process CurrentProcess in ProcessList)

	{

		String label = String.Format(

			"Process: {0} ({1})",

			CurrentProcess.ProcessName,

			CurrentProcess.Id);

		if (MainList.Items.IndexOf(label) == -1)

		{

			MainList.Items.Add(label);

		}

	}


	// Remove old

	int Length = MainList.Items.Count;

	for (int i = 0; i < Length; ++i)

	{

		String s = MainList.Items[i].ToString();

		bool ProcessFound = false;

		foreach (Process CurrentProcess in ProcessList)

		{

			String label = String.Format(

				"Process: {0} ({1})",

				CurrentProcess.ProcessName,

				CurrentProcess.Id);

			if (s == label)

			{

				ProcessFound = true;

				break;

			}

		}

		if (!ProcessFound)

		{

			MainList.Items.RemoveAt(i);

			--Length;

			--i;

		}

	}

}

Najłatwiej jest się pozbyć częstego mrugania przez nie zmienianie tego, co nie uległo zmianie. Masz jak najbardziej możliwość to zrobić, bo lista procesów w systemie nie zmienia się aż tak często. Jak to działa? MainList to oczywiście kontrolka-lista. Co wywołanie zegara wykonywane jest UpdateList, które wykonuje dwie operacje: najpierw dodaje, później odejmuje elementy z listy.

Dodawanie: dla każdego procesu, który uruchomiony jest w systemie, sprawdź, czy znajduje się już na liście. Jeśli nie, dodaj go. Jeśli tak - nie rób nic.

Usuwanie: dla każdego wpisu na liście, jeśli nie istnieje już taki proces w systemie - usuń element listy.

To połowa rozwiązania, którego szukasz. Druga połowa to "przewijanie" listy, jeśli zmieni się na niej ilość wpisów, tak, aby zawsze było widoczne to co chcesz. Zwracam uwagę, że przy usuwaniu nie użyłem foreach. Przyczyna jest prosta: zewnętrzna pętla operuje na elementach listy a ja z tej listy usuwam elementy. Foreach sobie nie poradzi, jeśli "pod nim" coś podbierzemy.

A co do wcześniejszej propozycji - przyczyna przeciągłego i drażniącego mrugania to wynik wykonywania Lista.Items.Add(...) -- za każdym razem kiedy to robisz, WinForms renderuje od nowa kontrolkę (mruganie). Jeśli możesz stworzyć zmienną ListBox.ObjectCollection i do niej dodać elementy po czym całą kolekcję przypisać do Lista.Items, będziesz miał tylko pojedyncze renderowanie. Jest to szczególnie przydatne przy wypełnianiu drzew (TreeView). Jeśli zależy Ci na absolutnie zerowym mruganiu - musisz wyrzeźbić własną kontrolkę, przykro mi.


([alex]) #5

Gdyby zmienić pętle:

   int Length = MainList.Items.Count;int i = 0; i  Length; ++i) [/code]na:[code=php]for (int i = MainList.Items.Count-1; i =0; --i) 
to przy usuwaniu operacje:         --Length;i; [/code]stają się zbędne.

(Ryan) #6

Jasne, ale

  • miałbym więcej tłumaczenia dlaczego tak a nie inaczej (uważam, że --L;--i; jest bardziej organoleptyczne)

  • count wstecz ma mniejszą spójność trafień w cache ;]

Nie wspomnę nawet o tym gdzie Ty byłeś, jak trzeba było kod pisać. :stuck_out_tongue_winking_eye:


(Max Graczyk) #7

Dzięki! Ten cały kod działa bez problemu, a procesy dodają się i usuwają z listy bez problemów i mrugania!


(somekind) #8

Czemu pre a nie post?


(Ryan) #9

To w tym wypadku bez większego znaczenia. Przyzwyczajenie wynikające z tego, że padaczne kompilatory C (np. niektóre dla mikrokontrolerów) dla post- tworzą dodatkową (zbędną w tym wypadku) zmienną. Większość kompilatorów dzisiaj optymalizuje jednak takie prostackie przypadki.


(somekind) #10

Zwłaszcza w C#, w którym nigdy ten problem nie występował, dlatego tak mnie to zdziwiło. Tak myślałem, że to pewno przyzwyczajenie bo raczej nic racjonalnego :slight_smile:

Ale użycie pętli wstecz, jak zwrócił uwagę alex, byłoby tu czytelniejsze :slight_smile:


(Ryan) #11

Co kto lubi. Dla mnie konstrukcja "for (int i = MainList.Items.Count-1; i >=0; --i)" jest odrobinę nieintuicyjna z uwagi na -1 oraz >=. Kwestia gustu i umowy między ludźmi pracującymi nad projektem. Mój poprzedni szef pewnie kazałby mi dodać komentarz w kodzie, bo nie wiadomo w co jeszcze ten for obrośnie i kto się będzie za 10 lat kodem opiekował.