IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
Avatar
Michaal.K
Člen
Avatar
Michaal.K:15.3.2016 14:24

Ahoj,
chci vytvořit aplikaci, která bude komunikovat s několika zařízeními po sériové lince. A uživatel bude moci odesílat na vybraný port data a zároveň přijaté řetězce se budou zobrazovat. Ve formuláři SettingsForm bude moci jednotlivým sériovým portům nastavovat (rychlost, paritu, stopbit a případně další parametry). Program jsem už začal psát a narazil jsem na několik chyb, jsem začátečník v objektově orientovaném programováním a tak nevím jestli jsem to navrhnul dobře.
Na hlavním formuláři MainForm se nachází StripMenu (nastavení, připojit, odpojit), richTextBox (pro zobrazení přijatých dat), textBox (pro zadání řetězce který chci odeslat na vybraný port např. 1@sendtext, kde 1 je číslo portu, @ je oddělovač a zbytek je odesílaný text) a tlačítko ok (odešle data).
Dále mám třídu Communication, která by měla obsluhovat sériové porty (nastavit defaultně rychlost, paritu,...). Tato třída by měla přijímat data, která předá mainformu a ten je zobrazí, při odesílání dostane číslo portu, data a odešle na port. Zde v konstruktoru vytvářím sériové porty, počet je dán podle toho kolik jich je v pc. Následně je ukládám do listu serialPorts, který je public. Dále jsou zde metody Connected (otevření všech portů), Disconnect (zavření portů), WriteData (odeslání dat na danný port) a comPort_DataRe­ceived (příjem dat s číslem portu). Odesílání a příjem dat mi funguje. Viz kód.

