Список работ

Воспроизведение графа по его xml-описанию

Курсовая работа по дисциплине Программная инженерия

Ефремова Н. Г., А-13-13

Содержание

Постановка задачи

Разрабатывается приложение, воспроизводящее граф по его xml-описанию. Первоначально разрабатывается xml-описание графа, а затем создается приложение, позволяющее выбрать xml-файл и построить граф по данным этого файла. Отображенный граф можно переместить по горизонтали и вертикали. Так же в нем можно изменить цвет вершин и ребер, размер вершин и ширину ребер.

Xml-описание графа

В качестве примера приводится xml-описание показанного на рис. 1 графа.

Рисунок графа для демонстрации xml-описания

Рис. 1. Граф для демонстрации структуры xml-описания

<?xml version="1.0" encoding="utf-8" ?>
<graph>
  <!-- Graph vertices -->
  <!-- Vertex name and number -->
  <!-- The coordinates of the vertex -->
  <verts>
   <graphVertex vName = "v1" vNumber = "1">
     <x>125</x>
     <y>200</y>
   </graphVertex>
   <graphVertex vName = "v2" vNumber = "2">
     <x>125</x>
     <y>175</y>
   </graphVertex>
   <graphVertex vName = "v3" vNumber = "3">
     <x>50</x>
     <y>150</y>
   </graphVertex>
   <graphVertex vName = "v4" vNumber = "4">
     <x>200</x>
     <y>150</y>
   </graphVertex>
   <graphVertex vName = "v5" vNumber = "5">
     <x>125</x>
     <y>100</y>
   </graphVertex>
   <graphVertex vName = "v6" vNumber = "6">
     <x>50</x>
     <y>50</y>
   </graphVertex>
   <graphVertex vName = "v7" vNumber = "7">
     <x>200</x>
     <y>50</y>
   </graphVertex>
  </verts>
  <!-- Graph edges -->
  <!-- Edge start vertex -->
  <!-- Edge end vertex -->
  <edge eName = "1">
    <start>1</start>
    <end>2</end>
  </edge>
  <edge eName = "2">
    <start>2</start>
    <end>3</end>
  </edge>
  <edge eName = "3">
    <start>2</start>
    <end>4</end>
  </edge>
  <edge eName = "4">
    <start>2</start>
    <end>5</end>
  </edge>
  <edge eName = "5">
    <start>5</start>
    <end>6</end>
  </edge>
  <edge eName = "6">
    <start>5</start>
    <end>7</end>
  </edge>
  <!-- Projection matrix -->
  <ortho>
    <xmi>0</xmi>
    <xma>220</xma>
    <ymi>0</ymi>
    <yma>220</yma>
  </ortho>
</graph>

Узлы verts, edge и ortho, находящиеся на верхнем уровне, содержат соответственно описания вершин графа, его ребер и параметров матрицы проецирования.
Для вершины указывается ее имя, номер (соответственно атрибуты vName и vNumber) и ее координаты.
Для ребра - вершины, которые это ребро соединяет.
Для отображения вершин употребляются точки, а ребер - линии.

Используемые программные средства

Язык программирования Visual C#, проект Window Forms Application. Графика реализована с помощью библиотек Tao.OpenGL.dll и Tao.Platform.Windows.dll. Эти библиотеки нетрудно найти в интернете.

Для подключения библиотек в Solution Explorer (рис. 2) добавляется ссылка (References - правая кнопка мыши Add Reference).

Solution Explorer: добавление ссылок на Tao.OpenGL.dll и Tao.Platform.Windows.dll

Рис. 2. Добавление ссылки

Выбираемые ссылки показаны на рис. 3.

Solution Explorer: выбор ссылок на Tao.OpenGL.dll и Tao.Platform.Windows.dll

Рис. 3. Выбор ссылок Tao.OpenGL.dll и Tao.Platform.Windows.dll

Область графического вывода добавляется в форму после выбора Tollbox - General - simpleOpenGlControl (рис. 4).

Tollbox - General - simpleOpenGlControl

Рис. 4. Добавление в форму области графического вывода

В рассматриваемом проекте эта область имеет имя GR (рис. 5)

Задание имени элемента simpleOpenGlControl

Рис. 5. Имя области графического вывода

Для работы с xml-файлом создается объект XmlDocument:

XmlDocument xDoc = new XmlDocument();

