"Soak new vegetables in the old jar":SOD MVVM framework, making Winforms glow with the new year

"Soak new vegetables in the old jar":SOD MVVM framework, making Winforms glow with the new year

Due to the necessity of MVVM technology on WinForms, I found that it is not difficult to implement the MVVM framework. The key lies in the two-way binding of the model and the view, that is, changes in the model cause changes in the view content, and changes in the view can also cause changes in the model.

最后更新 11/23/2021 9:13 PM
用户1177503
预计阅读 19 分钟
分类
Winform
专题
Winform control library
标签
.NET Winform MVVM Winform open source project open source

1. Hot MVVM framework

One of the most popular technologies in recent years has been front-end technology. Various front-end frameworks, front-end standards and front-end design styles have emerged one after another. Among the many front-end frameworks, frameworks with MVC and MVVM functions have become dazzling stars. For example, GitHub has attracted a lot of attention. Because it is a Chinese work, its design style and document friendliness are better for Chinese people, so I also recommend it to the company for adoption. The reason why I recommend it is because of its excellent MVVM function. Compared with jQuery and others, it saves code, more conforms to the appetite of back-end programmers, and is more conducive to the division of labor between UI designers and programmers.

The following is a schematic diagram of Vue.js implementing MVVM functions:

Are the advantages of the Vue.js framework familiar? That's right, this is the MVVM technology that was popular in WPF in the early years. Compared with WinForms technology, WPF can provide UI designers with more powerful design capabilities and create a brighter and better looking interface. It's just that many MS technologies are always very advanced and the technology is updated quickly. When WPF was newly launched, WinForms still occupied the main field of desktop development. Later, before it became popular, the era of mobile development has arrived. Web-based front-end technology has developed greatly, thus overtaking WPF, but the MVVM idea introduced by WPF has been carried forward in the Web front-end. Now various MVVM-based front-end frameworks are mushrooming.

2. MVVM requirements on WinForms

With the vigorous development of Web front-end technology, various cross-platform HTML5-based mobile front-end development technologies have gradually matured, and various applications have gradually transformed from traditional C/S to B/S ,APP model. Front-end technologies based on C/S model such as WPF have gradually declined, so MVVM on WPF is not widely used. At present, many legacy or new C/S systems still use WinForms technology to develop and maintain. However, there is no good MVVM framework on WinForms. The UI effect, overall development quality and development efficiency of WinForms have not been effectively improved. It is technically difficult to transition to a different development style such as WPF development. Therefore, if there is an MVVM framework that can be used on WinForms, it will undoubtedly be a boon to the majority of back-end. NET programmers.

I have always been a struggling. NET developer and architect. I have extensive involvement in Web and desktop and back-end development technologies. I deeply understand the psychology of developers laughing at themselves as "code farmers". I work hard and have no time to accompany my girlfriend and family. Therefore, I have been summarizing and sorting out ways to improve development efficiency and improve development quality. After nearly 10 years, I have developed and improved a set of development frameworks-the SOD Framework. Recently, I have studied the technology to improve Web front-end development. The MVVM idea of the Vue.js framework once again made me feel the necessity of MVVM technology on WinForms. I found that it is not difficult to implement the MVVM framework. The key lies in the two-way binding of the model and the view, that is, changes in the model cause changes in the view content, and changes in the view can also cause changes in the model.

3. Implementation principle of SOD WinForms MVVM

To implement this change, the bound party must have a property change notification function. When the binding party changes, the bound party must be notified to do the corresponding processing. In. NET, the interface to implement this notification function is:

INotifyPropertyChanged

It is defined in System.dll and has been supported since. NET 2.0. The following is the specific definition of the interface:

namespace System.ComponentModel
{
    // 摘要:
    //     向客户端发出某一属性值已更改的通知。
    public interface INotifyPropertyChanged
    {
        // 摘要:
        //     在更改属性值时发生。
        event PropertyChangedEventHandler PropertyChanged;
    }
}

