【我们一起写框架】MVVM的WPF框架(四)—DataGrid

简介:

前言
这个框架写到这里,应该有很多同学发现,框架很多地方的细节,其实是违背了MVVM的设计逻辑的。

没错,它的确是违背了。

但为什么明知道违背设计逻辑,还要这样编写框架呢?

那是因为,我们编写的是框架,是使用MVVM的概念编写框架,而并不是要完美的实现MVVM设计。

两者有什么区别呢?区别就是前者是实战,后者只是个理念。

在实战架构中,并不是UI的东西都一定要放在UI层写,逻辑的东西放在逻辑层写的。因为,架构的目的是让程序员更好的写代码,而不是让代码死死的固定在某一层。

所以,我们在编写框架时,设计模式中该切割的东西,就不要犹豫的切割。因为,架构师是设计模式的使用者,而不是被使用者。

举个例子,当你的逻辑全部提取到某一层中以后,你突然发现,该逻辑执行过程中要弹出提示框,但提示框又是属于UI层的,此时你犹豫了,把提示框移动到逻辑层,不符合设计理念,但不在逻辑层做,开发又很难受。

遇到这样的情况,我们该怎么做呢?

很简单,让设计理念去死吧,不要犹豫,直接把弹出提示框封装到逻辑层中即可。

现实中,设计逻辑永远是要向开发逻辑低头的,因为实战永远高于理论。

框架是什么?
框架就是规则,规则在人类社会被称之为法律;换言之,框架是代码界的法律。

人类社会建立法律之初,是抱着人人守法,秩序稳定的理想的。

可现实是残酷的,总有人,因为各种原因,践踏法律。

事实上,代码界也一样,总是会那不守规矩的程序员触犯法律,他们会让代码跨边界引用类库或者拒绝使用接口声明对象等等。

为什么不能准守规则呢?

因为他们想更快速的完成任务,所以他们不惜触犯法律,也要拼一次一夜暴富。。。

所以,架构师作为代码界的人民警察,一定要做好惩治工作。。。

因为,当一个坏代码出现后,马上就会有若干个类似的坏代码出现,犹如劣币逐良币一样,时间一长,框架就会被破坏。

接着好代码就得依赖着坏代码写。

当坏代码多了到一定程度,好代码就会变成Bug了。。。

所以,任重道远,人民警察还需警惕。。。

为什么要编写数据控件
我们之前编写的数据控件功能相对单一;完全可以用属性和事件代替,所以有些同学会觉得,数据控件好像没什么用。

其实不然,现实中我们要处理的逻辑,并不是简单的对象属性一对一绑定就能处理解决的。

我们需要做很多操作,其中也包括UI操作。而数据控件就是用来应对这种复杂的UI操作的。

因为数据控件通过绑定UI控件后,已经将复杂的UI操作,变成了简单的数据逻辑操作了。

如果没有数据控件,那当我们实现一个控件联动时,就得在Xaml.cs文件中处理了。

如果该控件联动还要触发数据变化,那我们就又得从Xaml.cs文件中,穿越回ViewModel中处理逻辑了;亦或者,我们直接在Xaml.cs文件中处理数据逻辑。

不论哪种模式,都会将我们好容易做的逻辑层与UI层混淆到一起。而这个问题,并不是一个弹出框那么简单的UI越界问题,因为它包含了更多复杂的业务逻辑。

数据控件解决这个烦恼。

我们通过数据控件,实现了控件是控件,数据是数据,清晰的,层次分离;并且通过简洁的绑定,实现了数据变化与控件变化同步。

DataGrid数据控件
DataGrid数据控件可以说是数据控件的精髓了,因为DataGrid相对复杂,不像其他的数据控件那样功能单一。

所以,当然我们学习了DataGrid数据控件后,就可以更好的理解,数据控件的意义了。

下面我们先看下DataGrid数据控件的代码:

public class DataGrid<T> : Control<T>
{
    private Action<T> LoadAction = null;
    public Action<T> SelectCallBack = null;
    private Func<object, bool> DataFilter = null;
    
