Как правильно обрабатывать событие Click в WinForms? C#

184
22 января 2019, 04:10

Есть форма Form1 и одна единственная кнопка button1

    private void button1_Click(object sender, EventArgs e)
    {
        //do something..
    }

Задумался над тем как правильно обрабатывать событие Click, в том плане, что не просто писать кучу кода вместо do something, а сделать это по крайней мере в стиле ООП. Вопрос 1. Подскажите пожалуйста, как решают этот вопрос при разработке коммерческих программ?

Сейчас я делаю примерно так

//Файл Form1.cs
private Department department = new Department();
private void button1_Click(object sender, EventArgs e)
{
        department.Add += (obj, ev) =>
        {
                Task task = Task.Factory.StartNew(() =>
                {
                    //do something..
                });
        };
        department.Adding(Employees.Engineer);
}
//Файл Department.cs
class Department
{
    public delegate void DepartmentHandler(object sender, DepartmentEventArgs e);
    public event DepartmentHandler Add;
    public void Adding(Employees employee)
    {
        Add?.Invoke(this, new DepartmentEventArgs(employee));
    }
}
class DepartmentEventArgs: EventArgs
{
    public readonly Employees _employee;
    public DepartmentEventArgs(Employees employee)
    {
        _employee = employee;
    }
}

Я думаю суть понятна. Вопрос 2. Подскажите пожалуйста, правильно ли я делаю, написав код таким образом?

Всем большое спасибо за ответы!

Answer 1

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

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

1) Пример на простейшее событие выбора в ComboBox

Создадим такой класс, который потом будет использовать для создания свойства в форме

public class SimpleEventHandler
{
    private Action _handlerExecutor;
    //ctor
    public SimpleEventHandler(Action handlerExecutor)
    {
        _handlerExecutor = handlerExecutor ??
            throw new ArgumentNullException(nameof(handlerExecutor));
        Handler = new EventHandler(OnEvent);
    }
    /// <summary>
    /// Ссылка на метод,
    /// который буден вызван при возникновении события у целевого контрола 
    /// </summary>
    public EventHandler Handler { get; private set; }
    /// <summary>
    /// Вызов по событию исполняемого метода
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnEvent(object sender, EventArgs e)
    {
        _handlerExecutor();
    }
}

Тогда вьюшка будет такой

public partial class MainView : Form, IMainView
{
    private BindingSource _bsGroups = new BindingSource();
    public MainView()
    {
        InitializeComponent();
        SetBindings();
        this.CenterToScreen();
        this.Text = "SimpleEvent";
    }
    private void SetBindings()
    {
        //ComboBox
        _bsGroups.DataSource = typeof(BindingList<Group>);
        _comboBox.DisplayMember = nameof(Group.Number);
        _comboBox.DataSource = _bsGroups;
    }

    /// <summary>
    /// Список групп для комбобокса
    /// </summary>
    public BindingList<Group> Groups
    {
        get => _bsGroups.List as BindingList<Group>;
        set => _bsGroups.DataSource = value;
    }
    /// <summary>
    /// Выбранная группа
    /// </summary>
    public Group SelectedGroup => _bsGroups.Current as Group;
    /// <summary>
    /// Событие выбора в комбобоксе группа
    /// </summary>
    public SimpleEventHandler SelectedGroupChanged
    {
        set
        {
            _bsGroups.CurrentChanged += value.Handler;
        }
    }
    public void ShowMessage(string message)
    {
        MessageBox.Show(message);
    }
}

А презентер такой

public class MainPresenter
{
    private readonly IMainView _mainView;
    //ctor
    public MainPresenter(IMainView mainView)
    {
        _mainView = mainView ?? throw new ArgumentNullException(nameof(mainView));
        LoadGroups();
        //событие выбора в комбобоксе
        _mainView.SelectedGroupChanged = new SimpleEventHandler(OnSelectedGroup);
    }
    /// <summary>
    /// Событие выбора в комбобоксе
    /// </summary>
    private void OnSelectedGroup()
    {
        string message = $"Вы выбрали: {_mainView.SelectedGroup.Number}";
        _mainView.ShowMessage(message);
    }
    /// <summary>
    /// Загрузка данных для комбобокса
    /// </summary>
    private async void LoadGroups()
    {
        var groups = await Program.Repository.GetAllGroups();
        _mainView.Groups = new BindingList<Group>(groups.ToList());
    }
}