EntityBase, the entity class base class of the SOD framework, implements this interface:

public abstract class EntityBase : INotifyPropertyChanged, ICloneable, PWMIS.Common.IEntity
{
    /// <summary>
    /// 属性改变事件
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// 触发属性改变事件
    /// </summary>
    /// <param name="propertyFieldName">属性改变事件对象</param>
    protected virtual void OnPropertyChanged(string propertyFieldName)
    {
        if (this.PropertyChanged != null)
        {
            string currPropName = EntityFieldsCache.Item(this.GetType()).GetPropertyName(propertyFieldName);
            this.PropertyChanged(this, new PropertyChangedEventArgs(currPropName));
        }
    }
    // 其他代码略… …
}

Therefore, the entity classes of the SOD framework can be directly used as Models on MVVM to provide View as bound objects. Therefore, if we only need to solve how to implement binding operations for View elements in WinForms form, our WinForms application can realize MVVM functionality. On WinForms, basically all controls have implemented binding functions, which are the DataBindings of the control. You can add binding to it, for example, the following example:

this.textbox1.DataBindings.Add("Text", userEntity, "Name");

This way, when the content entered in the text frame changes, the value of the userEntity.Name property of the entity class object will also change. If userEntity is an SOD entity class and userEntity.Name changes, the Text property of the text box will also change simultaneously.

The data controls (WinForms,WebForms) of the SOD framework all implement the IDataControl interface, which defines several important properties, LinkObject, LinkProperty:

/// <summary>
/// 数据映射控件接口
/// </summary>
public interface IDataControl
{

    /// <summary>
    /// 与数据库数据项相关联的数据
    /// </summary>
    string LinkProperty
    {
        get;
        set;
    }

    /// <summary>
    /// 与数据关联的表名
    /// </summary>
    string LinkObject
    {
        get;
        set;
    }
    // 其他接口方法内容略… …

We can use LinkObject to specify the entity class object to be bound, and LinkProperty to specify the properties of the object to be bound. Therefore, the two-way binding between the WinForms control and the SOD entity class can be achieved through the following code:

public void BindDataControls(Control.ControlCollection controls)
{
    var dataControls = MyWinForm.GetIBControls(controls);
    foreach (IDataControl control in dataControls)
    {
        //control.LinkObject 这里都是 "DataContext"
        object dataSource = GetInstanceByMemberName(control.LinkObject);
        if (control is TextBox)
        {
            ((TextBox)control).DataBindings.Add("Text", dataSource, control.LinkProperty);
        }
        if (control is Label)
        {
            ((Label)control).DataBindings.Add("Text", dataSource, control.LinkProperty);
        }
        if (control is ListBox)
        {
            ((ListBox)control).DataBindings.Add("SelectedValue", dataSource, control.LinkProperty, false, DataSourceUpdateMode.OnPropertyChanged);
        }
    }
}

In addition, we may need to bind some commands to the view, and it is relatively simple to implement this feature:

private Dictionary<object, CommandMethod> dictCommand;
public delegate void CommandMethod();

public void BindCommandControls(Control control,CommandMethod command)
{
    if (control is Button)
    {
        dictCommand.Add(control, command);
        ((Button)control).Click += (sender, e) => {
            dictCommand[sender]();
        };
    }
}

After this process, we only need to write the following lines of code on the form loading event:

SubmitedUsersViewModel DataContext{get;set;}

private void Form1_Load(object sender, EventArgs e)
{
    base.BindDataControls(this.Controls);
    base.BindCommandControls(this.button1, DataContext.SubmitCurrentUsers);
    base.BindCommandControls(this.button2, DataContext.UpdateUser);
    base.BindCommandControls(this.button3, DataContext.RemoveUser);
}

In the above code, a view model object DataContext is first defined, which is used as an object bound to the view control in the method BindDataControls. The Name property of the CurrentUser property in it is bound to the text box control, so CurrentUser.Name is bound as a compound property. For label controls and list box controls, it is a similar process, as shown in the following figure:

In this way, if you do simple data property settings on the view and write a small amount of code behind binding code, a program with two-way binding functions will be fine.

4. MVVM example solution

4.1 solutions overview

In order to demonstrate to you the SOD framework's support for MVVM, we built a simple solution, which is divided into three project assemblies, corresponding to the three major parts of MVVM:

