首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WPF 自製 CheckListBox 控件:同時支援選取與勾選的完整實作

     編輯:關於.NET

動機

現有 Toolkit 的 CheckListBox 原始碼過於複雜,且單純重定義 ListBoxItem 模板只能加上勾選 UI,無法提供 IsChecked 邏輯屬性與 CheckedItems 集合。決定從 ListBox/ListBoxItem 繼承,打造真正支援「可選取、可勾選」的控件。

架構設計

  • CheckableListBoxItem 繼承 ListBoxItem,新增 IsChecked 依賴屬性與 Checked/UnChecked 路由事件。
  • CheckListBox 繼承 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));
}

ControlTemplate 關鍵點

  • 使用 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}
};

CheckListBox 執行動畫

小結

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

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved