
Привет, Хабр!
Каждый программист хочет прокачать свои навыки и каждая компания хочет видеть у себя квалифицированных специалистов, но как этого достичь? На помощь приходят олимпиады, об участии в одной из них и будет эта статья.
Отступление
Статья получилась объемная, поэтому весь код и большая часть изображений находятся под спойлерами. Все еще тестирую формат подачи статей. Если такой формат покажется Вам неудобным, просьба написать об этом. Также все картинки кликабельны.
О конкурсе
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]); } } } }
Больше скриншотов,текста и кода
Интерфейс является главным экраном, позволяющий выбрать роль «Пользователь» или «Администратор», а также посмотреть лучшие моменты с матчей. Как разместить элементы по центру не смог найти.

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

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

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

Интерфейс является главным экраном, позволяющий выбрать роль «Пользователь» или «Администратор», а также посмотреть лучшие моменты с матчей. Как разместить элементы по центру не смог найти.
Реализация загрузки изображений
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.
Больше изображений, текста и кода

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

Интерфейс для администраторов, позволяющий вносить изменения в список команд и экспортировать данные в Excel.
Код для создания галереи, когда не знаешь как работать с БД
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 место, но об этом, усложненных заданиях и последствиях отключения Интернета на площадках в следующей статье.