Нашите персонализирани графични компоненти изискват ръчно чертане, така че ще трябва да подкласираме Canvas
, което е стандартният компонент, предвиден за директна манипулация на графиката. Техниката, която ще използваме, ще бъде да заменим paint
метода Canvas
с персонализирания чертеж, от който се нуждаем. Ще използваме Graphics
обекта, който автоматично се предава в paint
метода на всички компоненти, за достъп до цветове и методи за рисуване.
Ще създадем два персонализирани компонента за графики: стълбовидна диаграма и линейна графика. Ще започнем с изграждането на общ рамков клас за двете графики, които споделят някои базови елементи.
Изграждане на обща рамка на графика
Линейната графика и стълбовата диаграма, които ще изградим, са достатъчно сходни, за да можем да създадем родово
Graph
клас, за да извършите някои от досадната работа по оформлението. След като приключим, можем да разширим класа за конкретния вид графика, от която се нуждаем.
Първото нещо, което трябва да направите, когато проектирате персонализирани графични компоненти, е да поставите писалка върху хартия и да нарисувате картина на това, от което се нуждаете. Тъй като броим пиксели, е лесно да се объркаме относно разположението на елементите. Влагането на някои мисли в именуването и позиционирането на елементи ще ви помогне да поддържате кода по-чист и по-лесен за четене по-късно.
Линейната графика и стълбовидната диаграма използват едно и също оформление за заглавието и редовете, така че ще започнем със създаването на обща графика, съдържаща тези две характеристики. Оформлението, което ще създадем, е показано на фигурата по-долу.

