Diskuze: C# - předávání dat mezi třídou a formulářem
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.


Jan Vargovský:9.5.2018 18:55
Klasickým observer vzorem, třeba...
pseudokód:
class Komunikace
{
public event Action<string> OnDataReceived;
void SeriovyPort_DataReceived(...){
string data = seriovyPort.ReadExisting();
OnDataReceived?.Invoke(data);
}
}
class MainForm{
ctor()
{
var komunikace = new Komunikace();
komunikace.OnDataReceived +=str => richTextBoxu.Text += str;
}
}
Osobně bych to řešil tak že bych do třídy komunikace přidat proměnnou
typu reference na RichEdit a do kontruktoru komunikace jako parametr typ
RichEdit a z MainFormu pak:
new komunikace(this->prislusny_richedit);
Třída komunikace si to v konstruktoru uloží do své proměnné a v té
události zavolá this->rich_edit->Text = ...
Myslím že z hlediska rychlosti vykonání té akce to bude asi nejrychlejší
řešení.
Radek Chalupa
- konzultace a školení programování, (C/C++, C#, WinAPI, .NET, COM, ATL, MFC...)
- vývoj software na zakázku
Michaal.K:10.5.2018 10:15
Ahoj,
díky za odpověď. Mohl bys to trochu lépe rozvést a uvést nějaký
příklad kódu. Děkuji
Michaal.K:10.5.2018 10:40
Ahoj, díky za odpověď. Tuto možnost vůbec neznám. Našel jsem článek
na observer i zde na itnetwork, ale moc moudrej ztoho nejsem.
Zkoušel jsem tvůj kód doplnit a upravit, ale pořád to nefunguje.
Nevím jestli jsem dobře popsal svůj problém. Pokud mi po sériové lince
přijdou nějaká data uloží se ve třídě Komunikace.cs do proměnné data a
já bych potřeboval jakmile přidnou nějaké data tak aby se zobrazila v
MainFormu v richTextBoxu.
Díky za pomoc
Radek Chalupa:10.5.2018 11:27
Jednoduchá ukázka - viz. podstatný úsek kódu:
class akce
{
private RichTextBox _rtb = null;
public akce(RichTextBox rtb)
{
this._rtb = rtb;
neco_udelej();
}
private void neco_udelej()
{
if (null == this._rtb)
throw new Exception("není nastaven textbox");
this._rtb.Text = Environment.TickCount.ToString();
}
}
public partial class Form1 : Form
{
private akce _a = null;
public Form1()
{
// Pokud bude akce ve vlastním threadu
CheckForIllegalCrossThreadCalls = false;
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (null != _a)
return;
_a = new akce(this.rich_text_1);
}
}
Radek Chalupa
- konzultace a školení programování, (C/C++, C#, WinAPI, .NET, COM, ATL, MFC...)
- vývoj software na zakázku
Marian Benčat:10.5.2018 12:14
Se vší úctou pane Chalupa (Céčkaře vždy uznávám), ale předávat do třídy určené k nějaké síťové komunikaci nějaké UI prvky je hloupost porušující snad úplně všechny pravidla...
Nějaká abstrakce? SoC? Enkapsulace?
Chápu, že jste přišel z C/C++ kde se třeba řeší výkon na každém kousku kódu, takže i indirect call je prostě znát, tak v HLL jazyku snad nebudemezahazovat všechny poučky pro správně napsaný software, aby jsme něco ušetřili?
Takovéto premature optimalizace, které způsobí 100 problémů jinde, mi nikdy nešly na rozum..
Michaal.K:10.5.2018 12:46
Tak ve třídě komunikace v metodě SeriovyPort_DataReceived program vyhodí chybovou hlášku na řádku:
this.zapisLogu.AppendText(data);
Neošetřená výjimka typu System.InvalidOperationException vznikla v System.Windows.Forms.dll.
Další informace: Operace mezi vlákny není platná: Přístup k ovládacímu prvku richTextBoxLog proběhl z jiného vlákna než z vlákna, v rámci kterého byl vytvořen.
Marian Benčat:10.5.2018 12:55
Ano, protože pan Chalupa zapomněl, že je to Single Thread Apartment, kde má přístup k UI prvkům pouze thread, který je vytvořil = hlavní UI thread. Tedy tyto UI "úpravy" musíš nějakým způsobem delegovat do hlavního vlákna.
A proto, by nějaká třída pro síťovou komunikaci (nebo cokoliv jiného na jiné vrstvě než je UI) v žádném případě neměla mít referenci na UI prvky, protože neví, jak ty UI prvky fungují a že běží v nějakém WinForms..
EDIT:
Nezapomněl, hackuje to:
CheckForIllegalCrossThreadCalls = false;
Nikdy nedělej:
CheckForIllegalCrossThreadCalls = false;
Je to hackování, které možná někdy bude fungovat, ale většinou ne.. K
UI prvkům "odjinud" z jiných threadů musíš delegovat do hlavního threadu,
třeba přes:
https://msdn.microsoft.com/…yzhdc6b.aspx
+20 Zkušeností
+2,50 Kč

Radek Chalupa:10.5.2018 14:16
Pokud programátor ví co dělá a vidí trochu pod povrch, je schopen
rozhodnout kdy to bude fungovat. A když potřebuju udělat jenom jednoduchou
aplikaci s jedním formem a jednou třídou na komunikaci, nemyslím že je
třeba brát kanón na vrabce...
Samozřejmě pokud by ta zmíněná třída měla být vytvořena pro nějakou
univerzální knihovnu, je to jiná situace.
Ostatně .NET je postaven nad Windows API a ve Windows API lze bez jakýchkoliv
problému přistupovat k oknům (zde tedy UI prvkům) z jiného vlákna.
Michaal.K:10.5.2018 14:21
Tak jsem to nakonec vyřešil nakonec takto:
Třída Komunikace.cs
class Komunikace
{
SerialPort seriovyPort = new SerialPort(); // Novy seriovy port
public delegate void DataReceivedEventHandler(string data); // Deklarace delagata, ktery bude slouzit jako predpis metody predstavujici reakci na udalost (handler)
public event DataReceivedEventHandler OnSerialDataReceived; // Deklarace udalosti
// Konstruktor tridy Komunikace
public Komunikace()
{
seriovyPort.PortName = parametrySerialTrans.ComPort; // Nastavim nazev portu serioveho portu z tridy Parametry
seriovyPort.BaudRate = parametrySerialTrans.Rychlost; // Nastavim rychlost serioveho portu z tridy Parametry
seriovyPort.Parity = parametrySerialTrans.Parita; // Nastavim paritu serioveho portu z tridy Parametry
seriovyPort.StopBits = parametrySerialTrans.StopBit; // Nastavim stop bit serioveho portu z tridy Parametry
seriovyPort.Handshake = Handshake.None;
seriovyPort.ReadTimeout = 500;
seriovyPort.WriteTimeout = 500;
seriovyPort.DtrEnable = true;
seriovyPort.RtsEnable = true;
seriovyPort.DataReceived += new SerialDataReceivedEventHandler(SeriovyPort_DataReceived); // Prida udalost, ktera je vyvolana pokud dojde k zapisu do prijimaciho stringu
}
/// <summary>
/// Metoda, ktera je vyvolana pokud dojde k zapisu do prijimaciho stringu
/// </summary>
private void SeriovyPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(300); // Cekam nez se objevi vsechny data v prijimacim stringu
string data = seriovyPort.ReadExisting();
//string data = seriovyPort.ReadLine();
// Zkontroluju jestli je event nekde z venci teto tridy pouzit, kdyby nebyl nebudu ho volat, protoze je null = exception
if (OnSerialDataReceived != null)
{
OnSerialDataReceived(data);
}
}
}
MainForm:
public partial class MainForm : Form
{
private Komunikace komSeriovyPort = new Komunikace(); // Komunikace se cteckou carovych kodu
public MainForm()
{
InitializeComponent();
komSeriovyPort.OnSerialDataReceived += new Komunikace.DataReceivedEventHandler(komSeriovyPort_OnSerialDataReceived); // Prida udalost, ktera je vyvolana pokud dojde k zapisu do prijimaciho stringu
}
/// <summary>
/// Metoda se vyvola pokud dojde k zapisu do prijimaciho stringu
/// </summary>
public void komSeriovyPort_OnSerialDataReceived(string data)
{
this.Invoke(new EventHandler(delegate
{
if (data[(data.Length) - 1] == '\r') // Testuji zda nakonci prijateho retezce je znak \r
{
data = data.Remove((data.Length) - 1); // smazu znak \r ktery zpusoboval dalsi odradkovani.
}
richTextBoxLog.AppendText("[" + DateTime.Now.ToShortTimeString() + "] " + data); // Dany text zapisu do richTextBoxLog
richTextBoxLog.ScrollToCaret(); // Posunu kurzor na konec, tim dosahnu rolovani textu
richTextBoxLog.Refresh(); // Provedu refresh richtextBoxu
}));
}
}
Prosím o kontrolu jestli to tak může být. Zatím to funguje jak má.
Jestli to není nějak krkolomně napsané.
Díky moc
Marian Benčat:10.5.2018 14:35
Myslím si, že jestli je něco kanón na vrabce, tak je to především vaše optimalizace v tomto případě a hackování threading modelu právě "runtime" ve kterém pracujete...
To, že se impliciítně nesmí šahat do hlavního UI threadu z jiných vláken má svoje hodně dobré důvody, které jistě znáte, tak nevím, proč je obcházet, obzvláště, pokud je řešení poměrfně dost jednoduché a comprehensible.
Díky bohu, nový a rozumný .NET už nad Windows API postavený není.
Marian Benčat:10.5.2018 14:38
Základní pointa je správně, tedy Invoke() na controlu (způsob "delegování" do hlavního threadu)
Radek Chalupa:10.5.2018 15:23
Upřímně řečeno právě že nevím jaké jsou ty zmíněné hodně dobré důvody nepřistupovat ke GUI přes vlákna. Uvítám nějaký příklad.
A nad čím je postavený ten "nový a rozumný net"? To že je tam nějaká multiplatformní mezivrstva přece neznamená že ta implementace na "spodní" úrovni nevolá příslušné sužby/API toho kterého systému?
Marian Benčat:10.5.2018 16:28
Tak především to, že pokud je něco striktně určeno pro použití v STA (třeba jako celé WinForms / WPF), tak nejsou v základu threadsafe a jakmile něco není threadsafe tak přístup k tomu z více threadu (třeba v tomto případě main UI thread a ostatní) může způsobit poměrně vážné problémy..(to už ale jistě víte).
A proč nejsou ty controly threadsafe? Protože je mnohem lepší je nedělat implicitně threadsafe a locknout jejich použití do single thread prostředí, protože je to:
- výkonné (žádný locking na úrovni každé komponety - navíc CAS instrukce by nebyly zde ani použitelné, ani vhodné)
- bezpečné (žádný multithread race condition)
- jednoduché na implementaci a použití
To co jsem napsal o :NET, tím jsem myslel, že v něm není přímo zadrátování winapi a "iisko".
Mimochodem, mám pocit (ale ruku do ohně za to nedám), že:
CheckForIllegalCrossThreadCalls = false
stále způsobuje vyhazování exception, jen se nějakým způsobem handlí. Takže co jste získal tím posláním UI prvků do třídy komunikace, jste 10x ztratil exceptionou a chain of responsibility
Jan Vargovský:11.5.2018 6:44
Jo, jestli ti nevadí, že je to psáno stylem jak v roku 2005, tak je to ok.
Když teda zavřu oči u toho Thread.Sleep(...)
Pánové diskutující trošku OT.
Chápu přístup obou z Vás a v podstatě bych souhlasil s oběma, kdyby tam nebyl ten hack na CheckForIllegalCrossThreadCalls.
Radek Chalupa Ten předpoklad "pokud programátor ví" bych v tomhle případě zamítnul. Jestli píšu utilitu sám pro sebe, tak to tam klidně můžete takhle naprasit, ale nedovolil bych si to napsat nikde do kódu, který by viděl někdo další. Udělat to "správně" zabere asi tak 15 sekund. Nehledě na to, že jestli tam něco bude editovat, tak se bude méně bít do hlavy, že hledá UI funkcionalitu po všech souborech.
@Marian Benčat (bohužel přestalo fungovat označování lidí ) na můj vkus bereš až moc
dogmaticky všechny design patterny a best practices (ať už je to modularita,
solid nebo cokoliv jiného).
ad nový .NET) a co .net core 3? Já jsem zvědavý jakým způsobem to chcou portnout na ostatní platformy (první jsem si myslel, že to prostě jen bude WF/WPF atd. na .NET Coru pro windows only, ale když to má být i v .net standardu, tak už to cross platform "musí" být)
Michaal.K:11.5.2018 7:41
Díky za info. No tak vadí, že je to napsaný jak z roku 2005 Mohl bys mi kód poupravit, aby
odpovídal novým trendům nebo případně vysvětlit co napsat jinak?
Rád si doplním znalosti...
Díky moc
Marian Benčat:11.5.2018 9:14
Já jsem úplně poslední člověk co se řídí dogmaty, ale předávat si do nějaké komunikující třídy UI control, za to by můj podřízený letěl, to je pravda
Jan Vargovský:11.5.2018 15:18
Líbí se mi tvůj přístup, nehledáte vývojáře?
Marian Benčat:11.5.2018 15:25
hledáme
v jednom kuse mizí..
Jan Vargovský:11.5.2018 15:28
definovat vlastní delegát už je dle mě zbytečné, vystačíš si s Action<...>, Func<...>.
public delegate void DataReceivedEventHandler(string data); // Deklarace delagata, ktery bude slouzit jako predpis metody predstavujici reakci na udalost (handler)
public event DataReceivedEventHandler OnSerialDataReceived; // Deklarace udalosti
na
public event Action<string> OnSerialDataReceived;
Invoke už nemusíš dělat přes delegáte, ale stačí ti anonymní metoda
this.Invoke(new EventHandler(delegate
{
if (data[(data.Length) - 1] == '\r') // Testuji zda nakonci prijateho retezce je znak \r
{
data = data.Remove((data.Length) - 1); // smazu znak \r ktery zpusoboval dalsi odradkovani.
}
richTextBoxLog.AppendText("[" + DateTime.Now.ToShortTimeString() + "] " + data); // Dany text zapisu do richTextBoxLog
richTextBoxLog.ScrollToCaret(); // Posunu kurzor na konec, tim dosahnu rolovani textu
richTextBoxLog.Refresh(); // Provedu refresh richtextBoxu
})
na
this.Invoke(() =>
{
...
})
pak
komSeriovyPort.OnSerialDataReceived += new Komunikace.DataReceivedEventHandler(komSeriovyPort_OnSerialDataReceived);
ne
komSeriovyPort.OnSerialDataReceived += komSeriovyPort_OnSerialDataReceived;
já bych nepsal tohle:
SerialPort seriovyPort = new SerialPort();
seriovyPort.PortName = parametrySerialTrans.ComPort; // Nastavim nazev portu serioveho portu z tridy Parametry
seriovyPort.BaudRate = parametrySerialTrans.Rychlost; // Nastavim rychlost serioveho portu z tridy Parametry
seriovyPort.Parity = parametrySerialTrans.Parita; // Nastavim paritu serioveho portu z tridy Parametry
seriovyPort.StopBits = parametrySerialTrans.StopBit; // Nastavim stop bit serioveho portu z tridy Parametry
seriovyPort.Handshake = Handshake.None;
seriovyPort.ReadTimeout = 500;
seriovyPort.WriteTimeout = 500;
seriovyPort.DtrEnable = true;
seriovyPort.RtsEnable = true;
ale radši tohle:
seriovyPort = new SerialPort {
Handshake = Handshake.None,
ReadTimeout = 500,
WriteTimeout = 500,
DtrEnable = true,
RtsEnable = true,
}
protože pak máš hned výčet propert v intellisense, které ti chybí nainicializovat.
if (OnSerialDataReceived != null)
{
OnSerialDataReceived(data);
}
na
OnSerialDataReceived?.Invoke(data);
Už jen třeba proto, že to tvoje není threadsafe a tohle je jako atomická operace (tohle tě nemusí trápit, ale do budoucna používej raději elvis operátor.
Jan Vargovský:11.5.2018 15:31
Jestli člověk jednou napíše něco nesmyslného a už není, tak se není
čemu divit
Erik Šťastný:11.5.2018 15:49
Pokud je to myšleno vážně, tak programátorovi je to asi fuk, i ti špatní si v dnešní době dokážou najít rychle práci.
Spíše vy jste ti co pořád dokola hledáte nového člověka, než aby jste si vychovali vlastního a odnaučili ho špatné věci. U nás se teda postupuje zcela opačným způsobem.
Jan Vargovský:11.5.2018 16:04
Zrovna věnuju kolegovi celý můj den a řeším s ním jeho úkol. Všude se převychovávají lidi, mě osobně baví lidi učit a předávat jim své zkušenosti. Rád bych ale zažil team, kdy každý ví co dělat a řešili byste spolu fakt jen takové špeky, jestli někoho nenapadne něco lepšího.
Marian Benčat:12.5.2018 9:32
Pak musíš nabírat jen lidi stejné kvality jako jsi ty.
Luboš Běhounek Satik:12.5.2018 13:04
Pak musíš nabírat jen lidi stejné kvality jako jsi ty.
Zobrazeno 31 zpráv z 31.