Usercontrol c windows forms

In this post, I would like to explain briefly that how one can make user controls in C# Winforms

The possibilities of creating own controls in C#, apart from the available out of the box controls are categorized as below

Extended Controls, by deriving from an existing control
UserControl to create a control by grouping several other controls
CustomControls, draw  control with the help of GDI +

Table of Contents

  • User Controls in C#
  • Create User Control in C#
    • Test the user control, in C#
    • Summary

User Controls in C#

User controls are normally a combination of more than one control in a single logical unit for achieving some specific functionality and to improve the reusability.

User controls are similar to any other class in .NET. The difference is that user controls are always derived from the UserControl class in System.Windows.Forms namespace.

User controls are segregated into partial classes for separating the logic from the designer.

User controls can be created directly inside your project.But for reusability and better maintainability, it is suggested to create user controls as separate dll, Windows Forms Control Library.

Create User Control in C#

Here I give an example of creating usercontrol in C# WinForms applications.Steps for creating a C# windows Forms user control is detailing below,

1. Open the Visual Studio and start a new project.
Select windows Control Library from Visual studio templates for Windows applications.

2. Name the project as you desired(Here I named as UserControlLibrary) and click OK.

3. Usercontrol1 will be created automatically.

4. Change the name to your desired name (Here I named LoginControl). Remember to rename it at all places properly.

5. Create your user control User Interface.In this tutorial I am just writing a small login control as below.Name the 2 textboxes as txtUserId and txtPassword and button as btnLogin

6. Write the user control code logic. An example given below (Yes, it is a simple logic just to explain only)

I have given 2 properties to the user control, UserId, and Password which user can set to some
values through the Property Window once after drag & drop the Usercontrol in the form.

In real scenarios, these properties shall be set by values retrieved from the database or any other data store.

Read the Sample code for Creating Usercontrol in C#,

using System; 
using System.Windows.Forms; 

namespace UserControlLibrary
{
    public partial class LoginControl : UserControl
    {
        public LoginControl()
        {
            InitializeComponent();
        }
 
       private string userid;
       public string UserId
       {
            get
            {
                return userid;
            }
            set
            {
                userid = value;
            }
        }
 
        private string password;
        public string Password
        {
            get
            {
                return password;
            }
            set
            {
                password = value;
            }
        }

        private void btnLogin_Click(object sender, EventArgs e)        
        {            
              if (UserId == txtUserId.Text && Password == txtPassword.Text)            
              {                 
                   MessageBox.Show("Login Successful");             
              }             
              else                
                 MessageBox.Show("Login Failed.Invalid Credentials");        
        }   
   } 
}

During Login button click user credentials are compared with the user entered login credentials in the textbox controls txtUserId and txtPassword.

Hard-coded values are assigned to Properties UserID and Password during design time.If both matches user will be allowed to log in.

Use your authentication mechanism(database driven or anything) instead of the sample here.The idea of this article is only to explain how to create UserControl with some properties and consume it.s below.This user control is ready for use now.

Test the user control, in C#

1. Create New Windows Forms Project, I named the project as TestUserControl

2. Drag & drop the User control to the form.

3. Select the Usercontrol and go to Properties window.Set UserID as “Techymedia” and Password to “Rajeev”(hardcoded to make the         logic simple)

4. Set the TestUserControl project as the startup project.

5. Run the application.You can see the following result as per the code logic.If entered User ID as “TechyMedia” and Password as “Rajeev”      the login successful message will be shown and invalid login in other cases.

Summary

This article covered user controls in C# and how to create user controls in C#.Hope you found this article helpful.If you have any queries or feedback to share on Usercontrols in C#,  write it in the comments section below.

Most of the time you need to debug the usercontrol at design time. To understand how to debug user control at design time read the article Design time debugging of User controls

Creating custom controls in C# Windows Forms can significantly enhance the user experience by providing tailored functionalities that are not available in the standard set of controls. This article will guide you through the process of creating custom controls, extending existing ones, and incorporating them into your Windows Forms applications.

Why Custom Controls?

Custom controls are beneficial when you need specific behavior or appearance that standard controls cannot provide. They help you.

  • Encapsulate reusable logic
  • Simplify your forms by combining multiple controls into one
  • Create a unique look and feel for your application

Types of Custom Controls

  1. Derived Controls: Extend existing controls and override their behavior or appearance.
  2. Composite Controls: Combine multiple existing controls into a single reusable control.
  3. Owner-Drawn Controls: Provide custom rendering logic for existing controls.

Creating a Simple Derived Control

Let’s start by creating a simple derived control. We’ll extend the standard Button control to create a custom button with additional properties.

