Привет, Хабр!

Каждый программист хочет прокачать свои навыки и каждая компания хочет видеть у себя квалифицированных специалистов, но как этого достичь? На помощь приходят олимпиады, об участии в одной из них и будет эта статья.

Отступление


Статья получилась объемная, поэтому весь код и большая часть изображений находятся под спойлерами. Все еще тестирую формат подачи статей. Если такой формат покажется Вам неудобным, просьба написать об этом. Также все картинки кликабельны.

О конкурсе


WorldSkills — это соревнование, целью которого является выявление профессионалов в конкретной области. Конкурс берет свои истоки с WorldSkills International (WSI), международной некоммерческой ассоциации.

Участие в конкурсе бесплатное. Возраст участников от 18 до 28 лет, учащиеся в коллежах или ВУЗах.

Конкурс в ВУЗе длится 5 дней: в первый день открытие и проверка рабочих мест, затем три дня соревнований, в заключительный день — подведение итогов и закрытие.

О выборе компетенции и подготовке


Об олимпиаде я узнал случайно во время одной из практик в университете на 2 курсе. Меня пригласили поучаствовать в компетенции «Разработка программных решений для бизнеса». Для решения задач требовались знания в C# или Java, работа с базой данных, и как я узнал в процессе первой олимпиады Android. Открытие олимпиады было запланировано на начало июня, а за окном был уже конец апреля. В этот момент я не знал абсолютно ничего, что требовалось.

Моему удивлению не было предела, когда всех потенциальных участников собрали для подготовки. В аудитории сидело 7 человек, 6 из которых были уже третьекурсниками, седьмым был я. Почему же я был так удивлен? На третьем курсе студентам читают курс по Базам данных, который длится 2 семестра, а значит у всех было на год практики больше. Отказываться не хотелось, поэтому я попросил у преподавателей книги по БД, нашел курс по C# в Интернете и начал готовиться, периодически появляясь на подготовительных занятия.

За две н��дели до начала олимпиады мы узнали, что от нашей кафедры могут участвовать не более трех человек. Прошел отборочный тур. За 1,5 часа мы должны были создать базу данных в СУБД MSSQL по ER-модели, импортировать данные из файла Excel и показать их в приложении. Одним словом, мне разрешили участвовать в олимпиаде от нашей кафедры.

Теперь о конкурсе


Первый день соревнования или «C -1»


В этот день прошло долгожданное открытие. Всех участников собрали в актовом зале, где рассказали о истории WorldSkills, компетенциях и представили всех экспертов с участниками.
После этой церемонии все разошлись по своим площадкам проверять оборудование. Был проведен «ритуал посвящения», на котором мы выбирали свои рабочие места и расписывались за технику безопасности. На тот момент я не знал что можно проверять в ПО, поэтому сразу после жеребьевки ушел готовиться к следующему дню.

Второй день соревнования или начало соперничества


Что же представляла из себя олимпиада?

Для нашей компетенции за 2 дня (три для других компетенций) по 6 часов с перерывами от нас требовалось написать клиент-серверное приложение на C#/Java с запросами к БД. Точнее сказать «все что успеем написать», поскольку принципом олимпиады является «делай что можешь и как можешь».

По словам экспертов, критериев было на несколько десятков страниц A4. Критерии выдаются только после окончания сессии и только экспертам, поэтому о них ничего сказать не могу.

Перейдем к событиям второго дня. За первую сессию требовалось реализовать следующее:

  • Создать базу данных по известной ER-диаграмме
  • Импортировать данные из файлов Excel в базу
  • Создать 4 экрана по макетам в презентации

А за вторую сверстать еще пять экранов по макетам.

Если кратко, все задания были решены, кроме отображения картинок из базы данных и создания кастомного списка элементов.

Немного скриншотов:




Интерфейс позволяет найти игроков по имени (даже по первой букве), сезону и команде. По двойному клику на изображение, которое не было реализовано, открывается подробная информация о игроке. Сейчас бы реализовал это через DataGridViewImageColumn.