Выбора цвета (рис. 6) осуществляется средствами добавленного в проект объекта ColorDialog.

Диалог выбора цвета

Рис. 6. Выбираем цвет для glColor3f

Объект ColorDialog добавляется в проект (размещается непосредственно под формой, рис. 7) в результате выполнения цепочки ToolBox - Dialogs - Timer. Меню ToolBox, если оно отсутствует, можно показать посредством меню VIEW - ToolBox, или нажав Ctrl+Alt+X.

Расположение объекта ColorDialog

Рис. 7. Добавление объекта ColorDialog

Xml-файл открывается при помощи объекта OpenFileDialog:

OpenFileDialog OPF = new OpenFileDialog();

При этом используется фильтр по расширению:

OPF.Filter = "xml-файлы (*.xml)|*.xml";

Интерфейс пользователя

Позволяет выбрать файл с xml-описанием графа, задать размер и цвет вершин графа, ширину и цвет ребер графа, а также переместить граф по горизонтали и вертикали (рис. 8).

Форма C#-проекта

Рис. 8. Интерфейс пользователя

Реализация

Полный код формы приложения:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Xml;
// OpenGL
using Tao.OpenGl;
using Tao.Platform.Windows; // SimpleOpenGLControl

namespace GL_XML
{
    public partial class Form1 : Form
    {
        // Параметры матрицы проецирования
        double xmi = 0, xma = 0, ymi = 0, yma = 0;
        double rV = 1, gV = 0, bV = 0; // Начальный цвет вершин красный
        double rD = 0, gD = 1, bD = 0; // Начальный цвет ребер зеленый
        string fl = ""; // Имя файла
        // Предельные значения для числа вершин и ребер графа
        public static int numberOfVerts = 100;
        public static int numberOfEdges = 100;
        // Массивы атрибутов вершин и ребер графа
        string[,] attrValVrts = new string[2, numberOfVerts];
        string[] attrValDgs = new string[numberOfEdges];
        int nVrts = 0, nDgs = 0; // Число вершин и ребер в графе
        // Массивы вершин, их координат и ребер в графе
        string[] vN = new string[100];
        double[,] xy = new double[2, 100]; // Массив координат вершин графа в мировой системе координат
        double[,] xy2 = new double[2, 100]; // Массив координат вершин графа в видовой системе координат
        string[,] vE = new string[2, 100];
        public int kV = -1; // kV - индекс выбранной вершины графа

