(сви примери кода су дати у С# језику. Претпоставља се да знате нешто више од основа C#-a)
GDI+ у GUI програмирању
Скоро свака књига у себи садржи једно или пар поглавња која описују GDI+ и стога ћу само у најкраћим цртама да објасним њега а у наставку текста ће бити речи о примени GDI+a за прављење GUI интерфејса( изненадићете се какве све могућности постоје!), односно претставићу најефектнији( бар колико је мени познато, а познато ми је) метод за моделирање изгледа button-a, форми и других GUI компоненти. На крају, објаснићу могућности израде игара у С#-у уз помоћ GDI+.
- Укратко о основама графичког модела GDI+ -GDI+ садржи гомилу класа, функциа, енумерација које служе за "цртање" графике али такође се преко овог модела у .NET-у врши штампање. Он је ипак врло једноставан, обзиром да може исцртавати само 2D графику без подршке за било какве 3D ефектне, нема подршку за обраду звука и није нарочито брз. Оно у чему лежи његова главна предност је то што вам он омогућава да не размишљате о томе уз помоћ каквог хардверу се извршава "цртање" или, код штампања, какве су карактеристике штампача и да ли су оне одговарајуће. GDI+ вам даје "идеализовану подлогу за цртање", претстављену класом Graphics а ви једноставно на тој "подлози" вршите сво цртање. Да би сте било шта нацртали користећи GDI+ морате имати ту "подлогу".
Да бисте савладали основне елементе цртања помоћу GDI+, потребно је да знате да направите и користите класе чије објашњење следи.
-
Graphics смо већ објаснили, а ево примера како се добија објекат овог типа:
Graphics grap = Form1.CreateGraphics();
Овде је Form1 објекат типа Form. Класа Graphics нема конструктор већ се она добија користећи функцију CreateGraphics(). На пример ову функцију има и класа Button. CreateGraphics() враћа "подлогу за цртање" по контроли чију сте функцију позвали тако да не можете користити нпр. објекат Graphics који сте добили од неког Button-a да цртате нешто по некој другој контроли( нпр. форми) већ само по тој. Корисно је, када вам објекат Graphics више није потребан, да га "ручно" уклоните користећи функцију Dispose() јер Graphics користи системске ресурсе.
-
Pen и Brush су класе које се користе да одреде особине( дебљина, боја, текстура) линија и других облика који се могу исцртавати. Праве се као и сваки други објекти нпр:
Pen pen1 = new Pen(Color.Red, 5); // боја је црвена а дебљина је 5 пиксела
Color је класа преко које су претстављене боје. За све примере у пројекат морате укључити: System.Drawing и System.Drawing2D.
Из класе Brush су изведене класе SolidBrush i TextureBrush. Ево примера прављења свих ових објеката:
Graphics grap = this.CreateGraphics();
Pen pen1 = new Pen(Color.Red, 5);
SolidBrush sb = new SolidBrush(Color.Green);
TextureBrush tb = new TextureBrush( new Bitmap("MojaSlika.jpg"), WrapMode.Tile);
-
Bitmap је класа која служи за , штуро речно, све доступне операције над сликама различитих формата. Изведена је из класе Image а за детаљнији опис функција погледајте у документацију MSDN-a. Bitmap има више преоптерећених конструктора што обезбеђује разнолике начине за израду слике а најуобичајени је да се наводи локација слике коју желите да "отворите".
Bitmap bmPicture = new Bitmap( "MojaSlika.png");
- Само цртање помоћу класе Graphics је поприлично једноставно, али треба обратити пажњу на то где се пише код. Наиме, када год је потребно да се неки део форме или цела форма исцрта поново позива се одређена функција. Која ће функција бити позвана одређује се помоћу евент-а( догађаја) Paint( о догађајима овде нећемо писати, то се подразумева да знате). Ево пар примера исцртавања помоћу горе направљених објеката.
private void Form1_Paint( object sender, PaintEventArgs e)
{
Graphics grap = e.Graphics;
Pen pen1 = new Pen(Color.Red, 5);
SolidBrush sb = new SolidBrush(Color.Green);
Bitmap bmPicture = new Bitmap( "MojaSlika.jpg");
grap.DrawRectangle( pen1, 10,10,60,40);
grap.FillEllipse( sb, 30, 60, 30,30);
grap.DrawImage( bmPicture, 100,100);
}
Из овог примера би требало да схватите све што је потребно да бисте исцртавали графичке елементе. Још једном да напоменем, ово је само штуро објашњење јер су ово основне стари, за детаљније упутство требало би да погледате у некој књизи, ми ћемо се овде бавити неким напреднијим техникама коришћења GDI+a.
- Појам Региона( Region) и његова употреба у дизајнирању облика компоненети -Региони нису тако често коришћени и још су ређе објашњавани али нама ће у даљем тексту они бити потребни па је неопходно да их објаснимо. Регион није никаква компликована ствар, чак напротив- врло је просто у основи. Када поставите дугме на форму, оно ће имати своје димензије и у тим димензијама ће и бити приказано на екрану корисника. Међутим, важно својство сваке контроле је и Region, размотримо шта се догађа када корисник кликне курсором на неко дугме, на пример. Прво се подиже event "ButtonClick" и затим се извршава одређени код. Али да би систем знао да је дугме притиснуто, он неће проверавати положај и величину дугмета у односу на курсор већ положај регион у односу на курсор. Јасно, када направите "обично" дугме његов ће Регион имати облик правоугаоника и имаће димензије исте као и дугме. Ово је битно јер, ви не можете директно мењати облик дугмета али можете то урадити индиректно помоћу његовог својства Region јер се објекат Region може мењати. На пример, можете направити регион који ће имати облик звезде и онда тај регион доделити неком дугмету и то дугме ће имати облик звезде. Ово нам даје могућност комплетне манипулације над изгледом свих контрола. Главни део овог текста је посвећен томе, уједно претстављајући и алгоритме које сам ја написао а који служе за веома напредно коришћење ове технике( у комбинацију са сликама).
Регион има 5 различитих конструктора а нама ће требати само онај који као једини аргумент узима објекат класе GraphicsPath и од података из њега прави регион који се касније може додатки некој контроли. Наредна ствар јесте претстављање класе GraphicsPath.
- Употреба објеката GraphicsPath -Ова класа такође није компликована, а на неки начин слична је класи Graphics али се суштински разликују. У GraphicsPath можете додавати линије, кругове, правоугаонике, елипсе и друге елементе( на сличан начин као и код Graphics класе) али се ти елементи неће исцртавати већ ће формирати "пут"( path) по коме треба да се исцрта нешто, на пример регион. Пример једноставног коришћења ове класе и прављења региона од ње би могао бити овакав:
//velicina elipse koju cemo dadati GraphicsPath-u
int pocetakX = 5, pocetakY = 5, sirina = 30, duzina = 80;
//pravljenje objekat klase GraphicsPath i crtanje elipse
GraphicsPath gPath = new GraphicsPath();
gPath.AddEllipse( pocetakX, pocetakY, duzina, sirina);
//nekom dugmetu koje ima naziv btnButton1 podesi boju i region
btnButton1.BackColor = Color.Red;
btnButton1.Region = new Region( gPath);
Већ смо поменули да има сличности између класа Graphics и GraphicsPath. Наиме, и GraphicsPath можете посматрати као "платно за цртање" па из тога следи закључак да то платно има своју леву и горњу ивицу( остале нису дефинисане, односно дужина и ширина могу бити "бесконачне"), па тако смо помоћу променљивих pocetakX и pocetakY одредили удаљеност елипсе од горње и леве ивице тог "платна".
Ефекат који смо овим произвели није ништа нараочито користан, чак напратив, али у наставку ћемо упознати практичнију примену ове технике.
Моделирање изгледа контрола помоћу GraphicsPath-a i Regiona Доста је било теорије, сад да видимо ефекат овог што је објашњено. Прво ево слике која илуструје какав изглед button-a желимо да направимо:

Код који реализује ове промене изгледа дугмади се налази у ове три функције:
private static void RegionShape1(ref Button btn)
{
GraphicsPath path = new GraphicsPath();
const int startY = 10;
const int startX = 35;
for( int y = startY, x = startX ; y < btn.Height; ++y, --x)
path.AddRectangle( new Rectangle( x, y, btn.Width - 2*x, 1));
btn.Region = new Region( path);
}
private static void RegionShape2( ref Button btn)
{
GraphicsPath path = new GraphicsPath();
int maxX = btn.Height/2;
int x = 0;
bool turn = false;
for( int y = 0; y < btn.Height; ++y)
{
if( turn == false && x < maxX)
++x;
else if( turn == false && x == maxX) turn = true;
if( turn == true && x > 0)
--x;
path.AddRectangle( new Rectangle( x,y, btn.Width - 2*x, 1));
}
btn.Region = new Region( path);
}
private static void RegionShape3( ref Button btn)
{
GraphicsPath path = new GraphicsPath();
path.AddEllipse( 5, 5, btn.Width-10, btn.Height-10);
btn.Region = new Region( path);
}
Нема ту ничега компликованог, све се своди на то да ви из кода задате кординате по којима ће се исцртавати регион. Ове три фунцкије нису ништа посебно добро написане, али су послужиле као пример да видите на који начин можете сами моделирати изглед контрола. Функције се такође могу променити тако да допуштају мењање изгледа и других контрола а не само дугмади, но то је на вама( обзиром да се својство Region понаша исто за све контроле неће вам бити тешко да то урадите). Пројекат који демострира ово је прикачен уз ову поруку и зове се "Regions_Models"( види на дну ове поруке).
Можете правити разне облике као што су звезде, елипсе, правоугаоници и шта вам год падне на памет, све што је потребно јесте да осмислите "алгоритам" који ће објекту GraphicsPath да дода одговарајуће облике. Следећи одељак говори о много лакшем и погоднијем начину за моделирање изгледа контрола, користећи алгоритам који сам ја написао.
Алгоритам за добијање облика контроле из одређене слике Ово је врло моћна ствар- нацртате слику у на пример Paint-у а добијете дугме које има ОБЛИК те слике. Да појасним, рецимо да имамо овакву слику:

а желимо да наша контрола, на пример Button, има ОБЛИК те слике. Да се разумемо, не мисли се на постављање слике у позадини неке контроле већ да се одреди транспарентна боја за ту слику( ово ћемо радити из програма) и да се затим направи Region који има облик оних нетранспарентних делова те слике. Да бисмо ово урадили морамо прво да поставимо слику у позадину неког button-а( додуше не морамо, али ефекат ће бити глуп) и да затим за тај button одредимо регион који ће имати облик нетранспарентних делова те слике. Ајмо редом, отворите пројекат и нацртајте једно дугме у форми и назовите га btn1( уз ово поруку закачен је фајл који садржи све пројекте који су коришћени у овом туториалу, а пројекат за ово поглавље се налази у фолдеру "GraphicsInterface"). Даље, треба поставити неку слику за позадину дугмета, то може да изгледа овако:

код који треба написати да би се добила ово је:
private void Form1_Load(object sender, System.EventArgs e)
{
Bitmap bmpPicture = new Bitmap("picture_button.png");
btn1.Size = new Size( bmpPicture.Width, bmpPicture.Height);
btn1.BackgroundImage = bmpPicture;
}
Сада долази компликованији део, треба променити Region дугмета тако да се не приказује бела боја у позадини односно да сам облик дугмета поприми облик слике( ако се узме да је бела боја транспарентна). Није баш једноставно, а "алгоритам" који сам лично написао и који врло често користим решава вас свих ових мука. Дакле тај алгоритам од прослеђене слике прави одговарајући облик за класу GraphicsPath. Следи код:
public static GraphicsPath GPFromBitmap( Bitmap bmPicture)
{
int width = bmPicture.Width;
int height = bmPicture.Height;
Color tranCol = bmPicture.GetPixel(0,0); //uzmi transparentnu boju
GraphicsPath ret = new GraphicsPath();
//prođi svaki red vertikalno
for( int y = 0; y < height; ++y)
//prođi kroz sve pixele u datom redu
for( int x = 0; x < width;)
{
int startP = x, endP = x;
//traži prvu NE transparentnu tačku u ovom redu
for(; startP < width &&
bmPicture.GetPixel( startP, y) == tranCol; ++startP );
//traži prvu sledeću transparentnu tačku
for(endP = startP; endP < width &&
bmPicture.GetPixel( endP, y) != tranCol; ++endP);
//racunaj novi X
x = endP+1;
//od dobijene vrednosti endP-a izračunaj dužinu
endP = endP - startP;
//dodaj liniju GraphicsPath-a
ret.AddRectangle( new Rectangle( startP, y, endP, 1));
}
return ret;
}
У анализу рада овог кода нећу улазири обзиром да је се у самом коду налазе коментари, напомену ћу само да се за транспарентну боју узима боја која се налази у левом горњем ћошку. Да бисмо ово видели на делу потребно изменити код функцији Form1_Load(), који смо написали раније, да изгледа овако:
private void Form1_Load(object sender, System.EventArgs e)
{
Bitmap bmpPicture = new Bitmap("picture_button.png");
btn1.Size = new Size( bmpPicture.Width, bmpPicture.Height);
btn1.BackgroundImage = bmpPicture;
//ovo je dodato
btn1.Region = new Region( GPFromBitmap( bmpPicture));
}
Када покренемо програм, резултат ће бити овакав:

И то је то! Овај код је потпуно флексибилан, тако да ако промените изглед слике или чак њену величину, и сам изглед и величина дугмета на форми ће се променити! Требало би мало да експериментишете са начинима на које можете добити најразличитије изгледе контрола, цртајући различите слике које ће те контроле користити.
При цртању слика које ће бити коришћене треба водити рачуна да транспарентну боју( ону која се налази у левом-горњем ћошку слике) не користите за бојење неког дела те слике који желите да буде приказан јер ће се онда, логично, тај део слике сматрати транспарентним и неће бити приказан. Такође, пожељно је да се транспарентна боја простире на неких десетак пиксела од леве и доње ивице слике и 5 од горње и десне ивице. Ово треба урадити јер само ако се слика користи за неко дугме јер дугме исцртава одређене линије по својим ивицама када је у фокусу, а те боје не би требало да се приказују обзиром да дугме има графички интерфејс.
Сада би требало да сте у могућности да вашим контролама дате графички изглед какав год пожелите. Још једном ћу напоменути, сви ови примери су показивани на контроли button али ово знање можете да примењујете на било којој другој контроли, мада се обично ово примењује на форме и дугмад.
Хтео сам да у оквиру овог текста објасним такође и основне принципе програмирања игара користећи GDI+, али ћe то морати да сачека неку другу прилику, када будем имао више времена.
Enjoy
