WPF userControl wielokrotne użycie

0

Mam aplikację w której potrzebuję w różnych miejscach komunikować się z wagą poprzez RS232. W tym celu przygotowałem UserControl do komunikacji z wagą, wyświetlania i rejestrowania jej wskazań. Aplikacja jest pisana z myślą o terminalach dotykowych i wykorzystuje okna nakładające się na siebie. Po uruchomieniu aplikacji wybieram użytkownika i loguję się, jak logowanie przebiegnie prawidłowo to wyświetlam następne okno z wyborem trybu pracy, po wyborze trybu pracy przechodzę do kolejnego okna i tutaj pojawia się moja UserControl do komunikacji z wagą. Jej pierwsze uruchomienie odbywa się bezproblemowo -problem pojawia się jeśli wyjdę z tego okna (wykorzystuję najprostszą metodę "Close()" dla okna) i próbuję wejść jeszcze raz w tryb z obsługą wagi - moje UserControl przy ponownej próbie otwarcia zawiesza cały program.

Co do mojego UserControl i jego inicjalizacji:

    public partial class ucKontrolkaWagi : UserControl
    {
        private Thread waga;

        public ucKontrolkaWagi()
        {
            InitializeComponent();
            
            bt_Tara.IsEnabled = Params.IloscWag > 0;
            bt_Zero.IsEnabled = Params.IloscWag > 0;
            bt_Waga.IsEnabled = Params.IloscWag > 1;
            
            Params.NrAktualnejWagi = 1;

            if (waga != null)
            {
                if (!waga.IsAlive)
                {
                    waga.Start();
                }
            }
            else
            {
                waga = new Thread(odczytWagi);
                waga.Start();
            }
        }

Wywołanie kontroli wagi w XAML okna trybu pracy:

  • definicja:
xmlns:komponenty="clr-namespace:Terminal.Komponenty"
  • dodanie:
<komponenty:ucKontrolkaWagi x:Name="KontrolkaWagi" Grid.Column="3" Grid.Row="1" Grid.RowSpan="4" />

Czy na podstawie tego fragmentu kodu ktoś może mi pomóc gdzie tkwi problem? Ta kontrolka ma się pojawić w wielu miejscach (tzn w wielu oknach ale te oknach ale za każdym razem będzie jej pojedyncza instancja) i trochę komplikuje mi to dalsze prace nad oprogramowaniem.

0

Co się dzieje w odczytWagi?

Przy tworzeniu user controlki zawsze waga początkowo będzie równa null i zawsze wpadniesz w tego else. Ważnym pewnie jest jak zwalniasz zasoby przy zamykaniu kontrolki.

0

po pierwsze port COM można otworzyć tylko raz w całym systemie i dopóki nie zostanie zamknięty nic innego nie może z niego korzystać. Po drugie biorąc to pod uwagę masz dwa wyjścia - albo otwierać port, wysyłać komendę, odbierać odpowiedź i zamykać port za każdym razem albo zrobić obsługę wagi (odczyt i sterowanie) jako singelton (w sensie sposobu działania niekoniecznie implementacji) w aplikacji i podpinać się do niego samym "wyświetlaczem". Które jest lepsze/prostsze? - musisz sam zadecydować.

Ja mam u siebie zrobione tak, że mam obiekt (odpowiada tylko i wyłącznie za komunikację z wagą i odbieranie od niej odpowiedzi) tworzony i konfigurowany przy starcie programu. Udostępnia on metody do sterowania wagą (taruj, zeruj, zważ, ...) oraz zdarzenia zmiana_wagi i ważenie. Pierwsze jest wywoływane za każdym razem jeśli waga się zmieni a drugie po prawidłowym ważeniu (opis dalej). Dzięki temu po pierwsze już podczas uruchamiania programu wiem czy jest komunikacja z modułem wagi czy nie, mogę się podpiąć do wagi w różnych miejscach programu.

Musisz też pamiętać, że jeśli wysyłasz coś do wagi z wątku to musisz używać Invoke jak zmieniasz coś w głównym wątku.

Co do samego ważenia to u mnie działa to tak, że user wysyła żądanie ważenia (po prostu wciska guzik), wątek wysyła żądanie odczytu masy do wagi i dopiero po tym odczycie zwraca zważoną masę. Pierwotna wersja działała tak, że brała ostatnio odczytaną wartość ale czasami była ona nieprawidłowa.

EDIT: co do samego błędu to obstawiam, że nie kończysz wątku po zamknięciu formy i otworzenie innej, a co za tym idzie próba ponownego otwarcia portu wykrzacza wątek

0
TomekWaw napisał(a):

Co się dzieje w odczytWagi?

Kod odczytWagi:

void odczytWagi()
        {

            SerialPort port = null;

            switch (Params.NrAktualnejWagi)
            {
                case 1:
                    port = Params.Waga1;
                    break;
                case 2:
                    port = Params.Waga2;
                    break;
                case 3:
                    port = Params.Waga3;
                    break;
            }
            
            if (port.IsOpen)
            {
                
                port.DiscardInBuffer();
                port.DiscardOutBuffer();

                while (!Params.czyZatrzymacWage)
                {
                    port.WriteLine("U1DWY");
                    string odczytZPortuWagi = port.ReadLine();
                    char stabilna = odczytZPortuWagi[1]; //zapisanie pierwszego znaku ze stringa jako info o tym czy waga stabilna (S) czy nie (U)
                    Params.stabilizacjaWagi = stabilna;
                    char plusMinus = odczytZPortuWagi[2]; //sprawdzenie drugiego znaku ze stringa czy waga dodatnia (null) czy ujemna (-)
                    Params.plusMinusWagi = plusMinus;
                    if (!tb_wskazaniaWagi.CheckAccess())
                    {
                        Dispatcher.Invoke(DispatcherPriority.Send,
                            (Action)delegate
                            {
                                wyswietlacz(odczytZPortuWagi, stabilna);
                            }
                            );
                    }
                    else
                    {
                        wyswietlacz(odczytZPortuWagi, stabilna);
                    }
                    Thread.Sleep(50);
                }
            }
            else
            {
                if (!tb_wskazaniaWagi.CheckAccess())
                {
                    Dispatcher.Invoke(DispatcherPriority.Send,
                        (Action)delegate
                        {
                            wyswietlacz("Port zamknięty przy starcie", 'S');
                        }
                        );
                }
                else
                {
                    wyswietlacz("Port zamknięty przy starcie", 'S');
                }
                port.Open();
            }
            
        }
TomekWaw napisał(a):

Przy tworzeniu user controlki zawsze waga początkowo będzie równa null i zawsze wpadniesz w tego else. Ważnym pewnie jest jak zwalniasz zasoby przy zamykaniu kontrolki.

Obawiam się że właśnie z tym sobie nie radzę :( Generalnie zamknięcie okna w którym jest kontrolka wagi robię przez najprostsze Close() i komunikacja z wagą jest przerywana (widzę to na indykatorze wagowym) ale pewnie coś gdzieś zostaje. Tutaj mam problem jak prawidłowo zwolnić zasoby i w pełni "odładować" kontrolkę

0
abrakadaber napisał(a):

po pierwsze port COM można otworzyć tylko raz w całym systemie i dopóki nie zostanie zamknięty nic innego nie może z niego korzystać.

Staram się w miejscach gdzie używam portu COM sprawdzać wpierw czy jest już otwarty i nie próbować go otwierać ponownie w przypadku gdy już jest otwarty

abrakadaber napisał(a):

Po drugie biorąc to pod uwagę masz dwa wyjścia - albo otwierać port, wysyłać komendę, odbierać odpowiedź i zamykać port za każdym razem

A masz doświadczenie jaki to ma wpływ na wydajność aplikacji? Generalnie odczyt wagi ma być praktycznie ciągły i nie wiem czy jak będę otwierał port i zamykał za każdym razem to czy to nie wpłynie negatywnie na wydajność.

abrakadaber napisał(a):

albo zrobić obsługę wagi (odczyt i sterowanie) jako singelton (w sensie sposobu działania niekoniecznie implementacji) w aplikacji i podpinać się do niego samym "wyświetlaczem".

To wydaje się sensowniejsze natomiast obawiam się że moja wiedza na temat programowania wciąż jest zbyt mała żeby to tak ładnie ogarnąć.

abrakadaber napisał(a):

Co do samego ważenia to u mnie działa to tak, że user wysyła żądanie ważenia (po prostu wciska guzik), wątek wysyła żądanie odczytu masy do wagi i dopiero po tym odczycie zwraca zważoną masę. Pierwotna wersja działała tak, że brała ostatnio odczytaną wartość ale czasami była ona nieprawidłowa.

U mnie niestety jest odwrotna sytuacja - operator musi cały czas obserwować wyświetlacz wagi i dopiero po osiągnięciu jakiejś określonej wagi zarejestrować odczyt (tzw. standaryzacja). Podczas rejestracji sprawdzam jeszcze czy zapisywany odczyt jest stabilny - to powinno rozwiązać problem o którym piszesz że ostatnia odczytana wartość nie zawsze była prawidłowa.

Z ciekawości - na jakiej wadze pracujesz? Ja podpinam sam indykator wagowy AXIS ME-00 do którego podpinam taką platformę jaka jest potrzebna w danym projekcie.

EDIT: co do samego błędu to obstawiam, że nie kończysz wątku po zamknięciu formy i otworzenie innej, a co za tym idzie próba ponownego otwarcia portu wykrzacza wątek

0
  1. ja działam z wagami firmy Radwag, Fawag, Mettler Toledo i Rhewa.
  2. co do wydajności przy zamykaniu i otwieraniu połączenia - Radwag w terminalach PUE5 dodał swój serwis, który pośredniczy w komunikacji z wagą. Działa właśnie na takiej zasadzie - przychodzi polecenie, otwarcie portu, wysłanie polecenia do wagi, odpowiedź z wagi, zamknięcie portu, odpowiedź do pytającego. Odpytuję wagę ok 10 razy na sekundę (częściej nie ma sensu - nie widać praktycznie różnicy) i działa to na dziesiątkach terminali. Natomiast na starszych miernikach port mam otwarty od uruchomienia programu aż do jego zamknięcia. Różnicy pomiędzy jednym a drugim nie zauważyłem.
  3. po prostu musisz swoją usercontrol rozbić na dwa niezależne elementy - wątek z obsługą fizyczną miernika i część wizualną. Wątek tworzysz np. przy starcie a zamykasz przy kończeniu
0
abrakadaber napisał(a):

po prostu musisz swoją usercontrol rozbić na dwa niezależne elementy - wątek z obsługą fizyczną miernika i część wizualną. Wątek tworzysz np. przy starcie a zamykasz przy kończeniu

Czyli np podczas uruchamiania programu w tle wywołuję thread który otwiera port, wysyła do miernika zapytanie o odczyt wagi, odczytuje odpowiedź i przechowuje ją np w jakimś ogólnie dostępnym stringu, zamyka port i tak w kółko a w momencie gdy potrzebuję użyć odczytu i zaprezentować go wizualnie to wywołuję userControl który wyświetla aktualną wartość tego stringa do którego mój thread wysyła cały czas w tle odczyty wagi - czy dobrze rozumiem?
Jak rozwiązać kwestie tarowania (lub ustawiania na wadze zera) gdzie muszę wysłać inną komendę zapamiętującą tarę, odczytać jej wartość - muszę przerwać aktualny thread w którym odbywa się odczyt wagi, wysłać komendę tarowania i po jej wykonaniu ponownie wznowić mój thread?

1
dzilupl napisał(a):

Czyli np podczas uruchamiania programu w tle wywołuję thread który otwiera port, wysyła do miernika zapytanie o odczyt wagi, odczytuje odpowiedź
dokładnie

i przechowuje ją np w jakimś ogólnie dostępnym stringu,

<wg mnie="mnie"> takie podejście jest brzydkie - dużo ładniej jest skorzystać ze zdarzeń (do poczytania http://4programmers.net/C_sharp/Wprowadzenie/Rozdzia%C5%82_6#id-Zdarzenia http://www.altcontroldelete.pl/artykuly/obsluga-zdarzen-w-c-delegaty-i-eventy/ ) </wg mnie>

zamyka port

niekoniecznie - możesz go mieć cały czas otwartego

i tak w kółko

dokładnie

a w momencie gdy potrzebuję użyć odczytu i zaprezentować go wizualnie to wywołuję userControl który wyświetla aktualną wartość tego stringa do którego mój thread wysyła cały czas w tle odczyty wagi - czy dobrze rozumiem?

jeśli użyjesz zdarzeń to wtedy wątek niejako automatycznie będzie wysyłał sygnał o zmianie wagi do kontrolki wyświetlającej (oczywiście trzeba to napisać najpierw)

Jak rozwiązać kwestie tarowania (lub ustawiania na wadze zera) gdzie muszę wysłać inną komendę zapamiętującą tarę, odczytać jej wartość - muszę przerwać aktualny thread w którym odbywa się odczyt wagi, wysłać komendę tarowania i po jej wykonaniu ponownie wznowić mój thread?

ale dlaczego? Możesz dodać sobie zmienną, która będzie "mówiła" wątkowi co ma robić. Np. przed wysłaniem komendy na port możesz zrobić coś takiego

switch (corobic)
{
  case 0:
    komenda = "odczytaj_wage";
    break;
  case 1:
    komenda = "taruj_wage";
    corobic = 0;
    break;
  case 2:
    komenda = "zeruj_wage";
    corobic = 0;
    break;
}
port_com.write(komenda);

a gdzieś dalej po wciśnięciu taruj corobic = 1

0

abrakadaber, czy możesz mnie jeszcze trochę bardziej "oświecić" :) ?
Zrobiłem obsługę komunikacji z miernikiem jako osobną klasę :

class IndykatorAXIS_ME00_ObslugaWagi
    {
        public static void standardowaPracaWagi()
        {
            SerialPort port = Params.comWaga1;
            string adresIndykatoraUx = Params.daneWagi1[1].ToString();

            if (!port.IsOpen)
            {
                port.Open();
            }

            while (true)
            {
                switch (Params.trybPracyWagi)
                {
                    case 1: // podstawowy tryb, ciągły odczyt wagi
                        odczytajWage(port, adresIndykatoraUx);
                        break;
                    case 2: // tarowanie wagi
                        Params.trybPracyWagi = 1;
                        break;
                    case 3: // ustawianie zero wagi
                        Params.trybPracyWagi = 1;
                        break;
                }
                Thread.Sleep(Params.threadSleepDlaOdczytuWagi);
            }
        }

        private static void odczytajWage(SerialPort comWagi, string adresUxIndykatora)
        {
            comWagi.DiscardInBuffer();
            comWagi.DiscardOutBuffer();
            comWagi.WriteLine(String.Format("{0}{1}",adresUxIndykatora,IndykatorAXIS_ME00_KomunikatyWazenie.DajAktualnyWynik));
            comWagi.ReadLine();
        }
}

Brak w niej jeszcze obsługi tarowania i zerowania ale to jest kwestia tylko wysłania odpowiednich komunikatów do miernika. Samą komunikację nawiązuję podczas uruchamiania programu i cały czas pracuje ona w tle:

Thread waga = new Thread(IndykatorAXIS_ME00_ObslugaWagi.standardowaPracaWagi);
waga.Start();

Teraz mam moje UC do prezentacji wskazań wagi - najprostsza wersja z TextBox jako wyświetlaczem i dwoma przyciskami do obsługi tarowania i zerowania. Chcąc wykorzystać zdarzenia to powinienem je stworzyć w obrębie tego UC czy po stronie obsługi samego miernika? W jaki najbardziej odpowiedni sposób powinienem przekazać odczyt do wyświetlacza?

1 użytkowników online, w tym zalogowanych: 0, gości: 1