diff --git a/Melskin/Controls/ColorPicker/ColorPanel.xaml.cs b/Melskin/Controls/ColorPicker/ColorPanel.xaml.cs
index 353a802..3a94b2f 100644
--- a/Melskin/Controls/ColorPicker/ColorPanel.xaml.cs
+++ b/Melskin/Controls/ColorPicker/ColorPanel.xaml.cs
@@ -229,6 +229,8 @@ public class ColorPanel : Control
{
SelectPresetColorCommand = new RelayCommand(ExecuteSelectPresetColor);
Loaded += OnPanelLoaded;
+ // 拦截面板内所有未处理的左键点击事件,防止穿透给其他控件(如DataGrid)导致焦点切换问题
+ this.MouseLeftButtonDown += (s, e) => e.Handled = true;
}
///
@@ -317,8 +319,15 @@ public class ColorPanel : Control
Debug.Assert(colorCanvas != null, nameof(colorCanvas) + " != null");
if (colorCanvas == null) return;
colorCanvas.CaptureMouse();
+ // 先解绑再绑定,防止重复点击时引发的多次订阅问题
+ colorCanvas.MouseMove -= ColorCanvas_MouseMove;
+ colorCanvas.MouseUp -= ColorCanvas_MouseUp;
+
colorCanvas.MouseMove += ColorCanvas_MouseMove;
colorCanvas.MouseUp += ColorCanvas_MouseUp;
+
+ // 标记事件为已处理,防止冒泡触发 DataGrid 的焦点切换
+ //e.Handled = true;
}
private void ColorCanvas_MouseUp(object sender, MouseButtonEventArgs e)
@@ -329,6 +338,7 @@ public class ColorPanel : Control
colorCanvas.ReleaseMouseCapture();
colorCanvas.MouseMove -= ColorCanvas_MouseMove;
colorCanvas.MouseUp -= ColorCanvas_MouseUp;
+ //e.Handled = true;
}
private void ColorCanvas_MouseMove(object sender, MouseEventArgs e)
diff --git a/Melskin/Controls/Expander.xaml b/Melskin/Controls/Expander.xaml
index 54bcdf3..a69c2cf 100644
--- a/Melskin/Controls/Expander.xaml
+++ b/Melskin/Controls/Expander.xaml
@@ -23,6 +23,7 @@
+
+
+
@@ -108,6 +112,7 @@
+
+
+
@@ -179,6 +187,7 @@
+
+
+
@@ -254,6 +266,7 @@
+
+
+
@@ -298,7 +314,7 @@
-
+
+
\ No newline at end of file
diff --git a/Melskin/Controls/SearchableComboBox.cs b/Melskin/Controls/SearchableComboBox.cs
index c7bb7cc..0852f0b 100644
--- a/Melskin/Controls/SearchableComboBox.cs
+++ b/Melskin/Controls/SearchableComboBox.cs
@@ -13,8 +13,8 @@ namespace Melskin.Controls
///
public class SearchableComboBox : ComboBox
{
- private TextBox _editableTextBox;
- private bool _isInternalOperation;
+ private TextBox editableTextBox;
+ private bool isInternalOperation;
static SearchableComboBox()
{
@@ -41,30 +41,30 @@ namespace Melskin.Controls
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
- if (_isInternalOperation) return;
+ if (isInternalOperation) return;
- _isInternalOperation = true;
+ isInternalOperation = true;
base.OnSelectionChanged(e);
// 重点 1:选中项改变后,必须彻底清空过滤器,否则下次打开下拉框只剩下一项
this.Items.Filter = null;
- _isInternalOperation = false;
+ isInternalOperation = false;
}
private void OnEditableTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
- if (_isInternalOperation) return;
+ if (isInternalOperation) return;
- string searchText = _editableTextBox.Text;
+ string searchText = editableTextBox.Text;
string selectedText = GetItemDisplayText(SelectedItem);
// 重点 2:断开锁定逻辑
// 如果文本框的内容和当前选中项的文本不一致,说明用户正在打字替换内容
if (SelectedItem != null && searchText != selectedText)
{
- _isInternalOperation = true;
+ isInternalOperation = true;
SelectedItem = null; // 必须将 SelectedItem 设为 null,否则 WPF 会强行还原文本
- _isInternalOperation = false;
+ isInternalOperation = false;
}
// 重点 3:执行过滤
@@ -79,7 +79,7 @@ namespace Melskin.Controls
};
// 自动打开下拉框
- if (!IsDropDownOpen && _editableTextBox.IsFocused && !string.IsNullOrEmpty(searchText))
+ if (!IsDropDownOpen && editableTextBox.IsFocused && !string.IsNullOrEmpty(searchText))
{
IsDropDownOpen = true;
}
diff --git a/MelskinTest/MainWindow.xaml b/MelskinTest/MainWindow.xaml
index d68bac0..bdbb8d7 100644
--- a/MelskinTest/MainWindow.xaml
+++ b/MelskinTest/MainWindow.xaml
@@ -632,7 +632,9 @@
-
+
+
+
diff --git a/ShrlAlgo.Addin.Test/ProgressManager.cs b/ShrlAlgo.Addin.Test/ProgressManager.cs
new file mode 100644
index 0000000..171e812
--- /dev/null
+++ b/ShrlAlgo.Addin.Test/ProgressManager.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Threading;
+
+namespace ShrlAlgo.Addin.Test
+{
+ public static class ProgressManager
+ {
+ ///
+ /// 启动带进度条的 Revit 主线程任务
+ ///
+ /// 进度条窗口标题
+ /// 需要在 Revit 主线程执行的任务逻辑
+ public static void Run(string title, Action revitTask)
+ {
+ var cts = new CancellationTokenSource();
+ var viewModel = new ProgressViewModel(title, cts);
+ var reporter = new ProgressReporter(viewModel);
+
+ // 使用 ManualResetEvent 等待 WPF 窗口初始化完成
+ using (var viewReadyEvent = new ManualResetEvent(false))
+ {
+ // 1. 创建独立的 STA 线程运行 WPF UI
+ var uiThread = new Thread(() =>
+ {
+ viewModel.UIDispatcher = Dispatcher.CurrentDispatcher;
+ var window = new ProgressWindow(viewModel);
+ window.Show();
+
+ viewReadyEvent.Set(); // 通知主线程 UI 已就绪
+
+ Dispatcher.Run(); // 开启消息循环
+ });
+
+ uiThread.SetApartmentState(ApartmentState.STA); // 必须是 STA
+ uiThread.IsBackground = true;
+ uiThread.Start();
+
+ viewReadyEvent.WaitOne(); // 阻塞主线程,直到 WPF 窗体显示
+
+ // 2. 在 Revit 主线程中执行耗时任务
+ try
+ {
+ revitTask(reporter, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ // 捕获取消异常,静默处理即可
+ }
+ catch (Exception ex)
+ {
+ Autodesk.Revit.UI.TaskDialog.Show("Task Error", ex.Message);
+ }
+ finally
+ {
+ // 3. 任务结束,通知 WPF 线程关闭窗口并停止调度器
+ if (viewModel.UIDispatcher != null && !viewModel.UIDispatcher.HasShutdownStarted)
+ {
+ viewModel.UIDispatcher.InvokeAsync(() =>
+ {
+ viewModel.CloseAction?.Invoke();
+ Dispatcher.CurrentDispatcher.InvokeShutdown();
+ });
+ }
+ uiThread.Join(1000); // 等待 UI 线程彻底退出
+ cts.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/ShrlAlgo.Addin.Test/ProgressReporter.cs b/ShrlAlgo.Addin.Test/ProgressReporter.cs
new file mode 100644
index 0000000..f79e75c
--- /dev/null
+++ b/ShrlAlgo.Addin.Test/ProgressReporter.cs
@@ -0,0 +1,96 @@
+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.Threading;
+
+namespace ShrlAlgo.Addin.Test
+{
+ // 用于给外界调用的报告器
+ public class ProgressReporter
+ {
+ private readonly ProgressViewModel _viewModel;
+ private DateTime _lastUpdate = DateTime.MinValue;
+
+ public ProgressReporter(ProgressViewModel viewModel)
+ {
+ _viewModel = viewModel;
+ }
+
+ ///
+ /// 更新进度 (自带节流控制,防止频繁更新导致UI卡顿)
+ ///
+ public void Report(int current, int total, string message = null)
+ {
+ // 限制 UI 刷新频率(每 50 毫秒最多刷新一次)
+ if ((DateTime.Now - _lastUpdate).TotalMilliseconds < 50 && current != total)
+ return;
+
+ _lastUpdate = DateTime.Now;
+ _viewModel.Update(current, total, message);
+ }
+
+ public void ReportMessage(string message) => _viewModel.UpdateMessage(message);
+ }
+
+ // WPF 的 ViewModel
+ public class ProgressViewModel : INotifyPropertyChanged
+ {
+ private readonly CancellationTokenSource _cts;
+ public Dispatcher UIDispatcher { get; set; }
+ public Action CloseAction { get; set; }
+
+ public ProgressViewModel(string title, CancellationTokenSource cts)
+ {
+ Title = title;
+ _cts = cts;
+ }
+
+ private string _title;
+ public string Title { get => _title; set => SetProperty(ref _title, value); }
+
+ private string _message = "正在准备...";
+ public string Message { get => _message; set => SetProperty(ref _message, value); }
+
+ private double _progressValue;
+ public double ProgressValue { get => _progressValue; set => SetProperty(ref _progressValue, value); }
+
+ private double _maximum = 100;
+ public double Maximum { get => _maximum; set => SetProperty(ref _maximum, value); }
+
+ public void Cancel()
+ {
+ if (!_cts.IsCancellationRequested)
+ {
+ Message = "正在取消任务,请稍候...";
+ _cts.Cancel();
+ }
+ }
+
+ // 跨线程更新 UI
+ public void Update(int current, int total, string message)
+ {
+ UIDispatcher?.InvokeAsync(() =>
+ {
+ ProgressValue = current;
+ Maximum = total;
+ if (!string.IsNullOrEmpty(message)) Message = message;
+ });
+ }
+
+ public void UpdateMessage(string message)
+ {
+ UIDispatcher?.InvokeAsync(() => Message = message);
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected void SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ field = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/ShrlAlgo.Addin.Test/ProgressWindow.xaml b/ShrlAlgo.Addin.Test/ProgressWindow.xaml
new file mode 100644
index 0000000..af1f06e
--- /dev/null
+++ b/ShrlAlgo.Addin.Test/ProgressWindow.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ShrlAlgo.Addin.Test/ProgressWindow.xaml.cs b/ShrlAlgo.Addin.Test/ProgressWindow.xaml.cs
new file mode 100644
index 0000000..c1e1bc4
--- /dev/null
+++ b/ShrlAlgo.Addin.Test/ProgressWindow.xaml.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace ShrlAlgo.Addin.Test
+{
+ ///
+ /// ProgressWindow.xaml 的交互逻辑
+ ///
+ public partial class ProgressWindow : Window
+ {
+ private readonly ProgressViewModel _viewModel;
+
+ public ProgressWindow(ProgressViewModel viewModel)
+ {
+ InitializeComponent();
+ _viewModel = viewModel;
+ DataContext = _viewModel;
+
+ // 绑定关闭委托
+ _viewModel.CloseAction = this.Close;
+ }
+
+ private void BtnCancel_Click(object sender, RoutedEventArgs e)
+ {
+ _viewModel.Cancel();
+ (sender as System.Windows.Controls.Button).IsEnabled = false;
+ }
+
+ // 屏蔽系统的 Alt+F4 关闭,强制必须等主线程任务结束
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ if (!_viewModel.UIDispatcher.HasShutdownStarted)
+ {
+ e.Cancel = true;
+ _viewModel.Cancel(); // 尝试取消
+ }
+ base.OnClosing(e);
+ }
+ }
+}
diff --git a/ShrlAlgo.Addin.Test/RevitAddin.cs b/ShrlAlgo.Addin.Test/RevitAddin.cs
index 3fc02bc..43c3446 100644
--- a/ShrlAlgo.Addin.Test/RevitAddin.cs
+++ b/ShrlAlgo.Addin.Test/RevitAddin.cs
@@ -9,24 +9,55 @@ using Autodesk.Revit.UI.Selection;
using Nice3point.Revit.Toolkit.External;
using ShrlAlgoToolkit.Core.Assists;
+using ShrlAlgoToolkit.RevitCore.Base;
namespace ShrlAlgo.Addin.Test;
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
-public class RevitAddin : ExternalCommand
+public class RevitAddin : BaseCommand
{
- public override void Execute()
+ protected override void ExecuteCommand()
{
- try
+ // 获取文档中所有的墙
+ var walls = new FilteredElementCollector(Document)
+ .OfClass(typeof(Wall))
+ .ToElements();
+
+ if (walls.Count == 0) return;
+
+ // 直接调用我们的进度条管理器!
+ ProgressManager.Run("批量修改墙参数", (reporter, token) =>
{
-
- }
- catch (Exception)
- {
- throw;
- }
+ // 利用之前封装的 TransactionExtensions 开启事务
+ Document.InvokeInTransaction(() =>
+ {
+ for (int i = 0; i < walls.Count; i++)
+ {
+ // 1. 检查用户是否点击了“取消”
+ token.ThrowIfCancellationRequested();
+
+ var wall = walls[i];
+
+ // 2. 你的 Revit API 操作 (例如修改参数)
+ var commentsParam = wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS);
+ if (commentsParam != null && !commentsParam.IsReadOnly)
+ {
+ commentsParam.Set($"自动写入 - {i}");
+ }
+
+ // 3. 报告进度 (内置了防卡顿机制,每秒调用几万次都不会卡)
+ reporter.Report(i + 1, walls.Count, $"正在处理墙: {wall.Name}");
+
+ // 如果逻辑非常非常耗时,可以考虑在这里短暂休眠让出一点点CPU(通常不用加)
+ // Thread.Sleep(1);
+ }
+ }, "Batch Update Walls");
+ });
+
+ // 任务结束(无论完成还是取消),后续代码继续执行
+ TaskDialog.Show("提示", "操作结束或已被取消!");
}
}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerViewModel.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerViewModel.cs
deleted file mode 100644
index 5f9c4ae..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerViewModel.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System.Windows.Media;
-using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using CommunityToolkit.Mvvm.Messaging;
-
-namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
-{
- public partial class ColorPickerViewModel : ObservableObject
- {
- public ColorPickerViewModel(Color color)
- {
- SelectedColor = color;
- //this.IsActive = true;
- //WeakReferenceMessenger.Default.Register(this, (r, m) =>
- //{
- // // Handle the message here, with r being the recipient and m being the
- // // input message. Using the recipient passed as input makes it so that
- // // the lambda expression doesn't capture "this", improving performance.
- //});
- }
-
- [ObservableProperty]
- public partial Color SelectedColor { get; set; }
-
- [RelayCommand]
- private void Confirm(object obj)
- {
- if (obj is System.Windows.Window window)
- {
- var colorMessage = new Autodesk.Revit.DB.Color(
- SelectedColor.R,
- SelectedColor.G,
- SelectedColor.B
- );
- //发布
- window.DialogResult = true;
- WeakReferenceMessenger.Default.Send(colorMessage);
- }
- }
- }
-}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerWin.xaml b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerWin.xaml
deleted file mode 100644
index 7acf844..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerWin.xaml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerWin.xaml.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerWin.xaml.cs
deleted file mode 100644
index 19935b1..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ColorPickerWin.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace ShrlAlgoToolkit.RevitAddins.Common.Controls;
-
-///
-/// ColorPickerWin.xaml 的交互逻辑
-///
-public partial class ColorPickerWin
-{
- public ColorPickerWin(ColorPickerViewModel viewModel)
- {
- DataContext = viewModel;
- InitializeComponent();
- }
-}
\ No newline at end of file
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProcessEventHandler.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProcessEventHandler.cs
deleted file mode 100644
index 61cb832..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProcessEventHandler.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using Autodesk.Revit.DB;
-
-namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
-{
- class ProcessEventHandler : IExternalEventHandler
- {
- public ProgressMonitorView ProgressBarView { get; set; }
- public ProgressMonitorViewModel ViewModel { get; set; }
- public IEnumerable Collection { get; set; }
- ///
- /// 附加的参数,用于传额外的变量
- ///
- public object AddinParameter { get; set; }
-
- bool Cancel = false;
- private readonly ExternalEvent externalEvent;
- private readonly Action action;
- private readonly string transactionName;
- private readonly bool useTransaction;
-
- private delegate void ProgressBarDelegate();
- public ProcessEventHandler(Action action, string transactionName = "处理任务", bool useTransaction = true)
- {
- externalEvent = ExternalEvent.Create(this);
- this.action = action;
- this.transactionName = transactionName;
- this.useTransaction = useTransaction;
- }
- public void Execute(UIApplication app)
- {
- if (app == null)
- {
- CloseWindow();
- return;
- }
-
- if (app.ActiveUIDocument == null)
- return;
-
- if (app.ActiveUIDocument.Document == null)
- return;
-
- if (ProgressBarView == null)
- return;
-
- if (ViewModel == null)
- return;
- Transaction ts = null;
- // 根据条件决定是否创建并启动事务
- if (useTransaction)
- {
- ts = new(app.ActiveUIDocument.Document, transactionName);
- ts.Start();
- }
- ProgressBarView.btnCancel.Click += CurrentUI_Closed;
- for (ViewModel.CurrentValue = 0; ViewModel.CurrentValue < ViewModel.MaxValue; ViewModel.CurrentValue++)
- {
- System.Threading.Thread.Sleep(20);
- if (Cancel)
- break;
- var elem = Collection.ElementAt(ViewModel.CurrentValue);
- try
- {
- action(app, elem, AddinParameter);
- }
- catch(Exception ex)
- {
- CloseWindow();
- // 如果发生异常且事务已启动,则回滚事务
- if (useTransaction && ts != null)
- {
- ts.RollBack();
- }
- Common.Assists.LogAssist.ToLog(ex.Message);
- return;
- }
- //await Task.Delay(50);
- //ViewModel.CurrentContext = $"处理 {ViewModel.CurrentValue} / {ViewModel.MaxValue} 完成";
- ProgressBarView.Dispatcher.Invoke(() => ViewModel.CurrentContext = $"处理 {ViewModel.CurrentValue} / {ViewModel.MaxValue} 完成", System.Windows.Threading.DispatcherPriority.Background);
- //ProgressBarView.Dispatcher.Invoke(ViewModel.NotifyUI, System.Windows.Threading.DispatcherPriority.Background);
- }
-
- // 根据条件决定是否提交事务
- if (useTransaction && ts != null)
- {
- //取消的时候,回滚,而不是提交部分处理的事务
- if (Cancel)
- {
- ts.RollBack();
- }
- else
- {
- ts.Commit();
- }
- }
- CloseWindow();
- // 确保事务对象被正确处理
- ts?.Dispose();
- }
-
- //private async Task Wait()
- //{
- // await Task.Delay(50);
- //}
-
- private void CloseWindow()
- {
- ProgressBarView.Closed -= CurrentUI_Closed;
- ProgressBarView.Close();
- //关闭窗口后,需要重设,否则同一个外部事件的Cancel会被上次取消过的情况影响
- Cancel = false;
- }
-
- private void CurrentUI_Closed(object sender, EventArgs e)
- {
- Cancel = true;
- }
-
- public string GetName()
- {
- return "进度监视";
- }
- public void Raise()
- {
- externalEvent.Raise();
- }
- }
-}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressBarManager.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressBarManager.cs
deleted file mode 100644
index 0938baa..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressBarManager.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using Autodesk.Revit.DB;
-
-namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
-{
- class ProgressBarManager
- {
- private readonly UIDocument uidoc;
- private readonly IEnumerable collection;
- private readonly object addinParameter;
- private readonly Action action;
- private readonly string taskName;
- private readonly bool useTransaction;
- ProgressMonitorViewModel viewModel;
- ProgressMonitorView progressBarView;
-
- #region 非模态处理
- private readonly ProcessEventHandler progressEventHandler;
- ///
- /// 非模态进度条,只能开启一次,请勿在遍历中使用
- ///
- ///
- ///
- public ProgressBarManager(ProcessEventHandler progressEventHandler, IEnumerable collection, string taskName = "处理任务", object addinParameter = null)
- {
- this.progressEventHandler = progressEventHandler;
- progressEventHandler.Collection = collection;
- progressEventHandler.AddinParameter = addinParameter;
- this.collection = collection;
- this.taskName = taskName;
- }
- public void ProgressModeless()
- {
- if (!progressEventHandler.Collection.Any())
- {
- return;
- }
- viewModel = new()
- {
- MaxValue = collection.Count(),
- Title = taskName
- };
-
- progressBarView ??= new();
- //progressBarView = ProgressMonitorView.Instance;
- progressBarView.DataContext = viewModel;
- progressBarView.Loaded += CurrentUI_ContentRendered;
- progressBarView.Closed += CurrentUI_Closed;
- progressBarView.Topmost = true;
- //progressEventHandler = new(collection, action)
- //{
- // ProgressBarView = progressBarView,
- // ViewModel = viewModel
- //};
- if (collection.Any())
- {
- progressEventHandler.ProgressBarView = progressBarView;
- progressEventHandler.ViewModel = viewModel;
- //progressBarView.Show();
- progressBarView.Show();
- //progressEventHandler.Raise();
- }
-
- }
- private void CurrentUI_ContentRendered(object sender, EventArgs e)
- {
- progressEventHandler.Raise();
- }
- #endregion
- #region 模态处理
- bool Cancel { get; set; }
- //private delegate void ProgressBarDelegate();
- ///
- /// 模态进度条
- ///
- ///
- ///
- ///
- public ProgressBarManager(UIDocument uidoc, ICollection collection, Action action, string taskName = "处理任务", bool useTransaction = true, object addinParameter = null)
- {
- this.uidoc = uidoc;
- this.collection = collection;
- this.addinParameter = addinParameter;
- this.action = action;
- this.taskName = taskName;
- this.useTransaction = useTransaction;
- }
- public void ProgressModal()
- {
- if (!collection.Any())
- {
- return;
- }
- viewModel = new ProgressMonitorViewModel
- {
- MaxValue = collection.Count(),
- Title = taskName
- };
- progressBarView = new ProgressMonitorView
- {
- DataContext = viewModel
- };
- //progressBarView = ProgressMonitorView.Instance;
- //progressBarView.DataContext = viewModel;
- progressBarView.Closed += CurrentUI_Closed;
- progressBarView.ContentRendered += FireUPModal;
-
- progressBarView.ShowDialog();
- }
- private async void FireUPModal(object sender, EventArgs e)
- {
- progressBarView.ContentRendered -= FireUPModal;
- Transaction ts = null;
- // 根据条件决定是否创建并启动事务
- if (useTransaction)
- {
- ts = new Transaction(uidoc.Document, taskName);
- ts.Start();
- }
- for (viewModel.CurrentValue = 0; viewModel.CurrentValue < viewModel.MaxValue; viewModel.CurrentValue++)
- {
- if (Cancel)
- break;
-
- //System.Threading.Thread.Sleep(50);
-
- try
- {
- var t = collection.ElementAt(viewModel.CurrentValue);
- //Debug.WriteLine(ViewModel.CurrentValue);
- action(uidoc, t, addinParameter);
- }
- catch (Exception)
- {
- CloseWindow();
- // 如果发生异常且事务已启动,则回滚事务
- if (useTransaction && ts != null && ts.GetStatus() == TransactionStatus.Started)
- {
- ts.RollBack();
- }
- }
- viewModel.CurrentContext = $"处理 {viewModel.CurrentValue} / {viewModel.MaxValue} 完成";
- await Task.Delay(50);
- //progressBarView.Dispatcher.Invoke(viewModel.NotifyUI, System.Windows.Threading.DispatcherPriority.Background);
- }
- // 根据条件决定是否提交事务
- if (useTransaction && ts != null)
- {
- ts.Commit();
- }
-
- CloseWindow();
- // 确保事务对象被正确处理
- ts?.Dispose();
- }
-
- private void CloseWindow()
- {
- progressBarView.Closed -= CurrentUI_Closed;
- progressBarView.Close();
- }
-
- private void CurrentUI_Closed(object sender, EventArgs e)
- {
- Cancel = true;
- }
- #endregion
- }
-}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressManager.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressManager.cs
new file mode 100644
index 0000000..8541d85
--- /dev/null
+++ b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressManager.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Threading;
+
+
+namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
+{
+ public static class ProgressManager
+ {
+ ///
+ /// 启动带进度条的 Revit 主线程任务
+ ///
+ /// 进度条窗口标题
+ /// 需要在 Revit 主线程执行的任务逻辑
+ public static void Run(string title, Action revitTask)
+ {
+ var cts = new CancellationTokenSource();
+ var viewModel = new ProgressViewModel(title, cts);
+ var reporter = new ProgressReporter(viewModel);
+
+ // 使用 ManualResetEvent 等待 WPF 窗口初始化完成
+ using (var viewReadyEvent = new ManualResetEvent(false))
+ {
+ // 1. 创建独立的 STA 线程运行 WPF UI
+ var uiThread = new Thread(() =>
+ {
+ viewModel.UIDispatcher = Dispatcher.CurrentDispatcher;
+ var window = new ProgressWindow(viewModel);
+ window.Show();
+
+ viewReadyEvent.Set(); // 通知主线程 UI 已就绪
+
+ Dispatcher.Run(); // 开启消息循环
+ });
+
+ uiThread.SetApartmentState(ApartmentState.STA); // 必须是 STA
+ uiThread.IsBackground = true;
+ uiThread.Start();
+
+ viewReadyEvent.WaitOne(); // 阻塞主线程,直到 WPF 窗体显示
+
+ // 2. 在 Revit 主线程中执行耗时任务
+ try
+ {
+ revitTask(reporter, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ // 捕获取消异常,静默处理即可
+ }
+ catch (Exception ex)
+ {
+ Autodesk.Revit.UI.TaskDialog.Show("Task Error", ex.Message);
+ }
+ finally
+ {
+ // 3. 任务结束,通知 WPF 线程关闭窗口并停止调度器
+ if (viewModel.UIDispatcher != null && !viewModel.UIDispatcher.HasShutdownStarted)
+ {
+ viewModel.UIDispatcher.InvokeAsync(() =>
+ {
+ viewModel.CloseAction?.Invoke();
+ Dispatcher.CurrentDispatcher.InvokeShutdown();
+ });
+ }
+ uiThread.Join(1000); // 等待 UI 线程彻底退出
+ cts.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorView.xaml b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorView.xaml
deleted file mode 100644
index 6fa4510..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorView.xaml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorView.xaml.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorView.xaml.cs
deleted file mode 100644
index 3484f54..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorView.xaml.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Windows;
-
-namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
-{
- ///
- /// Interaction logic for ProgressMonitorView.xaml
- ///
- public partial class ProgressMonitorView : Window
- {
- public ProgressMonitorView()
- {
- InitializeComponent();
- }
- //private static ProgressMonitorView instance;
-
- //public static ProgressMonitorView Instance
- //{
- // get
- // {
- // instance ??= new ProgressMonitorView();
- // return instance;
- // }
- //}
- }
-}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorViewModel.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorViewModel.cs
deleted file mode 100644
index baceaf2..0000000
--- a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressMonitorViewModel.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
-{
- partial class ProgressMonitorViewModel : ObservableObject
- {
- [ObservableProperty]
- public partial int MaxValue { get; set; } = 100;
-
- [ObservableProperty]
- public partial int CurrentValue { get; set; } = 0;
-
- [ObservableProperty]
- public partial string CurrentContext { get; set; } = string.Empty;
-
- [ObservableProperty]
- public partial string Title { get; set; } = "处理任务";
-
- public void NotifyUI()
- {
- var classType = this.GetType();
- if (classType != null)
- {
- var currentProperties = classType.GetProperties();
- foreach (var currentProperty in currentProperties)
- OnPropertyChanged(currentProperty.Name);
- }
- }
- }
-}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressReporter.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressReporter.cs
new file mode 100644
index 0000000..e991865
--- /dev/null
+++ b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressReporter.cs
@@ -0,0 +1,38 @@
+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.Threading;
+
+namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
+{
+ // 用于给外界调用的报告器
+ public class ProgressReporter
+ {
+ private readonly ProgressViewModel _viewModel;
+ private DateTime _lastUpdate = DateTime.MinValue;
+
+ public ProgressReporter(ProgressViewModel viewModel)
+ {
+ _viewModel = viewModel;
+ }
+
+ ///
+ /// 更新进度 (自带节流控制,防止频繁更新导致UI卡顿)
+ ///
+ public void Report(int current, int total, string message = null)
+ {
+ // 限制 UI 刷新频率(每 50 毫秒最多刷新一次)
+ if ((DateTime.Now - _lastUpdate).TotalMilliseconds < 50 && current != total)
+ return;
+
+ _lastUpdate = DateTime.Now;
+ _viewModel.Update(current, total, message);
+ }
+
+ public void ReportMessage(string message) => _viewModel.UpdateMessage(message);
+ }
+}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressViewModel.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressViewModel.cs
new file mode 100644
index 0000000..ffdc20c
--- /dev/null
+++ b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressViewModel.cs
@@ -0,0 +1,64 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Threading;
+
+namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
+{
+ // WPF 的 ViewModel
+ public class ProgressViewModel : INotifyPropertyChanged
+ {
+ private readonly CancellationTokenSource _cts;
+ public Dispatcher UIDispatcher { get; set; }
+ public Action CloseAction { get; set; }
+
+ public ProgressViewModel(string title, CancellationTokenSource cts)
+ {
+ Title = title;
+ _cts = cts;
+ }
+
+ private string _title;
+ public string Title { get => _title; set => SetProperty(ref _title, value); }
+
+ private string _message = "正在准备...";
+ public string Message { get => _message; set => SetProperty(ref _message, value); }
+
+ private double _progressValue;
+ public double ProgressValue { get => _progressValue; set => SetProperty(ref _progressValue, value); }
+
+ private double _maximum = 100;
+ public double Maximum { get => _maximum; set => SetProperty(ref _maximum, value); }
+
+ public void Cancel()
+ {
+ if (!_cts.IsCancellationRequested)
+ {
+ Message = "正在取消任务,请稍候...";
+ _cts.Cancel();
+ }
+ }
+
+ // 跨线程更新 UI
+ public void Update(int current, int total, string message)
+ {
+ UIDispatcher?.InvokeAsync(() =>
+ {
+ ProgressValue = current;
+ Maximum = total;
+ if (!string.IsNullOrEmpty(message)) Message = message;
+ });
+ }
+
+ public void UpdateMessage(string message)
+ {
+ UIDispatcher?.InvokeAsync(() => Message = message);
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected void SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ field = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressWindow.xaml b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressWindow.xaml
new file mode 100644
index 0000000..f3ffbd6
--- /dev/null
+++ b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressWindow.xaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressWindow.xaml.cs b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressWindow.xaml.cs
new file mode 100644
index 0000000..b18aa63
--- /dev/null
+++ b/ShrlAlgoToolkit.RevitAddins/Common/Controls/ProgressWindow.xaml.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+
+namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
+{
+ ///
+ /// ProgressWindow.xaml 的交互逻辑
+ ///
+ public partial class ProgressWindow : Window
+ {
+ private readonly ProgressViewModel _viewModel;
+
+ public ProgressWindow(ProgressViewModel viewModel)
+ {
+ InitializeComponent();
+ _viewModel = viewModel;
+ DataContext = _viewModel;
+
+ // 绑定关闭委托
+ _viewModel.CloseAction = this.Close;
+ }
+
+ private void BtnCancel_Click(object sender, RoutedEventArgs e)
+ {
+ _viewModel.Cancel();
+ (sender as System.Windows.Controls.Button).IsEnabled = false;
+ }
+
+ // 屏蔽系统的 Alt+F4 关闭,强制必须等主线程任务结束
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ if (!_viewModel.UIDispatcher.HasShutdownStarted)
+ {
+ e.Cancel = true;
+ _viewModel.Cancel(); // 尝试取消
+ }
+ base.OnClosing(e);
+ }
+ }
+}
diff --git a/ShrlAlgoToolkit.RevitAddins/Common/Converters/Rv2WinColorConverter.cs b/ShrlAlgoToolkit.RevitAddins/Common/Converters/Rv2WinColorConverter.cs
index 1848191..b73ed6a 100644
--- a/ShrlAlgoToolkit.RevitAddins/Common/Converters/Rv2WinColorConverter.cs
+++ b/ShrlAlgoToolkit.RevitAddins/Common/Converters/Rv2WinColorConverter.cs
@@ -1,12 +1,9 @@
using System.Globalization;
using System.Windows.Data;
-using Color = System.Windows.Media.Color;
+using ShrlAlgoToolkit.RevitAddins.Common.Converters;
-using ShrlAlgoToolkit.RevitAddins.UI.Converters;
-using ShrlAlgoToolkit;
-using ShrlAlgoToolkit.RevitAddins;
-using ShrlAlgoToolkit.RevitAddins.UI;
+using Color = System.Windows.Media.Color;
namespace ShrlAlgoToolkit.RevitAddins.Common.Converters;
@@ -15,7 +12,7 @@ namespace ShrlAlgoToolkit.RevitAddins.Common.Converters;
///
public class Rv2WinColorConverter : IValueConverter
{
- public static Converters.Rv2WinColorConverter Instance { get; } = new Converters.Rv2WinColorConverter();
+ public static Rv2WinColorConverter Instance { get; } = new Rv2WinColorConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var color = (Autodesk.Revit.DB.Color)value;
diff --git a/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayCmd.cs b/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayCmd.cs
index 90dc0fe..09b5866 100644
--- a/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayCmd.cs
+++ b/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayCmd.cs
@@ -2,8 +2,6 @@
using Nice3point.Revit.Toolkit.External;
using ShrlAlgoToolkit.RevitAddins.RvView;
-using ShrlAlgoToolkit;
-using ShrlAlgoToolkit.RevitAddins;
namespace ShrlAlgoToolkit.RevitAddins.DrawSheet;
@@ -15,7 +13,7 @@ public class SystemDisplayCmd : ExternalCommand
{
public override void Execute()
{
- var view = new DrawSheet.SystemDisplayView()
+ var view = new SystemDisplayView()
{
DataContext = new SystemDisplayViewModel(UiApplication)
};
diff --git a/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayView.xaml b/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayView.xaml
index 5fad320..b87a5d9 100644
--- a/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayView.xaml
+++ b/ShrlAlgoToolkit.RevitAddins/DrawSheet/SystemDisplayView.xaml
@@ -8,8 +8,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mep="clr-namespace:ShrlAlgoToolkit.RevitAddins.RvMEP"
xmlns:rvMep="clr-namespace:ShrlAlgoToolkit.RevitAddins.RvMEP"
- xmlns:ui="https://github.com/ShrlAlgo/Melskin"
xmlns:rvView="clr-namespace:ShrlAlgoToolkit.RevitAddins.RvView"
+ xmlns:ui="https://github.com/ShrlAlgo/Melskin"
Title="系统设置"
Width="550"
Height="450"
@@ -75,19 +75,26 @@
-->
-
+
+
+
-
+
@@ -145,6 +145,9 @@
+
+ Code
+
True
True
diff --git a/ShrlAlgoToolkit.RevitCore/Extensions/TransactionExtensions.cs b/ShrlAlgoToolkit.RevitCore/Extensions/TransactionExtensions.cs
index ba14040..eea0660 100644
--- a/ShrlAlgoToolkit.RevitCore/Extensions/TransactionExtensions.cs
+++ b/ShrlAlgoToolkit.RevitCore/Extensions/TransactionExtensions.cs
@@ -231,5 +231,51 @@ namespace ShrlAlgoToolkit.RevitCore.Extensions
options.SetFailuresPreprocessor(failuresPreprocessor);
ts.SetFailureHandlingOptions(options);
}
+ ///
+ /// 封装普通事务
+ ///
+ public static void InvokeInTransaction(this Document doc, Action action, string transactionName = "DefaultTransaction")
+ {
+ using (Transaction t = new Transaction(doc, transactionName))
+ {
+ t.Start();
+ try
+ {
+ action();
+ t.Commit();
+ }
+ catch (Exception)
+ {
+ if (t.GetStatus() == TransactionStatus.Started)
+ t.RollBack();
+ throw; // 将异常向上抛出
+ }
+ }
+ }
+
+ ///
+ /// 封装事务组 (TransactionGroup)
+ ///
+ public static void InvokeInTransactionGroup(this Document doc, Action action, string groupName = "DefaultGroup", bool assimilate = true)
+ {
+ using (TransactionGroup tg = new TransactionGroup(doc, groupName))
+ {
+ tg.Start();
+ try
+ {
+ action();
+ if (assimilate)
+ tg.Assimilate(); // 融合所有子事务
+ else
+ tg.Commit();
+ }
+ catch (Exception)
+ {
+ if (tg.GetStatus() == TransactionStatus.Started)
+ tg.RollBack();
+ throw;
+ }
+ }
+ }
}
}