Step 1. Create a New Control Class

  1. Open your Windows Forms project in Visual Studio.
  2. Add a new class to your project (e.g., CustomButton.cs).
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    namespace CustomControls
    {
        public class CustomButton : Button
        {
            public Color HoverColor { get; set; } = Color.Yellow;
            private Color originalColor;
    
            protected override void OnMouseEnter(EventArgs e)
            {
                base.OnMouseEnter(e);
                originalColor = this.BackColor;
                this.BackColor = HoverColor;
            }
            protected override void OnMouseLeave(EventArgs e)
            {
                base.OnMouseLeave(e);
                this.BackColor = originalColor;
            }
        }
    }
    

Step 2. Use your Custom Control

  1. Build your project to compile the new control.
  2. Open the form designer, and you will see CustomButton in the Toolbox.
  3. Drag and drop CustomButton onto your form, or add it programmatically.
    CustomButton customButton = new CustomButton
    {
        Text = "Hover Me",
        Size = new Size(100, 50),
        Location = new Point(50, 50),
        HoverColor = Color.LightBlue
    };
    this.Controls.Add(customButton);
    

Creating a Composite Control

Next, let’s create a composite control that combines a TextBox and a Button. This control will serve as a search box with a button to trigger the search.

Step 1. Create a New UserControl

Add a new UserControl to your project (e.g., SearchBox.cs).

Step 2. Design the Control

  1. Open the SearchBox in the designer.
  2. Drag a TextBox and a Button onto the control.
  3. Arrange them as desired.

Step 3. Add Custom Properties and Events

using System;
using System.Windows.Forms;
namespace CustomControls
{
    public partial class SearchBox : UserControl
    {
        public string SearchText
        {
            get { return textBox1.Text; }
            set { textBox1.Text = value; }
        }
        public event EventHandler SearchClicked;
        public SearchBox()
        {
            InitializeComponent();
            button1.Text = "Search";
            button1.Click += (s, e) => SearchClicked?.Invoke(this, e);
        }
    }
}

Step 4. Use Your Composite Control

  1. Build your project.
  2. Open the form designer, and you will see SearchBox in the Toolbox.
  3. Drag and drop SearchBox onto your form, or add it programmatically.
    SearchBox searchBox = new SearchBox
    {
        Size = new Size(200, 30),
        Location = new Point(50, 50)
    };
    searchBox.SearchClicked += (s, e) => MessageBox.Show($"Searching for: {searchBox.SearchText}");
    this.Controls.Add(searchBox);
    
    

Owner-Drawn Controls

For more complex customization, you might need to create owner-drawn controls. Here’s an example of a custom list box with owner-drawn items.

Step 1. Create the Owner-Drawn Control

Add a new class to your project (e.g., CustomListBox.cs).

using System;
using System.Drawing;
using System.Windows.Forms;
namespace CustomControls
{
    public class CustomListBox : ListBox
    {
        public CustomListBox()
        {
            this.DrawMode = DrawMode.OwnerDrawFixed;
            this.ItemHeight = 30;
        }
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            if (e.Index < 0) return;
            e.DrawBackground();
            e.DrawFocusRectangle();

            string text = this.Items[e.Index].ToString();
            e.Graphics.DrawString(text, e.Font, Brushes.Black, e.Bounds);
            base.OnDrawItem(e);
        }
    }
}

Step 2. Use the Owner-Drawn Control

  1. Build your project.
  2. Open the form designer, and you will see CustomListBox in the Toolbox.
  3. Drag and drop CustomListBox onto your form, or add it programmatically.
    CustomListBox customListBox = new CustomListBox
    {
        Size = new Size(200, 150),
        Location = new Point(50, 50)
    };
    customListBox.Items.Add("Item 1");
    customListBox.Items.Add("Item 2");
    customListBox.Items.Add("Item 3");
    this.Controls.Add(customListBox);
    
    

Conclusion

Creating custom controls in C# Windows Forms allows you to enhance the functionality and user experience of your applications. By extending existing controls, combining multiple controls into a composite control, or implementing owner-drawn controls, you can achieve a high level of customization tailored to your specific needs. Start by experimenting with simple derived controls and gradually move to more complex scenarios as you become more comfortable with the process. Happy coding!

When developing Windows Forms (WinForms) applications in C#, one of the powerful features available is the ability to create custom user controls. User controls allow you to encapsulate a set of reusable UI elements and functionality into a single, easily manageable component. This not only helps in organizing your code but also promotes code reusability and maintainability. In this blog post, we will explore how to create user controls in WinForms using C#.

Why Use User Controls?

User controls offer several advantages when it comes to WinForms development:

  1. Reusability: Once created, user controls can be easily reused across multiple forms within the same project or even in different projects.

  2. Modularity: User controls promote modularity by encapsulating related UI elements and logic into a single component.

  3. Ease of Maintenance: By separating different parts of your application into user controls, you can easily update and maintain specific functionalities without affecting the rest of the application.