2) Событые имеет значемые для презентера параметры.

Создадим такой класс посредник

public class ParameterizedEventHandler
{
    private Action<Dictionary<string, object>> _handlerExecutor;
    //ctor
    public ParameterizedEventHandler(Action<Dictionary<string, object>> handlerExecutor)
    {
        _handlerExecutor = handlerExecutor ??
            throw new ArgumentNullException(nameof(handlerExecutor));
    }
    public void RaiseEvent(Dictionary<string, object> parameters)
    {
        _handlerExecutor(parameters);
    }
}

Параметры в презентер будем передовать в словаре вот так

public partial class MainView : Form, IMainView
{
    public MainView()
    {
        InitializeComponent();
        this.CenterToScreen();
        this.Text = nameof(ParamEvent);
        //событие клика мыши по картинке
        _pictureBox.MouseClick += _pictureBox_MouseClick;
    }
    /// <summary>
    /// Обработка события нажатия кнопки мыши по картинке
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void _pictureBox_MouseClick(object sender, MouseEventArgs e)
    {
        //собираем нужную инфо
        string mouseButton = e.Button.ToString();
        int x = e.X;
        int y = e.Y;
        //формируем параметры
        Dictionary<string, object> parameters = new Dictionary<string, object>();
        parameters.Add(nameof(mouseButton), mouseButton);
        parameters.Add(nameof(x), x);
        parameters.Add(nameof(y), y);
        //вызываем событие и передаем эти параметры
        MouseClickEvent.RaiseEvent(parameters);
    }
    private ParameterizedEventHandler _MouseClickEvent;
    public ParameterizedEventHandler MouseClickEvent
    {
        get => _MouseClickEvent;
        set
        {
            if (_MouseClickEvent != null) return;
            _MouseClickEvent = value;
        }
    }
    public void ShowMessage(string message)
    {
        MessageBox.Show(message);
    }
}

Презентер такой

public class MainPresenter
{
    private readonly IMainView _mainView;
    //ctor
    public MainPresenter(IMainView mainView)
    {
        _mainView = mainView ?? throw new ArgumentNullException(nameof(mainView));
        //
        _mainView.MouseClickEvent = new ParameterizedEventHandler(OnMouseClick);
    }
    private void OnMouseClick(Dictionary<string, object> parameters)
    {
        string message = $"Была нажата кнопка мыши: {parameters["mouseButton"]}"
            + Environment.NewLine
            + $"Координаты курсора мыши: X={parameters["x"]}, Y={parameters["y"]}";
        _mainView.ShowMessage(message);
    }
}

3) Пример на кнопки с привязкой к свойствам Enabled и Text

Для кнопки слева сделана привязка к свойству Enabled с помощью такого класса