  1. WinFormMvvm: WinForm sample program main program, assembly where the view class is located
  2. WinFormMvvm.Model: Model Class Assembly
  3. WinFormMvvm.ViewModel: View Model Assembly

The diagram of the built solution is as follows:

** Note: This solution is made using SOD Ver 5.5.5.1019, because this is the current version of SOD on nuget, and the latest SOD framework has incorporated the MvvmForm.cs file of the WinFormMvvm project into the framework. **

程序在 App.config 中指定了本次附加测试的数据库,数据库类型为 Access,默认的连接字符串可能要求 Office 2007 以上版本支持。

Here is the content of App.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name ="default" connectionString ="Provider=Microsoft.ACE.OLEDB.12.0;Jet OLEDB:Engine Type=6;Data Source=testdb.accdb" providerName="Access"/>
  </connectionStrings>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="PWMIS.Core" publicKeyToken="17ba13a12b9fd814" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.5.5.1019" newVersion="5.5.5.1019" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

If you need support from a lower version of Access database, or switch to another database (such as SqlServer), please read the information provided by referring to the following steps:

  1. Open the following link:

http://pwmis.codeplex.com/

  1. See the content section "3. Modify the connection configuration of the App.config file";

  2. Click the link "2.2.3 Extended Data Access Class Configuration" under this section.

4.2 Create a WinForm view of MVVM

This is a simple WinForm form form with three SOD "data controls", including: a label control displays the user ID, a text box control displays the user name, a list box control displays the existing user list, and three buttons are used to add, modify and delete data to the list.

For data controls, you can open the "Toolbox" in this form designer interface, select the context menu "Options" in the "General" tab, browse to the packages\PDF.NET.SOD.WinForm.Extensions.5.5.5.1020\lib directory, select "Pwmis.Windows.dll", you can see the SOD data control, and then drag it onto the form.

** Note that we will not directly set click events for these three button controls, but will use command binding. For example, corresponding to the add button, we bind the command as follows (a method of the view model): **

base.BindCommandControls(this.button1, DataContext.SubmitCurrentUsers);

This binds the click event of the button control that adds the user to the SubmitCurrentUsers method of the DataContext.

For the binding of data controls, only the following line of code is needed:

base.BindDataControls(this.Controls);

As mentioned earlier, this method will iterate through all data controls in the first parameter of the method, find the LinkObject and LinkProperty properties, and implement the binding of the data control and the view model object. What is bound here is the Properties of the CurrentUser object of the DataContext object.

Click the "..." button next to the LinkProperty property of the data control in the Property Browser, and the following "Data Control Property Selector" form will pop up:

Since the object we want to bind here is the DataContext object of the current form, we need to browse and select it to the main assembly, so that in the property name column, all properties and sub-properties of this object will be displayed. Note that if the DataContext object does not appear in the list, you need to check whether the Form form declares a DataContext object, and you need to compile the assembly first. Finally, click OK and we set the information to bind to the data control.

4.3 Create a model for MVVM

Our model is very simple, it is responsible for creating new users, loading existing users, adding, modifying or deleting users, and these operations are all targeted at the database, which is our usual CRUD operations. Since it is an example and there is not much logic, we can just look at the code directly:

public class UserModel
{
    private static int index = 0;
    private LocalDbContext context;
    public UserModel()
    {
        context = new LocalDbContext();
    }