Steps to Create a User Control in WinForms

To create a user control in WinForms using C#, follow these steps:

  1. Create a New User Control: Right-click on your project in Visual Studio, select «Add» -> «User Control,» and give it a meaningful name.

  2. Design the User Control: In the designer view, add the necessary UI elements such as buttons, labels, textboxes, etc., and customize the layout as needed.

  3. Add Functionality: Implement the desired functionality by writing C# code in the code-behind file of the user control. You can handle events, perform calculations, or interact with other controls within the user control.

  4. Build and Compile: Build your project to compile the user control along with the rest of your application.

  5. Add User Control to Forms: To use the user control in a form, simply drag and drop it from the Toolbox onto the form’s designer view. You can then interact with the user control just like any other WinForms control.

Example: Creating a Custom User Control

Let’s create a simple custom user control that displays a welcome message and a button to close the control:

public partial class CustomUserControl : UserControl
{
    public CustomUserControl()
    {
        InitializeComponent();
        InitializeControl();
    }

    private void InitializeControl()
    {
        Label lblMessage = new Label();
        lblMessage.Text = "Welcome to Custom User Control!";
        lblMessage.Location = new Point(10, 10);
        this.Controls.Add(lblMessage);

        Button btnClose = new Button();
        btnClose.Text = "Close";
        btnClose.Location = new Point(10, 40);
        btnClose.Click += (sender, e) => this.Parent.Controls.Remove(this);
        this.Controls.Add(btnClose);
    }
}

You can now use this custom user control in your WinForms application to display a welcome message with a close button.

Conclusion

Creating user controls in WinForms using C# is a powerful way to enhance the functionality and reusability of your applications. By encapsulating related UI elements and logic into custom controls, you can build modular, maintainable, and scalable applications. Experiment with user controls in your next WinForms project and harness the benefits they offer in streamlining your development process.

Для начинающих знакомство с WinForms рано или поздно появляется ситуация, когда нужно пользователю показывать разные «страницы» приложения, в зависимости от его действий. Банальны пример: список и возможность редактирования отдельной записи. Самое первое что приходит на ум — просто переключатся между окнами. Вот только такой подход имеет определенные проблемы:

  • главная проблема — в Application.Run уже находится главная форма, которая определяет всю работу приложения. Её либо скрывать и хранить где-то в закромах для доступа, либо постоянно лезть в ApplicationContext и переназначать MainForm. Последний подход опасен тем что забыв переназначить, мы либо тупо грохним приложение послез закрытия текущей формы, либо оставим висеть программу в процессах.
  • визуально окна будут мигать, появляться в рандомных местах, разных размеров и прочее. Да, можно написать код, который будет получать размеры и позицию закрываемого окна и назначать новому, но это дополнительный простор для багов.

Чтобы упростить себе жизнь, можно не прыгать по формам, а просто оперировать UserControl. Вообще новички почему-то очень редко юзают этот инструмент, который очень и очень упрощает проектирование приложения. В частности с помощью него легко можно:

  • показывать кастомный список с любым дизайнерским оформлением, а не ограничиваться exel-подобным гридом
  • дробить сложный UI на отдельные части
  • переиспользовать в качестве partial
  • в целом строить логику на оперировании контролами (в том числе и навигации по приложению)

примечаниеВсе примеры будут приводится на .net core 8, с использованием синтаксиса C# 12. Проект набирался в VisualStudio 2022. Также дизайн редактировался на мониторе с масштабированием 200%, так что открывая пример на обычном мониторе вполне возможны искажения в ту или иную сторону (например текст может быть слишком мелким, или окно слишком большое/маленькое).

примечание 2Я не являюсь профессиональным разработчиком на WinForms. Данный пример — исключительно попытка показать возможности UserControl и альтернативу ситуации со 100500 окнами. Для построения действительно правильной архитектуры в комерческих приложениях стоит ознакомится с профильной литературой и материалом от более матерых WinForms-developer

Для начала разберем самый примитивный пример: одна форма, два юзер-контрола и переключения между ними. Создаем два UserControl, на которых будет по Label (чтобы различать их) и кнопка перехода на другой контрол.

Далее, создаем класс, который будет управлять размещение контролов на главной форме. Он будет стоять из следующих частей:

  • метод регистрации главного окна
  • метод, позволяющий зачистить текущий контрол на форме (для простоты примера я выбрал грубый подход, когда тупо сносим все контролы)
  • методы, размещающие требуемые новые контролы.
  • для доступа из любой точки приложения — singelton в виде статического свойства.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
