Files
Shrlalgo.RvKits/NeoUI/Melskin/Controls/Accordion.xaml.cs
2026-01-02 17:30:41 +08:00

130 lines
6.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace Melskin.Controls
{
/// <summary>
/// Accordion 控件提供了一个可折叠的面板容器,允许用户通过点击标题来展开或收起内容。
/// 该控件继承自 ItemsControl因此可以使用数据绑定来动态添加 AccordionItem 子项。
/// 每个 AccordionItem 可以独立地设置其标题和是否默认展开的状态。
/// </summary>
public class Accordion : ItemsControl
{
static Accordion()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Accordion), new FrameworkPropertyMetadata(typeof(Accordion)));
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is AccordionItem;
}
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new AccordionItem();
}
///// <inheritdoc />
//protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
//{
// base.OnItemsChanged(e);
// 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;
// }
// }
//}
/// <summary>
/// 重写此方法是处理数据绑定时添加事件的更可靠方式
/// </summary>
/// <param name="element"></param>
/// <param name="item"></param>
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 />
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
if (element is AccordionItem accordionItem)
{
accordionItem.PreviewMouseLeftButtonDown -= AccordionItem_PreviewMouseLeftButtonDown;
}
base.ClearContainerForItemOverride(element, item);
}
private void AccordionItem_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (sender is AccordionItem clickedItem)
{
// 获取点击源的模板父级(即该视觉元素属于哪个控件)
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)
{
if (clickedItem.IsExpanded)
{
clickedItem.IsExpanded = false;
}
else
{
// 折叠其他所有项
// 注意ItemContainerGenerator 可能只包含已生成的容器,直接遍历 Items 有时更安全,
// 但如果用了虚拟化ContainerFromIndex 可能会返回 null。
// 对于 Accordion 通常项数不多,直接遍历 Items 对应的 Container 即可。
foreach (var item in this.Items)
{
if (this.ItemContainerGenerator.ContainerFromItem(item) is AccordionItem container && container != clickedItem)
{
container.IsExpanded = false;
}
}
clickedItem.IsExpanded = true;
}
// 标记事件已处理,防止事件继续冒泡触发 ToggleButton 自身的点击行为
// (因为我们在这里手动切换了 IsExpanded如果 ToggleButton 再处理一次可能会抵消)
e.Handled = true;
}
// 如果不是 Header 点击(例如点了 CheckBox什么都不做让事件继续传播
// 这样 CheckBox 就能接收到点击并正常勾选了。
}
}
}
}