За да създадем родовия Graph
клас, ще подкласираме Canvas
. Централният регион е мястото, където ще се показват действителните графични данни; ще оставим това за разширение на Graph
за изпълнение. Ще приложим останалите елементи - заглавна лента, вертикална линия вляво, хоризонтална линия отдолу и стойности за диапазона - в основния клас. Можем да посочим шрифт и твърд код за измерване на пикселите, но потребителят няма да може да преоразмери графиката. По-добър подход е измерването на елементите спрямо текущия размер на компонента, така че преоразмеряването на приложението да доведе до правилно преоразмеряване на графиката.
Ето нашия план: Ще вземем String
заглавие, int
минимална стойност и int
максимална стойност в конструктора. Те ни дават цялата информация, от която се нуждаем, за да изложим рамката. Ще продължим четири променливи, използвани в подкласове - на top
, bottom
, left
, както и right
стойностите на границите на изготвяне региона на графика. Ще използваме тези променливи, за да изчислим позиционирането на графични елементи по-късно. Нека започнем с бърз поглед към Graph
декларацията на класа.
импортиране на java.awt. *; импортиране на java.util. *; публичен клас Graph разширява Canvas {// променливи, необходими public int top; публичен int дъно; публичен int вляво; публично международно право; int titleHeight; int labelWidth; FontMetrics fm; int padding = 4; Заглавие на низ; int min; int max; Векторни елементи;
За да изчислим правилното разположение на графичните елементи, първо трябва да изчислим регионите в нашето общо оформление на графиката, които съставят рамката. За да подобрим външния вид на компонента, добавяме 4-пикселна подложка към външните ръбове. Ще добавим заглавието, центрирано в горната част, като вземем предвид областта на подплънките. За да сме сигурни, че графиката не е нарисувана отгоре на текста, трябва да извадим височината на текста от заглавната област. Трябва да направим същото за етикетите min
и max
диапазона на стойност. Ширината на този текст се съхранява в променливата labelWidth
. Трябва да запазим препратка към показателите на шрифта, за да направим измерванията. Theitems
вектор се използва за проследяване на всички отделни елементи, които са добавени към компонента Графика. Клас, използван за съхранение на променливи, свързани с елементите на графиката, е включен (и обяснен) след Graph
класа, който е показан по-нататък.
публична графика (заглавие на низ, int min, int max) {this.title = заглавие; this.min = мин; this.max = max; артикули = нов вектор (); } // краен конструктор
Конструкторът взема заглавието на графиката и диапазона от стойности и ние създаваме празен вектор за отделните елементи на графиката.
публична промяна на празнотата (int x, int y, int width, int height) {super.reshape (x, y, width, height); fm = getFontMetrics (getFont ()); titleHeight = fm.getHeight (); labelWidth = Math.max (fm.stringWidth (ново цяло число (min) .toString ()), fm.stringWidth (ново цяло число (max) .toString ())) + 2; отгоре = подложка + titleHeight; отдолу = размер (). височина - подложка; вляво = подложка + labelWidth; вдясно = размер (). ширина - подложка; } // край на преоформянето
Забележка: В JDK 1.1 reshape
методът се заменя с public void setBounds(Rectangle r)
. Вижте документацията за API за подробности.
Ние заместваме reshape
метода, който се наследява по веригата от Component
класа. В reshape
метод се нарича, когато компонентът е оразмерен и когато тя е изложена за първи път. Използваме този метод за събиране на измервания, така че те винаги да бъдат актуализирани, ако компонентът бъде преоразмерен. Получаваме метриките на шрифта за текущия шрифт и присвояваме на titleHeight
променливата максималната височина на този шрифт. Получаваме максималната ширина на етикетите, тестваме кой е по-голям и след това използваме този. Най- top
, bottom
, left
, и right
променливи се изчислява от други променливи и представляват границите на чертеж регион център графика. Ще използваме тези променливи в подкласовете на Graph
. Имайте предвид, че всички измервания отчитат токразмер на компонента, така че преначертаването ще бъде правилно при всякакъв размер или аспект. Ако използвахме твърдо кодирани стойности, компонентът не можеше да бъде преоразмерен.
След това ще нарисуваме рамката за графиката.
публична невалидна боя (Графика g) {// изчертаване на заглавието fm = getFontMetrics (getFont ()); g.drawString (заглавие, (размер (). ширина - fm.stringWidth (заглавие)) / 2, отгоре); // изчертаваме максималните и минималните стойности g.drawString (ново цяло число (min) .toString (), подложка, отдолу); g.drawString (ново цяло число (max) .toString (), подложка, top + titleHeight); // начертайте вертикалните и хоризонталните линии g.drawLine (вляво, отгоре, отляво, отдолу); g.drawLine (вляво, отдолу, отдясно, отдолу); } // крайна боя
Рамката е изчертана в paint
метода. Изчертаваме заглавието и етикетите на съответните им места. Изчертаваме вертикална линия в лявата граница на областта за рисуване на графика и хоризонтална линия в долната граница.
В този следващ фрагмент задаваме предпочитания размер за компонента, като заместваме preferredSize
метода. В preferredSize
метод също се наследява от Component
класа. Компонентите могат да определят предпочитан размер и минимален размер. Избрах предпочитана ширина 300 и предпочитана височина 200. Мениджърът на оформлението ще извика този метод, когато изложи компонента.
публично измерение preferSize () {return (ново измерение (300, 200)); }} // крайна графика
Забележка: В JDK 1.1 preferredSize
методът се заменя с public Dimension getPreferredSize()
.
След това се нуждаем от съоръжение за добавяне и премахване на елементите, които ще бъдат изобразени.
публична невалидна addItem (име на низ, стойност на int, цветна колона) {items.addElement (нова GraphItem (име, стойност, col)); } // край на addItem public void addItem (Име на низ, стойност int) {items.addElement (нов GraphItem (име, стойност, Color.black)); } // край на addItem public void removeItem (String name) {for (int i = 0; i <items.size (); i ++) {if (((GraphItem) items.elementAt (i)). title.equals (name )) items.removeElementAt (i); }} // край на removeItem} // край на графика
Моделирах методите addItem
и removeItem
методите след подобни методи в Choice
класа, така че кодът ще има познато усещане. Забележете, че addItem
тук използваме два метода; имаме нужда от начин за добавяне на елементи със или без цвят. Когато се добавя елемент, се създава нов GraphItem
обект и се добавя към вектора на елементите. Когато елемент бъде премахнат, първият във вектора с това име ще бъде премахнат. Най- GraphItem
класа е много проста; ето кода:
импортиране на java.awt. *; клас GraphItem {Заглавие на низ; стойност int; Цветен цвят; публичен GraphItem (заглавие на низ, стойност на int, цвят на цвета) {this.title = заглавие; this.value = стойност; this.color = цвят; } // краен конструктор} // краен GraphItem
В GraphItem
класа действа като титуляр за променливите, свързани с графика предмети. Включих Color
тук, в случай че ще бъде използван в подклас на Graph
.
С тази рамка на място можем да създадем разширения за обработка на всеки тип графика. Тази стратегия е доста удобна; не е нужно да се затрудняваме отново да измерваме пикселите за рамката и можем да създадем подкласове, за да се съсредоточим върху попълването на областта за рисуване на графика.
Изграждане на стълбовидната диаграма
Сега, когато имаме графична рамка, можем да я персонализираме чрез разширяване
Graph
и внедряване на персонализирано рисуване. Ще започнем с обикновена диаграма, която можем да използваме точно както всеки друг компонент. Типична диаграма е показана по-долу. Ще попълним региона за рисуване на графики, като заменим
paint
метод за извикване на суперклас
paint
method (to draw the framework), then we'll perform the custom drawing needed for this type of graph.
import java.awt.*; public class BarChart extends Graph { int position; int increment; public BarChart(String title, int min, int max) { super(title, min, max); } // end constructor
To space the items evenly, we keep an increment
variable to indicate the amount we will shift to the right for each item. The position variable is the current position, and the increment value is added to it each time. The constructor simply takes in values for the super constructor (Graph
), which we call explicitly.
Now we can get down to some actual drawing.
public void paint(Graphics g) { super.paint(g); increment = (right - left)/(items.size()); position = left; Color temp = g.getColor(); for (int i = 0; i < items.size(); i++) { GraphItem item = (GraphItem)items.elementAt(i); int adjustedValue = bottom - (((item.value - min)*(bottom - top)) /(max - min)); g.drawString(item.title, position + (increment - fm.stringWidth(item.title))/2, adjustedValue - 2); g.setColor(item.color); g.fillRect(position, adjustedValue, increment, bottom - adjustedValue); position+=increment; g.setColor(temp); } } // end paint } // end BarChart
Let's take a close look at what's happening here. In the paint
method, we call the superclass paint
method to draw the graph framework. We then find the increment
by subtracting the right edge from the left edge, and then dividing the result by the number of items. This value is the distance between the left edges of the graph items. Because we want the graph to be resizable, we base these values on the current value of the left
and right
variables inherited from Graph
. Recall that the left
, right
, top
, and bottom
values are the current actual pixel measurements of the graph drawing region taken in the reshape
method of Graph
, and therefore available for our use. If we did not base our measurements on these values, the graph would not be resizable.
We'll draw the rectangles in the color specified by the GraphItem
. To allow us to go back to the original color, we set a temporary color
variable to hold the current value before we change it. We cycle through the vector of graph items, calculating an adjusted vertical value for each one, drawing the title of the item and a filled rectangle representing its value. The increment is added to the x position variable each time through the loop.
The adjusted vertical value ensures that if the component is stretched vertically, the graph will still remain true to its plotted values. To do this properly, we need to take the percentage of the range the item represents and multiply that value by the actual pixel range of the graph drawing region. We then subtract the result from the bottom
value to correctly plot the point.
As you can see from the following diagram, the total horizontal pixel size is represented by right - left and the total vertical size is represented by bottom - top.

We take care of the horizontal stretching by initializing the position
variable to the left edge and increasing it by the increment
variable for each item. Because the position
and increment
variables are dependent on the actual current pixel values, the component is always resized correctly in the horizontal direction.
За да сме сигурни, че вертикалното нанасяне винаги е правилно, трябва да картографираме стойностите на елементите на графиката с действителни пикселни разположения. Има едно усложнение: Стойностите max
и min
трябва да имат значение за позицията на стойността на елемента на графиката. С други думи, ако графиката започва от 150 и отива до 200, елемент със стойност 175 трябва да се появи по средата на вертикалната ос. За да постигнем това, намираме процента от обхвата на графиката, който елементът представлява, и го умножаваме по действителния обхват на пикселите. Тъй като нашата графика е обърната от координатната система на графичния контекст, изваждаме това число, за bottom
да намерим правилната точка на графиката. Не забравяйте, че началото (0,0) е в горния ляв ъгъл за кода, но долният ляв ъгъл за стила на графиката, който създаваме.