清理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

@@ -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<ColorPickerViewModel>(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);
}
}
}
}

View File

@@ -1,59 +0,0 @@
<ui:MelWindow
Height="390"
Icon="{DynamicResource RevitIcon}"
Title="颜色设置"
Width="340"
WindowStartupLocation="CenterOwner"
d:DataContext="{d:DesignInstance Type=windows:ColorPickerViewModel}"
mc:Ignorable="d"
x:Class="ShrlAlgoToolkit.RevitAddins.Common.Controls.ColorPickerWin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ShrlAlgoToolkit.RevitAddins.Common.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="https://github.com/ShrlAlgo/Melskin"
xmlns:windows="clr-namespace:ShrlAlgoToolkit.RevitAddins.Common.Controls"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--<controls:StandardColorPicker
x:Name="ColorPicker"
Grid.ColumnSpan="2"
SelectedColor="{Binding SelectedColor, Delay=25, UpdateSourceTrigger=PropertyChanged}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseLeftButtonUp">
<b:InvokeCommandAction Command="{Binding UpdateColorCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=controls:ColorPicker}}" />
</b:EventTrigger>
</b:Interaction.Triggers>
</controls:StandardColorPicker>-->
<ui:ColorPanel
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
SelectedColor="{Binding SelectedColor}"
x:Name="ColorPicker" />
<StackPanel
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="1"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button
Command="{Binding ConfirmCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
Content="确定" />
<Button Command="Close" Content="取消" />
</StackPanel>
</Grid>
</ui:MelWindow>

View File

@@ -1,13 +0,0 @@
namespace ShrlAlgoToolkit.RevitAddins.Common.Controls;
/// <summary>
/// ColorPickerWin.xaml 的交互逻辑
/// </summary>
public partial class ColorPickerWin
{
public ColorPickerWin(ColorPickerViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}

View File

@@ -1,128 +0,0 @@
using Autodesk.Revit.DB;
namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
{
class ProcessEventHandler<T> : IExternalEventHandler
{
public ProgressMonitorView ProgressBarView { get; set; }
public ProgressMonitorViewModel ViewModel { get; set; }
public IEnumerable<T> Collection { get; set; }
/// <summary>
/// 附加的参数,用于传额外的变量
/// </summary>
public object AddinParameter { get; set; }
bool Cancel = false;
private readonly ExternalEvent externalEvent;
private readonly Action<UIApplication, T, object> action;
private readonly string transactionName;
private readonly bool useTransaction;
private delegate void ProgressBarDelegate();
public ProcessEventHandler(Action<UIApplication, T, object> 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<string>(() => 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();
}
}
}

View File

@@ -1,168 +0,0 @@
using Autodesk.Revit.DB;
namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
{
class ProgressBarManager<T>
{
private readonly UIDocument uidoc;
private readonly IEnumerable<T> collection;
private readonly object addinParameter;
private readonly Action<UIDocument, T, object> action;
private readonly string taskName;
private readonly bool useTransaction;
ProgressMonitorViewModel viewModel;
ProgressMonitorView progressBarView;
#region
private readonly ProcessEventHandler<T> progressEventHandler;
/// <summary>
/// 非模态进度条,只能开启一次,请勿在遍历中使用
/// </summary>
/// <param name="progressEventHandler"></param>
/// <param name="collection"></param>
public ProgressBarManager(ProcessEventHandler<T> progressEventHandler, IEnumerable<T> 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();
/// <summary>
/// 模态进度条
/// </summary>
/// <param name="uidoc"></param>
/// <param name="collection"></param>
/// <param name="action"></param>
public ProgressBarManager(UIDocument uidoc, ICollection<T> collection, Action<UIDocument, T, object> 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
}
}

View File

@@ -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
{
/// <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

@@ -1,36 +0,0 @@
<Window
x:Class="ShrlAlgoToolkit.RevitAddins.Common.Controls.ProgressMonitorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls1="clr-namespace:ShrlAlgoToolkit.RevitAddins.Common.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:windows="clr-namespace:ShrlAlgoToolkit.RevitAddins.Common.Controls"
Title="{Binding Title}"
d:DataContext="{d:DesignInstance Type=windows:ProgressMonitorViewModel}"
Background="{DynamicResource MaterialDesign.Brush.Background}"
Icon="{DynamicResource RevitIcon}"
ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
TextElement.Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<StackPanel Margin="10">
<TextBlock Text="{Binding CurrentContext}" />
<Separator />
<ProgressBar
Width="300"
Height="23"
Maximum="{Binding MaxValue}"
Value="{Binding CurrentValue}" />
<Separator />
<Button
x:Name="btnCancel"
HorizontalAlignment="Right"
Content="取消"
IsCancel="True" />
</StackPanel>
</Window>

View File

@@ -1,25 +0,0 @@
using System.Windows;
namespace ShrlAlgoToolkit.RevitAddins.Common.Controls
{
/// <summary>
/// Interaction logic for ProgressMonitorView.xaml
/// </summary>
public partial class ProgressMonitorView : Window
{
public ProgressMonitorView()
{
InitializeComponent();
}
//private static ProgressMonitorView instance;
//public static ProgressMonitorView Instance
//{
// get
// {
// instance ??= new ProgressMonitorView();
// return instance;
// }
//}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
/// <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);
}
}

View File

@@ -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<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,58 @@
<Window
x:Class="ShrlAlgoToolkit.RevitAddins.Common.Controls.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:local1="clr-namespace:ShrlAlgoToolkit.RevitAddins.Common.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{Binding Title}"
Width="450"
Height="180"
d:DataContext="{d:DesignInstance Type=local1:ProgressViewModel}"
ResizeMode="NoResize"
ShowInTaskbar="False"
Topmost="True"
WindowStartupLocation="CenterScreen"
WindowStyle="ToolWindow"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary Source="pack://application:,,,/ShrlAlgoToolkit.RevitAddins;component/WPFUI.xaml" />
</Window.Resources>
<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"
HorizontalAlignment="Right"
Click="BtnCancel_Click"
Content="取 消" />
</Grid>
</Grid>
</Window>

View File

@@ -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
{
/// <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);
}
}
}