MVVM(Model-View-ViewModel)是一种软件架构模式,常用于构建客户端应用程序,特别是在WPF(Windows Presentation Foundation)和Silverlight应用中,它将应用程序分为三个主要部分:
一、项目结构
1. Model(模型)
表示应用程序的数据和业务逻辑。它包含数据对象和处理这些数据的业务规则,与用户界面无关。
2. View(视图)
负责用户界面的呈现,如WPF中的窗口、页面等。它只关注如何展示数据,不包含业务逻辑。
3.ViewModel(视图模型)
:作为View和Model之间的桥梁。它从Model获取数据并进行处理,然后提供给View使用,同时也处理View的用户交互事件并更新Model。
二、MVVM的逻辑结构:
View和ViewModel之间通过数据绑定(Data Binding)进行通信。View可以自动反映ViewModel中数据的变化,而ViewModel可以接收View的用户输入。
ViewModel和Model之间通过方法调用进行交互。ViewModel调用Model的方法来获取或更新数据。
具体细节和实现步骤
1. 创建Model:定义数据类和业务逻辑类。
2. 创建ViewModel:继承自 INotifyPropertyChanged 接口,以便在属性值变化时通知View。在ViewModel中定义属性和命令(Commands)。
3. 创建View:在XAML中进行数据绑定,将View的控件属性绑定到ViewModel的属性上。
4. 设置DataContext:在View的代码隐藏文件中,将View的 DataContext 设置为ViewModel的实例。
ViewModel是什么:
ViewModel是MVVM架构中的核心部分,它包含了View所需的数据和行为。它将业务逻辑从View中分离出来,使得View只关注用户界面的呈现,而ViewModel负责处理数据和用户交互。
在CAD中应用MVVM的简单案例代码和注释:
假设我们有一个简单的CAD应用,需要在视图中显示一个矩形的位置和大小。
效果如下:
1. Model类 (RectangleModel.cs)
2. ViewModel类 (RectangleViewModel.cs)
// RectangleViewModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using CadMvvmExample;
using WpfCircleGenerator.ViewModels;
namespace CadMvvmExample
{
/// <summary>
/// 矩形视图模型
/// 处理业务逻辑和数据绑定
/// </summary>
public class RectangleViewModel : INotifyPropertyChanged
{
private readonly RectangleModel _model = new()
{
X = 10,
Y = 10,
Width = 100,
Height = 50
};
public double X
{
get => _model.X;
set { _model.X = value; OnPropertyChanged(); }
}
public double Y
{
get => _model.Y;
set { _model.Y = value; OnPropertyChanged(); }
}
public double Width
{
get => _model.Width;
set
{
if (value <= 0) throw new ArgumentException("宽度必须大于0");
_model.Width = value;
OnPropertyChanged();
}
}
public double Height
{
get => _model.Height;
set
{
if (value <= 0) throw new ArgumentException("高度必须大于0");
_model.Height = value;
OnPropertyChanged();
}
}
// 生成矩形的命令
public ICommand GenerateRectangleCommand { get; }
public RectangleViewModel()
{
GenerateRectangleCommand = new RelayCommand(GenerateRectangle);
}
private void GenerateRectangle()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
using (doc.LockDocument())//锁文档
using (Transaction tr = db.TransactionManager.StartTransaction())
{
try
{
BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
// 根据输入的参数创建矩形
Point3d p1 = new Point3d(X, Y, 0);
Point3d p2 = new Point3d(X + Width, Y, 0);
Point3d p3 = new Point3d(X + Width, Y + Height, 0);
Point3d p4 = new Point3d(X, Y + Height, 0);
Polyline rect = new Polyline(4);
rect.AddVertexAt(0, new Point2d(p1.X, p1.Y), 0, 0, 0);
rect.AddVertexAt(1, new Point2d(p2.X, p2.Y), 0, 0, 0);
rect.AddVertexAt(2, new Point2d(p3.X, p3.Y), 0, 0, 0);
rect.AddVertexAt(3, new Point2d(p4.X, p4.Y), 0, 0, 0);
rect.Closed = true;
// 将矩形添加到模型空间
btr.AppendEntity(rect);
tr.AddNewlyCreatedDBObject(rect, true);
tr.Commit();
ed.WriteMessage("\n矩形已生成。\n");
ed.ZoomExtentsX(rect); // 缩放到矩形范围
}
catch (Exception ex)
{
ed.WriteMessage($"\n生成矩形时出错: {ex.Message}");
tr.Abort();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// 简单的命令实现类
public class RelayCommand : ICommand
{
private readonly Action _execute;
public RelayCommand(Action execute)
{
_execute = execute;
}
public bool CanExecute(object? parameter)
{
return true;
}
public event EventHandler? CanExecuteChanged;
public void Execute(object? parameter)
{
_execute();
}
}
/// <summary>
/// 矩形数据模型
/// 纯粹的数据实体,不包含任何逻辑
/// </summary>
public class RectangleModel
{
/// <summary>矩形左上角X坐标</summary>
public double X { get; set; }
/// <summary>矩形左上角Y坐标</summary>
public double Y { get; set; }
/// <summary>矩形宽度(必须大于0)</summary>
public double Width { get; set; }
/// <summary>矩形高度(必须大于0)</summary>
public double Height { get; set; }
}
}
3. View类 (MainWindow.xaml.cs)
// MainWindow.xaml.cs
using System.Windows;
using CadMvvmExample;
namespace CadMvvmExample
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// 备用设置DataContext的方式(如果不在XAML中设置)
// this.DataContext = new RectangleViewModel();
}
}
}
.XAML视图 (MainWindow.xaml)
<!-- MainWindow.xaml -->
<Window x:Class="CadMvvmExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:CadMvvmExample"
mc:Ignorable="d"
Title="CAD矩形绘制工具(作者@山水)"
Height="200"
Width="350"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<!-- 显式设置ViewModel实例 -->
<vm:RectangleViewModel />
</Window.DataContext>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- X坐标输入 -->
<TextBlock Grid.Row="0" Grid.Column="0"
Text="X坐标:"
Margin="0,0,10,5"
VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding X, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
Margin="0,0,0,5"/>
<!-- Y坐标输入 -->
<TextBlock Grid.Row="1" Grid.Column="0"
Text="Y坐标:"
Margin="0,0,10,5"
VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding Y, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
Margin="0,0,0,5"/>
<!-- 宽度输入 -->
<TextBlock Grid.Row="2" Grid.Column="0"
Text="宽度:"
Margin="0,0,10,5"
VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding Width, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
Margin="0,0,0,5"/>
<!-- 高度输入 -->
<TextBlock Grid.Row="3" Grid.Column="0"
Text="高度:"
Margin="0,0,10,5"
VerticalAlignment="Center"/>
<TextBox Grid.Row="3" Grid.Column="1"
Text="{Binding Height, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
Margin="0,0,0,5"/>
<!-- 绘制按钮 -->
<Button Grid.Row="4" Grid.ColumnSpan="2"
Content="绘制矩形"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,10,0,0"
Padding="15,5"
FontWeight="Bold"
Command="{Binding GenerateRectangleCommand}"/>
</Grid>
</Window>
4.Command类
// Commands.cs
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Windows;
using System.Windows.Forms.Integration;
using WpfCircleGenerator.Views;
[assembly: ExtensionApplication(typeof(CadMvvmExample.MyExtension))]
[assembly: CommandClass(typeof(CadMvvmExample.CadCommands))]
namespace CadMvvmExample
{
public class MyExtension : IExtensionApplication
{
public void Initialize()
{
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n插件已加载!输入\"xx\"运行。(山水qq443440204)\n");
}
public void Terminate() { }
}
public static class Dimensions
{
public const int Pheight = 1300; // 面板高度
public const int Pwidth = 400; // 面板宽度
}
/// <summary>
/// AutoCAD命令定义类
/// </summary>
public class CadCommands
{
private static MainWindow _mainWindow;
/// <summary>
/// 注册"XX"命令 - 弹出矩形参数窗口
/// </summary>
[CommandMethod("XX")]
public static void ShowRectangleWindow()
{
if (_mainWindow == null )
{
// 定义一个具名的事件处理程序
EventHandler idleHandler = null;
idleHandler = (s, e) =>
{
_mainWindow = new MainWindow();
// 设置AutoCAD主窗口为Owner
var interopHelper = new System.Windows.Interop.WindowInteropHelper(_mainWindow);
interopHelper.Owner = Application.MainWindow.Handle;
_mainWindow.Show();
// 单次执行,移除事件处理程序
Application.Idle -= idleHandler;
};
// 为Idle事件添加事件处理程序
Application.Idle += idleHandler;
}
else
{
// 如果窗口已经打开,将其激活并显示在最前面 切换可是状态
_mainWindow.Activate();
/* if (_mainWindow.Visibility == System.Windows.Visibility.Visible)
{
_mainWindow.Hide();
}
else
{
_mainWindow.Show();
}*/
}
}
}
}
通过这个完整的MVVM(Model-View-ViewModel)示例,我们展示了如何将WPF的数据绑定机制与CAD二次开发相结合,实现清晰的分层架构。Model负责数据存储,ViewModel处理业务逻辑并通知UI更新,View则专注于界面呈现,三者各司其职,显著提升了代码的可维护性和可测试性。
三、关键收获
-
解耦与复用:ViewModel完全独立于View,可轻松适配不同界面或平台(如AutoCAD插件或独立WPF应用)。
-
实时响应:通过
INotifyPropertyChanged
和UpdateSourceTrigger=PropertyChanged
,实现了数据的双向实时同步。 -
扩展性强:在此基础上,可进一步集成命令(
ICommand
)、验证逻辑或CAD绘图功能。
四、未来优化方向
-
输入验证:为属性添加范围检查(如宽度/高度不能为负值)。
-
CAD集成:在ViewModel中调用AutoCAD API,将矩形数据直接绘制到模型空间。
-
UI增强:结合
ObservableCollection
实现多矩形管理,并添加可视化操作面板。
MVVM模式不仅适用于WPF应用,在CAD二次开发中同样能大幅提升代码质量。希望本文能为您在复杂项目中应用分层架构提供实践参考。
进一步学习建议:
-
探索
Prism
或MVVM Light
框架简化MVVM实现 -
研究AutoCAD .NET API与WPF的交互最佳实践
-
尝试将此模式扩展到三维实体建模场景