Как я сохранял изображения
        private void download_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFile = new SaveFileDialog();
            saveFile.DefaultExt = ".jpg";
            saveFile.AddExtension = true;
            //Папка по умолчанию - Библиотеки/Изображения
            saveFile.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures);
            saveFile.Filter = "Bitmap files (*.bmp)|*.bmp|Image files (*.jpg;*.png)|*.jpg;*.png";

            if (saveFile.ShowDialog() == DialogResult.OK)
            {
                if (!saveFile.FileName.Equals(""))
                {

                    String[] arr = saveFile.FileName.Split('.');

                    //Получаем путь для сохранения от начала до "\" включительно
                    String filepath = arr[0].Substring(0, arr[0].LastIndexOf('\\') + 1);

                    for (int i = 1; i <= imageList1.Images.Count; i++)
                    {
                        Image image = Image.FromFile("..\\Pictures\\" + (i + offset) + ".jpg");

                        image.Save(filepath + i + "." + arr[1]);
                    }
                }
            }
        }


Больше скриншотов,текста и кода



Интерфейс является главным экраном, позволяющий выбрать роль «Пользователь» или «Администратор», а также посмотреть лучшие моменты с матчей. Как разместить элементы по центру не смог найти.

Реализация загрузки изображений

        private int countImages()
        {
            sqlConnection = new SqlConnection(connectionString);

            using (sqlConnection)
            {
                sqlConnection.Open();

                String sqlcomm = "SELECT Count(*) FROM [Pictures$]";

                SqlCommand command = new SqlCommand(sqlcomm, sqlConnection);

                int result = (int)command.ExecuteScalar();

                return result;
            }
        }

        private void loadPage()
        {
            sqlConnection = new SqlConnection(connectionString);

            using (sqlConnection)
            {
                sqlConnection.Open();

                String sqlcomm = "SELECT [Img] , [CreateTime] FROM [Pictures] P" +
                    " Order By P.CreateTime Desc" +
                    " Offset @click Rows FETCH NEXT @svm ROWS ONLY";
                SqlCommand cmd = new SqlCommand(sqlcomm, sqlConnection);
                cmd.Parameters.AddWithValue("@click", click);
                cmd.Parameters.AddWithValue("@svm", svm);

                dataAdapter.SelectCommand = cmd;
                DataSet data = new DataSet();
                dataAdapter.Fill(data);

                imageList1.Images.Clear();
                listView1.Clear();

                for (int i = 0; i < data.Tables[0].Rows.Count; i++)
                {
                    imageList1.Images.Add(Image.FromFile("..\\Pictures\\" + data.Tables[0].Rows[i].ItemArray[0].ToString()));
                }

                listView1.LargeImageList = imageList1;

                for (int i = 0; i < imageList1.Images.Count; i++)
                    listView1.Items.Add("").ImageIndex = i;

            }
        }

        private void left_Click(object sender, EventArgs e)
        {
            if (click > 0)
            {
                if (!right.Enabled)
                {
                    right.Enabled = true;
                }

                click -= svm;

                loadPage();
            }
            else
            {
                left.Enabled = false;
            }

        }

        private void right_Click(object sender, EventArgs e)
        {
            if (click + svm < totalPhotos)
            {
                if (!left.Enabled)
                {
                    left.Enabled = true;
                }
                click += svm;

                loadPage();
            }
            else
            {
                right.Enabled = false;
            }
        }





Интерфейс для авторизации технических администраторов и организаторов матчей, позволяющий запомнить последний введенный логин и пароль. В моем случае я писал данные в файл на жесткий диск без шифрования.

Проверка существования введенного логина и пароля в БД
        private int checkData(string jobnumber, string password)
        {
            //Защита от SQL-Injection при помощи параметризованных команд
            String sqlchecked = "if (Isnull((Select RoleId From Admin$ Where Jobnumber like(@Jobnumber) and Passwords like(@Password)) , 0) = 0)" +
                " Select 0 " +
                " else " +
                " Select RoleId From Admin$  Where Jobnumber like(@Jobnumber) and Passwords like(@Password)";

            sqlConnection = new SqlConnection(connectionString);
            int result = 0;
            using (sqlConnection)
            {
                sqlConnection.Open();

                SqlCommand command = new SqlCommand(sqlchecked, sqlConnection);
                //Автоматическое определение типа вводимых данных
                command.Parameters.AddWithValue("@Jobnumber", jobnumber);
                command.Parameters.AddWithValue("@Password", password);
                //Получение данных из первого столбца первой строки таблицы
                result = (int)command.ExecuteScalar();
            }
            return result;
        }


Если Вы знаете лучший способ проверить данные, просьба написать в комментариях.




А так выглядит недоделанный экран, здесь я просто накидал компоненты на форму согласно макету. Можете, пожалуйста, подсказать как сделать такой кастомный список на C#?