    #region 分页
    private volatile int _CurrentPage = 1;
    public int CurrentPage
    {
        get { return _CurrentPage; }
        set
        {
            _CurrentPage = value;
            if (_CurrentPage > PageCount)
            {
                _CurrentPage = PageCount;
            }
            if (_CurrentPage < 1)
            {
                _CurrentPage = 1;
            }
            OnPropertyChanged();
        }
    }
    private int _PageCount = 1;
    public int PageCount  { get {  return _PageCount;  }  set {  _PageCount = value;   OnPropertyChanged();  }  } 
    private int _RecordCount = 0;
    public int RecordCount
    {
        get { return _RecordCount; }
        set
        {
            _RecordCount = value;
            if (_RecordCount <= SkipNumber)
            {
                PageCount = 1;
            }
            else
            {
                PageCount = int.Parse(Math.Ceiling((double)RecordCount / (double)SkipNumber).ToString());
            }
            if (_CurrentPage > PageCount)
            {
                _CurrentPage = PageCount;
            }
            OnPropertyChanged();
        }
    }
    private int _SkipNumber = 30;
    public int SkipNumber { get { return _SkipNumber; } set { _SkipNumber = value; OnPropertyChanged(); } }
    private TextBox<string> _JumpTextBox = new TextBox<string>();
    public TextBox<string> JumpTextBox
    {
        get { return _JumpTextBox; }
        set { _JumpTextBox = value; OnPropertyChanged(); }
    }
    #region 跳页
    public BaseCommand JumpCommand
    {
        get
        {
            return new BaseCommand(JumpCommand_Executed);
        }
    }
    void JumpCommand_Executed(object send)
    {
        int pagenum = 0;
 
        if (int.TryParse(JumpTextBox.Text, out pagenum))
        {
            if (pagenum <= PageCount && pagenum > 0)
            {
                CurrentPage = pagenum;
 
                if (LoadAction != null)
                {
                    LoadAction(Condition);
                }
            }
            else
            {
                MessageBox.Show("请正确填写跳转页数。", "提示信息");
            }
        }
        else
        {
            MessageBox.Show("请正确填写跳转页数。", "提示信息");
        }
    }
    #endregion
    #region 上一页
    public BaseCommand PreviousCommand
    {
        get
        {
            return new BaseCommand(PreviousCommand_Executed);
        }
    }
    void PreviousCommand_Executed(object send)
    {
        if (CurrentPage > 1)
        {
            CurrentPage -= 1;
            if (LoadAction != null)
            {
                LoadAction(Condition);
            }
        }
        else
        {
            MessageBox.Show("已至首页。", "提示信息");
        }
    }
    #endregion
 
    #region 下一页
    public BaseCommand NextCommand
    {
        get
        {
            return new BaseCommand(NextCommand_Executed);
        }
    }
    void NextCommand_Executed(object send)
    {
        if (CurrentPage < PageCount)
        {
            CurrentPage += 1;
 
            if (LoadAction != null)
            {
                LoadAction(Condition);
            }
        }
        else
        {
            MessageBox.Show("已至末页。", "提示信息");
        }
    }
    #endregion
    #endregion
 
    private ObservableCollection<T> _ItemsSource = new ObservableCollection<T>();
    public ObservableCollection<T> ItemsSource
    {
        get { return _ItemsSource; }
        set
        {
            _ItemsSource = value;
            if (_ItemsSource != null && _ItemsSource.Count > 0 && SelectedItem == null)
            {
                SelectedItem = _ItemsSource.First();
            }
            OnPropertyChanged();
        }
    }
    public void SetItemsSource(List<T> itemSource)
    {
        ItemsSource = new ObservableCollection<T>(itemSource);
    }
    public T _SelectedItem;
    public T SelectedItem
    {
        get { return _SelectedItem; }
        set
        {
            _SelectedItem = value;
            if (SelectCallBack != null)
            {
                SelectCallBack(_SelectedItem);
            }
            OnPropertyChanged();
        }
    }
    private ICollectionView _ItemsSourceView;
    public ICollectionView ItemsSourceView
    {
        get
        {
            _ItemsSourceView = CollectionViewSource.GetDefaultView(_ItemsSource);
            return _ItemsSourceView;
        }
        set
        {
            _ItemsSourceView = value;
            OnPropertyChanged();
        }
    }
    private T _Condition = (T)Activator.CreateInstance(typeof(T));
    public T Condition { get { return _Condition; } set { _Condition = value; OnPropertyChanged(); } }
 
