清理ColorPicker,进度条。修复UI问题

This commit is contained in:
2026-02-23 16:49:34 +08:00
parent 1d939d52ed
commit 18174b404c
29 changed files with 1028 additions and 576 deletions

View File

@@ -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
{
/// <summary>
/// 启动带进度条的 Revit 主线程任务
/// </summary>
/// <param name="title">进度条窗口标题</param>
/// <param name="revitTask">需要在 Revit 主线程执行的任务逻辑</param>
public static void Run(string title, Action<ProgressReporter, CancellationToken> 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();
}
}
}
}
}

View File

@@ -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;
}
/// <summary>
/// 更新进度 (自带节流控制防止频繁更新导致UI卡顿)
/// </summary>
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<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,55 @@
<Window
x:Class="ShrlAlgo.Addin.Test.ProgressWindow"
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:local="clr-namespace:ShrlAlgo.Addin.Test"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{Binding Title}"
Width="450"
Height="180"
ResizeMode="NoResize"
ShowInTaskbar="False"
Topmost="True"
WindowStartupLocation="CenterScreen"
WindowStyle="ToolWindow"
mc:Ignorable="d">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 状态文本 -->
<TextBlock
Grid.Row="0"
Margin="0,0,0,10"
FontSize="14"
Text="{Binding Message}"
TextTrimming="CharacterEllipsis" />
<!-- 进度条 -->
<ProgressBar
Grid.Row="1"
Height="25"
Maximum="{Binding Maximum}"
Value="{Binding ProgressValue}" />
<!-- 进度数字与取消按钮 -->
<Grid Grid.Row="2" Margin="0,15,0,0">
<TextBlock VerticalAlignment="Center" Foreground="Gray">
<Run Text="{Binding ProgressValue, Mode=OneWay, StringFormat={}{0:0}}" />
<Run Text=" / " />
<Run Text="{Binding Maximum, Mode=OneWay, StringFormat={}{0:0}}" />
</TextBlock>
<Button
Width="80"
Height="30"
HorizontalAlignment="Right"
Click="BtnCancel_Click"
Content="取 消" />
</Grid>
</Grid>
</Window>

View File

@@ -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
{
/// <summary>
/// ProgressWindow.xaml 的交互逻辑
/// </summary>
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);
}
}
}

View File

@@ -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("提示", "操作结束或已被取消!");
}
}