    public List<UserEntity> GetAllUsers()
    {
        var list= OQL.From<UserEntity>().ToList(context.CurrentDataBase);
        int max = list.Max(p => p.ID);
        index = ++max;
        return list;
    }
    public void UpdateUser(UserEntity user)
    {
        int count= context.Update<UserEntity>(user);
    }

    public void AddUsers(IList<UserEntity> users)
    {
        int count = context.AddList(users);
    }

    public void SubmitUser(UserEntity user)
    {
        int count = context.Add(user);
    }

    public void RemoveUser(UserEntity user)
    {
        int count = context.Remove(user);
    }

    public UserEntity CreateNewUser(string userName="NoName")
    {
        return new UserEntity()
        {
              ID= ++index,
              Name =userName
        };
    }
}

The user model class uses the user entity class, which is also very simple, with only one ID attribute and one Name attribute. The details are as follows:

public class UserEntity:EntityBase
{
    public UserEntity()
    {
        TableName = "Tb_User";
        PrimaryKeys.Add("UserID");
    }
    public int ID {
        get { return getProperty<int>("UserID"); }
        set { setProperty("UserID", value); }
    }

    public string Name
    {
        get { return getProperty<string>("UserName"); }
        set { setProperty("UserName", value); }
    }

}

Although this user entity class is very simple, it can be directly provided to the view as an element bound to the model, because the SOD entity classes all implement the "attribute modification notification" interface, which has been explained in detail above.

The next step is to manipulate the data context of this user entity class. The user model class shows how to use it, but its definition is simple:

class LocalDbContext : DbContext
{
    public LocalDbContext()
        : base("default")
    {
        //local 是连接字符串名字
    }

    protected override bool CheckAllTableExists()
    {
        //创建用户表
        CheckTableExists<UserEntity>();
        return true;
    }
}

At this point, the entire definition of a simple MVVM model class is complete.

4.4 Create a view model for MVVM

The view model is an abstraction of the view that encapsulates the main view processing logic. Unlike MVP's Presenter, the view model does not contain abstractions of detailed view elements, such as an abstract list control, but rather encapsulates data that may be used by the view, and may include model object calls to the backend MVVM.

In this example, the function of our user view model is also very simple, which is to provide a list of users required by the view and respond to user commands to add, modify, and delete the view. The detailed code is as follows

public class SubmitedUsersViewModel
{

    private UserModel model = new UserModel();
    public BindingList<UserEntity> Users { get; private set; }
    public UserEntity CurrentUser { get; private set; }


    UserEntity _selectUser;
    /// <summary>
    /// 当前选择的用户,如果设置,则会设置当前用户
    /// </summary>
    public UserEntity SelectedUser {
        get { return _selectUser; }
        set {
            _selectUser = value;
            this.CurrentUser.ID = value.ID;
            this.CurrentUser.Name = value.Name;
        }

    }

    int _selectedUserID;
    public int SelectedUserID
    {
        get { return _selectedUserID; }
        set {
            _selectedUserID = value;
            var obj = this.Users.FirstOrDefault(p=>p.ID==value);
            if (obj != null)
            {
                this.CurrentUser.ID = obj.ID;
                this.CurrentUser.Name = obj.Name;
                _selectUser = this.CurrentUser;
            }

        }
    }

    public SubmitedUsersViewModel()
    {
        var data = model.GetAllUsers();
        Users = new BindingList<UserEntity>(data);
        CurrentUser = new UserEntity();

    }

    public void UpdateUser()
    {
        var obj = this.Users.FirstOrDefault(p => p.ID == this.CurrentUser.ID);
        if (obj != null)
        {
            obj.Name = this.CurrentUser.Name;
            //更新后必须调用 ResetBindings 方法,否则控件上的数据会丢失一行
            this.Users.ResetBindings();

            model.UpdateUser(obj);
        }

    }
    public void UpdateUser(int id,string name)
    {
        var obj = this.Users.FirstOrDefault(p => p.ID == id);
        if (obj != null)
        {
            obj.Name = name;
            //更新后必须调用 ResetBindings 方法,否则控件上的数据会丢失一行
            this.Users.ResetBindings();

            model.UpdateUser(obj);
        }
    }

