TOC

This article has been localized into Polish by the community.

Kontrolki listy:

Kontrolka ListBox

W ostatnim artykule przyjrzeliśmy się ItemsControl, prawdopodobnie jest to najprostsza lista w WPF. Kontrolka ListBox jest kolejną kontrolką w kolejce, która dodaje trochę więcej funkcjonalności. Jedną z głównych różnic jest to, że w kontrolce ListBox można zaznaczać, pozwala to końcowemu użytkownikowi na wybranie jednego lub wielu elementów z listy i automatycznie pokazać informację zwrotną.

Oto przykład bardzo prostej kontrolki ListBox:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxSample" Height="120" Width="200">
    <Grid Margin="10">
		<ListBox>
			<ListBoxItem>ListBox Item #1</ListBoxItem>
			<ListBoxItem>ListBox Item #2</ListBoxItem>
			<ListBoxItem>ListBox Item #3</ListBoxItem>
		</ListBox>
	</Grid>
</Window>

To jest tak proste, jak to tylko możliwe: Deklarujemy kontrolkę ListBox i w środku niej, deklarujemy trzy kontrolki ListBoxItem, każdą z jej własnym tekstem. Jednakże kontrolka ListBoxItem to właściwie ContentControl i możemy dla niej zdefiniować niestandardową zawartość:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxSample" Height="120" Width="200">
	<Grid Margin="10">
		<ListBox>
			<ListBoxItem>
				<StackPanel Orientation="Horizontal">
					<Image Source="/WpfTutorialSamples;component/Images/bullet_blue.png" />
					<TextBlock>ListBox Item #1</TextBlock>
				</StackPanel>
			</ListBoxItem>
			<ListBoxItem>
				<StackPanel Orientation="Horizontal">
					<Image Source="/WpfTutorialSamples;component/Images/bullet_green.png" />
					<TextBlock>ListBox Item #2</TextBlock>
				</StackPanel>
			</ListBoxItem>
			<ListBoxItem>
				<StackPanel Orientation="Horizontal">
					<Image Source="/WpfTutorialSamples;component/Images/bullet_red.png" />
					<TextBlock>ListBox Item #3</TextBlock>
				</StackPanel>
			</ListBoxItem>
		</ListBox>
	</Grid>
</Window>

Dla każdej z kontrolek ListBoxItem dodajemy StackPanel, w którym dodajemy Image i TextBlock. Daje to nam pełną kontrolę nad zawartością a także renderowaniem tekstu, co można zobaczyć na zrzucie ekranu, gdzie dla każdego numerku został użyty inny kolor.

Ze zrzutu ekranu można jeszcze zobaczyć kolejną różnicę pomiędzy kontrolką ItemsControle a Listbox: Domyślnie obramowanie jest pokazane na około kontrolki, co sprawia że wygląda ona jak realna kontrolka a nie po prostu wynik.

Wiązanie danych kontrolki ListBox

Manualne definiowanie elementów kontrolki ListBox daje dobry przykład ale przez większość czasu, twoje kontrolki będą wypełnione elementami ze źródła danych używając wiązania danych. Domyślnie, jeśli powiążesz ze sobą list elementów oraz kontrolkę ListBox, to ich metoda ToString() zostanie użyta do przedstawienia każdego z przedmiotów. Bardzo rzadko będziesz tego używać, ale na szczęście możemy łatwo zdeklarować szablon który będzie użyty do renderowania każdego z elementów.

Użyłem ponownie przykład TODO z artykułu o kontrolkach ItemsControl, gdzie stworzyliśmy fajną listę TODO, używając prostej klasy związanej z kodem oraz w tym przypadku kontrolki ListBox dla wizualnej reprezentacji. Oto przykład:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxDataBindingSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxDataBindingSample" Height="150" Width="300">
    <Grid Margin="10">
		<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<Grid Margin="0,2">
						<Grid.ColumnDefinitions>
							<ColumnDefinition Width="*" />
							<ColumnDefinition Width="100" />
						</Grid.ColumnDefinitions>
						<TextBlock Text="{Binding Title}" />
						<ProgressBar Grid.Column="1" Minimum="0" Maximum="100" Value="{Binding Completion}" />
					</Grid>
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
	</Grid>