internal class UIManager
{
    private Form? mainForm;
 
    public static UIManager Instance { get; } = new();
 
    private UIManager() { }
 
    public void RegMainForm(Form form)
        => mainForm = form;
 
    public void ShowAdminUserControl()
    {
        CheckRegMainForm();
        DropOld();
        var controls = mainForm!.Controls;
        controls.Add(new AdminUserControl
        { 
            Dock = DockStyle.Fill
        });
    }
    public void ShowClientUserControl()
    {
        CheckRegMainForm();
        DropOld();
        var controls = mainForm!.Controls;
        controls.Add(new ClientUserControl
        {
            Dock = DockStyle.Fill
        });
    }
 
    private void DropOld()
    {
        var controls = mainForm!.Controls;
        var olds = controls.Cast<Control>().ToArray();
        controls.Clear();
        foreach (Control control in olds)
            control.Dispose();
    }
    private void CheckRegMainForm()
    {
        if (mainForm is null)
            throw new Exception("main form not registration");
    }
}

Теперь меняем инициализацию приложения на старте: нам нужно до отображения формы зарегестрировать её в UIManager

C#
1
2
3
4
ApplicationConfiguration.Initialize();
var mainForm = new MainForm();
UIManager.Instance.RegMainForm(mainForm);
Application.Run(mainForm);

Далее — добавляем логику переключения на самих котролах

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public partial class AdminUserControl : UserControl
{
    public AdminUserControl() { InitializeComponent(); }
 
    private void ButtonClick(object sender, EventArgs e)
        => UIManager.Instance.ShowClientUserControl();
}
 
public partial class ClientUserControl : UserControl
{
    public ClientUserControl() { InitializeComponent(); }
 
    private void ButtonClick(object sender, EventArgs e)
        => UIManager.Instance.ShowAdminUserControl();
}

Билдим проект. Теперь у нас в toolbox появились наши UserControl. Открываем дизайн главной формы и размещаем на ней один из контролов. Запускаем и проверяем что все работает.

Если глянуть использование ОЗУ при очень частых переключениях, то можно заметить что память всё равно растет. Связано это с тем что GC не вызывается. И его принудительное пинание не особо меняет погоду. Причина в том что при наличии свободных 10+Гб clr не видит смысла лишний раз прелопачивать ОЗУ. Лично на моем ноуте я мог добить ОЗУ до целых 2ГБ и только тогда память начинала переиспользоваться.

Окей, в целом этого примера уже достаточно для освоения и разработки условного singel-form приложения. Правда прям в таком виде использовать будет не удобно по многим причинам:

  • нет «выравнивания» размера окна, если контрол имеет каки-либо ограничения
  • «закрытие» старого окна может сопровождаться определеными проверками (например мы в разделе редактирования и у нас есть не сохраненные данные). Это стоит учитывать.
  • класс навигации также знает про создание компонентов. По хорошему это нужно вынести в отдельную фабрику.
  • не плохо бы иметь возможность задавать заголовок окна относительно содержимого.

Окей, сделаем новый более сложный пример и дополним этими фичами.

полное ТЗ

Сделаем следующее приложение:

  • на старте показываем окно авторизации
  • далее мы показываем список неких Items, которые вычитываются из mock данных в ОЗУ
  • каждый Item имеет кнопку «редактировать», которая перекидывает на форму редактирования конкретной записи
  • также на главной форме со списком есть кнопка «добавить», которая позволяет открыть форму добавления (по сути таже форма, что и для редактирования, только без заполненных полей)

Для начала введем не обязательный интерфейс для контролов

C#
1
2
3
4
5
6
internal interface IViewUserControl
{
    public string? Title { get; }
    public bool Closing();
    public bool Close();
}

Title позволит указать в заголовке окна текст, Closing — выполнить проверку перед переключением или закрытием. Насчет метода Close — он 100% должен быть, но есть подозрение что всё таки в качестве void при отработке Closing.

Далее — главное окно. Его создаем как отдельный класс, не через дизайнер.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
internal class MainForm : Form
{
    private Control? viewControl;
 
    public Control? View
    {
        get => viewControl;
        set
        {
            viewControl = value;
            Controls.Remove(viewControl);
            Controls.Add(value);
        }
    }
 
    protected override void OnClosing(CancelEventArgs e)
    {
        base.OnClosing(e);
        if (viewControl is IViewUserControl view)
            e.Cancel = !(view.Closing() && view.Close());
    }
}

Тут всё просто: можем задать текущий внутренний контрол (учитывая ситуацию, когда главное окно должно содержать какое-то количество других контролов. Например общего меню. По сути это аналог Layout). Также переопределяем закрытие: предварительно спрашиваем внутренний контрол на возможность закрыть.