    #region 方法 
    public DataGrid()
    {
    }
    public void BindSource(Action<T> loadAction, T conditionRow = default(T))
    {
        LoadAction = loadAction;
        if (LoadAction != null)
        {
            CurrentPage = 1;
            LoadAction(conditionRow);
        }
    }
    public void BindSource(Action loadAction)
    {
        LoadAction = new Action<T>((obj) => {
            loadAction();
        }); ;
        if (LoadAction != null)
        {
            CurrentPage = 1;
            LoadAction(default(T));
        }
    }
    public void ItemsSourceReBind()
    {
        BindSource(LoadAction);
    }
    public void SelectedItemReBind()
    {
        T newitem = (T)Activator.CreateInstance(typeof(T));
        List<System.Reflection.PropertyInfo> plist = typeof(T).GetProperties().ToList();
 
        foreach (var propertyInfo in plist)
        {
            propertyInfo.SetValue(newitem, propertyInfo.GetValue(SelectedItem));
        }
        SelectedItem = newitem;
    }
    public void SetFilter(Func<object, bool>  dataFilter)
    {
        try
        {
            DataFilter = dataFilter;
            _ItemsSourceView = CollectionViewSource.GetDefaultView(_ItemsSource);
            _ItemsSourceView.Filter = new Predicate<object>(DataFilter);
        }
        catch(Exception ex)
        {
            
        }
    }
    public void Refresh()
    {
        if (_ItemsSourceView == null)
        {
            _ItemsSourceView = CollectionViewSource.GetDefaultView(this.ItemsSource);
        }
        _ItemsSourceView.Refresh();
    }
    #endregion
}

从代码中我们可以看到,DataGrid控件不仅包含了基础属性,还包含了上一页,下一页,刷新,甚至过滤的功能。

下面,我们看下一下DataGrid控件的基础应用。

Xaml页面代码如下:

<DataGrid  Margin="5" FontSize="12" ItemsSource="{Binding TestDataGrid.ItemsSource}" AutoGenerateColumns="True"
                   SelectedItem="{Binding TestDataGrid.SelectedItem}" > </DataGrid>

ViewModel页面代码如下:

public DataGrid<User> TestDataGrid { get; set; }
TestDataProxy proxy = new TestDataProxy();
public VM_PageDataGrid()
{
    TestDataGrid = new DataGrid<User>();
    int currentPage = TestDataGrid.CurrentPage;
    int skipNumber = TestDataGrid.SkipNumber;
    proxy.GeDataGridData(null, currentPage, skipNumber, (list, count, msg) =>
    {
        TestDataGrid.SetItemsSource(list);
        TestDataGrid.RecordCount = count;
    });
 
    TestDataGrid.SelectCallBack = (user) =>
    {
        MessageBox(user.Name);
    };
}

我们可以看到,基础的DataGrid应用很简单,只要设置好绑定,然后将读取的数据赋值给数据控件的ItemSource属性即可。(这里我们使用SetItemSource方法为ItemSource赋值)

然后我们会发现,只要我们操作数据控件的ItemSource,不论是增加数据,删除数据,变更数据,页面都会自动的同步刷新。

DataGrid的中级应用
我们在上面的代码中可以看到,DataGrid数据控件还包含了分页功能。那么如何实现分页功能呢。

很简单,我们只需要在Xaml页面多绑定几个属性即可实现。

Xaml代码如下:

<StackPanel DataContext="{Binding TestDataGrid}" Orientation="Horizontal"  DockPanel.Dock="Bottom" HorizontalAlignment="Right" Margin="0,10,0,0">
    <Button  Content="上一页" Width="60" Command="{Binding PreviousCommand}" Height="20" Margin="20,0,0,0" VerticalAlignment="Top" />
    <Button  Content="下一页" Width="60" Command="{Binding NextCommand}" Height="20" Margin="20,0,0,0" VerticalAlignment="Top" />
    <TextBlock VerticalAlignment="Center" Text="每页" Margin="20,0,0,0"></TextBlock>
    <TextBlock VerticalAlignment="Center" Text="{Binding  SkipNumber}"  Margin="0,0,0,0"></TextBlock>
    <TextBlock VerticalAlignment="Center" Text="条"></TextBlock>
    <TextBlock VerticalAlignment="Center" Text="{Binding CurrentPage}"  Margin="20,0,0,0"></TextBlock>
    <TextBlock VerticalAlignment="Center" Text="/"></TextBlock>
    <TextBlock VerticalAlignment="Center" Text="{Binding PageCount}"></TextBlock>
    <TextBlock VerticalAlignment="Center" Text="总记录数:" Margin="20,0,0,0"></TextBlock>
    <TextBlock VerticalAlignment="Center" Text="{Binding RecordCount}"></TextBlock>
    <TextBox  VerticalAlignment="Center" Width="40" Height="20" Margin="40,0,0,0" Text="{Binding JumpTextBox.Text}" ></TextBox>
    <Button  Content="GO"  Command="{Binding JumpCommand}" Width="40" Height="20" Margin="5,0,0,0" VerticalAlignment="Top" />
