I arrived at New Orleans for the AU2002 early and found myself having a bit extra time do deal with something that bothers me: in the article I published a couple of days ago on the topic of binding entity property with WPF modeless UI, I only demonstrated how to control entity's property (Rotation property of a block reference) from UI side. As a better AutoCAD plugin UI design, I should/could have made the binding between the entities and UI work in both ways: the user can control the entity property from the UI, and the UI can also reflect changes of the entity property made by the user in the AutoCAD editor. So, I quickly reworked the previous project.
Here were two things I took into account that happen in AutoCAD editor and ought to be reflected in the modeless UI:
1. The Rotation property of a monitored block reference can be changed by the CAD user in AutoCAD editor (through "Rotate" command, or simply via the block reference's grip). When this happens, the UI should reflect the change;
2. A monitored block reference (e.g. it is selected by the user to show up in the modeless UI) can be erased from AutoCAD editor, either by the user manually, or by other custom command. When this happens, the UI should get rid of the block reference from its monitored block list.
While the code changes were quite easy because the fully workable code in the previous project, I decided to create a brand new DLL project, and copy the code over for refactoring, in which I renamed most classes.
Although quite portions of the code here are the same as the previous project, I post all the code of this updated project here for the benefit of my readers' convenience. Here it goes.
The WPF support code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace BlockRotationDashboard
{
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The CAD operations:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlockRotationDashboard
{
public class CadHelper
{
public static ObjectId SelectBlock()
{
var dwg = Application.DocumentManager.MdiActiveDocument;
var opt = new PromptEntityOptions(
"\nSelect a block:");
opt.SetRejectMessage("\nInvalid selection: not a block!");
opt.AddAllowedClass(typeof(BlockReference), true);
var res = dwg.Editor.GetEntity(opt);
if (res.Status == PromptStatus.OK)
{
return res.ObjectId;
}
else
{
return ObjectId.Null;
}
}
public static void WriteMessage(string msg)
{
var dwg = Application.DocumentManager.MdiActiveDocument;
dwg.Editor.WriteMessage($"{msg}");
}
public static void ZoomToEntity(ObjectId entId)
{
Extents3d ext;
using (var tran =
entId.Database.TransactionManager.StartOpenCloseTransaction())
{
var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);
ext = ent.GeometricExtents;
tran.Commit();
}
ZoomToExtents(ext, 1.0);
}
public static void ZoomToExtents(Extents3d ext, double marginRatio)
{
var w = ext.MaxPoint.X - ext.MinPoint.X;
var h = ext.MaxPoint.Y - ext.MinPoint.Y;
var delta = Math.Max(w, h) * marginRatio;
var minPt = new[]
{
ext.MinPoint.X-delta, ext.MinPoint.Y-delta, ext.MinPoint.Z
};
var maxPt = new[]
{
ext.MaxPoint.X+delta, ext.MaxPoint.Y+delta, ext.MaxPoint.Z
};
dynamic comApp =
Autodesk.AutoCAD.ApplicationServices.Application.AcadApplication;
comApp.ZoomWindow(minPt, maxPt);
}
public static void RotateBlock(ObjectId blkId, int rotation)
{
var dwg = CadApp.DocumentManager.MdiActiveDocument;
if (dwg.Database.FingerprintGuid != blkId.Database.FingerprintGuid)
{
throw new InvalidOperationException(
"Entity is not from current drawing database!");
}
using (dwg.LockDocument())
{
using (var tran =
blkId.Database.TransactionManager.StartTransaction())
{
var blk =
(BlockReference)tran.GetObject(blkId, OpenMode.ForWrite);
blk.Rotation = Math.PI * rotation / 180.0;
tran.Commit();
}
}
CadApp.UpdateScreen();
}
}
}
The "Model" in Model-View-ViewModel" pattern (I highlight the major code changes in red):
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlockRotationDashboard
{
public class MonitoredBlock
{
public ObjectId ObjectId { get; set; } = ObjectId.Null;
public int Rotation { get; set; } = 0;
}
public class MonitoredBlocks : Dictionary<string, List<MonitoredBlock>>
{
public MonitoredBlocks()
{
Application.DocumentManager.DocumentToBeDestroyed += (o, e) =>
{
if (ContainsKey(e.Document.Database.FingerprintGuid))
{
RemoveDatabaseEventHandlers(e.Document.Database);
Remove(e.Document.Database.FingerprintGuid);
}
};
}
public static Action<ObjectId, int> BlockAdded;
public static Action<ObjectId> BlockRemoved;
public static Action<ObjectId, int> BlockRotated;
public static bool ChangedfromUI = false;
public void AddBlock(ObjectId blkId)
{
var db = blkId.Database;
int rotation = 0;
using (var tran = db.TransactionManager.StartOpenCloseTransaction())
{
var blk = tran.GetObject(blkId, OpenMode.ForRead) as BlockReference;
if (blk!=null) rotation= Convert.ToInt32(blk.Rotation * 180 / Math.PI);
}
var key = db.FingerprintGuid;
if (!ContainsKey(key))
{
Add(key, new List<MonitoredBlock>());
HookUpDatabaseEventHandlers(blkId.Database);
}
var mblk = new MonitoredBlock { ObjectId = blkId, Rotation = rotation };
this[key].Add(mblk);
BlockAdded?.Invoke(blkId, rotation);
}
public void RemoveBlock(ObjectId blkId)
{
var key = blkId.Database.FingerprintGuid;
if (ContainsKey(key))
{
var blks = this[key];
foreach (var blk in blks)
{
if (blk.ObjectId == blkId)
{
blks.Remove(blk);
return;
}
}
}
}
public bool IsDoubleSelected(ObjectId blkId)
{
var key = blkId.Database.FingerprintGuid;
if (ContainsKey(key))
{
foreach (var blk in this[key])
{
if (blk.ObjectId == blkId) return true;
}
return false;
}
else
{
return false;
}
}
private void HookUpDatabaseEventHandlers(Database db)
{
db.ObjectModified += Db_ObjectModified;
db.ObjectErased += Db_ObjectErased;
}
private void Db_ObjectErased(object sender, ObjectErasedEventArgs e)
{
var entId = e.DBObject.ObjectId;
if (ContainsKey(entId.Database.FingerprintGuid))
{
var blks = this[entId.Database.FingerprintGuid];
foreach (var blk in blks)
{
if (blk.ObjectId == entId)
{
blks.Remove(blk);
BlockRemoved?.Invoke(entId);
break;
}
}
}
}
private void Db_ObjectModified(object sender, ObjectEventArgs e)
{
if (ChangedfromUI) return;
var entId = e.DBObject.ObjectId;
if (ContainsKey(entId.Database.FingerprintGuid))
{
var blks = this[entId.Database.FingerprintGuid];
foreach (var blk in blks)
{
if (blk.ObjectId==entId)
{
var rotate = Convert.ToInt32(((BlockReference)e.DBObject).Rotation * 180 / Math.PI);
if (rotate != blk.Rotation)
{
blk.Rotation= rotate;
BlockRotated?.Invoke(blk.ObjectId, rotate);
}
break;
}
}
}
}
private void RemoveDatabaseEventHandlers(Database db)
{
try
{
db.ObjectErased -= Db_ObjectErased;
db.ObjectModified -= Db_ObjectModified;
}
catch { }
}
}
}
The "View" in "Model-View-ViewModel" pattern (no major changes made to the 2 views at all, including the very few lines of code-behind remaining unchanged, except for changing the Slider's interval from 30 to 1, so that the UI can control/reflect block's rotation as small as 1 degree):
The UserControl class "BlockMonitorRowView":
<UserControl x:Class="BlockRotationDashboard.BlockMonitorRowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BlockRotationDashboard"
mc:Ignorable="d"
d:DesignHeight="36" d:DesignWidth="650">
<Grid Height="36">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100*"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="Handle:" VerticalAlignment="Center" />
<TextBlock Text="{Binding Handle, Mode=OneTime}" VerticalAlignment="Center" Margin="10,0,0,0"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Text="Rotation:" VerticalAlignment="Center" />
<TextBlock Text="{Binding Rotation, Mode=OneWay}" VerticalAlignment="Center" Margin="10,0,0,0"/>
</StackPanel>
<Slider Grid.Column="2" VerticalAlignment="Center" Minimum="0" Maximum="360"
Interval="1" IsSnapToTickEnabled="True" Value="{Binding Rotation}"
TickPlacement="Both" LargeChange="1" SmallChange="1" TickFrequency="1"/>
<Button Grid.Column="3" Content="Zoom" Width="60" Height="22"
VerticalAlignment="Center" HorizontalAlignment="Right"
Command="{Binding ZoomCommand}" CommandParameter="{Binding BlockId}"/>
<Button Grid.Column="4" Content="Remove" Width="60" Height="22"
VerticalAlignment="Center" HorizontalAlignment="Right"
Command="{Binding RemoveCommand}" CommandParameter="{Binding BlockId}"/>
</Grid>
</UserControl>
The Window class "BlockMonitorView":
<Window x:Class="BlockRotationDashboard.BlockMonitorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BlockRotationDashboard"
mc:Ignorable="d" d:DesignWidth="800"
Closing="TheWindow_Closing"
x:Name="TheWindow"
Width="660" MinHeight="250"
SizeToContent="Height" ShowInTaskbar="False" ResizeMode="NoResize"
Title="Block Rotation Dashboard" Height="225">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="150"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="5,0,5,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="Monitored Blocks:" VerticalAlignment="Center"/>
<TextBlock Text="{Binding BlockCount}" VerticalAlignment="Center"
FontWeight="DemiBold" Foreground="Blue" Margin="10,0,0,0"/>
</StackPanel>
<Button Grid.Column="1" Content="Add Block >" Width="80" Height="22"
VerticalAlignment="Center" HorizontalAlignment="Right"
Command="{Binding AddBlockCommand}"
CommandParameter="{Binding ElementName=TheWindow}"/>
</Grid>
<ListBox Grid.Row="1" Margin="5,5,5,5"
ItemsSource="{Binding Blocks}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<local:BlockMonitorRowView DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
This is the window's code-behind:
using System.Windows;
namespace BlockRotationDashboard
{
/// <summary>
/// Interaction logic for BlockMonitorView.xaml
/// </summary>
public partial class BlockMonitorView : Window
{
public BlockMonitorView()
{
InitializeComponent();
}
public BlockMonitorView(BlockMonitorViewModel viewModel) : this()
{
Loaded += (o, e) =>
{
DataContext = viewModel;
};
}
private void TheWindow_Closing(
object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
Visibility = Visibility.Hidden;
}
}
}
The "ViewModel" of the "Model-View-ViewModel" pattern:
Firstly, the "BlockMonitorRowViewModel" class, which is the same as the one used in previous project, except for being renamed:
using Autodesk.AutoCAD.DatabaseServices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlockRotationDashboard
{
public class BlockMonitorRowViewModel : ViewModelBase
{
private int _rotation = 0;
private ObjectId _blkId = ObjectId.Null;
public BlockMonitorRowViewModel(ObjectId blkId, int rotation)
{
_blkId = blkId;
_rotation = rotation;
RemoveCommand = new RelayCommand(
RemoveBlock, (o) => { return true; });
ZoomCommand = new RelayCommand(
ZoomToBlock, (o) => { return true; });
}
public string Handle => _blkId.Handle.ToString();
public ObjectId BlockId => _blkId;
public int Rotation
{
get => _rotation;
set
{
_rotation = value;
OnPropertyChanged("Rotation");
RotateBlockAction?.Invoke(_blkId, _rotation);
}
}
public static Action<ObjectId> RemoveBlockAction;
public static Action<ObjectId> ZoomAction;
public static Action<ObjectId, int> RotateBlockAction;
public RelayCommand RemoveCommand { private set; get; }
public RelayCommand ZoomCommand { private set; get; }
private void RemoveBlock(object cmdParam)
{
RemoveBlockAction?.Invoke(_blkId);
}
private void ZoomToBlock(object cmdParam)
{
ZoomAction?.Invoke(_blkId);
}
}
}
The "BlockMonitorViewModel" class, in which I added some comments at changed code lines:
using Autodesk.AutoCAD.DatabaseServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BlockRotationDashboard
{
public class BlockMonitorViewModel : ViewModelBase
{
private MonitoredBlocks _monitoredBlocks;
private ObservableCollection<BlockMonitorRowViewModel> _blocks;
private string _currentDbGuid = "";
public BlockMonitorViewModel(MonitoredBlocks monitoredBlocks)
{
_monitoredBlocks = monitoredBlocks;
_blocks = new ObservableCollection<BlockMonitorRowViewModel>();
AddBlockCommand = new RelayCommand(
AddBlockToDashboard, (o) => { return true; });
MonitoredBlocks.BlockAdded = OnBlockAdded;
// The actions to be triggered when monitored blocks
// is rotated or erased in AutoCAD editor
MonitoredBlocks.BlockRemoved = OnBlockRemoved;
MonitoredBlocks.BlockRotated = OnBlockRotated;
BlockMonitorRowViewModel.RemoveBlockAction = OnBlockRemove;
BlockMonitorRowViewModel.ZoomAction = OnBlockZoom;
BlockMonitorRowViewModel.RotateBlockAction = OnBlockRotate;
_currentDbGuid =
CadApp.DocumentManager.MdiActiveDocument.Database.FingerprintGuid;
ResetView();
CadApp.DocumentManager.DocumentActivated += (o, e) =>
{
if (e.Document.Database.FingerprintGuid != _currentDbGuid)
{
_currentDbGuid = e.Document.Database.FingerprintGuid;
ResetView();
}
};
}
public ObservableCollection<BlockMonitorRowViewModel> Blocks => _blocks;
public int BlockCount => _blocks.Count;
public RelayCommand AddBlockCommand { private set; get; }
#region private methods
private void ResetView()
{
_blocks.Clear();
if (_monitoredBlocks.ContainsKey(_currentDbGuid))
{
var blks = _monitoredBlocks[_currentDbGuid];
foreach (var blk in blks)
{
_blocks.Add(new BlockMonitorRowViewModel(blk.ObjectId, blk.Rotation));
}
}
OnPropertyChanged("BlockCount");
}
private void AddBlockToDashboard(object cmdParam)
{
var window = cmdParam as System.Windows.Window;
var blkId = ObjectId.Null;
try
{
if (window != null) window.Visibility = System.Windows.Visibility.Hidden;
CadApp.MainWindow.Focus();
while (true)
{
blkId = CadHelper.SelectBlock();
if (!blkId.IsNull)
{
if (_monitoredBlocks.IsDoubleSelected(blkId))
{
CadHelper.WriteMessage(
"\nThe block has already been listed in rotation dashboard!");
}
else
{
break;
}
}
else
{
break;
}
}
}
finally
{
if (window != null) window.Visibility =
System.Windows.Visibility.Visible;
}
if (blkId == ObjectId.Null) return;
_monitoredBlocks.AddBlock(blkId);
}
private void OnBlockAdded(ObjectId blkId, int rotation)
{
_blocks.Add(new BlockMonitorRowViewModel(blkId, rotation));
OnPropertyChanged("BlockCount");
}
// This method gets called when block reference in drawing is erased
private void OnBlockRemoved(ObjectId blkId)
{
foreach (var blk in _blocks)
{
if (blk.BlockId == blkId)
{
_blocks.Remove(blk);
break;
}
}
OnPropertyChanged("BlockCount");
}
// This method gets called when user clicks "remove" button in the UI
// which will stop the block's rotation being monitored
private void OnBlockRemove(ObjectId blkId)
{
_monitoredBlocks.RemoveBlock(blkId);
foreach (var blk in _blocks)
{
if (blk.BlockId == blkId)
{
_blocks.Remove(blk);
break;
}
}
OnPropertyChanged("BlockCount");
}
private void OnBlockZoom(ObjectId blkId)
{
CadHelper.ZoomToEntity(blkId);
}
// this method gets called when the monitored block is rotated in
// AutoCAD editor
private void OnBlockRotated(ObjectId blkId, int rotation)
{
foreach (var row in _blocks)
{
if (row.BlockId==blkId)
{
row.Rotation = rotation;
break;
}
}
}
// This method gets called when user changes Slider's value in the UI
private void OnBlockRotate(ObjectId bkId, int rotation)
{
try
{
MonitoredBlocks.ChangedfromUI = true;
CadHelper.RotateBlock(bkId, rotation);
}
finally
{
MonitoredBlocks.ChangedfromUI = false;
}
}
#endregion
}
}
Then finally the CommandClass/Method, which is the same as the previous project:
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
[assembly: CommandClass(typeof(BlockRotationDashboard.MyCommands))]
namespace BlockRotationDashboard
{
public class MyCommands
{
private static MonitoredBlocks _blocks = null;
private static BlockMonitorView _view = null;
private static BlockMonitorViewModel _viewModel = null;
[CommandMethod("MonitorBlockRotation", CommandFlags.Session)]
public static void MonitorBlocks()
{
var dwg = CadApp.DocumentManager.MdiActiveDocument;
var ed = dwg.Editor;
if (_blocks == null)
{
_blocks = new MonitoredBlocks();
}
if (_view == null)
{
_viewModel = new BlockMonitorViewModel(_blocks);
_view = new BlockMonitorView(_viewModel);
CadApp.ShowModelessWindow(_view);
}
else
{
_view.Visibility = System.Windows.Visibility.Visible;
}
}
}
}
Thanks to the use of MVVM pattern, the changed "business requirement" of monitoring the blocks' change in AutoCAD editor and reflecting the changes in the modeless UI is rather easy: the UI code (XAML code and the bit of code-behind) remains unchanged at all; so is the code for AutoCAD operation in class "CadHelper". I only updated the "Model" (class MonitoredBlocks) to have it hook up with Database_ObjectModified/ObjectErase events, so that when monitored change happens, the "Model" notifies the "ViewModel" and then the "View" gets updated.
If a Win Form is used, it is likely the UI's code-behind has to be updated, unless certain design pattern is used, such as model-view-controller/presenter that are suitable to Win form. I used such pattern with Win Form before and they are no match to MVVM pattern for WPF.
See the video clip showing the updated action:
The source code can be downloaded here.
Bug Fixing Update - 2022-09-29
In the
discussion thread which inspired me for this article and the
previous article, one reader (thank you,
Genésio Hanauer!) pointed out that Database.FingerpointGuid is not unique if the drawings opened in the AutoCAD session are originated from the same drawing (such as they are created from the same template drawing), thus using Database.FingerprintGuid as the key for the Dictionary to store data model in order to segregate data from drawings could result error. So, I updated the class
MonitoredBlocks to use
Database.UnmanagedObject, which is the type of
IntPtr, as the Dictionary's key. Here is the updated code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using System;
using System.Collections.Generic;
namespace BlockRotationDashboard
{
public class MonitoredBlock
{
public ObjectId ObjectId { get; set; } = ObjectId.Null;
public int Rotation { get; set; } = 0;
}
public class MonitoredBlocks : Dictionary<IntPtr, List<MonitoredBlock>>
{
public MonitoredBlocks()
{
Application.DocumentManager.DocumentToBeDestroyed += (o, e) =>
{
if (ContainsKey(e.Document.Database.UnmanagedObject))
{
RemoveDatabaseEventHandlers(e.Document.Database);
Remove(e.Document.Database.UnmanagedObject);
}
};
}
public static Action<ObjectId, int> BlockAdded;
public static Action<ObjectId> BlockRemoved;
public static Action<ObjectId, int> BlockRotated;
public static bool ChangedfromUI = false;
public void AddBlock(ObjectId blkId)
{
var db = blkId.Database;
int rotation = 0;
using (var tran = db.TransactionManager.StartOpenCloseTransaction())
{
var blk = tran.GetObject(blkId, OpenMode.ForRead) as BlockReference;
if (blk!=null) rotation= Convert.ToInt32(blk.Rotation * 180 / Math.PI);
}
var key = db.UnmanagedObject;
if (!ContainsKey(key))
{
Add(key, new List<MonitoredBlock>());
HookUpDatabaseEventHandlers(blkId.Database);
}
var mblk = new MonitoredBlock { ObjectId = blkId, Rotation = rotation };
this[key].Add(mblk);
BlockAdded?.Invoke(blkId, rotation);
}
public void RemoveBlock(ObjectId blkId)
{
var key = blkId.Database.UnmanagedObject;
if (ContainsKey(key))
{
var blks = this[key];
foreach (var blk in blks)
{
if (blk.ObjectId == blkId)
{
blks.Remove(blk);
return;
}
}
}
}
public bool IsDoubleSelected(
ObjectId blkId)
{
var key = blkId.Database.UnmanagedObject;
if (ContainsKey(key))
{
foreach (var blk in this[key])
{
if (blk.ObjectId == blkId) return true;
}
return false;
}
else
{
return false;
}
}
private void HookUpDatabaseEventHandlers(Database db)
{
db.ObjectModified += Db_ObjectModified;
db.ObjectErased += Db_ObjectErased;
}
private void Db_ObjectErased(object sender, ObjectErasedEventArgs e)
{
var entId = e.DBObject.ObjectId;
if (ContainsKey(entId.Database.UnmanagedObject))
{
var blks = this[entId.Database.UnmanagedObject];
foreach (var blk in blks)
{
if (blk.ObjectId == entId)
{
blks.Remove(blk);
BlockRemoved?.Invoke(entId);
break;
}
}
}
}
private void Db_ObjectModified(object sender, ObjectEventArgs e)
{
if (ChangedfromUI) return;
var entId = e.DBObject.ObjectId;
if (ContainsKey(entId.Database.UnmanagedObject))
{
var blks = this[entId.Database.UnmanagedObject];
foreach (var blk in blks)
{
if (blk.ObjectId==entId)
{
var rotate = Convert.ToInt32(((BlockReference)e.DBObject).Rotation * 180 / Math.PI);
if (rotate != blk.Rotation)
{
blk.Rotation= rotate;
BlockRotated?.Invoke(blk.ObjectId, rotate);
}
break;
}
}
}
}
private void RemoveDatabaseEventHandlers(Database db)
{
try
{
db.ObjectErased -= Db_ObjectErased;
db.ObjectModified -= Db_ObjectModified;
}
catch { }
}
}
}
I have also updated the source that can be downloaded in the link above.