Теперь займемся внутренней логикой приложения: объявим класс отвечающий за текущую авторизацию пользователя

C#
1
2
3
4
5
6
7
internal record User(int Id, string Login);
 
internal class UserContext
{
    public User? CurrentUser { get; set; }
    public UserContext() { }
}

Добавим сервис с mock данными. В нем предусмотрим возможность вычитки, обновления и удаления Items. Так же он будет содержать проверку логин-пароль. (По сути мы эмулируем БД)

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
internal record UserData(int Id, string Login, string Password);
 
internal record DataItem(
    int Id,
    string Title,
    DateTime Date,
    string Text,
    byte[]? Image,
    UserData Author);
 
internal class DataService
{
    private static readonly UserData[] _usersMock;
    private static readonly List<DataItem> _dataMock;
    private readonly UserContext _userContext;
 
    private int AuthorId => _userContext.CurrentUser!.Id;
 
    static DataService()
    {
        var index = 1;
        var admin = new UserData(index++, "admin", "Nya!");
        var client = new UserData(index++, "client", "123");
        _usersMock = [admin, client];
 
        index = 0;
        _dataMock =
        [
            new DataItem(index++,
                "Hatsune Miku (初音ミク)",
                new DateTime(2007, 8, 31),
                @"Hatsune Miku (Japanese: 初音ミク), also...",
                null,
                admin),
            new DataItem(index++,
                "Kagamine Rin (鏡音リン)",
                new DateTime(2007, 12, 27),
                @"Kagamine Rin(Japanese: 鏡音リン), officially...",
                null,
                client),
            new DataItem(index++,
                "Kagamine Len (鏡音レン)",
                new DateTime(2007, 12, 27),
                @"Kagamine Len(Japanese: 鏡音リン), officially...",
                null,
                client),
            new DataItem(index++,
                "Megurine Luka (巡音ルカ)",
                new DateTime(2009, 1, 30),
                @"Megurine Luka (巡音ルカ, Megurine Ruka), codenamed CV03...",
                null,
                admin)
        ];
    }
    public DataService(UserContext userContext)
    {
        _userContext = userContext;
    }
 
    public User? SignIn(string login, string password)
    {
        var user = _usersMock
            .FirstOrDefault(x => 
                x.Login.Equals(login, StringComparison.OrdinalIgnoreCase)
                && x.Password == password);
        return user is null
            ? default
            : new User(user.Id, user.Login);
    }
    public IEnumerable<DataItem> LoadData(string filter)
    {
        var query = _dataMock.Where(x => x.Author.Id == AuthorId);
        if (!string.IsNullOrWhiteSpace(filter))
            query = query.Where(x => x.Title.Contains(filter)
                                  || x.Text.Contains(filter));
        return query.ToArray();
    }
    public DataItem SaveData(int? id, string title, DateTime date, string text, byte[]? image)
    {
        var author = _usersMock.First(x => x.Id == AuthorId);
        if (id.HasValue)
        {
            var index = _dataMock.FindIndex(x => x.Id == id);
            return _dataMock[index] = new DataItem(id.Value, title, date, text, image, author);
        }
        else
        {
            var nextId = _dataMock.Max(x => x.Id) + 1;
            var dataItem = new DataItem(nextId, title, date, text, image, author);
            _dataMock.Add(dataItem);
            return dataItem;
        }
    }
    public void DeleteData(int id)
        => _dataMock.RemoveAll(x => x.Id == id && x.Author.Id == AuthorId);
}

Ещё один сервис, выполняющий «упаковку» изображения в разрешение 200х200. Этот сервис исключительно для визуала + далее показать в фабрике определенный момент по тому как можно такие не глобальные сервисы создавать только по необходимости.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
internal class ImageService
{
    public byte[] Conversion(string path)
    {
        using var image = Image.FromFile(path);
        using var bitmap = new Bitmap(200, 200);
        using var graphic = Graphics.FromImage(bitmap);
 
        int w = bitmap.Width, h = bitmap.Height, x = 0, y = 0;
 
        if (image.Width > image.Height)
        {
            var k = image.Height / (double)h;
            w = Convert.ToInt32(Math.Round(image.Width / k));
            x = -(w - bitmap.Width) / 2;
        }
        else
        {
            var k = image.Width / (double)w;
            h = Convert.ToInt32(Math.Round(image.Height / k));
            y = -(h - bitmap.Height) / 2;
        }
 
        graphic.DrawImage(image, x, y, w, h);
 
        using var memory = new MemoryStream();
        bitmap.Save(memory, ImageFormat.Jpeg);
        return memory.ToArray();
    }
}

Теперь добавляем наши контролы. Пока у нас не готова навигация, помечаю места для вызова перехода на другую страницу как TODO: %name control%.