</StackPanel>
<GroupBox DockPanel.Dock="Top" Header="DataGrid" Margin="10,0,0,0" >
    <DataGrid  Margin="5" FontSize="12" ItemsSource="{Binding TestDataGrid.ItemsSource}" AutoGenerateColumns="True"
                       SelectedItem="{Binding TestDataGrid.SelectedItem}" >
    </DataGrid>
</GroupBox>

这样我们就实现了分页功能,代码很简单,并且彻底分割了UI和ViewModel。

但是那么复杂的UI,就这样简单的被彻底搞定了吗?

当然是不可能的!UI很复杂,仅仅靠数据控件是无法彻底搞定的。

那么我们应该怎么办呢?

很简单,我们去编写UI控件就好啦。

当然,我们要编写的UI控件不是普通的UI控件,而是配合数据控件应用的UI控件。

这种定制UI控件在功能上与其他自定义控件是一样,但好处就在于,编写方便,易于理解和二次开发。


本篇文章就先讲到这了,下一篇文章我们将一起为框架编写UI控件。

框架代码已经传到Github上了,并且会持续更新。

相关文章:

【我们一起写框架】MVVM的WPF框架(一)—序篇

【我们一起写框架】MVVM的WPF框架(二)—绑定

【我们一起写框架】MVVM的WPF框架(三)—数据控件

To be continued——DataGrid
Github地址:https://github.com/kiba518/KibaFramework


注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!

目录
相关文章
|
1月前
|
设计模式 前端开发 C#
使用 Prism 框架实现导航.NET 6.0 + WPF
使用 Prism 框架实现导航.NET 6.0 + WPF
86 10
|
2月前
|
设计模式 前端开发 C#
WPF 项目中 MVVM模式 的简单例子说明
本文通过WPF项目中的加法操作示例,讲解了MVVM模式的结构和实现方法,包括数据模型、视图、视图模型的创建和数据绑定,以及命令的实现和事件通知机制。
|
3月前
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(10) -- 在DataGrid上直接编辑保存数据
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(10) -- 在DataGrid上直接编辑保存数据
|
3月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
81 0
|
3月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
96 0
|
3月前
|
前端开发 开发者 C#
WPF开发者必读:MVVM模式实战,轻松实现现代桌面应用架构,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,MVVM(Model-View-ViewModel)模式通过分离应用程序的逻辑和界面,提高了代码的可维护性和可扩展性。本文介绍了MVVM模式的三个核心组件:Model(数据模型)、View(用户界面)和ViewModel(处理数据绑定和逻辑),并通过示例代码展示了如何在WPF项目中实现MVVM模式。通过这种方式,开发者可以构建更加高效和可扩展的桌面应用程序。
160 0
|
3月前
|
前端开发 C# 开发者
WPF开发者必读:MVVM模式实战,轻松构建可维护的应用程序,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,MVVM(Model-View-ViewModel)模式通过分离关注点,提高了代码的可维护性和可扩展性。本文详细介绍了MVVM模式的三个核心组件:Model(数据模型)、View(用户界面)和ViewModel(处理数据绑定与逻辑),并通过示例代码展示了如何在WPF项目中实现MVVM模式。通过这种模式,开发者可以更高效地构建桌面应用程序。希望本文能帮助你在WPF开发中更好地应用MVVM模式。
182 0
|
3月前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
166 0
|
3月前
|
开发框架 前端开发 搜索推荐
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作
|
3月前
|
开发框架 前端开发 JavaScript
在WPF应用中实现DataGrid的分组显示,以及嵌套明细展示效果
在WPF应用中实现DataGrid的分组显示,以及嵌套明细展示效果
在WPF应用中实现DataGrid的分组显示,以及嵌套明细展示效果