public class Communication
    {
        public List<SerialPort> serialPorts = new List<SerialPort>();

        public delegate void DataReceivedEventHandler(SerialPort comPort, string data);     //deklarace delagata, ktery bude slouzit jako predpis metody predstavujici reakci na udalost (handler)
        public event DataReceivedEventHandler OnSerialDataReceived;                         //deklarace udalosti

        // Konstruktor tridy
        public Communication()
        {
            // Defaultni nastaveni vsech seriovych portu, ktere jsou v pocitaci
            // BaudRate: 19200, Parity: none, DataBits = 8, StopBits = 1
            var portNames = SerialPort.GetPortNames();
            foreach (var port in portNames)
            {
                SerialPort comPort = new SerialPort(port, 19200, Parity.None, 8, StopBits.One);

                comPort.Handshake = Handshake.None;
                comPort.ReadTimeout = 500;
                comPort.WriteTimeout = 500;
                comPort.DtrEnable = true;
                comPort.RtsEnable = true;

                comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);       // prida udalost, ktera je vyvolana pokud dojde k zapisu do prijimaciho stringu

                serialPorts.Add(comPort);           // Pridani parametru com portu do listu serialPorts

                //MessageBox.Show(comPorts[i].PortName.ToString() + " " + comPorts[i].BaudRate.ToString() + " " + comPorts[i].DataBits.ToString());
            }
            // TODO Cislo 1: Mel bych seradit com porty pomoci Sort (pri pripojeni usb rs232 dojde k rozhazeni com portu)
        }

        public void Connected()
        {
            // otevru vsechny dostupne porty
            foreach (var comPort in serialPorts)
            {
                try
                {
                    if (!comPort.IsOpen)
                        comPort.Open();

                    MessageBox.Show(comPort.PortName + " otevren!");
                    //sp.Write("Test");
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Port nelze otevřít " + ex.Message, MainForm.appName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        public void Disconnect()
        {
            // po ukonceni aplikace pokud je otevreny seriovy port tak ho uzavru
            foreach (var comPort in serialPorts)
            {
                if (comPort.IsOpen)
                    comPort.Close();
            }
        }

        public void WriteData(int adrPortuList, string data)
        {
            //Thread.Sleep(500);
            SerialPort comPort = serialPorts[adrPortuList];         // Nacteni danych parametru com portu kam se maji data vyslat
            comPort.Write(data + "\n");                               // Pred vyslanim pridam LF(line feed) znak \n vyzaduje protokol SCPI, ktery komunikuje s mericimi pristroji
        }

        void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            Thread.Sleep(500);
            SerialPort comPort = (SerialPort)sender;                // <- Obtain the serial port
            string data = comPort.ReadExisting();

            //zkontroluju jestli je event nekde z venci teto tridy pouzit, kdyby nebyl nebudu ho volat, protoze je null = exception
            if (OnSerialDataReceived != null)
            {
                OnSerialDataReceived(comPort, data);
            }
        }

    }

Další třída je SettingsForm, kde mi měl uživatel nastavit pomocí comboboxů rychlost, paritu a stopbit. Změněné hodnoty by se měli uložit zpátky do listu serialPorts po stisku tlačítka OK. V metodě SettingsFormLoad generuji komponety (tabcontrol, comboboxy, labely) podle počtu sériových portů v pc. Viz. kód.

public partial class SettingsForm : Form
    {
        public Communication comm = new Communication();

        private static List<ComboBox> GetComboBoxes(Control parent)
        {
            List<ComboBox> result = new List<ComboBox>();
            foreach (var control in parent.Controls)
            {
                if (control is ComboBox)
                {
                    result.Add(control as ComboBox);
                }
            }
            return result;
        }

        void ButtonOKClick(object sender, EventArgs e)
        {
            List<ComboBox> allCombos = new List<ComboBox>();            // Ulozeni vsech comboboxu
            foreach (var tab in tabControl1.TabPages)
            {
                allCombos.AddRange(GetComboBoxes((Control)tab));        // Nacti vsechny comboboxy, ktere co jsou v dane tabPage
            }

            for (int i = 0; i < tabControl1.TabPages.Count; i++)
            {
                comm.serialPorts[i].BaudRate = Convert.ToInt32(allCombos[(3 * i)].SelectedItem);
                comm.serialPorts[i].Parity = (Parity)Enum.Parse(typeof(Parity), allCombos[(3 * i) + 1].Text);
                comm.serialPorts[i].StopBits = (StopBits)Enum.Parse(typeof(StopBits), allCombos[(3 * i) + 2].Text);
            }
            this.Close();
        }
    }

Při otevření formuláře SettingsForm nabiju do comboboxů hodnoty rychlosti, parity, stopbitu z listu serialPorts. Pak uživatel změní v comboboxech hodnoty, které já sice uložím do listu serialPorts, ale při opětovném otevření SettingsFormu si tyto hodnoty přepíši na defalultní v konstruktoru třídy Communication.
A teď nevím jestli mám postavit úplně jinak třídu Communication přidat jí atributy rychlost, parita, stopbit a v hlavním MainFormu vytvořit jednotlivé sériové porty, které bych pak s nastavením všech parametrů uložil do listu?? Ukládání sériových portů do listu se mi líbí a i se mi sním dobře pracuje.
Prosím o rady jestli mám nechat stávající řešení a upravit konstruktor Communication a případně jak, nebo jestli mám třídu Communication postavit jinak nebo zvolit úplně jiné řešení??? Jsem začátečník takže budu rád za jakoukoliv radu a nakopnuní a případné kousky kódu ;-) Celý zdroják programu můžu nahrát....
To jsem se nějak rozepsal :-( Díky moc...

 
Odpovědět
15.3.2016 14:24
Avatar
Odpovídá na Michaal.K
sadlomaslox25:15.3.2016 19:51

musis sdilet instanci comm pres celou aplikaci. budto si udelej tridu Communication jako Singleton, nebo si instanci tridy Communication vytvor ve formu 1 a posli si to do formu 2 pres konstruktor formu2.

 
Nahoru Odpovědět
15.3.2016 19:51
Avatar
Michaal.K
Člen
Avatar
Odpovídá na sadlomaslox25
Michaal.K:16.3.2016 8:02

Ahoj a jak se dělá sdílení instance přes celou aplikaci?? Mohl bys mi to blíže popsat?? Díky

 
Nahoru Odpovědět
16.3.2016 8:02
Avatar
Ondřej Štorc
Tvůrce
Avatar
Odpovídá na Michaal.K
Ondřej Štorc:16.3.2016 9:30

Třeba takto:
v program.cs:

private static Communication instance;
public static Communication Instance
{
        get
        {
                if(instance == null)
                        instance = new Communication();
                return instance;
        }
}

private Communication() { /* aby mohla existovat jen jedna instance třídy */}

a v kódu k tomu budeš přistupovat nějak takto:

var communication = Communication.Instance;
...
Akceptované řešení
+20 Zkušeností
+2,50 Kč
Řešení problému
Nahoru Odpovědět
16.3.2016 9:30
Život je příliš krátký na to, abychom bezpečně odebírali USB z počítače..
Avatar
Michaal.K
Člen
Avatar
Odpovídá na Ondřej Štorc
Michaal.K:16.3.2016 10:35

Ahoj, díky moc za rady. Tak jsem instanci přidal do program.cs

static class Program
    {
        private static Communication instance;
        public static Communication Instance
        {
            get
            {
                if (instance == null)
                    instance = new Communication();
                return instance;
            }
        }

        private Communication() { /* aby mohla existovat jen jedna instance třídy */}

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }

A v řádku: private Communication() { /* aby mohla existovat jen jedna instance třídy */} mi to hází chybu: Method must have a return type...
Ale nějak nechápu funkci instance. Jak pak budu přistupovat třeba k listu serialPorts ve třídě Communication?? A jak se teď bude chovat konstruktor ve třídě Communication, teď mi tam dochází k přepisování listu při volání SettingsFormu? Prosím o vysvětlení.
Jinak je ta aplikace z pohledu OOP navržena dobře?
Díky...

Editováno 16.3.2016 10:37
 
Nahoru Odpovědět
16.3.2016 10:35
Avatar
Ondřej Štorc
Tvůrce
Avatar
Odpovídá na Michaal.K
Ondřej Štorc:16.3.2016 10:49

Ahoj, moje chyba. Ten kód nahoře má být samozřejmě v těle třídy Communication. Ani nevím proč jsem to tam psal... :)