Авторизация. Сильно не мудрю, поэтому прям в форме вычитка из БД через сервис и прописывание в контекст.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
internal partial class AuthUserControl : UserControl
{
    private readonly UserContext _userContext;
    private readonly DataService _dataService;
 
    public AuthUserControl(
        UserContext userContext,
        DataService dataService)
    {
        InitializeComponent();
        _userContext = userContext;
        _dataService = dataService;
    }
 
    private void SignInButtonClick(object sender, EventArgs e)
    {
        if (!IsValid())
            return;
 
        var login = loginTextBox.Text;
        var password = passwordTextBox.Text;
        var user = _dataService.SignIn(login, password);
        if (user == null)
        {
            errorLabel.Visible = true;
        }
        else
        {
            _userContext.CurrentUser = user;
            // TODO: on main
        }
    }
    private bool IsValid()
        => !string.IsNullOrEmpty(loginTextBox.Text)
        && !string.IsNullOrEmpty(passwordTextBox.Text);
}

Создадим контрол для отбражения отдельной записи. На контроле будет две функциональные кнопки: «редактировать» и «удалить». По их нажатию мы просто будем пинать ивенты, а обработку вынесем наружу. В самом конструкторе зададим установку отображения значений входящей записи (текст, изображение и прочее).

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
internal partial class DataItemUserControl : UserControl
{
    private readonly DataItem _dataItem;
 
    public event Action<DataItemUserControl, DataItem>? OnDataDelete;
    public event Action<DataItemUserControl, DataItem>? OnDataEdit;
 
    public DataItemUserControl(DataItem dataItem)
    {
        InitializeComponent();
        _dataItem = dataItem;
        titleLabel.Text = dataItem.Title;
        dateLabel.Text = $"{dataItem.Date:dd MMMM yyyy}";
        richTextBox1.Text = dataItem.Text;
        this.SetImage(imagePictureBox, dataItem.Image);
    }
 
    private void EditButtonClick(object sender, EventArgs e)
        => OnDataEdit?.Invoke(this, _dataItem);
    private void DeleteButtonClick(object sender, EventArgs e)
    {
        if (this.ShowConfirmation("Delete item?"))
            OnDataDelete?.Invoke(this, _dataItem);
    }
}

Основное представление. Тут просто список в виде FlowLayoutPanel, а также кнопка «добавить». Для наглядности добавил отображение текущего профиля и строку для фильтрации наших записей. LoadData — загрузка данных из «БД» и закидываем в виде юзер-контролов на панель. Так как контролы могут только информировать через ивенты об попытке редактирования/удаления, не забываем подвязать обработчики (в идеале эту логику стоит вынести в сами DataItemUserControl, но без подвязки данных очень трудно пропихнуть удаление на главное представление, а также их тоже придется запихнуть в фабрику. Мне лень было дополнительно тратить время на продумывание всего этого, потому оставил так). Также реализуем интерфейс IViewUserControl, чтобы можно было задать заголовок окну.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
internal partial class MainUserControl : UserControl, IViewUserControl
{
    private readonly UserContext _userContext;
    private readonly DataService _dataService;
 
    public string? Title => "client main section";
    private int WidhtDataItemControl => flowLayoutPanel.ClientSize.Width - 25;
 
    public MainUserControl(
        UserContext userContext,
        DataService dataService)
    {
        InitializeComponent();
        _userContext = userContext;
        _dataService = dataService;
        _uiNavigation = uiNavigation;
    }
 
    public bool Close() => true;
    public bool Closing() => true;
 
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        userTitleLabel.Text = $"Hello, {_userContext.CurrentUser!.Login}!";
        LoadData();
    }
 
    private void SearchButtonClick(object sender, EventArgs e)
        => LoadData();
    private void AddButtonClick(object sender, EventArgs e)
    { /*TODO: on add*/ }
    private void OnDataEdit(DataItemUserControl _, DataItem dataItem)
    { /*TODO: on edit*/ }
    private void OnDataDelete(DataItemUserControl control, DataItem dataItem)
    {
        _dataService.DeleteData(dataItem.Id);
        flowLayoutPanel.Controls.Remove(control);
        control.Dispose();
    }
 
    private void LoadData()
    {
        var filter = filterTextBox.Text;
        Clear(flowLayoutPanel);
        var data = _dataService.LoadData(filter);
 
        var editMethod = OnDataEdit;
        var deleteMethod = OnDataDelete;
 
        foreach (var item in data)
        {
            var control = new DataItemUserControl(item)
            {
                Width = WidhtDataItemControl
            };
            control.OnDataEdit += editMethod;
            control.OnDataDelete += deleteMethod;
            flowLayoutPanel.Controls.Add(control);
            control.Disposed += (_, __) =>
            {
                control.OnDataEdit -= editMethod;
                control.OnDataDelete -= deleteMethod;
            };
        }
    }
    private void Clear(Control control)
    {
        var olds = new Control[control.Controls.Count];
        control.Controls.CopyTo(olds, 0);
        control.Controls.Clear();
        foreach (IDisposable child in olds)
            child.Dispose();
    }
}