</Window>
using System;
using System.Windows;
using System.Collections.Generic;

namespace WpfTutorialSamples.ListBox_control
{
	public partial class ListBoxDataBindingSample : Window
	{
		public ListBoxDataBindingSample()
		{
			InitializeComponent();
			List<TodoItem> items = new List<TodoItem>();
			items.Add(new TodoItem() { Title = "Complete this WPF tutorial", Completion = 45 });
			items.Add(new TodoItem() { Title = "Learn C#", Completion = 80 });
			items.Add(new TodoItem() { Title = "Wash the car", Completion = 0 });

			lbTodoList.ItemsSource = items;
		}
	}

	public class TodoItem
	{
		public string Title { get; set; }
		public int Completion { get; set; }
	}
}

Cała magia dzieje się w kontrolce ItemTemplate, którą zdefiniowaliśmy dla kontrolki ListBox. W niej określamy, że każdy element kontrolki ListBox powinien składać się z kontrolek Grid podzielonej na dwie kolumny, wraz z kontrolką TextBlock pokazującą tytuł w pierwszej, a kontrolką ProgressBar pokazującą stan ukończenia w drugiej kolumnie. Aby pozyskać te wartości, używamy bardzo prostego wiązania danych, które jest wytłumaczone w sekcji o wiązaniu danych tego poradnika.

W pliku związanym z kodem, zadeklarowaliśmy bardzo prostą klasę TodoItem, aby trzymała każdy z naszych elementów TODO. W konstruktorze okna, inicjalizujemy listę, do której dodajemy trzy elementy TODO, a następnie przypisujemy ją do własności ItemsSource kontrolki ListBox. Kombinacja własności ItemsSource oraz kontrolki ItemTemplate, którą określiliśmy w części o XAML, jest wszystkim co WPF potrzebuje aby renderować elementy listy TODO.

Zwróć uwagę na własność HorizontalContentAlignment, która jest ustawiona na wartość Stretch w kontrolce ListBox. Domyślne wyrównywanie zawartości dla elementu ListBox to Left, co znaczy że każdy element zajmuje tyle miejsca w poziomie ile potrzebuje. Rezultat? Cóż, nie do końca to co chcemy:

Używając wyrównania Stretch, każdy z elementów jest rozciągnięty tak żeby zajmował całe dostępne miejsce, jak można zauważyć z poprzedniego zrzutu ekranu.

Praca z zaznaczaniem w kontrolce ListBox

Jak wspomniane, kluczowa różnica pomiędzy kontrolkami ItemsControl i ListBox jest taka, że ListBox obsługuje i wyświetla zaznaczone przez użytkownika elementy. W związku z tym, wiele pytań na temat kontrolki ListBox jest związana z zaznaczaniem. Aby pomóc niektórym z tych pytań, stworzyłem większy przykład, pokazujący trochę sztuczek związanych z zaznaczaniem:

<Window x:Class="WpfTutorialSamples.ListBox_control.ListBoxSelectionSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxSelectionSample" Height="250" Width="450">
	<DockPanel Margin="10">
		<StackPanel DockPanel.Dock="Right" Margin="10,0">
			<StackPanel.Resources>
				<Style TargetType="Button">
					<Setter Property="Margin" Value="0,0,0,5" />
				</Style>
			</StackPanel.Resources>
			<TextBlock FontWeight="Bold" Margin="0,0,0,10">ListBox selection</TextBlock>
			<Button Name="btnShowSelectedItem" Click="btnShowSelectedItem_Click">Show selected</Button>
			<Button Name="btnSelectLast" Click="btnSelectLast_Click">Select last</Button>
			<Button Name="btnSelectNext" Click="btnSelectNext_Click">Select next</Button>
			<Button Name="btnSelectCSharp" Click="btnSelectCSharp_Click">Select C#</Button>
			<Button Name="btnSelectAll" Click="btnSelectAll_Click">Select all</Button>
		</StackPanel>
		<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch" SelectionMode="Extended" SelectionChanged="lbTodoList_SelectionChanged">
			<ListBox.ItemTemplate>
				<DataTemplate>
					<Grid Margin="0,2">
						<Grid.ColumnDefinitions>
							<ColumnDefinition Width="*" />
							<ColumnDefinition Width="100" />
						</Grid.ColumnDefinitions>
						<TextBlock Text="{Binding Title}" />
						<ProgressBar Grid.Column="1" Minimum="0" Maximum="100" Value="{Binding Completion}" />
					</Grid>
				</DataTemplate>
			</ListBox.ItemTemplate>
		</ListBox>
	</DockPanel>
