【WPF】自定義一個自刪除的多功能ListBox

2022-12-27 18:00:08

原文地址 https://www.cnblogs.com/younShieh/p/17008534.html

❤如果本文對你有所幫助,不妨點個關注和推薦呀,這是對筆者最大的支援~❤

我需要一個ListBox,他在介面上分為幾列,每列對應一系列的資料。第一行是各資料的標題,支援橫向捲動,豎向只支援資料來源捲動,標題不隨之捲動。視覺上與ListView類似。支援等比拉伸,支援多選,支援從介面去更改內部資料來源,支援子項從ListBox中刪除自己。為了實現這些功能,我決定自定義一個特殊的列表,當然他還是繼承自ListBox。

  1. 首先他支援等比拉伸,且資料分列顯示。首先想到Grid的ColumnDefinitions可以滿足。
<Grid.ColumnDefinitions>
    <ColumnDefinition Width="74*" />
    <ColumnDefinition Width="179*" />
    <ColumnDefinition Width="157*" />
</Grid.ColumnDefinitions>
  1. 支援從介面去更改內部資料來源,支援子項從ListBox中刪除自己。
    為了從介面上滿足功能,需要重寫ItemContainerStyle樣式,繪製出該子項Style。在Style中為了處理事件響應,可以通過在資料來源子項中增加Command來對對應事件進行處理。但是處理邏輯可能有點繁雜,而且單從資料處理而言,子項無法從父項中刪除自己,只能通過視覺樹獲取父ListBox,實現該功能。這樣寫的話程式碼耦合性會有點高,且資料處理時會調取介面處理。我希望資料來源內部只是在處理資料,只能通過介面自上而下的存取資料來源,而不是資料來源和介面都有迴圈呼叫。
    第二種方法,我直接重寫一個控制元件繼承自ListBoxItem,將之前重寫ItemContainerStyle樣式複製到該控制元件前臺,並在後臺程式碼中對相應事件進行處理。程式碼如下:
public partial class TestItem : ListBoxItem
{
    public TestItem()
    {
        InitializeComponent();
    }

    private void Delete_Click(object sender, RoutedEventArgs e)
    {
    }
}

因為我是繼承自ListBoxItem的,所以可以通過

ItemsControl.ItemsControlFromItemContainer(this) is ListBox listBox

獲取到父listbox的物件,獲取到父物件後,就可以從ListBox中刪除自己。或是更改ListBox中繫結的資料來源。

  1. 我們還需要把這個自定義的ListBoxItem放到我們自定義的ListBox中,讓所有子項都應用這個自定義的ListboxItem,而不是預設的ListboxItem。程式碼如下:
internal class TestListBox : ListBox
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new TestItem();
    }
}
  1. 為了實現資料與標題分離的捲動效果,我單獨將標題拿到外部,對標題進行單獨顯示,Listbox只顯示資料。外部滑動條支援整體橫向滑動,Listbox內部滑動條支援內部豎向滑動。給兩個滑動條都加上最小寬度。
<ScrollViewer
            Grid.Row="1"
            Margin="0,20,0,0"
            HorizontalScrollBarVisibility="Auto"
            VerticalScrollBarVisibility="Disabled">
            <Border
                MinWidth="{Binding MinWidth, ElementName=listbox}"
                BorderBrush="#DFDFDF"
                BorderThickness="1">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="52" />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="79*" />
                        <ColumnDefinition Width="104*" />
                        <ColumnDefinition Width="80*" />
                    </Grid.ColumnDefinitions>
                    <Border Grid.ColumnSpan="20" Background="#F2F2F2" />
                    <TextBlock Text="ID" />
                    <TextBlock Grid.Column="1" Text="編號" />
                    <TextBlock Grid.Column="2" Text="操作" />
                    <controls:TestListBox
                        x:Name="listbox"
                        Grid.Row="1"
                        Grid.ColumnSpan="3"
                        MinWidth="1100"
                        ItemsSource="{Binding ItemsSourceData}"
                        Style="{StaticResource BaseListBoxStyle.WithOutHorizontalScrollViewer}" />
                </Grid>
            </Border>
        </ScrollViewer>

在Listbox的樣式中取消了橫向滑動條的顯示。

 <Style x:Key="BaseListBoxStyle.WithOutScrollViewer" TargetType="ListBox">
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        <Setter Property="ScrollViewer.CanContentScroll" Value="False" />
        <Setter Property="ScrollViewer.PanningMode" Value="Both" />
        <Setter Property="Stylus.IsFlicksEnabled" Value="False" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBox">
                    <Border
                        x:Name="Bd"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        SnapsToDevicePixels="true">
                        <ScrollViewer
                            Focusable="false"
                            HorizontalScrollBarVisibility="Disabled"
                            VerticalScrollBarVisibility="Auto">
                            <ItemsPresenter Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>