Форма (точнее контрол) редактирования. Также реализуем IViewUserControl, но в нашем случае главное метод Closing — нужно выполнить проверку, что данные (не) были изменены, и соответственно вызвать уведомление. В остальном всё просто: распихиваем данные (при наличии) по нужным контролам, подвязываем обработку «сохранить» и «назад». Валидация и прочие проверки по вкусу.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
internal partial class EditDataItemUserControl : UserControl, IViewUserControl
{
    private readonly DataService _dataService;
    private readonly ImageService _imageService;
    private DataItem? originalDataItem;
    private bool chageImage;
 
    public string? Title { get; }
 
    public EditDataItemUserControl(
        DataItem? dataItem, 
        DataService dataService,
        ImageService imageService)
    {
        InitializeComponent();
        _dataService = dataService;
        _imageService = imageService;
        SetData(dataItem);
        Title = dataItem is null
            ? "New record"
            : $"Edit record: {dataItem.Title}";
    }
 
    public bool Close() => true;
    public bool Closing()
    {
        if (HaveChanges())
        {
            if (originalDataItem == null && AllEmptry())
                return true;
 
            switch (this.ShowWarning("Save changes?"))
            {
                case DialogResult.Yes:
                    if (IsValid())
                    {
                        Save();
                        return true;
                    }
                    else
                    { 
                        this.ShowAlert("Title can't be emptry!", "not valid data", MessageBoxIcon.Warning);
                        return false;
                    }
                case DialogResult.No:
                    return true;
                default:
                    return false;
            }
        }
        return true;
    }
 
    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);
        this.AlignByCenter(centralPanel, backgroundImage);
    }
 
    private void EditPictureBoxClick(object sender, EventArgs e)
    {
        var dialog = new OpenFileDialog
        {
            Multiselect = false,
            Filter = "image|*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tif;*.tiff;*.webp"
        };
        if (dialog.ShowDialog(this) == DialogResult.OK)
        {
            try
            {
                var raw = _imageService.Conversion(dialog.FileName);
                this.SetImage(imagePictureBox, raw);
                chageImage = true;
            }
            catch
            {
                this.ShowError("Can't open image or parse image");
            }
        }
    }
    private void SaveButtonClick(object sender, EventArgs e)
    {
        if (IsValid())
            Save(true);
        else
            this.ShowAlert("Title can't be emptry!", "not valid data", MessageBoxIcon.Warning);
    }
    private void BackButtonClick(object sender, EventArgs e)
    { /*TODO: on main*/ }
 
    private void SetData(DataItem? dataItem)
    {
        originalDataItem = dataItem;
        titleTextBox.Text = dataItem?.Title ?? string.Empty;
        dateTimePicker.Value = dataItem?.Date ?? DateTime.Now;
        richTextBox.Text = dataItem?.Text ?? string.Empty;
        this.SetImage(imagePictureBox, dataItem?.Image);
    }
    private void Save(bool notifier = false)
    {
        var id = originalDataItem?.Id;
        var title = titleTextBox.Text;
        var text = richTextBox.Text;
        var date = dateTimePicker.Value;
        var image = (imagePictureBox.DataContext as MemoryStream)?.ToArray();
        originalDataItem = _dataService.SaveData(id, title, date, text, image);
        chageImage = false;
        if (notifier)
            this.ShowAlert("Completed");
    }
    private bool HaveChanges()
        => chageImage
        || originalDataItem is null
        || originalDataItem.Title != titleTextBox.Text
        || originalDataItem.Text != richTextBox.Text
        || originalDataItem.Date != dateTimePicker.Value;
    private bool IsValid()
        => !string.IsNullOrWhiteSpace(titleTextBox.Text);
    private bool AllEmptry()
        => string.IsNullOrWhiteSpace(titleTextBox.Text)
        && string.IsNullOrWhiteSpace(richTextBox.Text)
        && imagePictureBox.Image is null;
}

Фабрика. Как можно заметить — всё написано с подходом Dependency Injection. Можно и без него, но это более «взрослый» подход, о котором нужно читать отдельную лекцию (не в данном разборе). Просто смиритесь что «так надо». По всему приложение есть два основных сервиса, которые используются постоянно: UserContext и DataService (далее ещё добавится UINavigation). Зашьем прям в нашей фабрике эти инстанцы, причем ленивой инициализацией. И надобавляем методов инициализации наших UI компонентов.