        public Form1()
        {
            InitializeComponent();
            GR.InitializeContexts();
            colorDialog1.FullOpen = true;
            //colorDialog1.Color = Color.Black; // Начальный цвет colorDialog
        }
        // Загрузка области графического вывода
        private void GR_Load(object sender, EventArgs e)
        {
            Gl.glClearColor(1, 1, 1, 1);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT); // Очистка буфера цвета
            Gl.glEnable(Gl.GL_POINT_SMOOTH);
        }
        // Подготовка в выводу изображения
        private void stRth()
        {
            Gl.glClearColor(1, 1, 1, 1);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT); // Очистка буфера цвета
            Gl.glMatrixMode(Gl.GL_PROJECTION); // Текущей стала матрица проецирования
            Gl.glLoadIdentity();
            Gl.glOrtho(xmi, xma, ymi, yma, -1, 1); // Прямоугольное проецирования
            Gl.glMatrixMode(Gl.GL_MODELVIEW); // Текущей стала видовая матрица
            Gl.glLoadIdentity();
            Gl.glTranslated((double)DX.Value, (double)DY.Value, 0); // Матрица перемещения графа
        }
        // Рисует ребра графа
        private void drwDgs()
        {
            double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
            Gl.glLineWidth((float)LW.Value); // Ширина ребра
            Gl.glColor3d(rD, gD, bD); // Цвет ребер
            Gl.glBegin(Gl.GL_LINES);
            for (int i = 0; i <= nDgs; i++)
            {
                for (int k = 0; k <= nVrts; k++)
                {
                    if (vN[k] == vE[0, i])
                    {
                        x1 = xy[0, k];
                        y1 = xy[1, k];
                    }
                    if (vN[k] == vE[1, i])
                    {
                        x2 = xy[0, k];
                        y2 = xy[1, k];
                    }
                }
                Gl.glVertex2d(x1, y1);
                Gl.glVertex2d(x2, y2);
            }
            Gl.glEnd();
        }
        // Рисует вершины графа
        private void drwVrts()
        {
            Gl.glPointSize((float)PS.Value); // Размер вершины
            Gl.glColor3d(rV, gV, bV); // Цвет вершин
            Gl.glBegin(Gl.GL_POINTS);
            double cV = 0.7;
            for (int i = 0; i <= nVrts; i++)
            {
                if (i == kV)    // kV - индекс выбранной вершины графа
                    Gl.glColor3d(cV, cV, cV); // Цвет выбранной вершины (серый)
                else
                    Gl.glColor3d(rV, gV, bV); // Цвет прочих вершин
                Gl.glVertex2d(xy[0, i], xy[1, i]);
            }
            Gl.glEnd();
        }
        // Кнопка Файл. Формруем массивы вершин, ребер и координат вершин
        private void FileLoad()         {
            nVrts = -1;
            nDgs = -1;
            OpenFileDialog OPF = new OpenFileDialog();
            OPF.Filter = "xml-файлы (*.xml)|*.xml";
            DialogResult dlg = OPF.ShowDialog(); // Выбираем xml-файл
            if (dlg == DialogResult.OK)
            {
                fl = OPF.FileName;
                label1.Text = "Выбран файл: " + fl;
                XmlDocument xDoc = new XmlDocument();
                xDoc.Load(fl);
                // Корневой элемент xml-документа
                XmlElement xRoot = xDoc.DocumentElement;
                // Обходим узлы корневого элемента
                foreach (XmlNode xnode in xRoot)
                {
                    if (xnode.Name == "verts")
                    {
                        //nVrts = xnode.ChildNodes.Count; // Число вершин в графе
                        foreach (XmlNode childnode in xnode.ChildNodes)
                        {
                            XmlNode nmb = childnode.Attributes[1];
                            nVrts++;
                            // Сохраняем значения атрибутов вершины
                            attrValVrts[0, nVrts] = childnode.Attributes[0].Value;
                            attrValVrts[1, nVrts] = childnode.Attributes[1].Value;
                            vN[nVrts] = nmb.Value;
                            xy[0, nVrts] = Single.Parse(childnode.ChildNodes[0].InnerText);
                            xy[1, nVrts] = Single.Parse(childnode.ChildNodes[1].InnerText);
                        }
                    }
                    if (xnode.Name == "edge")
                    {
                        nDgs++;
                        // Сохраняем значение атрибута ребра
                        attrValDgs[nDgs] = xnode.Attributes[0].Value;
                        vE[0, nDgs] = xnode.ChildNodes[0].InnerText;
                        vE[1, nDgs] = xnode.ChildNodes[1].InnerText;
                    }
                    if (xnode.Name == "ortho")
                    {
                        xmi = Single.Parse(xnode.ChildNodes[0].InnerText);
                        xma = Single.Parse(xnode.ChildNodes[1].InnerText);
                        ymi = Single.Parse(xnode.ChildNodes[2].InnerText);
                        yma = Single.Parse(xnode.ChildNodes[3].InnerText);
                    }
                }
                graph();
                //System.IO.StreamWriter txtFl = new System.IO.StreamWriter(@"C:\xy2.txt");
                // xy - массив координат вершин графа в мировой системе координат
                // xy2 - массив координат вершин графа в видовой системе координат
                for (int k = 0; k <= nVrts; k++)
                {
                    double x2 = xy[0, k] / xma * GR.Width;
                    double y2 = xy[1, k] / yma * GR.Height;
                    xy2[0, k] = x2;
                    xy2[1, k] = y2;
                    //txtFl.WriteLine("" + Math.Round(x2, 1) + "\t" + Math.Round(y2, 1));
                }
                //txtFl.Close();
            }
        }
        // Кнопка Файл. Формируем массивы вершин, ребер и координат вершин
        private void buttonFileLoad_Click(object sender, EventArgs e)
        {
            FileLoad();
        }
        private void graph()
        {
            if (String.IsNullOrEmpty(fl))
            {
                MessageBox.Show("Не выбран xml-файл");
                return;
            }
            stRth(); // Подготовка к графическому выводу
            drwDgs(); // Рисуем ребра
            drwVrts(); // Рисуем вершины
            Gl.glFlush(); // Отображаем примитивы на экране
            GR.Invalidate();
        }                
        // Изменен размер вершины
        private void PS_ValueChanged(object sender, EventArgs e)
        {
            graph();
        }
        // Изменена ширина ребра
        private void LW_ValueChanged(object sender, EventArgs e)
        {
            graph();
        }
        // Перемещение по X
        private void DX_ValueChanged(object sender, EventArgs e)
        {
            graph();
        }
        // Перемещение по Y
        private void DY_ValueChanged(object sender, EventArgs e)
        {
            graph();
        }
        // Диалог colorDialog выбора цвета
        private void clrPck(ref double r, ref double g, ref double b)
        {
            if (colorDialog1.ShowDialog() == DialogResult.Cancel) return;
            double c = 1.0f / 255.0f;
            r = c * colorDialog1.Color.R;
            g = c * colorDialog1.Color.G;
            b = c * colorDialog1.Color.B;
        }
        // Цвет ребер
        private void button2_Click_1(object sender, EventArgs e)
        {
            clrPck(ref rD, ref gD, ref bD);
            graph();
        }
        // Цвет вершин
        private void button3_Click_1(object sender, EventArgs e)
        {
            clrPck(ref rV, ref gV, ref bV);
            graph();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
        // Обработчик перемещения мыши
        private void GR_MouseMove(object sender, MouseEventArgs e)
        {
            double mX = e.X;    // x-координата мыши
            double mY = GR.Height - e.Y;    // Высота окна вывода минус y-координата мыши
            // Показываем координаты мыши в форме
            labelMouse.Text = "Mouse: " + Convert.ToString(mX) + ", " + Convert.ToString(mY);
            // kV - индекс выбранной вершины графа
            if (kV > -1)
            {
                xy2[0, kV] = mX;
                xy2[1, kV] = mY;
                xy[0, kV] = mX / GR.Width * xma;
                xy[1, kV] = mY / GR.Height * yma; ;
                graph();
            }
        }
        // Обработчик нажатия на левую кнопку мыши
        private void GR_MouseClick(object sender, MouseEventArgs e)
        {
            double mX = e.X;    // x-координата мыши
            double mY = GR.Height - e.Y;    // Высота окна вывода минус y-координата мыши
            double dlt = 5.0; // Параметр критерия выбора вершины (в пикселях)
            // Показываем координаты мыши в форме
            labelMouse.Text = "Mouse: " + Convert.ToString(mX) + ", " + Convert.ToString(mY);
            // kV - индекс выбранной вершины графа
            if (kV > -1)
            {
                kV = -1;    // kV равен -1, если нет выбранной вершины
            }
            else
            {
                kV = -1;
                for (int k = 0; k <= nVrts; k++)
                {
                    if (Math.Abs(xy2[0, k] - mX) < dlt && Math.Abs(xy2[1, k] - mY) < dlt)
                    {
                        kV = k;
                        break;
                    }
                }
            }
            graph();    // Рисуем граф
        }
        // Добавляет дочерний элемент узла node с именем name и значением value типа double
        private void AddChild(XmlDocument doc, XmlNode parent, string name, double value)
        {
            XmlNode child = doc.CreateElement(name);
            child.InnerText = Convert.ToString(value);
            parent.AppendChild(child);
        }
        // Добавляет дочерний элемент узла node с именем name и значением value типа string
        private void AddChildStr(XmlDocument doc, XmlNode parent, string name, string value)
        {
            XmlNode child = doc.CreateElement(name);
            child.InnerText = value;
            parent.AppendChild(child);
        }
        // Добавляет атрибут узла node
        private void AddAttr(XmlDocument doc, XmlNode node, String name, string value)
        {
            XmlAttribute attr = doc.CreateAttribute(name);
            attr.Value = value;
            node.Attributes.Append(attr);
        }
        // Формируем xml-документ
        private void WriteXml(XmlDocument doc)
        {
            // Прежде сохраняем в xml параметры матрицы проецирования
            XmlNode ortho = doc.CreateElement("ortho");
            doc.DocumentElement.AppendChild(ortho);
            AddChild(doc, ortho, "xmi", xmi);
            AddChild(doc, ortho, "xma", xma);
            AddChild(doc, ortho, "ymi", ymi);
            AddChild(doc, ortho, "yma", yma);
            // Сохраняем в xml сведения о графе
            // Вершины
            XmlNode verts = doc.CreateElement("verts");
            doc.DocumentElement.AppendChild(verts);
            for (int k = 0; k <= nVrts; k++)
            {
                // Создаем элемент с именем graphVertex
                XmlNode vrt = doc.CreateElement("graphVertex");
                verts.AppendChild(vrt);
                // Формируем атрибуты элемента vrt
                AddAttr(doc, vrt, "vName", attrValVrts[0, k]);
                AddAttr(doc, vrt, "vNumber", attrValVrts[1, k]);
                // Создаем дочерние элементы x и y и добавляем их к элементу vrt
                AddChild(doc, vrt, "x", xy[0, k]);
                AddChild(doc, vrt, "y", xy[1, k]);
            }
            // Ребра
            for (int k = 0; k <= nDgs; k++)
            {
                // Создаем элемент с именем edge
                XmlNode edge = doc.CreateElement("edge");
                // Добавляем элемент к документу
                doc.DocumentElement.AppendChild(edge);
                AddAttr(doc, edge, "eName", attrValDgs[k]);
                // Создаем дочерние элементы start и end и добавляем их к элементу edge
                AddChildStr(doc, edge, "start", vE[0, k]);
                AddChildStr(doc, edge, "end", vE[1, k]);
            }
        }
        private void SaveXml(string fl)
        {
            XmlTextWriter textWriter = new XmlTextWriter(fl, Encoding.UTF8);
            // Заголовок xml
            textWriter.WriteStartDocument();
            // Открывающий тэг
            textWriter.WriteStartElement("graph");
            // Закрывающий тэг
            textWriter.WriteEndElement();
            textWriter.Close();
            // Создаем xml-документ
            XmlDocument docXml = new XmlDocument();
            docXml.Load(fl);
            WriteXml(docXml);
            // Сохраняем xml-документ в файле fl
            docXml.Save(fl);
        }
        private void SaveFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(fl))
            {
                MessageBox.Show("Не выбран xml-файл");
                return;
            }
            SaveXml(fl);
        }
        private void SaveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(fl))
            {
                MessageBox.Show("Не выбран xml-файл");
                return;
            }
            SaveFileDialog OPF = new SaveFileDialog();
            OPF.Filter = "xml-файлы (*.xml)|*.xml";
            DialogResult dlg = OPF.ShowDialog();
            if (dlg == DialogResult.OK)
            {
                fl = OPF.FileName;
                SaveXml(fl);
                label1.Text = "Граф сохранен в файле: " + fl;
            }
        }
        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }
        private void newFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FileLoad();
        }
    }
}