    public void SubmitUsers(UserEntity user)
    {
        //UserEntity newUser = new UserEntity();
        //newUser.ID = user.ID;
        //newUser.Name = user.Name;
        //Users.Add(newUser);
        if (!Users.Contains(user))
        {
            Users.Add(user);
            model.SubmitUser(user);
        }
    }
    public void SubmitCurrentUsers()
    {
        UserEntity newUser = model.CreateNewUser(CurrentUser.Name);
        SubmitUsers(newUser);
        CurrentUser.ID = newUser.ID;
    }

    public void RemoveUser()
    {
        if (SelectedUser == null)
        {

            return;
        }
        var obj = this.Users.FirstOrDefault(p => p.ID == SelectedUser.ID);
        if (obj != null)
        {
            this.Users.Remove(obj);
            //更新后必须调用 ResetBindings 方法,否则控件上的数据会丢失一行
            this.Users.ResetBindings();

            model.RemoveUser(obj);
        }
    }
}

4.5 Add NuGet package references

For the entire solution, we need to add PDF. NET Core packages, but for our WinForms main program, we need to add 2 additional related packages, an SOD WinForm extension and an SOD Access extension. The following is a schematic diagram of all packages installed in the solution:

4.6 run the solution

After the above process, we added the view element, set up the data binding of the view element, and created the model and view model objects. A simple MVVM example program will be fine. The following is the running renderings:

5. Summary of MVVM Models

By running this example, I believe you have experienced some of the features of MVVM, but it may be difficult to describe it properly. It just so happens that after I talked to several senior WPF experts, they summarized several core features (selling points) of MVVM:

  1. Decoupling of view logic (view model) and views (view elements, styles);

  2. Bidirectional data binding of views and view models or models, oriented towards data-driven views rather than view-driven data;

  3. The separation of views and view models fully codes interface functionality and provides TDD possibilities.

6. SOD WinForms MVVM support

Since the release of this Singles Day version of the SOD framework version 5.6.0.1111, you can already get direct WinForms MVVM support in later versions. If it is a previous version, you will need to do a little more work as this example program, but this will not have any impact on your existing SOD-supported solutions.

本示例方案将会放到框架的开源网站 http://pwmis.codeplex.com 上提供直接的下载,并且源码已经全部提交,可以通过下面地址查看详细的代码说明:

http://pwmis.codeplex.com/SourceControl/latest#SOD/Example/WinFormMvvm/WinFormMvvm/Readme.txt

For more information, join the community QQ group discussion, or donate to this framework, please visit the framework official website:

http://www.pwmis.com/sqlmap

Thank you for choosing the SOD framework. I believe it can bring great convenience to your development!

SOD Development Team

Doctor Deep Blue

2016.11.13

------------PS---------------

Thanks to @ Guangzhou-Yingu from the SOD development team, he has updated the nuget package of the SOD framework to the latest version in a timely manner, and there is no longer the nuget package problem mentioned above.

Finally, attach the SOD library warehouse address and screenshot information:

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 2/29/2024

Data display can also be done in Winform

In the process of developing winform, data display functions are often needed. I have been using the gridcontrol before. Today, I want to use an example to introduce to you how to use the table component in ant design blazor hybrid to display data.

继续阅读
同分类 / 同标签 2/29/2024

Can Winform's interface become better?

A few days ago, I introduced to you the use of blazor hybrid in winform, and I also said that with blazor's ui, our winform program design can be more beautiful. Next, I want to use an example of drawing in winform blazor hybrid to illustrate it, hoping to be helpful to you.

继续阅读