using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using HandyControl.Controls;
using PgItem = PropertyGridLib.Controls.PropertyItem;
namespace TeamAAS.Dialogs.Dialogs
{
///
/// 基于 PropertyGridLib 的通用属性编辑对话框。
/// 将任意对象传入 SelectedObject,PropertyGrid 自动生成编辑界面。
/// 可选执行按钮:传入 ExecuteAction 后显示"执行"按钮,点击后台执行不卡UI,执行中变为"停止"可取消。
///
public partial class PropertyGridDialog
{
/// 用户是否点击了确定
public bool Result { get; private set; }
/// 执行回调(对象, 取消令牌),传入则显示执行按钮
public Action ExecuteAction { get; set; }
private CancellationTokenSource _cts;
private bool _isExecuting;
private bool _refreshingProperties;
/// 最近一次持久提示。悬停提示结束后回落到这里。
private string _lastTip = string.Empty;
/// 当前编辑的对象
public object SelectedObject
{
get => PropertyGrid.SelectedObject;
set
{
PropertyGrid.SelectedObject = value;
Title = $"属性编辑 - {value?.GetType().Name ?? ""}";
}
}
/// 自定义窗口标题
public string DialogTitle
{
get => Title;
set => Title = value;
}
public PropertyGridDialog()
{
InitializeComponent();
UiScaler.Attach(this); // 分辨率/DPI 自适应
Closing += (s, e) => { if (_isExecuting) e.Cancel = true; };
PropertyGrid.AddHandler(Selector.SelectionChangedEvent, new SelectionChangedEventHandler(OnComboSelectionChanged), true);
PropertyGrid.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnComboPreviewMouseDown), true);
// 回车 = 确认(执行中除外)
PreviewKeyDown += (s, e) =>
{
if (e.Key == System.Windows.Input.Key.Enter && !_isExecuting)
{
BtnOK.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Button.ClickEvent));
e.Handled = true;
}
};
}
///
/// 设置编辑对象和标题。
///
public void Setup(object obj, string title = null)
{
PropertyGrid.SelectedObject = obj;
Title = title ?? $"属性编辑 - {obj?.GetType().Name ?? ""}";
}
///
/// 设置编辑对象、标题和执行回调。
///
public void Setup(object obj, string title, Action executeAction)
{
PropertyGrid.SelectedObject = obj;
Title = title ?? $"属性编辑 - {obj?.GetType().Name ?? ""}";
ExecuteAction = executeAction;
if (executeAction != null)
BtnExecute.Visibility = Visibility.Visible;
}
///
/// 设置底部提示栏的持久提示(线程安全)。null/空 = 显示最近一次持久提示。
///
public void ShowTip(string message)
{
void Apply()
{
if (string.IsNullOrWhiteSpace(message))
{
TxtTip.Text = _lastTip;
return;
}
_lastTip = message;
TxtTip.Text = message;
}
if (Dispatcher.CheckAccess()) Apply(); else Dispatcher.Invoke(Apply);
}
///
/// 显示鼠标悬停临时提示(线程安全)。message 为 null/空时结束悬停,回落到最近一次持久提示。
///
public void ShowHoverHint(string message)
{
void Apply()
{
TxtTip.Text = string.IsNullOrWhiteSpace(message) ? _lastTip : message;
}
if (Dispatcher.CheckAccess()) Apply(); else Dispatcher.Invoke(Apply);
}
/// 窗体自带按钮的悬停提示:把 Tag 文本写入底部提示栏
private void BtnTip_MouseEnter(object sender, MouseEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is string tip && !string.IsNullOrWhiteSpace(tip))
TxtTip.Text = tip;
}
/// 悬停结束:回落到最近一次持久提示
private void BtnTip_MouseLeave(object sender, MouseEventArgs e)
{
TxtTip.Text = _lastTip;
}
private void BtnExecute_Click(object sender, RoutedEventArgs e)
{
if (_isExecuting)
{
// 正在执行 → 停止
_cts?.Cancel();
return;
}
// 开始执行
_cts = new CancellationTokenSource();
_isExecuting = true;
BtnExecute.Content = "停止";
BtnExecute.Style = FindResource("ButtonDanger") as System.Windows.Style;
PropertyGrid.IsEnabled = false;
BtnOK.IsEnabled = false;
BtnCancel.IsEnabled = false;
var token = _cts.Token;
var obj = PropertyGrid.SelectedObject;
Task.Run(() =>
{
try
{
ExecuteAction?.Invoke(obj, token);
}
catch (OperationCanceledException) { }
catch (Exception ex)
{
Dispatcher.Invoke(() => Growl.Error($"执行失败: {ex.Message}"));
}
}).ContinueWith(t =>
{
Dispatcher.Invoke(() =>
{
_isExecuting = false;
BtnExecute.Content = "执行";
BtnExecute.Style = FindResource("ButtonInfo") as System.Windows.Style;
PropertyGrid.IsEnabled = true;
BtnOK.IsEnabled = true;
BtnCancel.IsEnabled = true;
if (t.IsCanceled)
Growl.Info("已停止");
});
});
}
private void BtnOK_Click(object sender, RoutedEventArgs e)
{
_cts?.Cancel();
// 强制让 PropertyGrid 中的所有编辑控件提交修改
// 关键:PropertyGridLib 的 ComboBox 使用 LostFocus 作为 UpdateSourceTrigger
// 所以需要在关闭前确保所有下拉框关闭且焦点移出
// 1. 关闭所有打开的 ComboBox 下拉框
CloseAllComboBoxes(PropertyGrid);
// 2. 临时将焦点移到按钮上,这会触发 PropertyGrid 内部控件的 LostFocus 事件
BtnOK.Focus();
// 3. 等待一个 Dispatcher 帧,让绑定更新生效
Dispatcher.Invoke(() => { }, System.Windows.Threading.DispatcherPriority.Input);
Result = true;
Close();
}
///
/// 递归关闭所有 ComboBox 的下拉框
///
private static void CloseAllComboBoxes(DependencyObject root)
{
if (root == null) return;
int count = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
if (child is System.Windows.Controls.ComboBox combo && combo.IsDropDownOpen)
{
combo.IsDropDownOpen = false;
}
CloseAllComboBoxes(child);
}
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
_cts?.Cancel();
Result = false;
Close();
}
private void OnComboSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_refreshingProperties || e.AddedItems == null || e.AddedItems.Count == 0) return;
var combo = e.OriginalSource as System.Windows.Controls.ComboBox
?? FindComboBox(e.OriginalSource as DependencyObject);
if (combo?.DataContext is not PgItem item) return;
var owner = PropertyGrid.SelectedObject;
if (owner == null || string.IsNullOrEmpty(item.Name)) return;
var pd = TypeDescriptor.GetProperties(owner)[item.Name];
if (pd == null || pd.IsReadOnly) return;
object val = combo.SelectedItem ?? combo.Text;
object current = pd.GetValue(owner);
if (val != null && !Equals(current, val))
{
try { pd.SetValue(owner, val); }
catch { /* 类型转换失败时仍走 LostFocus 写回 */ }
}
var refresh = pd.Attributes[typeof(RefreshPropertiesAttribute)] as RefreshPropertiesAttribute;
if (refresh == null || refresh.RefreshProperties == RefreshProperties.None) return;
if (Equals(current, val)) return;
_refreshingProperties = true;
try
{
PropertyGrid.RefreshProperties();
}
finally
{
_refreshingProperties = false;
}
}
///
/// PropertyGridLib 只在创建属性项时抓一次 TypeConverter 下拉项。展开前按当前对象重取,
/// 这样「先选卡再选轴」能看到该卡的轴列表。
///
private void OnComboPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var combo = FindComboBox(e.OriginalSource as DependencyObject);
if (combo == null || combo.DataContext is not PgItem item) return;
RefreshComboStandardValues(combo, item);
}
private void RefreshComboStandardValues(System.Windows.Controls.ComboBox combo, PgItem item)
{
var owner = PropertyGrid.SelectedObject;
if (owner == null || string.IsNullOrEmpty(item.Name)) return;
var pd = TypeDescriptor.GetProperties(owner)[item.Name];
var conv = pd?.Converter;
if (conv == null || !conv.GetStandardValuesSupported()) return;
var values = conv.GetStandardValues(new EditorTypeDescriptorContext(owner, pd));
if (values == null || values.Count == 0) return;
var list = values.Cast().ToList();
combo.ItemsSource = list;
}
private static System.Windows.Controls.ComboBox FindComboBox(DependencyObject start)
{
for (var d = start; d != null; d = VisualTreeHelper.GetParent(d))
{
if (d is System.Windows.Controls.ComboBox combo) return combo;
}
return null;
}
private sealed class EditorTypeDescriptorContext : ITypeDescriptorContext
{
public EditorTypeDescriptorContext(object instance, PropertyDescriptor property)
{
Instance = instance;
PropertyDescriptor = property;
}
public IContainer Container => null;
public object Instance { get; }
public PropertyDescriptor PropertyDescriptor { get; }
public object GetService(Type serviceType) => null;
public void OnComponentChanged() { }
public bool OnComponentChanging() => true;
}
}
}