Nahoru Odpovědět
16.3.2016 10:49
Život je příliš krátký na to, abychom bezpečně odebírali USB z počítače..
Avatar
Michaal.K
Člen
Avatar
Odpovídá na Ondřej Štorc
Michaal.K:16.3.2016 13:01

Tak jsem ten kód dal do těla třídy Communication, ale v řádku: instance = new Communication(); mi to hází chybu: The call is ambiguous between the following methods or properties: 'Sender_device_RS232­.Communication­.Communication()' and 'Sender_device_RS232­.Communication­.Communication()'
Mohl bys mi prosím blíže vysvětlit tu instanci? A jak se teď bude chovat konstruktor ve třídě Communication, teď mi tam dochází k přepisování listu při volání SettingsFormu? Prosím o vysvětlení.
Jinak je ta aplikace z pohledu OOP navržena dobře?
Díky

 
Nahoru Odpovědět
16.3.2016 13:01
Avatar
Ondřej Štorc
Tvůrce
Avatar
Odpovídá na Michaal.K
Ondřej Štorc:17.3.2016 10:24

Právě že v tom ten singleton spočívá a to že nemůžeš vytvářet nové instance objektu. Tudíž musíš sdílet jeden objekt pro celou aplikaci. Vymaž ten prázdný konstruktor a uprav ten tvůj jíž vytvořený na private... V kódu k němu pak přistupuj pomocí Communication­.Instance. Data, která jsou v communication tedy budou sdílená mezi formy.

Nahoru Odpovědět
17.3.2016 10:24
Život je příliš krátký na to, abychom bezpečně odebírali USB z počítače..
Avatar
Michaal.K
Člen
Avatar
Odpovídá na Ondřej Štorc
Michaal.K:17.3.2016 15:15

Ahoj, díky za vysvětlení a pomoc. Teď už vše funguje jak má.
Ještě jsem se tě chtěl zeptat jestli je ta aplikace z pohledu OOP navržena dobře? Nebo by jsi něco řešil jinak? Ještě jednou díky....

 
Nahoru Odpovědět
17.3.2016 15:15
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 9 zpráv z 9.