Редактирование графа

Добавленные в код обработчики событий мыши GR_MouseClick и GR_MouseMove позволяют выбрать вершину графа (GR_MouseClick) и перемещать ее вслед за мышиным курсором (GR_MouseMove) (рис. 9).

Граф после перемещения его вершины

Рис. 9. Измененный граф

Предварительно при загрузке графа из файла создается массив xy2 видовых координат вершин (см. процедуру buttonFileLoad_Click). Начало видовой системы координат находится в левом нижнем углу окна вывода. В проекте окно OpenGL имеет имя GR.
При перемещении выделенной вершины в обработчике GR_MouseMove видовые координаты мыши и, следовательно, выделенной вершины преобразовываются в мировые. Граф перерисовывается.
После нажатия на левую кнопку мыши выделение вершины, если таковое было, снимается.

Сохранение графа в файл

После доработки в форму было добавлено меню Файл (объект menuStrip1, рис. 10).

Элементы меню menuStrip1

Рис. 10. Добавлено меню

Порядок добавления меню menuStrip1 иллюстрирует рис. 11.

Выбор menuStrip в Toolbox

Рис. 11. Выбор menuStrip в Toolbox

После введения подменю в форму появляется возможность формирования меню.
Каждому пункту подменю поставлена в соответствие процедура, исполняемая при ударе (Click) пункта мышкой.

Задание обработчика события Click подпункта Сохранить иллюстрирует рис.  12.

Задание Click-обработчика

Рис. 12. Задание обработчика события Click

Введенные для сохранения графа процедуры см. в вышеприведенном коде программы.

Заключение

Имеются недостатки, подлежащие устранению:

Литература

  1. Боресков А. В. Графика трехмерной компьютерной игры на основе OpenGL. М.: ДИАЛОГ-МИФИ, 2004.

Список работ

Рейтинг@Mail.ru