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

Итак, предлагаем пользователю заполнить 4-е текстовых поля, двух типов данных. Два поля строковые и два числовые.

Хотим заставить пользователя обязательно что то написать во всех полях, а в двух последних должны быть только цифры. Если поле останется пустым при нажатии кнопки, то бортик не заполненного поля должен заморгать красным.

Первое что нам тут надо сделать это не дать возможность писать ничего кроме цифр в те два поля, которые могут иметь только цифровые значения, для этого повесим события KeyPress на оба этих поля и реализуем события следующим образом:

				
					        private void txtINN_KeyPress(object sender, KeyPressEventArgs e)
        {
            char number = e.KeyChar;
            if (!Char.IsDigit(number) && number != 8) // цифры и клавиша BackSpace
            {
                e.Handled = true;
            }
        }
        private void txtCash_KeyPress(object sender, KeyPressEventArgs e)
        {
            char number = e.KeyChar;
            if (!Char.IsDigit(number) && number != 8) // цифры и клавиша BackSpace
            {
                e.Handled = true;
            }
        }
				
			

number != 8 дает возможность стирать написанное используя BackSpace. А вы спросите, как же стрелки, кнопка Delete и т.д.? Да, согласен, надо переделать, и можно даже короче, и вообще на оба контрола достаточного одного обработчика:

				
					        private void txtINN_KeyPress(object sender, KeyPressEventArgs e)
        {
            char ch = e.KeyChar;
            e.Handled = !Char.IsDigit(ch) && !Char.IsControl(ch);
        }
				
			

Теперь можно использовать любые кнопки типа Up, Dwn, Del, Back, для которых Char.IsControl(ch) вернет true.  

Далее хотим проверить, что все наши TextBox поля были не пустыми, для этого при нажатии кнопки Start вызовем метод ValidateData

				
					        bool ValidateData()
        {
            validate = 0;
            foreach (Control t in this.Controls)
            {
                if (t.Name.StartsWith("txt"))
                {
                    if (t.Text.Length == 0)
                    {
                        t.PlaceholderText = "ЗАПОЛНИТЕ ЭТО ПОЛЕ!";
                        validate++;
                    }
                }
            }
            return validate == 0;
        }
				
			

Функция ValidateData вернет false если хоть один TextBox будет пустой и нам его надо захайлайтить. За это будет отвечать метод MarkInvalid. В итоге мы хотим поморгать цветом Barder, но в TextBox нет возможности менять цвет рамки, поэтому, для начала сделаем метод MarkInvalid по простому, зададим свойству BackColor пустого TextBox значение Color.Red

				
					        private void btnStart_Click(object sender, EventArgs e)
        {
            if (ValidateData())
            {
                this.Visible = false;
                FormMain frmain = new FormMain();
                frmain.Show();
            }
            else
            {
                lblErrorValidate.Text = "Ошибка Валидации! Заполните поля!";
                MarkInvalid();
            }
        }
				
			

MarkInvalid вызовется, если ValidateData вернет false

				
					        void MarkInvalid() 
        {
            foreach (Control t in this.Controls)
            {
                if (t.Name.StartsWith("txt"))
                {
                    if (t.Text.Length == 0)
                        t.BackColor = Color.Red;
                }
            }
        }
				
			

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

Идея такая, под каждый TextBox подложим контрол Panel, вернее надо положить все TextBox внутрь Panel, для каждого своей, но так что бы Panel была на пиксель шире и выше, то есть по чуть чуть вылезать со всех сторон. Её то мы и будем красить в красный цвет, что бы получить эффект закраски рамки.

Функции ValidateData и MarkInvalid придется переписать, так как наши TextBox-ы теперь напрямую не принадлежать форме, они вложены каждый в свою Panel:

				
					        bool ValidateData()
        {
            validate = 0;
            foreach (Control p in this.Controls)
            {
                if (p.Name.StartsWith("panel"))
                {
                    TextBox t = (TextBox)p.Controls[0];
                    if (t.Text.Length == 0)
                    {
                        t.PlaceholderText = "ЗАПОЛНИТЕ ЭТО ПОЛЕ!";
                        validate++;
                    }
                }
            }
            return validate == 0;
        }
				
			
				
					        void MarkInvalid() 
        {
            foreach (Control p in this.Controls)
            {
                if (p.Name.StartsWith("panel"))
                {
                    TextBox t = (TextBox)p.Controls[0];
                    if (t.Text.Length == 0)
                        if (p.BackColor == Color.Transparent)
                            p.BackColor = Color.Red;
                        else
                            p.BackColor = Color.Transparent;
                    else
                        p.BackColor = Color.Transparent;
                }
            }
        }
				
			

То есть теперь, изначально пробегаем не по TextBox контролам формы, а по всем Panel и уже внутри каждой панельки берем вложенный Textbox для проверки на пустоту. И меняем теперь свойство BackColor не у TextBox, а у той панельки в которую он вложен.

А еще мы хотим, что бы “рамка” у текстбокса не просто стала красной, а заморгала. Для этого запустим метод MarkInvalid в отдельном потоке, так что бы в нем закрутился бесконечный цикл), вот так:

				
					        private void btnStart_Click(object sender, EventArgs e)
        {
            if (ValidateData())
            {
                this.Visible = false;
                FormMain frmain = new FormMain();
                frmain.Show();
            }
            else
            {
                lblErrorValidate.Text = "Ошибка Валидации! Заполните поля!";
                Task.Run(() => MarkInvalid());
            }
        }
				
			

Запускаем MarkInvalid в отдельном потоке:

				
					Task.Run(() => MarkInvalid());
				
			

Внутри MarkInvalid запускаем цикл с остановкой на пол секунды:

				
					        void MarkInvalid()
        {
            while(validate > 0)
            {
                Invoke(new Action(() => DoMark()));

                Thread.Sleep(500);
            }
        }
        void DoMark() 
        {
            foreach (Control p in this.Controls)
            {
                if (p.Name.StartsWith("panel"))
                {
                    TextBox t = (TextBox)p.Controls[0];
                    if (t.Text.Length == 0)
                        if (p.BackColor == Color.Transparent)
                            p.BackColor = Color.Red;
                        else
                            p.BackColor = Color.Transparent;
                    else
                        p.BackColor = Color.Transparent;
                }
            }
        }
				
			

Обратите внимание, что метод MarkInvalid работает в своем собственном, отдельном потоке, но в нем мы меняем значения свойств контролов, которые лежат на форме и могут изменяться только из главного потока, то есть из потока формы. Что бы обойти это ограничение, нам надо обернуть вызов DoMark в специальные потоковые скобки: 

				
					Invoke(new Action(() => DoMark()));
				
			

Красота, теперь все работает как надо.

Если интересно, код проекта можно смотреть на github.

Валидация ввода данных TextBox WinForms
Tagged on:     

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.