port szeregowy, ThreadSleep() w UI

port szeregowy, ThreadSleep() w UI
DS
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 4 lata
  • Postów:8
0

Potrzebuję obsłużyć sekwencję komend-odpowiedzi do pewnego urządzenia podłączonego po porcie szeregowym.
Z grubsza taka przykładowa sekwencja wygląda tak;
// ***********************************************************************************
// PC:: OPEN PORT,
// => X, <=0x10,
// => V [171B], <=0x06 0x1e 0x34 0x30 0x32 0x03,
// =>S, <=0x06 0x1e 0x34 0x30 0x32 0x03,
// <= 96xVALUE+HEADER [???B],
// =>X, <=0x10,
// CLOSE PORT ::READER
// ***********************************************************************************
Odpowiedzi symuluję wysyłając odpowiednie znaki, lub sekwencje z RealTerm'a. Co do zasady trochę działa, ale...

Problem, który napotkałem polega na tym, że w mojej zmiennej ReaderAnswer (public string ReaderAnswer zadeklarowane w ciele głównego formularza Form1 ) pojawia się odebrana dana, ale dopiero po kliknięciu testowego MessageBox'a (poniżej fragment kodu), a bez MessageBox'a wartość pojawia się dopiero po IF (oczywiście wszystko wysłane na czas, ale obsługa portu szeregowego to iny wątek, Delegate, Invoke, itd.). Wstrzymywanie UI jest akceptowalne, więc używam ThreadSleep() żeby dać sobie szansę na wysłanie symulowanej odpowiedzi, ale jeśli nie będzie tych MessageBox'ów na kolejnych etapach, to dane są jakby spóźnione w mojej sekwencji, a tym samym nigdy się to nie wykona poprawnie. Innymi słowy zatrzymywanie wątku UI przez ThreadSleep niczego nie wnosi (choćbym czekał bardzo długo), a dane odczytane z bufora portu pojawiają się w mojej zmiennej tylko po zaklikaniu MessageBox'a.

Może komuś z Was przyjdzie coś do głowy, może robię jakiś oczywisty błąd...

