Files
Shrlalgo.RvKits/Melskin/Controls/Accordion.xaml.cs

130 lines
6.0 KiB
C#
Raw Normal View History

2026-01-02 17:30:41 +08:00
namespace Melskin.Controls
2025-09-04 22:39:00 +08:00
{
/// <summary>
/// Accordion 控件提供了一个可折叠的面板容器,允许用户通过点击标题来展开或收起内容。
/// 该控件继承自 ItemsControl因此可以使用数据绑定来动态添加 AccordionItem 子项。
/// 每个 AccordionItem 可以独立地设置其标题和是否默认展开的状态。
/// </summary>
2025-09-04 22:39:00 +08:00
public class Accordion : ItemsControl
{
static Accordion()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Accordion), new FrameworkPropertyMetadata(typeof(Accordion)));
}
/// <inheritdoc />
2025-09-04 22:39:00 +08:00
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is AccordionItem;
}
/// <inheritdoc />
2025-09-04 22:39:00 +08:00
protected override DependencyObject GetContainerForItemOverride()
{
return new AccordionItem();
}
2025-12-23 21:35:54 +08:00
///// <inheritdoc />
//protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
//{
// base.OnItemsChanged(e);
2025-09-04 22:39:00 +08:00
2025-12-23 21:35:54 +08:00
// if (e.NewItems != null)
// {
// // 注意:这里需要确保 e.NewItems 中的项是 AccordionItem 类型
// // 当通过 ItemsSource 绑定时,它们最初是数据项,在 GetContainerForItemOverride 之后才被包装
// // 因此,事件处理最好在容器生成后进行。
// // 为了简单起见,我们在此处添加事件,但这依赖于 ItemsSource 中的项已经是 AccordionItem。
// // 一个更健壮的方法是重写 PrepareContainerForItemOverride。
// foreach (AccordionItem item in e.NewItems)
// {
// item.PreviewMouseLeftButtonDown += AccordionItem_PreviewMouseLeftButtonDown;
// }
// }
// if (e.OldItems != null)
// {
// foreach (AccordionItem item in e.OldItems)
// {
// item.PreviewMouseLeftButtonDown -= AccordionItem_PreviewMouseLeftButtonDown;
// }
// }
//}
2025-09-04 22:39:00 +08:00
/// <summary>
/// 重写此方法是处理数据绑定时添加事件的更可靠方式
/// </summary>
/// <param name="element"></param>
/// <param name="item"></param>
2025-09-04 22:39:00 +08:00
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
if (element is AccordionItem accordionItem)
{
accordionItem.PreviewMouseLeftButtonDown -= AccordionItem_PreviewMouseLeftButtonDown; // 防止重复添加
accordionItem.PreviewMouseLeftButtonDown += AccordionItem_PreviewMouseLeftButtonDown;
}
}
/// <inheritdoc />
2025-09-04 22:39:00 +08:00
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
if (element is AccordionItem accordionItem)
{
accordionItem.PreviewMouseLeftButtonDown -= AccordionItem_PreviewMouseLeftButtonDown;
}
2025-12-23 21:35:54 +08:00
2025-09-04 22:39:00 +08:00
base.ClearContainerForItemOverride(element, item);
}
private void AccordionItem_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (sender is AccordionItem clickedItem)
{
2025-12-23 21:35:54 +08:00
// 获取点击源的模板父级(即该视觉元素属于哪个控件)
var originalSource = e.OriginalSource as FrameworkElement;
var parentControl = originalSource?.TemplatedParent as System.Windows.Controls.Primitives.ToggleButton;
// 【核心修复】:判断是否为有效的标题点击
// 1. 必须是 ToggleButton (HeaderButton 是 ToggleButton)
// 2. 必须 不是 CheckBox (排除内容里的复选框)
// 3. 必须 不是 RadioButton (排除内容里的单选框)
bool isHeaderClick = parentControl != null
&& parentControl is not System.Windows.Controls.CheckBox
&& parentControl is not System.Windows.Controls.RadioButton;
// 只有确信点击的是标题栏(HeaderButton)时,才由 Accordion 接管处理
if (isHeaderClick)
2025-09-04 22:39:00 +08:00
{
2025-12-23 21:35:54 +08:00
if (clickedItem.IsExpanded)
2025-09-04 22:39:00 +08:00
{
clickedItem.IsExpanded = false;
}
2025-12-23 21:35:54 +08:00
else
2025-09-04 22:39:00 +08:00
{
2025-12-23 21:35:54 +08:00
// 折叠其他所有项
// 注意ItemContainerGenerator 可能只包含已生成的容器,直接遍历 Items 有时更安全,
// 但如果用了虚拟化ContainerFromIndex 可能会返回 null。
// 对于 Accordion 通常项数不多,直接遍历 Items 对应的 Container 即可。
foreach (var item in this.Items)
2025-09-04 22:39:00 +08:00
{
2025-12-23 21:35:54 +08:00
if (this.ItemContainerGenerator.ContainerFromItem(item) is AccordionItem container && container != clickedItem)
{
container.IsExpanded = false;
}
2025-09-04 22:39:00 +08:00
}
2025-12-23 21:35:54 +08:00
clickedItem.IsExpanded = true;
2025-09-04 22:39:00 +08:00
}
2025-12-23 21:35:54 +08:00
// 标记事件已处理,防止事件继续冒泡触发 ToggleButton 自身的点击行为
// (因为我们在这里手动切换了 IsExpanded如果 ToggleButton 再处理一次可能会抵消)
2025-09-04 22:39:00 +08:00
e.Handled = true;
}
2025-12-23 21:35:54 +08:00
// 如果不是 Header 点击(例如点了 CheckBox什么都不做让事件继续传播
// 这样 CheckBox 就能接收到点击并正常勾选了。
2025-09-04 22:39:00 +08:00
}
}
}
}