Самый сложный для меня экран, так как здесь была работа с графиками, к которым я не готовился.

Третий день соревнования или неожиданная встреча с Android


Предыдущий день можно было считать разогревом по сравнению с третьим. На 3 сессии предстояло доверстать еще 8 экранов. А на заключительной сессии произошла смена планов и вместо презентации по разработанному продукту мы начали делать упрощенную версию для Android. А именно галерею с картинками, подгружаемых из базы данных. Сейчас это звучит легко, но в тот момент я был рад, что на сессию давали 15 минут для выхода в Интернет. За 3 часа был создан GridView с элементами в виде ImageView, в Adapter передан массив id картинок и переопределен интерфейс OnItemClickListener для создания новой Activity с картинкой.

Еще немного скриншотов:




Интерфейс для администратора, позволяющий посмотреть информацию по игрокам. Самый бесполезный экран, по моему мнению, так как вносить изменения нельзя.

| | |

Моя «идеальная» галерея за 3 часа с использованием Интернета и без навыков программирования под Android.

Больше изображений, текста и кода
Код для создания галереи, когда не знаешь как работать с БД
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        //список id изображений
        var images = ArrayList<Int>()

        images.add(R.drawable.a1)
        //...
        images.add(R.drawable.a12)

        listView.adapter = CustomAdapter(images.toTypedArray())

        listView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>, _: View, i: Int, _: Long ->
            var intent = Intent(this, FullscreenActivity::class.java)
            //передаем id изображения, которое нужно посмотреть
            intent.putExtra("id",images[i])
            startActivity(intent)
        }
    }
}

class CustomAdapter internal constructor(var images: Array<Int>) :
    BaseAdapter() {

    override fun getCount(): Int {
        return images.size
    }

    // элемент по позиции
    override fun getItem(position: Int): Int {
        return images[position]
    }

    // id по позиции
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    // пункт списка
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        //используем уже созданное представление
        var view: View? = convertView
        //если нет, создаем новое
        if (view == null) {
            val inflater = LayoutInflater.from(parent.context)
            view = inflater.inflate(R.layout.item, parent, false)
        }

        view!!.findViewById<ImageView>(R.id.imageView).setImageResource(images[position])

        return view
    }

}

class FullscreenActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fullscreen)

        //Получаем id изображения
        var id = intent.getIntExtra("id",-1)

        imageView2.setImageResource(id)
    }
}






По клику на сезон(правая таблица) открывается подробная информация о всех матчах.




Интерфейс для администраторов, позволяющий вносить изменения в список команд и экспортировать данные в Excel.

Как я экспортировал данные в Excel

        private void exportExcel_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.Filter = "Excel (*.xls)|*.xls|All files (*.*)|*.*";
            saveFileDialog.DefaultExt = ".xls";

            if (saveFileDialog.ShowDialog() != DialogResult.OK)
                return;

            if (saveFileDialog.FileName.Equals(""))
                return;

            DataTable dt = (DataTable)dataGridView2.DataSource;
            //Открываем файл
            StreamWriter wr = new StreamWriter(saveFileDialog.FileName);

            try
            {
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    wr.Write(dt.Columns[i].ToString().ToUpper() + "\t");
                }

                wr.WriteLine();

                //Записываем строки в файл Excel
                for (int i = 0; i < (dt.Rows.Count); i++)
                {
                    for (int j = 0; j < dt.Columns.Count; j++)
                    {
                        if (dt.Rows[i][j] != null)
                        {
                            wr.Write(Convert.ToString(dt.Rows[i][j]) + "\t");
                        }
                        else
                        {
                            wr.Write("\t");
                        }
                    }
                    //Переходим на следующую строку
                    wr.WriteLine();
                }
                //Закрываем файл
                wr.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }



Подведение итогов


В 12 часов состоялась церемония закрытия, где всем участникам вручались сертификаты, а победителей и призеров ждали медальки с дипломами. В нашей компетенции я занял 2 место. Между награждением компетенций выступали студенты с песнями и танцами.

Заключение


За месяц подготовки я освоил DDL и DML команды SQL, что значительно упростило работу на парах по БД на третьем курсе. Полученные знания в C# и Windows Form оставляют желать лучшего, кроме работы с базами данных и пользовательским интерфейсом ни с чем работать не пришлось.

В этом году я также принимал участие в олимпиаде WorldSkills и занял 1 место, но об этом, усложненных заданиях и последствиях отключения Интернета на площадках в следующей статье.