現有 Toolkit 的 CheckListBox 原始碼過於複雜,且單純重定義 ListBoxItem 模板只能加上勾選 UI,無法提供 IsChecked 邏輯屬性與 CheckedItems 集合。決定從 ListBox/ListBoxItem 繼承,打造真正支援「可選取、可勾選」的控件。
ListBoxItem,新增 IsChecked 依賴屬性與 Checked/UnChecked 路由事件。ListBox,覆寫 GetContainerForItemOverride 回傳 CheckableListBoxItem,並維護 ObservableCollection<object> CheckedItems。CheckedItems。[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CheckableListBoxItem))]
public class CheckListBox : ListBox {
private readonly ObservableCollection<object> _checkedItems = new ObservableCollection<object>();
private readonly Type _itemContainerType = typeof(FrameworkElement);
public CheckListBox() {
// 從 StyleTypedPropertyAttribute 讀取實際容器型別
var attr = GetType().GetCustomAttributes(typeof(StyleTypedPropertyAttribute), false);
if (attr.Length > 0) _itemContainerType = ((StyleTypedPropertyAttribute)attr[0]).StyleTargetType;
}
public static readonly DependencyProperty CheckedItemsProperty =
DependencyProperty.Register("CheckedItems", typeof(IList), typeof(CheckListBox), new PropertyMetadata(null));
public IList CheckedItems => (IList)GetValue(CheckedItemsProperty);
protected override DependencyObject GetContainerForItemOverride()
=> Activator.CreateInstance(_itemContainerType) as DependencyObject;
protected override void PrepareContainerForItemOverride(DependencyObject element, object item) {
var ckItem = element as CheckableListBoxItem;
ckItem.Checked += OnItemChecked;
ckItem.UnChecked += OnItemUnchecked;
base.PrepareContainerForItemOverride(element, item);
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item) {
var ckItem = element as CheckableListBoxItem;
ckItem.Checked -= OnItemChecked;
ckItem.UnChecked -= OnItemUnchecked;
base.ClearContainerForItemOverride(element, item);
}
private void OnItemChecked(object sender, RoutedEventArgs e) {
var item = (CheckableListBoxItem)e.Source;
if (!_checkedItems.Contains(item.Content)) _checkedItems.Add(item.Content);
SetValue(CheckedItemsProperty, _checkedItems);
}
private void OnItemUnchecked(object sender, RoutedEventArgs e) {
var item = (CheckableListBoxItem)e.Source;
_checkedItems.Remove(item.Content);
SetValue(CheckedItemsProperty, _checkedItems);
}
}
public class CheckableListBoxItem : ListBoxItem {
static CheckableListBoxItem() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckableListBoxItem),
new FrameworkPropertyMetadata(typeof(CheckableListBoxItem)));
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(CheckableListBoxItem),
new PropertyMetadata(false, OnIsChanged));
private static void OnIsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var ctl = (CheckableListBoxItem)d;
if ((bool)e.NewValue) ctl.RaiseCheckedEvent(); else ctl.RaiseUnCheckedEvent();
}
public bool IsChecked { get => (bool)GetValue(IsCheckedProperty); set => SetValue(IsCheckedProperty, value); }
public static readonly RoutedEvent CheckedEvent = EventManager.RegisterRoutedEvent("Checked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CheckableListBoxItem));
public event RoutedEventHandler Checked { add => AddHandler(CheckedEvent, value); remove => RemoveHandler(CheckedEvent, value); }
void RaiseCheckedEvent() => RaiseEvent(new RoutedEventArgs(CheckedEvent));
public static readonly RoutedEvent UnCheckedEvent = EventManager.RegisterRoutedEvent("UnChecked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CheckableListBoxItem));
public event RoutedEventHandler UnChecked { add => AddHandler(UnCheckedEvent, value); remove => RemoveHandler(UnCheckedEvent, value); }
void RaiseUnCheckedEvent() => RaiseEvent(new RoutedEventArgs(UnCheckedEvent));
}
ToggleButton 呈現勾選狀態,IsChecked 雙向綁定 TemplatedParent 的 IsChecked。ContentPresenter 顯示實際資料內容。IsSelected、IsMouseOver 視覺狀態。<ControlTemplate x:Key="toggleButtonTmp" TargetType="ToggleButton">
<Border ...><Path x:Name="pc" Opacity="0" .../></Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True"><Setter TargetName="pc" Property="Opacity" Value="1"/></Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="{x:Type local:CheckableListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CheckableListBoxItem}">
<Border x:Name="bd" ...>
<Grid>
<ToggleButton x:Name="tog" IsChecked="{Binding IsChecked, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource toggleButtonTmp}" />
<ContentPresenter Grid.Column="1" ... />
</Grid>
</Border>
<ControlTemplate.Triggers>...</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Grid>
<Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions>
<local:CheckListBox x:Name="lb" Grid.Column="0" SelectionMode="Multiple" />
<Grid Grid.Column="1">
<TextBlock Text="Checked Items:" />
<ListBox ItemsSource="{Binding CheckedItems, ElementName=lb}" />
<TextBlock Text="Selected Items:" />
<ListBox ItemsSource="{Binding SelectedItems, ElementName=lb}" />
</Grid>
</Grid>
lb.ItemsSource = new[] {
new Student{Name="狗", Age=30}, new Student{Name="兔", Age=31},
new Student{Name="蛇", Age=18}, new Student{Name="雞", Age=22},
new Student{Name="貓", Age=24}, new Student{Name="青蛙", Age=28},
new Student{Name="猴", Age=19}
};

繼承 ListBox/ListBoxItem 保留原生選取邏輯,擴充 IsChecked 與 CheckedItems 實現真正的雙模式互動。完整原始碼已上傳資源區(0 積分下載)。