$$ \newcommand{\floor}[1]{\left\lfloor{#1}\right\rfloor} \newcommand{\ceil}[1]{\left\lceil{#1}\right\rceil} \renewcommand{\mod}{\,\mathrm{mod}\,} \renewcommand{\div}{\,\mathrm{div}\,} \newcommand{\metar}{\,\mathrm{m}} \newcommand{\cm}{\,\mathrm{cm}} \newcommand{\dm}{\,\mathrm{dm}} \newcommand{\litar}{\,\mathrm{l}} \newcommand{\km}{\,\mathrm{km}} \newcommand{\s}{\,\mathrm{s}} \newcommand{\h}{\,\mathrm{h}} \newcommand{\minut}{\,\mathrm{min}} \newcommand{\kmh}{\,\mathrm{\frac{km}{h}}} \newcommand{\ms}{\,\mathrm{\frac{m}{s}}} \newcommand{\mss}{\,\mathrm{\frac{m}{s^2}}} \newcommand{\mmin}{\,\mathrm{\frac{m}{min}}} \newcommand{\smin}{\,\mathrm{\frac{s}{min}}} $$

Prijavi problem


Obeleži sve kategorije koje odgovaraju problemu

Još detalja - opišite nam problem


Uspešno ste prijavili problem!
Status problema i sve dodatne informacije možete pratiti klikom na link.
Nažalost nismo trenutno u mogućnosti da obradimo vaš zahtev.
Molimo vas da pokušate kasnije.

Цртање текста

Програми који цртају, често уз слике исписују и разне поруке (вероватно сте и сами видели много примера). За цртање текста у језику C# се користе функције DrawString, које су такође методе класе Graphics. Постоји укупно 6 истоимених функција DrawString, које све служе за приказ текста. Свакој од њих треба (између осталог) проследити текст који желимо да прикажемо, фонт којим ће тај текст бити исцртан и четку која се за то користи.

Укратко о фонтовима: Да бисмо задали фонт, треба да креирамо објекат типа Font. Објекти типа Font имају мноштво конструктора и многу се креирати на више начина. Један од једноставнијих је да задамо само име фамилије фонтова и величину. На пример:

Font font = new Font("Arial", 80);

Као што видимо, име фамилије фонтова се задаје као стринг. Поред Arial постоји још неколико десетина фамилија фонтова, а мђу познатијим и често коришћеним су Times New Roman и Verdana. Величина фонта је реалан број типа float.

Објекте типа Font није могуће мењати након што их креирамо. За такве објекте кажемо да су неизменљиви (енгл. immutable). Због тога својства фонта (нпр. логичка својства Bold, Italic, Underline, Strikeout, или целобројно својство Height, које даје размак између редова текста исписаних овим фонтом, изражен у пикселима) можемо само да очитавамо. Да бисмо употребили фонт са другачијим својствима, треба да формирамо нови објекат и да у конструктору специфицирамо та другачија својства. У програмима можемо да користимо произвољан број оваквих објеката, али најчешћа пракса је да се због једноставности ново-формирани фонт смешта у исту променљиву, чиме претходни објекат престаје да постоји.

У вези са фонтовима, треба имати на уму да се за мерење и изражавање величине фонта користе различите јединице мере. У том смислу, не треба мешати Pixel, који представља величину тачке (пиксела) на екрану, и Point, који представља такозвану типографску тачку, а која износи \(1 \over 72\) инча.

Од више постојећих функција DrawString, ми ћемо најчешће користити ову:

g.DrawString(string tekst, Font f, Brush cetka, float x, float y);

Овом функцијом се приказује задати текст тако да му горњи леви угао буде у тачки (x, y), користећи при томе фонт f и четку cetka.

За текст који желимо да прикажемо (или смо већ приказали) може нам бити потребно да знамо његову величину на екрану. У ту сврху користимо једну од функција g.MeasureString. Све ове функције враћају структуру SizeF, која има поља Width и Height типа float. Један од једноставнијих начина да измеримо величину текста помоћу неке од ових функција је нпр.

SizeF velicinaTeksta = CreateGraphics().MeasureString(tekst, new Font("Arial", 80));

Након ове наредбе, у пољима структуре velicinaTeksta су ширина и висина мереног текста. Подразумевана јединица мере је пиксел, што се може променити додатним подешавањима.


Комплетности ради, поменимо да за приказивање текста у комбинацији са графиком и даље можемо да користимо лабеле (није неопходно цртати и текст само зато што цртамо друге ствари). Међутим, када се текст појављује делом преко цртежа (или цртеж преко текста), лабелу је нешто теже уклопити у цртеж, јер она има свој прозор чија позадинска боја иницијално није транспарентна. Тако, лабела је пре свега погодна када се приказује у делу формулара где нема цртања.


Сигурно сте виђали светлеће рекламе са неонским цевима у облику слова. Оне привлаче пажњу тако што се различите групе слова укључују и искључују неким задатим редоследом који се понавља. Следи неколико примера инспирисаних таквим светлећим рекламама.

Пример - додавање слова

Напишите програм који приказује овакву анимацију (текст, фонт, боју и величину слова одабрати самостално)

../_images/anim_neon1.gif

У првом фрејму се приказује само прво слово, а у сваком следећем по једно слово више док се не прикажу сва слова. Након тога следи један фрејм у коме се не приказује ништа, па три фрејма са свим укљученим словима, а затим се све понавља. Интервал тајмера у нашој анимацији је 500 милисекунди.

Из описа (и посматрања примера) можемо да закључимо да пун циклус садржи четири фрејма више него што има слова у тексту.

const string CeoTekst = "PROGRAMIRANJE";
int BrSlova = CeoTekst.Length;
BrFrejmova = BrSlova + 4;

Величину клијентског дела прозора можемо да задамо тако да комплетан текст тачно стаје у прозор. Ради тога треба претходно измерити величину текста.

SizeF stringSize = CreateGraphics().MeasureString(CeoTekst, new Font("Arial", 80));
ClientSize = new Size((int)stringSize.Width, (int)stringSize.Height);

За опис стања ћемо користити текст који треба приказати (стринг TekstZaPrikaz) и бројач фрејмова по модулу n+4 (целобројна променљива IndeksFrejma), где смо са n привремено означили број слова у тексту. Приликом ажурирања стања, на основу индекса фрејма се одређује текст за приказ (неки почетни део текста, или празан стринг, или цео текст), а затим се индекс фрејма увећава за један по модулу n+4.

private void timer1_Tick(object sender, EventArgs e)
{
    if (IndeksFrejma < BrSlova) // ako treba ukljuciti neki pocetni deo teksta
        TekstZaPrikaz = CeoTekst.Substring(0, IndeksFrejma + 1);
    else if (IndeksFrejma == BrSlova)
        TekstZaPrikaz = ""; // ovo je frejm u kome se nista ne prikazuje
    else
        TekstZaPrikaz = CeoTekst; // ovo je jedan od poslednja tri frejma, prikazuje se ceo tekst

    IndeksFrejma = (IndeksFrejma + 1) % BrFrejmova;
    Invalidate();
}

Ево и комплетног програма (функција Form1_Paint је сасвим једноставна па је нисмо посебно коментарисали)

using System;
using System.Drawing;
using System.Windows.Forms;

namespace NenoskaReklama1
{
    public partial class Form1 : Form
    {
        const string CeoTekst = "PROGRAMIRANJE";
        string TekstZaPrikaz = "";
        int BrSlova = CeoTekst.Length;
        int BrFrejmova;
        int IndeksFrejma;

        public Form1()
        {
            InitializeComponent();
            SizeF stringSize = CreateGraphics().MeasureString(CeoTekst, new Font("Arial", 80));
            ClientSize = new Size((int)stringSize.Width, (int)stringSize.Height);
            Text = "Reklama";
            BackColor = Color.Black;

            // Imamo po jedan frejm za svako slovo, plus
            // jedan frejm za iskljucivanje i tri za ukljucivanje svih slova
            BrFrejmova = BrSlova + 4;
            IndeksFrejma = 0;
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.DrawString(TekstZaPrikaz, new Font("Arial", 80), Brushes.Yellow, 0, 0);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (IndeksFrejma < BrSlova) // ako treba ukljuciti neki pocetni deo teksta
                TekstZaPrikaz = CeoTekst.Substring(0, IndeksFrejma + 1);
            else if (IndeksFrejma == BrSlova)
                TekstZaPrikaz = ""; // ovo je frejm u kome se nista ne prikazuje
            else
                TekstZaPrikaz = CeoTekst; // ovo je jedan od poslednja tri frejma, prikazuje se ceo tekst

            IndeksFrejma = (IndeksFrejma + 1) % BrFrejmova;
            Invalidate();
        }
    }
}

Пример - појединачна слова

У овом примеру се прво приказује свако слово посебно, а затим се сва слова 3 пута укључе и искључе. Можете ли да поновите ово понашање?

../_images/anim_neon2.gif

(текст, интервал тајмера, фонт, боју и величину слова одабрати самостално)

Очигледно, у решењу овог примера увелико можемо да искористимо претходни. Једна битна разлика је то што се у овом примеру позиција за испис текста мења. Због тога је згодно да унапред израчунамо X координату сваког слова и све те координате сместимо у низ:

X = new float[BrSlova];
X[0] = 20.0f;
for (int i = 0; i < BrSlova - 1; i++)
{
    string slovo = CeoTekst.Substring(i, 1);
    SizeF velicinaSlova = g.MeasureString(slovo, font, 0, StringFormat.GenericTypographic);
    X[i+1] = X[i] + (int)(velicinaSlova.Width);
}

Овде смо функцију MeasureString користили мало другачије. При подразумеваном приказу текста се користи „додатна логика” у виду алгоритма који одређује положај слова (layout) распоређујући размаке око слова тако да текст као целина што боље изгледа. Овде нас та додатна логика омета, јер положај слова није исти када је оно саставни део текста и када се дописује на претходна слова. Због тога смо овде додали параметар StringFormat.GenericTypographic, којим кажемо да не желимо додатна подешавања размака пре или после текста, између слова итд.

Сада се за опис стања осим стринга TekstZaPrikaz и бројача IndeksFrejma користи и променљива PozicijaX, која (у зависности од редног брја текућег фрејма) одређује место за приказивање текста у текућем фрејму.

Све остало је врло слично претходном примеру. Следи комплетан програм

using System;
using System.Drawing;
using System.Windows.Forms;

namespace NeonskaReklama2
{
    public partial class Form1 : Form
    {
        const string CeoTekst = "PROGRAMIRANJE";
        string TekstZaPrikaz = "";
        int BrSlova = CeoTekst.Length;
        int BrFrejmova;
        int IndeksFrejma;
        float[] X; // koordinata slova teksta
        float PozicijaX;
        Font font;

        public Form1()
        {
            InitializeComponent();
            font = new Font("Arial", 80);
            Text = "Reklama";
            BackColor = Color.Black;

            // po jedan frejm za svako slovo, plus 3 puta po dva frejma za treperenje
            // (treperenje sadrzi po jedan frejm sa svim slovima i jedan bez slova)
            BrFrejmova = BrSlova + 6;
            IndeksFrejma = 0;

            Graphics g = CreateGraphics();
            SizeF velicinaTeksta = g.MeasureString(CeoTekst, font, 0, StringFormat.GenericTypographic);
            ClientSize = new Size((int)velicinaTeksta.Width + 40, (int)velicinaTeksta.Height);


            X = new float[BrSlova];
            X[0] = 20.0f;
            for (int i = 0; i < BrSlova - 1; i++)
            {
                string slovo = CeoTekst.Substring(i, 1);
                SizeF velicinaSlova = g.MeasureString(slovo, font, 0, StringFormat.GenericTypographic);
                X[i+1] = X[i] + (int)(velicinaSlova.Width);
            }

            PozicijaX = X[0];
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (IndeksFrejma < BrSlova) // ako treba ukljuciti jedno slovo
            {
                TekstZaPrikaz = CeoTekst.Substring(IndeksFrejma, 1);
                PozicijaX = X[IndeksFrejma];
            }
            else
            {
                PozicijaX = X[0];
                // ovo je jedan od frejmova za treperenje (naizmenicno sva slova i nista),
                if (IndeksFrejma % 2 == 0) // ako treba ukljuciti sva slova
                    TekstZaPrikaz = CeoTekst;
                else
                    TekstZaPrikaz = "";
            }
            Invalidate();

            // pripremamo se za sledeci frejm
            IndeksFrejma = (IndeksFrejma + 1) % BrFrejmova;
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.DrawString(TekstZaPrikaz, font, Brushes.Yellow, PozicijaX, 0, StringFormat.GenericTypographic);
        }
    }
}

Пример - слова клизе на горе

Напишите програм који приказује текст на овај начин, као у шпицама филмова.

../_images/anim_story_black.gif

У овом пројекту стање сцене описујемо помоћу три целоброне променљиве: YTeksta, IndeksPrvogVidljivogReda и BrojVidljivihRedova.

  • Променљива YTeksta представља Y координату највише линије текста у прозору. Ова вредност ће се при ажурирању сцене најчешће само смањивати за 1, а када највиша линија текста нестане, променљива YTeksta ће се повећати за висину једног реда текста.

  • Променљива IndeksPrvogVidljivogReda говори где се у низу стрингова који садржи текст за приказивање налази ред који ће бити приказан на највишој позицији. Када највиша линија текста нестане, ова вредност се повећава за један, осим ако је у највишој линији текста био последњи стринг из низа. У последњм случају IndeksPrvogVidljivogReda враћамо на 1 (нулти стринг из низа се појављује само на почетку).

  • Променљива BrojVidljivihRedova говори колико редова текста треба приказати. Када ред текста на врху нестан, ову променљиву смањујемо за 1, а када на дну има места за нови ред, онда је повећавамо за 1.

Као и обично, имамо три функције: конструктор формулара у коме иницијализујемо променљиве, функцију Form1_Paint у којој цртамо текст, и функцију timer1_Tick у којој ажурирамо фрејм.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace BeskrajnaPrica
{
    public partial class Form1 : Form
    {
        string[] CeoTekst = {
            "Ово је једна стара прича",
            "о једном човеку",
            "који је желео",
            "да му неко исприча причу"
        };
        int MarginaGoreDole = 10;
        int YTeksta = 80;
        int IndeksPrvogVidljivogReda = 0;
        int BrojVidljivihRedova = 1;
        int VisinaJednogReda = 0;

        public Form1()
        {
            InitializeComponent();
            Graphics g = CreateGraphics();
            // Odredjujemo velicinu prozora
            float maksDuzinaReda = 0;
            foreach (var s in CeoTekst)
            {
                SizeF stringSize = g.MeasureString(s, new Font("Arial", 40));
                maksDuzinaReda = Math.Max(maksDuzinaReda, stringSize.Width);
                VisinaJednogReda = Math.Max(VisinaJednogReda, (int)stringSize.Height);
            }
            ClientSize = new Size((int)maksDuzinaReda + 40, 4 * VisinaJednogReda);
            Text = "Beskrajna priča";
            BackColor = Color.SkyBlue;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            YTeksta--;
            // da li je prvi red izasao gore
            if (YTeksta < MarginaGoreDole)
            {
                YTeksta += VisinaJednogReda;

                IndeksPrvogVidljivogReda++;
                if (IndeksPrvogVidljivogReda == CeoTekst.Length)
                    IndeksPrvogVidljivogReda = 1;

                BrojVidljivihRedova--;
            }

            // da li ima mesta za jos jedan red
            int poz_sl_reda = YTeksta + VisinaJednogReda * BrojVidljivihRedova;
            if (poz_sl_reda + VisinaJednogReda + MarginaGoreDole < ClientSize.Height)
                BrojVidljivihRedova++;

            Invalidate();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            int i_red = IndeksPrvogVidljivogReda;
            int y = YTeksta;
            for (int i = 0; i < BrojVidljivihRedova; i++)
            {
                // prikazujemo jedan red tekstа i pripremamo sledeci
                g.DrawString(CeoTekst[i_red], new Font("Arial", 40), Brushes.Black, 20, y);

                y += VisinaJednogReda;
                i_red++;
                if (i_red == CeoTekst.Length)
                    i_red = 1;
            }
        }
    }
}

Задаци за вежбу

Треперећи текст: Напишите програм који приказује текст који трепери (појављује се и нестаје).

../_images/anim_neon_blink.gif

Анимирани стрип: Смислите кратак дијалог који можете да представите у неколико сличица стрипа. Напишите програм који приказује такве сличице.

../_images/anim_comics.gif

Слова клизе на лево: Овај пример је другачији по томе што се слова померају, али не би требало да буде тежак за реализацију.

(програм је сличан оном из примера са аутом који се креће лево - десно).

../_images/anim_neon3.gif

Слова која бледе: Измените програм „Слова клизе на горе” тако да слова док се пењу постају све блеђа.

../_images/anim_story_fade.gif