Нека тези от вас, които бяха на промоцията на 27-ми да ми пишат на mail-а, за да им пратя снимки :)
Колегата Никола Гоцев спомена, че е забелязал проблем със завъртането, който обаче аз не мога да открия. Затова тествайте програмата по-обстойно, за да можем да я оправим навреме ако има някакви проблеми.
С предишната статия завършихме основната част от нашата програма. Сега ще добавим някои допълнителни функции, които ще улеснят работата с програмата.
Запазване и отваряне на изображение
(Работа с файлове в Java, сериализация)
За да можем да съхраняваме красивите картинки, които сме нарисували ще добавим два компонента в менюто: „Отваряне“ и „Запазване“. Добавянето става по същия начин както го направихме в статия 1 - Откъде да започна?.
Чрез щракане на десен бутон и после „MoveUp“ или „MoveDown“ или просто чрез влачене можем да разместваме реда на компонентите в менюто.
Запазване
При щракване на запазване създаваме обект от тип „JFileChooser“, чрез който ще отваряме диалогов прозорец за запазване на файл. На метода „showSaveDialog“ подаваме като параметър текущата форма. След затваряне на прозореца чрез „fileChooser.getSelectedFile().getPath()“ вземаме пътя до избрания файл. Създаваме файлов поток за изход „fos“, който подаваме като параметър на потока за изход на обекти „objectOutputStream“. Той служи за преобразуване на обект в поредица от битове, които можем да запишем във файл или да изпратим по мрежата. Този процес се нарича сериализация.
NetBeans обаче подчертава последните два реда. Това се случва, защото те могат да „хвърлят“ изключение, а ние не го прихващаме.
Ако щракнем на лампичката (подсказването) в ляво можем да изберем „Surround Statement with try-catch“ и NetBeans автоматично ще промени кода ни така, че да хващаме изключението.
След като направим това и с двата реда NetBeans ни сигнализира за нова нередност. Тя се е получила, защото поставихме инициализацията на променливата „fos“ в „try“ блок, което означава, че тя може да остане неинициализирана.
Щракваме и на това предложение за промяна на кода.
Сещаме се, че е добре запазените ни изображения да имат някакво файлово разширение, примерно „trn“. За да направим в диалоговият прозорец да се показват само файлове с това разширение задаваме филтър на „fileChooser“.
Също така трябва да направим проверка дали потребителят е избрал някакъв файл от диалоговия прозорец или просто го е затворил/щракнал на „Cancel“. Ако е вторият случай, методът „getSelectedFile“ на „fileChooser“ връща „null“ и тогава прекратяваме запазването - излизаме от нашия метод с „return“.
Ще проверяваме също така дали името на зададения файл завършва на „.trn“. Ако не завършва ще добавяме това разширение към него.
Стигаме до самото запазване на изображението. Всъщност просто ще запишем обектът „figures“ чрез метода „writeObject“ и ще затворим потока за запис. Това е достатъчно за запазване на нашето изображение, защото то изцяло се съдържа в списъка „figures“ на класа „GlobalVars“.
Това е целият код по запазването. Изключенията, които прихващаме са от тип „файлът не е намерен“ и „входно/изходна грешка“.
Отваряне
Отварянето на така запазеното изображение става аналогично. Този път ползваме входни потоци и присвояваме прочетения обект на „GlobalVars.figures“. Преди това обаче трябва да го преобразуваме до типа на „figures“ - „ArrayList<Primitive>“. Новото изключение (в изключението), което може да възникне при преобразуването е от тип „ненамерен клас“.
Преместване в дълбочина
Нашата програма е за създаване на двуизмерни изображения, така че реално няма как да ги преместваме в дълбочина, но можем да променяме реда им на рисуване. Именно това ще разбираме под преместване в дълбочина отсега нататък.
Добавяме два обикновени бутона (не превключващи). Можем да зададем да са с еднаква широчина.
Преместване напред
За да преместим избраните фигури напред в изображението трябва да ги изместим назад в списъка с фигури. Това е така защото най-напред се рисува първата фигура от списъка, върху нея се рисува втората и т.н.
Обхождаме списъка отзад напред, като пропускаме последната фигура, защото тя е най-отпред в изображението и няма как да я преместим още по-напред (най-много да избие стъклото на монитора и да ни цапне по челото). Разменяме мястото на всяка избрана фигура с тази преди нея. Накрая рисуваме промененото изображение.
Преместване назад
То става аналогично на преместването напред.
Накрая обработваме събитията „пускане на мишката“ от двата нови бутона, като задаваме фокуса да е на платното за рисуване, както правехме в „jSliderRotationMousePressed“. Целта е да можем да използваме клавишът „Ctrl“ за избиране на няколко фигури. За да не повтаряме кода от метода „jSliderRotationMousePressed“ направо извикваме него. Това не е добра практика, защото при промяна на „jSliderRotationMousePressed“ ще се променят и долните два метода, а това невинаги ще бъде желателно.
Премахване на фигури
Добавяме нов бутон „Премахване“ и при натискането му обхождаме списъка и премахваме от него всички избрани фигури. Накрая рисуваме промененото изображение. Добавяме обработка и на събитието „пускане на мишката“.
Добре е да обърнем внимание на две неща в метода за премахване.
Първо, обхождаме списъка отзад напред, защото променяме размера му. Вероятно често срещана грешка е във „for“ цикъл от 0 до размера на списъка - 1, да се премахват елементи от него. При премахване на i-я елемент i+1-я заема неговото място. Ако сега увеличим брояча той вече ще сочи към елемента, който е бил на позиция i+2 преди премахването и така ще изпуснем елемента непосредствено след премахнатия. Този проблем се отстранява ако обхождаме списъка отзад напред.
Второ, при обхождане отпред назад, ако в условието за край на цикъла се извиква методът „size()“ това ще става на всяка итерация. От една страна е неефективно от друга страна някои компилатори (по-скоро за други езици) могат да оптимизират кода така, че да се извиква „size()“ само в началото. Тогава при промяна на размера на списъка ще се получи грешка.
При всички случаи бъдете много внимателни, когато променяте размера на списък при самото му обхождане.
Различни възможности за избор
Ще добавим възможност за избиране на обектите от списък. Този списък ще наричаме графичен списък, за да го различаваме от структурата ни от данни „figures“.
За целта добавяме графичен списък от палитрата с компоненти.
Добавяме глобална променлива „jListFigures“ и я инициализираме в конструктора на формата, точно както направихме с „jPanelObjectColor“ в статия 5 - Избиране.
Трябва когато изображението ни се промени да променяме и графичния списък с фигурите. Най-лесно това можем да направим в метода „drawFigures“, защото той се извиква всеки път, когато има някаква промяна.
Ще искаме когато някоя фигура бъде избрана, да се избира и в графичния списък. Затова в „drawFigures“ най-напред създаваме масива „selectedIndices“, в който ще поставим индексите на избраните фигурите от списъка с фигури. Най-напред чрез „Arrays.fill“ правим всички елементи на масива -1, а после в цикъла добавяме индексите на избраните.
Чрез „setListData“ подаваме масив от обекти, които графичният списък да изобразява. За да направим масив от нашия списък „figures“ ползваме метода му „toArray“.
След това с „setSelectedIndices“ задаваме, кои елементи от графичния списък да бъдат избрани.
Вероятно някои от вас се чудят какво точно или каква част от подадените обекти ще се изобразят в графичния списък. Той показва върнатия низ от метода „toString“ на обектите. Ние ще предефинираме този метод така, че в графичния списък да се показват типът на фигурата и нейните координати.
Можем да използваме реалните типове на обектите ни: „MyRectangle“ и „MyEllipse“, но ще бъде по-красиво ако са на български език. Затова нека направим ново поле „name“ от тип „String“ в класа „CommonPrimitive“. Допълваме го и със стандартни методи за достъп до полето: „getName“, „setName“.
За да предефинираме метода „toString“ в NetBeans е достатъчно да започнем неговото изписване и да натиснем „Ctrl+Space“.
NetBeans генерира стандартен „toString“ метод, който ние променяме така, че да връща желания низ.
Остава да зададем някъде стойност на полето „name“. Тъй като то е различно за всяка фигура това трябва да направим в класовете за фигури, а именно в техните конструктори.
По аналогичен начин процедираме и с „MyRectangle“.
Остава да обработим събитията свързани с графичния списък.
При натискане на мишката върху него, ако клавишът „Ctrl“ е натиснат се маха избора от всички фигури. Чрез „getSelectedIndices“ се вземат индексите на избраните от графичния списък елементи. За всеки от тях се избира съответната фигура от списъка с фигури и се променя фона на панела показващ цвета на избрания обект. Накрая изрисуваме изображението, за да се опреснят рамките на избраните фигури.
При пускане на мишката от графичния списък, в метода „jList1MouseReleased“ отново използваме лошата, но бърза практика с извикването на метода „jSliderRotationMousePressed“. Така пренасяме фокуса върху платното за рисуване и можем да използваме клавиша „Ctrl“ за избор на няколко обекта от изображението.
Функцията за избор на графичните примитиви от списък може да ви донесе точки на изпита.
Прозорец „За програмата“
Набързо ще създадем прозорче „За програмата“.
При щракване от менюто на „Помощ“ -> „За програмата“ ще създаваме диалогов прозорец със съобщение.
Методът „showMessageDialog“ приема като параметри родителски компонент, текст на съобщението, заглавие на прозореца и тип на съобщението.
В полето „title“ от настройките на главната форма можем да променяме надписа и на главния прозорец.
Програмата ни вече изглежда така.
Кодът до сега - Trayangle-7.zip
Програмата - Trayangle-7.jar
Картинката - Slunchogledi.trn
В следващата статия 8 ще покажем как се добавя нов примитив. Нещо, което може да ви се наложи да правите на изпита.
<< 6 - Преместване, завъртане | 8 - Добавяне на графичен примитив >> |
Коментари
Единствения проблем
Който аз имам със завъртането е,че елипсата не се върти изобщо,но сигурно съм изпуснал нещо някъде и затова не се получава.Ако забележа нещо нередно ще се обадя : )
Завъртане на елипса
Когато описвам как нещо се прави в класа „MyRectangle“ не го повтарям и за „MyEllipse“. Просто пиша, че става аналогично.
Провери да не би да си пропуснал да промениш метода си „draw“ в „MyEllipse“.
Един въпрос?
Какво още ще добавим като функционалност към програмата като цяло?Защото примерно аз искам да си добавя няколко неща, но тъй като съм новак в Джавата все още ще ми трябва малко помощ или поне насоки кое как да направя.
Нови функционалности
Могат да се добавят още много неща, като например:
и още много, много други.
Аз обаче мисля да не добавям нови функционалности, поне в скоро време. Нека кодът да остане по-прост.
Да не се получи, като при операционната система Minix, която Таненбаум е запазил проста, с учебна цел. Линус Торвалдс е искал да добави нови функции и така се е родило ядрото Linux.
Давайте предложения какво може да се добави или промени, какво не харесвате или харесвате в програмата.
Ще е интересно, като мине изпита да се похвалите кой какво е направил и дори можем да качим тук проектите ви. Става дума за изпълними програми, а който желае, защо не и програмен код.
Аз обмислях
това за очертанията на фигурите, прозрачността и копирането. Както и за групирането на няколко елемента в група, което също е и едно от изискванията за проекта доколкото знам.Иначе съм съгласен да кача проекта си след изпита тук, таман дотогава ще барна малко и дизайна и ще стане готино : )
Публикувай нов коментар