Предположим что редактирование записей происходит не часто, поэтому в инициализации EditDataItemUserControl сервис ImageService будет создавать каждый раз по новой.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
internal class UIFactory
{
    private readonly Lazy<UserContext> _globalUserContext;
    private readonly Lazy<DataService> _globalDataService;
    private readonly Lazy<MainForm> _mainForm;
 
    public UIFactory()
    {
        _globalUserContext = new(() => new());
        _globalDataService = new(() => new(_globalUserContext.Value));
        _mainForm = new(() =>
        {
            var mainForm = new MainForm
            {
                Size = new Size(1400, 720),
                StartPosition = FormStartPosition.CenterScreen,
                Icon = Resources.app
            };
            _globalUINavigation.Value.RegistrationMainForm(mainForm);
            return mainForm;
        });
    }
 
    public MainForm MainForm()
        => _mainForm.Value;
    public AuthUserControl AuthUserControl()
        => new(_globalUserContext.Value, _globalDataService.Valu);
    public MainUserControl MainUserControl()
        => new(_globalUserContext.Value, _globalDataService.Value);
    public EditDataItemUserControl EditUserControl(DataItem? dataItem)
        => new(dataItem, _globalDataService.Value, new());
}

Навигация. Подход прост (смотрим метод SwitchUserControl):

  • инициализируем через фабрику нужный контрол
  • если старый контрол реализует IViewUserControl — пинаем Closing и проверяем что отработало.
  • заменяем контрол на MainForm, старый — Dispose
  • опционально выравниваем размер окна по минимальным значениям и проставляем заголовок
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
internal class UINavigation(UIFactory uiFactory)
{
    private readonly UIFactory _uiFactory = uiFactory;
    private MainForm mainForm = default!;
 
    public void RegistrationMainForm(MainForm form)
        => mainForm = form;
    public void OnMain()
        => SwitchUserControl(_uiFactory.MainUserControl);
    public void OnAuthorization()
        => SwitchUserControl(_uiFactory.AuthUserControl);
    public void OnEdit(DataItem? dataItem = null)
        => SwitchUserControl(() => _uiFactory.EditUserControl(dataItem));
 
    private void CheckRegMainForm()
    {
        if (mainForm is null)
            throw new Exception("Main form not registration!");
    }
    private void SwitchUserControl(Func<UserControl> userControlFactory)
    {
        CheckRegMainForm();
        if (CloseCurrentUserControl())
        { 
            var userControl = userControlFactory();
            var old = mainForm.View;
            userControl.Dock = DockStyle.Fill;
            var delta = mainForm.Size - mainForm.ClientSize;
            mainForm.ClientSize = new Size(
                Math.Max(userControl.MinimumSize.Width, mainForm.ClientSize.Width),
                Math.Max(userControl.MinimumSize.Height, mainForm.ClientSize.Height));
            mainForm.MinimumSize = userControl.MinimumSize + delta;
            mainForm.Text = (userControl as IViewUserControl)?.Title ?? string.Empty;
            mainForm.View = userControl;
            old?.Dispose();
        }
    }
    private bool CloseCurrentUserControl()
        => mainForm.View is IViewUserControl view
            ? view.Closing() && view.Close()
            : true;
}

Далее добавляем в саму фабрику также инициализацию и для UINavigation (может немного хрустнуть в мозге от этой рекурсии в конструкторах, но изначальная у нас именно фабрика).

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal class UIFactory
{
    //... other fields
    private readonly Lazy<UINavigation> _globalUINavigation;
 
    public UIFactory()
    {
        //... 
        _globalUINavigation = new(() => new(this));
    }
 
    public UINavigation UINavigation()
        => _globalUINavigation.Value;
}

Распихиваем навигацию по конструкторам UI элементов и заменяем TODO: %name control% на нужные методы. Остается только подправить старт всего приложения, и готово.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
internal static class Program
{
    [STAThread]
    public static void Main()
    {
        ApplicationConfiguration.Initialize();
        var mainForm = Init();
        Application.Run(mainForm);
    }
 
    private static MainForm Init()
    {
        var factory = new UIFactory();
        var uiNavigation = factory.UINavigation();
        var mainForm = factory.MainForm();
        uiNavigation.OnAuthorization();
        return mainForm;
    }
}

Финальный код прилагается Nya.UserControlNavigationExample.zip

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • C windows system32 drivers etc hosts infected
  • Windows xp sp3 какого года
  • Что делать если микрофон на наушниках не работает на windows 10
  • A problem has been detected and windows has been shut down что делать 0x0000007e
  • Зачем надо активировать windows 10 на пк