public class SimpleButtonEventHandler : SimpleEventHandler, INotifyPropertyChanged
{
    private Func<bool> _setterEnableProperty;
    //ctor
    public SimpleButtonEventHandler(Action handlerExecutor) : this(handlerExecutor, null)
    {
    }
    public SimpleButtonEventHandler(Action handlerExecutor, Func<bool> setterEnableProperty) : base(handlerExecutor)
    {
        if (setterEnableProperty == null)
        {
            _setterEnableProperty = () => true;
        }
        else
        {
            _setterEnableProperty = setterEnableProperty;
        }
    }
    /// <summary>
    /// Доступность кнопки для нажатия
    /// </summary>
    private bool _Enabled = true;
    public bool Enabled
    {
        get => _Enabled;
        private set
        {
            _Enabled = value;
            OnPropertyChanged();
        }
    }
    /// <summary>
    /// Инициализация проверки доступности кнопки
    /// </summary>
    public void CheckEnabled()
    {
        Enabled = _setterEnableProperty();
    }
    //INPC
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Для кнопки справа используется привязка к свойствам Enabled и Text

public class TextButtonEventHandler : SimpleButtonEventHandler
{
    //ctor
    public TextButtonEventHandler(Action handlerExecutor) : base(handlerExecutor)
    { }
    public TextButtonEventHandler(Action handlerExecutor, Func<bool> setterEnableProperty) : base(handlerExecutor, setterEnableProperty)
    { }
    /// <summary>
    /// Надпись на кнопке
    /// </summary>
    private string _Text;
    public string Text
    {
        get => _Text;
        set
        {
            _Text = value;
            OnPropertyChanged();
        }
    }
}

Вьюшка такая

public partial class MainView : Form, IMainView
{
    private BindingSource _bsButtonClick = new BindingSource();
    private BindingSource _bsButtonText = new BindingSource();
    public MainView()
    {
        InitializeComponent();
        SetBindings();
        this.CenterToScreen();
        this.Text = nameof(ButtonEvents);
    }
    /// <summary>
    /// Устанавливаем привязки
    /// </summary>
    private void SetBindings()
    {
        // Кнопка слева
        _bsButtonClick.DataSource = typeof(SimpleButtonEventHandler);
        _buttonClickOk.DataBindings.Add("Enabled", _bsButtonClick, "Enabled");
        // Кнопка справа
        _bsButtonText.DataSource = typeof(TextButtonEventHandler);
        _buttonTextOk.DataBindings.Add("Enabled", _bsButtonText, "Enabled");
        _buttonTextOk.DataBindings.Add("Text", _bsButtonText, "Text");
    }
    /// <summary>
    /// Кнопка слева
    /// </summary>
    public SimpleButtonEventHandler ClickOK
    {
        get => _bsButtonClick.Current as SimpleButtonEventHandler;
        set
        {
            if (_bsButtonClick.Count > 0) return;
            _bsButtonClick.Add(value);
            _buttonClickOk.Click += value.Handler;
        }
    }
    /// <summary>
    /// Лейбл отсчета кликов
    /// </summary>
    public string ClickOutput
    {
        get => _labelCount.Text;
        set => _labelCount.Text = value;
    }

    /// <summary>
    /// Кнопка справа
    /// </summary>
    public TextButtonEventHandler TextOK
    {
        get => _bsButtonText.Current as TextButtonEventHandler;
        set
        {
            if (_bsButtonText.Count > 0) return;
            _bsButtonText.Add(value);
            _buttonTextOk.Click += value.Handler;
        }
    }
}

Презентер к ней

public class MainPresenter
{
    private readonly IMainView _mainView;
    private int _clickCount = 5;
    private int _clickCountText = 5;
    //ctor
    public MainPresenter(IMainView mainView)
    {
        _mainView = mainView ?? throw new ArgumentNullException(nameof(mainView));
        //Кнопка слева
        _mainView.ClickOK = new SimpleButtonEventHandler(OnClick, CanClick);
        _mainView.ClickOutput = _clickCount.ToString();//вывод в лейбл
        //Кнопка справа
        _mainView.TextOK = new TextButtonEventHandler(OnText, CanText);
        _mainView.TextOK.Text = "OK";
    }
    /// <summary>
    /// Кнопка слева Доступность
    /// </summary>
    /// <returns></returns>
    private bool CanClick()
    {
        return _clickCount > 0;
    }
    /// <summary>
    /// Кнопка слева обработка события клик
    /// </summary>
    private void OnClick()
    {
        --_clickCount;
        _mainView.ClickOutput = _clickCount.ToString();
        if (_clickCount == 0)
        {
            //проверить доступность кнопки, через вызов CanClick()
            _mainView.ClickOK.CheckEnabled();
        }
    }