// ******** START/STOP reading ********
private void pictureBox_startReading_Click(object sender, EventArgs e)
{
bool _error = false;
bool _readingEnabled = true;

        while (_readingEnabled)
        {
            connect();          // Open port
            Thread.Sleep(200);

            //step 1
            ReaderAnswer = string.Empty;
            expectedReaderAnswer = BiotekReader.biotek_str_dle_hex;           // "\x10";
            expectedReaderAnswerLength = expectedReaderAnswer.Length;

            Send_Biotek_X();

            Thread.Sleep(2000);       // muszę mieć czas na odpowiedź

            MessageBox.Show("1");   // KLUCZOWY MessageBox - jeśli go nie będzie, poniższy IF ustawi _error
            //MessageBox.Show(ReaderAnswer + " : " + expectedReaderAnswer);
            if (String.Equals(ReaderAnswer, expectedReaderAnswer))
            {
                //MessageBox.Show(ReaderAnswer + "   ok1   :-)");                 //
            }
            else
            {
                MessageBox.Show(ReaderAnswer + "   !! ok 1" + "\n" + "   ;-(");
                _error = true;
                break;
            }
            //MessageBox.Show(ReaderAnswer + " : " + expectedReaderAnswer);   // poglądając wartość ReaderAnswer będzie ona OK na tym etapie mimo, 
                                                                                                                            // że przed IF'em powyżej była Empty

            Thread.Sleep(1000);

            //step 2
            ReaderAnswer = string.Empty;
            expectedReaderAnswer = BiotekReader.biotek_str_status_6B_hex_2;     //!!
            expectedReaderAnswerLength = expectedReaderAnswer.Length;

            Send_Biotek_V();
            Thread.Sleep(2000);

            MessageBox.Show("2");   // kolejny KLUCZOWY dla poniższego IF'a MessageBox
            rtbox_test_RX.AppendText(" <" + ReaderAnswer);
            if (String.Equals(ReaderAnswer, expectedReaderAnswer))
				...
				...
				...

    // delegate used for Invoke
    internal delegate void StringDelegate(string data);

    /// <summary>
    /// Handle data received event from serial port.
    /// </summary>
    /// <param name="data">incoming data</param>
    public void OnDataReceived(string dataIn)
    {
        //Handle multi-threading
        if (InvokeRequired)
        {
            Invoke(new StringDelegate(OnDataReceived), new object[] { dataIn });
            return;
        }
Tasmanian Devil
Hej! Twój post prawdopodobnie zawiera niesformatowany kod. Użyj znaczników ``` aby oznaczyć, co jest kodem, będzie łatwiej czytać. (jestem botem, ta akcja została wykonana automatycznie, prawdopodobieństwo 0.9999705)
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
0

ten kod jak i samo opis jest dziwny. Co to jest Send_Biotek_X i Send_Biotek_V, gdzie się ustawia ReaderAnswer i jak w ogóle działa (ma jakieś timeouty, timmingi czy coś podobnego czy po prostu po wysłaniu polecenia trzeba zaczekać na odpowiedz) komunikacja z tym urządzeniem?


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
DS
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 4 lata
  • Postów:8
0

Tak, zdaję sobie sprawę, że to wszystko może być niezbyt zrozumiałe - samo zrozumienie protokołu komunikacyjnego tego konkretnego urzadzenia Biotek (nie publikuą dokumentacji, bo maja swoje komercyjne oprogramowanie) zajęło mi kilka tygodni (mogłem podsłuchać komunikację jego oryginalnego oprogramowania, ale mam też takie urządzenie starszego typu w domu). Dziś byłem już mocno sfrustrowany pisząc post niejako w desperacji... Ale miło mi, że ktoś to jednak przeczytał, więc postaram się to doprecyzować.

Na początek sama sekwencja komend i odpowiedzi pokazana na samym początku:

Send_Biotek_X to 1-bajtowa komenda X (wysyłam ASCII "X"), po której spodziewamy się 1-bajtowej odpowiedzi 0x10 (komunikujemy sie z urządzeniem). Nastepnie komenda V ((Send_Biotek_V) - to już trudniejsza sprawa, bo to jest 171 bajtów konfiguracji pomiaru, np. określona długość fali filtra optycznego (urządzenie ma ich kilka, do różnych celów wykorzystujemy różne gługości fali; mam statyczny string odpowiedniej konfiguracji dla mojego przypadku). Jeśli urządzenie zaakceptuje komendę V z parametrami, to odpowiada 6-bajtowym statusem. Status zawsze zaczyna się od dwóch bajtów 0x06, 0x1e (ACK, RS), nastepnie są trzy bajty faktycznego statusu (np. 0x34 0x30 0x32, albo 0x30 0x30 0x30) i kończy się 0x03 (EOT). Wtedy juz tylko podajemy jednobajtową komendę S (Send_Biotek_S), a urzadzenie powinno znowu odpowiedzieć 6-bajtowym statusem (dokładnie tym samym co poprzednio), a następnie zwraca 96 wartosci liczbowych, które są "kwintesencją tematu" (o to walczymy).
I to wszystko umiem wykonać ręcznie z dowolnego terminala - podaję komendy, lub wysyłam przykładowy ciąg dla komendy V, a urządzenie poprawnie odpowiada - sekwencja jest sprawdzona. Zatem wydaje się, że wystarczy "tylko" to zakodować i zabrać sie za interpretację wyników...

I tak dochodzimy do mojego kodu dotyczącego odbierania danych z urządzenia - założyłem, że jeśli dobrze dobiorę pauzy pomiędzy kolejnymi krokami, to w tym właśnie czasie obsługa portu szeregowego w innym wątku odbierze dane (które w warunkach testowych wysyłam z drugiego okienka z RealTerm'a, gdzie mam naszykowane sprawdzone odpowiedzi; porty szeregowe w kompie spięte ze sobą) i będa czekały do oceny ich poprawności zanim wyślę nastepną komendę. Dla przykładu spodziewam się w ReaderAnswer pojawienia się jednego bajtu o wartości 0x10:

        ReaderAnswer = string.Empty;
        expectedReaderAnswer = BiotekReader.biotek_str_dle_hex;           // "\x10";
        expectedReaderAnswerLength = expectedReaderAnswer.Length;

        Send_Biotek_X();

        Thread.Sleep(2000);       // muszę mieć czas na odpowiedź

Następujacy dalej IF ( czy ReaderAnswer = expectedReaderAnswer ? ) wykonuje sie zgodnie z przewidywaniem jedynie, gdy przed nim jest MessageBox.Show - jeśli go nie ma, to ReaderAnswer jest PUSTY (podglądam to zakładając Breakpoint'y). Jeśli nie ma MessageBox.Show, to ReaderAnswer pojawia się dopiero po wykonaiu sie IF...

Tego własnie nie mogę pojąć i nie znalezłem też żadnego pomysłu jak to oszukać :-(

edytowany 2x, ostatnio: dstachur
Grzegorz Świdwa
Grzegorz Świdwa
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 4 lata
  • Postów:385
0

Pokaż metodę connect

DS
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 4 lata
  • Postów:8
0

Obsługa portu szeregowego z wykorzystaniem kodu aplikacji Termie (David McClurg):

Kopiuj

//Open Port
                CommPort com = CommPort.Instance;
                if (!com.IsOpen)
                {
                    com.Open();
                    this.progressBar_CommStatus.Value = 100;
                }
Kopiuj
public void Open()
        {
			Close();

            try
            {
                _serialPort.PortName = Settings.Port.PortName;
                _serialPort.BaudRate = Settings.Port.BaudRate;
                _serialPort.Parity = Settings.Port.Parity;
                _serialPort.DataBits = Settings.Port.DataBits;
                _serialPort.StopBits = Settings.Port.StopBits;
                _serialPort.Handshake = Settings.Port.Handshake;

                // Set the read/write timeouts
                _serialPort.ReadTimeout = 50;
				_serialPort.WriteTimeout = 50;

				_serialPort.Open();
				StartReading();
			}
            catch (IOException)
            {
                StatusChanged(String.Format("{0} does not exist", Settings.Port.PortName));
            }
            catch (UnauthorizedAccessException)
            {
                StatusChanged(String.Format("{0} already in use", Settings.Port.PortName));
            }
            catch (Exception ex)
            {
                StatusChanged(String.Format("{0}", ex.ToString()));
            }

            // Update the status
            if (_serialPort.IsOpen)
            {
                string p = _serialPort.Parity.ToString().Substring(0, 1);   //First char
                string h = _serialPort.Handshake.ToString();
                if (_serialPort.Handshake == Handshake.None)
                    h = "no handshake"; // more descriptive than "None"

                StatusChanged(String.Format("{0}: {1} bps, {2}{3}{4}, {5}",
                    _serialPort.PortName, _serialPort.BaudRate,
                    _serialPort.DataBits, p, (int)_serialPort.StopBits, h));
            }
            else
            {
                StatusChanged(String.Format("{0} already in use", Settings.Port.PortName));
            }
        }		

private void StartReading()
		{
			if (!_keepReading)
			{
				_keepReading = true;
				_readThread = new Thread(ReadPort);
				_readThread.Start();
			}
		}
edytowany 1x, ostatnio: dstachur
Grzegorz Świdwa
Grzegorz Świdwa
Nad ranem przysiądę do tego i dam odp :)
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
1

Send_Biotek_X to 1-bajtowa komenda X (wysyłam ASCII "X"), po której spodziewamy się 1-bajtowej odpowiedzi 0x10 (komunikujemy sie z urządzeniem). Nastepnie komenda V ((Send_Biotek_V) - to już trudniejsza sprawa, bo to jest 171 bajtów konfiguracji pomiaru, ...

Ale mi nie chodzi o opis co ta komenda robi bo ja się tego domyślam. Także nie potrzebuję opisu protokołu bo on jest na razie nieważny. Mi chodzi o kod w jaki sposób wysyłasz coś do urządzenia. Ja wiem jak się to powinno robić przy użyciu CommPort ale nie wiem czy Ty to wiesz i czy robisz to poprawnie.

I tak dochodzimy do mojego kodu dotyczącego odbierania danych z urządzenia - założyłem, że jeśli dobrze dobiorę pauzy pomiędzy kolejnymi krokami,

To jest wg mnie błędne podejście - nie powinieneś czekać x czasu tylko powinieneś czekać aż coś przyjdzie. Oczywiście należało by założyć, że jeśli po jakimś czasie (5 - 10 sekund) nie ma odpowiedzi to uznaje się, że odpowiedź już nie nadejdzie.

to w tym właśnie czasie obsługa portu szeregowego w innym wątku odbierze dane

no i o ten kod, który "odbiera te dane" przede wszystkim chodzi. Bez kodu, który jest odpowiedzialny za wysyłanie (zapis do portu) i odbieranie (odczyt portu) jest naprawdę ciężko cokolwiek powiedzieć


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
Grzegorz Świdwa
Grzegorz Świdwa
Perfekcyjna odpowiedz
DS
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 4 lata
  • Postów:8
0

@abrakadaber:
> w jaki sposób wysyłasz coś do urządzenia
Wysyłam tak, jak napisałem. I działa to DOBRZE - urządzenie odpowiada tak, jak należy. Korzystam z cudzej biblioteki do obsługi portu szeregowego, nic nadzwyczajnego, w zasadzie dość książkowo, Załozyłem, że jesli zawodowy programista to napisał, zrobił obsługę wyjątkowych sytuacji, to wywaliłem mój kod obsługi portu szeregowego i użyłem cudzego.
Jedyna modyfikacja, to "sklejanie danych" odbieranych z bufora (metodą ReadExisting) w OnDataReceived - dla autora tej biblioteki nie miało to znaczenia, bo on doklejał pojawiające się dane do textbox'a, więc nie miało znaczenia w jakich porcjach sie pojawiają. Ja natomiast zauważyłem, że jesli moje urządzenie przykładowo poprawnie odpowiada na wysłaną komendę V, gdy spodziewam się 6 bajtów odpowiedzi, to OnDataReceived jest wołany 2, lub 3 razy, odbierając te 6 bajtów w kawałkach. Stąd składam ReaderAnswer do stanu, gdy jest on zgodny z expectedReaderAnswerLength. I dopiero wtedy badam, czy to jest prawidłowa odpowiedź na tym etapie sekwencji.
Ale to nie z tym mam problem...

> nie powinieneś czekać x czasu tylko powinieneś czekać aż coś przyjdzie
Stąd moje expectedReaderAnswer i expectedReaderAnswerLength zanim wyślę komendę do urządzenia. Niezależnie od tego ile urządzenie potrzebuje czasu na odpowiedź (na status to milisekundy, ale na pomiary to już 30-40 sekund) potrzebowałem w prosty sposób dawać sobie szansę na wysłanie odpowiedzi "z ręki", symulując urządzenie. Ale tak sobie teraz pomyślałem, że bedę czekał aż pojawi się tyle danych ile sie spodziewam sprawdzając ReaderAnswer.Length, a ewentualny timeout obsłużę timerem.

I jeszcze mały update z dnia dzisiejszego:
Zastąpiłem Thread.Sleep(2000) taką funkcją:

Kopiuj
        public void Wait(int ms)
        {
            DateTime start = DateTime.Now;
            while ((DateTime.Now - start).TotalMilliseconds < ms)
                Application.DoEvents();
        }

No i działa bez MessageBox.Show!! Czyli jak ja to rozumiem, Thread.Sleep usypiało także asynchroniczną obsługę odbierania danych, lub na etapie kompilacji nieco odwraca się kolejność wykonywania tych fragmentów kodu.

screenshot-20201231014153.png

A poza wszystkim, dziś odebrałem 2 kolejne urządzenia robiące w zasadzie to samo, co to pierwsze, ale których protokoły komunikacyjne są supełnie inne (raczej brak dokumentacji, trzeba podsłuchiwać oryginalne programy do ich obsługi, tylko jest problem z ich znalezieniem, bo sa stare - "epoki Win95"). Z tym wszystkimm przyjdzie mi się zmierzyć w najbliższym czasie, ale obsługę różnych "normalnych" funkcji tej apki chyba oddam komuś do napisania, bo to dla mnie taki lekko hobbistyczny projekcik - z C# mam do czynienia od kilku miesięcy, od 20+ lat nie kodowałem w niczym obiektowym. Ciagle mam niespodziewane problemy i strasznie mi sie to w czasie rozłazi, a ktoś tam na to trochę czeka...

edytowany 2x, ostatnio: dstachur
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
1
dstachur napisał(a):

No i działa bez MessageBox.Show!! Czyli jak ja to rozumiem, Thread.Sleep usypiało także asynchroniczną obsługę odbierania danych,

Jakbyś wiedział jak działa ta biblioteka i co robi (dlatego dwa razy prosiłem o kod ODBIERAJĄCY dane z portu COM i się nie doczekałem) to byś wiedział czy tam jest wątek czy nie. Pisanie na ślepo to nie programowanie a używanie tej funkcji to zabijanie wydajności - zobacz sobie ile Twój program zjada procesora podczas działania tej funkcji. Jak już to powinna ona wyglądać tak:

Kopiuj
public void Wait(int ms)
        {
            DateTime start = DateTime.Now;
            while ((DateTime.Now - start).TotalMilliseconds < ms)
            {
                Thread.Sleep(100);
                Application.DoEvents();
            }
        }

A poza wszystkim, dziś odebrałem 2 kolejne urządzenia robiące w zasadzie to samo, co to pierwsze, ale których protokoły komunikacyjne są supełnie inne (raczej brak dokumentacji, trzeba podsłuchiwać oryginalne programy do ich obsługi, tylko jest problem z ich znalezieniem, bo sa stare - "epoki Win95").

Przyzwyczaj się :p - tak to zazwyczaj działa w starych (nowe w większości przypadków starają się trzymać jakiś standard) mocno specyficznych urządzeniach


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
DS
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 4 lata
  • Postów:8
0

@abrakadaber:
*> zobacz sobie ile Twój program zjada procesora
*Widzisz, ja się na codzień zajmuję nieco innym IT i mam do dyspozycji kilka tysięcy rdzeni CPU różnej klasy, więc w tym przypadku może to zabijać 100% CPU (1 rdzenia?) przez te pół sekundy, byle efekt był właściwy. A jeśli wiedziałbym dlaczego działa to inaczej, niż wydaje mi sie, że powinno, to nie pytałbym na forum... Fajnie, że podpowiadasz jak zrobić to lepiej, ale żeby mnie tak "łajać" to bez sensu raczej...

> Pisanie na ślepo to nie programowanie
Ja chyba wole "pisać na ślepo", próbując różnych sposobów, niż nie pisać w ogóle. A jak mi się znudzi, to oddam to komuś bardziej profesjonalnemu. W kazdym razie jedni maja z tego chleb, a inni mają fun.

> Przyzwyczaj się :p - tak to zazwyczaj działa w starych (nowe w większości przypadków starają się trzymać jakiś standard) mocno specyficznych urządzeniach
Ja jestem przyzwyczajony, bo studia skończyłem ponad 20 lat temu, lutownicy używam od ponad 30 lat i raczej lubie dłubać w "starociach" :-)
Dawniej w instrukcji do nowego samochodu pisali jak regilować zawory, a dziś jest napisane, żeby nie pić kwasu z akumulatora - podobnie jest z brakiem dokumentacji do współczesnych modeli urządzeń. W przypadku mojej komunikacji ze starym urządzeniem diagnostycznym miałem zagwozdkę związaną z ustawianiem linii DTR (ComPort.DtrEnable = true) - dziś już nikt normalny nie używa tych sygnałów (kto ostatnio widział z bliska RS232 na DB25?!), ale dawniej to komputer musiał wykazać gotowość do przyjecia danych (bo nie potrafił robić zbyt wiele jednoczesnie). A jak czytasz współczesną dokumentację do obsługi portu szeregowego w C#, albo oglądasz na YT tutorial jak to zakodować, to nikt o DTR nie wspomina i jak nie spróbujesz "na ślepo" paru opcji, to nie pójdziesz dalej.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)