</Window>
using System;
using System.Windows;
using System.Collections.Generic;

namespace WpfTutorialSamples.ListBox_control
{
	public partial class ListBoxSelectionSample : Window
	{
		public ListBoxSelectionSample()
		{
			InitializeComponent();
			List<TodoItem> items = new List<TodoItem>();
			items.Add(new TodoItem() { Title = "Complete this WPF tutorial", Completion = 45 });
			items.Add(new TodoItem() { Title = "Learn C#", Completion = 80 });
			items.Add(new TodoItem() { Title = "Wash the car", Completion = 0 });

			lbTodoList.ItemsSource = items;
		}

		private void lbTodoList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
		{
			if(lbTodoList.SelectedItem != null)
				this.Title = (lbTodoList.SelectedItem as TodoItem).Title;
		}

		private void btnShowSelectedItem_Click(object sender, RoutedEventArgs e)
		{
			foreach(object o in lbTodoList.SelectedItems)
				MessageBox.Show((o as TodoItem).Title);
		}

		private void btnSelectLast_Click(object sender, RoutedEventArgs e)
		{
			lbTodoList.SelectedIndex = lbTodoList.Items.Count - 1;
		}

		private void btnSelectNext_Click(object sender, RoutedEventArgs e)
		{
			int nextIndex = 0;
			if((lbTodoList.SelectedIndex >= 0) && (lbTodoList.SelectedIndex < (lbTodoList.Items.Count - 1)))
				nextIndex = lbTodoList.SelectedIndex + 1;
			lbTodoList.SelectedIndex = nextIndex;
		}

		private void btnSelectCSharp_Click(object sender, RoutedEventArgs e)
		{
			foreach(object o in lbTodoList.Items)
			{
				if((o is TodoItem) && ((o as TodoItem).Title.Contains("C#")))
				{
					lbTodoList.SelectedItem = o;
					break;
				}
			}
		}

		private void btnSelectAll_Click(object sender, RoutedEventArgs e)
		{
			foreach(object o in lbTodoList.Items)
				lbTodoList.SelectedItems.Add(o);
		}


	}

	public class TodoItem
	{
		public string Title { get; set; }
		public int Completion { get; set; }
	}
}

Jak widzisz, stworzyłem kilka przycisków po prawej stronie kontrolki ListBox, aby uzyskać albo manipulować zaznaczeniem. Ustawiłem również właściwość SelectionMode na Extended, pozwala to na wybranie wielu elementów. Można to zrobić albo programując, jak ja w tym przykładzie, albo przez użytkownika końcowego który trzyma [Ctrl] lub [Shift] klikając na elementy.

Dla każdego z przycisków, zdefiniowałem procedurę obsługi kliknięć w kodzie. Każda akcja powinna być intuicyjna, a użyty kod w C# jest w miarę prosty, lecz jeśli dalej wątpisz w to, spróbuj uruchomić ten kod na twoim komputerze i przetestować różne możliwości w przykładzie.

Podsumowanie

Kontrolka ListBox jest dość podobna do kontrolki ItemsControl i można dla nich użyć wiele takich samych technik. Kontrolka ListBox oferuje trochę więcej funkcjonalności gdy jest jest porównana do kontrolki ItemsControl, szczególnie jeżeli mówimy o obsłudze zaznaczania. Po jeszcze więcej funkcjonalności powinieneś rzucić okiem na kontrolkę ListView, która jest później bardzo dokładnie opisana w tym poradniku, z wieloma artykułami tłumaczących jej działanie.

This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!