    /// <summary>
    /// Доступность кнопки справа
    /// </summary>
    /// <returns></returns>
    private bool CanText()
    {
        return _clickCountText > 0;
    }
    /// <summary>
    /// Кнопка справа обработчик
    /// </summary>
    private void OnText()
    {
        --_clickCountText;
        //выводим на кнопку величину
        _mainView.TextOK.Text = _clickCountText.ToString();
        if (_clickCountText == 0)
        {
            _mainView.TextOK.Text = "Всё";
            _mainView.TextOK.CheckEnabled();
        }
    }
}

4) Кнопка на ToolStrip

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

public class ToolStripBindableButton : ToolStripButton, IBindableComponent
{
    private ControlBindingsCollection _DataBindings;
    public ControlBindingsCollection DataBindings
    {
        get => _DataBindings = _DataBindings ?? new ControlBindingsCollection(this);
    }

    private BindingContext _BindingContext;
    public BindingContext BindingContext
    {
        get => _BindingContext = _BindingContext ?? new BindingContext();
        set => _BindingContext = value;
    }
}

После добавления в проект такого класса и компиляции эту кнопку можно будет добавить к тулбар и работать с ней таким образом

public partial class MainView : Form, IMainView
{
    private BindingSource _bsTool = new BindingSource();
    public MainView()
    {
        InitializeComponent();
        //привязка кнопки на тулбаре
        _bsTool.DataSource = typeof(SimpleButtonEventHandler);
        _toolStripBindableButton.DataBindings.Add("Enabled", _bsTool, "Enabled");
        this.CenterToScreen();
        this.Text = nameof(ToolStripEvent);
    }
    //кнопка
    public SimpleButtonEventHandler ToolButton
    {
        get => _bsTool.Current as SimpleButtonEventHandler;
        set
        {
            if (_bsTool.Count > 0) return;
            _bsTool.Add(value);
            _toolStripBindableButton.Click += value.Handler;
        }
    }
    //текстбокс событие изменения
    private SimpleEventHandler _InputTextChanged;
    public SimpleEventHandler InputTextChanged
    {
        get => _InputTextChanged;
        set
        {
            if (_InputTextChanged != null) return;
            _InputTextChanged = value;
            _textBox.TextChanged += value.Handler;
        }
    }
    //текстбокс содержимое
    public string InputText
    {
        get => _textBox.Text;
        set => _textBox.Text = value;
    }
    public void ShowMessage(string message)
    {
        MessageBox.Show(message);
    }
}

И презентер

public class MainPresenter
{
    private readonly IMainView _mainView;
    //ctor
    public MainPresenter(IMainView mainView)
    {
        _mainView = mainView ?? throw new ArgumentNullException(nameof(mainView));
        //кнопка на тулбаре
        _mainView.ToolButton = new SimpleButtonEventHandler(OnTool, CanTool);
        //изменение в текстбоксе
        _mainView.InputTextChanged = new SimpleEventHandler(OnText);
        //заставляем выкл. кнопке на тулбаре
        _mainView.ToolButton.CheckEnabled();
    }
    /// <summary>
    /// Кнопка на тулбаре
    /// </summary>
    /// <returns></returns>
    private bool CanTool()
    {
        return !String.IsNullOrEmpty(_mainView.InputText);
    }
    private void OnTool()
    {
        var message = $"Вы ввели: {_mainView.InputText}";
        _mainView.ShowMessage(message);
    }
    /// <summary>
    /// Событие изменения текста
    /// </summary>
    private void OnText()
    {
        //заставляем вкл. или выкл. кнопке на тулбаре
        _mainView.ToolButton.CheckEnabled();
    }
}

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

READ ALSO
Запрос к таблице

Запрос к таблице

Как можно сделать вот такой запрос к таблице: "/responsephp?que=есть"? У меня работает только с цифрами

156
Переход с мобильной версии на полную версию сайта

Переход с мобильной версии на полную версию сайта

Я сделал сайт с мобильной версиейПереход с основного сайта на мобильный субдомен осуществляется через PHP код

174
Не загружаются картинки в базу данных на Denwer

Не загружаются картинки в базу данных на Denwer

Пишу сайт на phpСайт установлен на Денвере

145
Как забрать данные из ссылки?

Как забрать данные из ссылки?

Подскажите пожалуйста, есть сайт с такой ссылкойhttps://site

165