Software Develop/C# , .NET , WPF

ListBox 사용하기

jaywapp 2023. 2. 7. 13:05

프로젝트를 진행하다가 보면 목록을 보여줘야 하는 경우가 빈번하게 발생한다. 그럴 때마다 어떤 Control을 사용하는게 좋을 지 고민하게 된다. 필자의 경우에는 Column 정보가 필요할 경우에는 ListView를, 각 Cell 단위의 작업이 필요할 경우에는 DataGrid를 Column 정보없이 하나 하나의 Item이 중요할 때에는 ListBox를 사용한다. 가장 최근에는 ListBox를 이용하여 원하는 목록 UI를 구성했다.

 

그래서 오늘은 ListBox를 사용하는 방법에 대해 설명하고자 한다.

 


ListBox란?

ListBox는 기본적으로 ListBoxItem의 목록을 나타낸다. ListBoxItem에 어떠한 내용이 있던간에 ListBox는 해당 ListBoxItem 목록을 나타내고 이를 사용하는 기능을 제공한다.

 

ListBoxItem이란?

ListBox 목록을 구성하는 단위이다. ListBoxItem 역시 하나의 ContentControl로써 동작한다. 기본적인 예제를 살펴보자.

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <ListBox>
            <ListBoxItem>Item1</ListBoxItem>
            <ListBoxItem>Item2</ListBoxItem>
            <ListBoxItem>Item3</ListBoxItem>
        </ListBox>
    </StackPanel>
</Window>

위 내용의 코드를 빌드하면 아래와 같은 결과가 나타난다.

 

위와 같은 예제만 본다면 ListBoxItem이 텍스트를 데이터를 출력하는 요소라고 오해할 수 있다. ListBoxItem을 더 자세히 살펴보자.

 

[DefaultEvent("Selected")]
public class ListBoxItem : ContentControl
{
    public static readonly DependencyProperty IsSelectedProperty;
    public static readonly RoutedEvent SelectedEvent;
    public static readonly RoutedEvent UnselectedEvent;

    public ListBoxItem();

    [Bindable(true)]
    [Category("Appearance")]
    public bool IsSelected { get; set; }

    public event RoutedEventHandler Selected;
    public event RoutedEventHandler Unselected;
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnMouseEnter(MouseEventArgs e);
    protected override void OnMouseLeave(MouseEventArgs e);
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e);
    protected override void OnMouseRightButtonDown(MouseButtonEventArgs e);
    protected virtual void OnSelected(RoutedEventArgs e);
    protected virtual void OnUnselected(RoutedEventArgs e);
    protected internal override void OnVisualParentChanged(DependencyObject oldParent);
}

 

여기서 중요한 부분은 ListBoxItem는 기본적으로 ContentControl이라는 점이다. 하나의 UI Control로 구성이 되어있고 하위 Content 객체를 가지고 있다. ListBoxItem에 Text를 할당하여 출력할 때에는 내부 Content 객체에 Text가 할당되어 출력이 되었을 뿐이다.

 

그렇다면 아래 예제와 결과를 살펴보자.

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <ListBox>
            <ListBoxItem>Item1</ListBoxItem>
            <ListBoxItem>
                <StackPanel>
                    <Button Content="Button1"/>
                    <Button Content="Button2"/>
                    <Button Content="Button3"/>
                </StackPanel>
            </ListBoxItem>
            <ListBoxItem>
                <CheckBox Content="CheckBox"/>
            </ListBoxItem>
        </ListBox>
    </StackPanel>
</Window>

위처럼 ListBoxItem은 하나의 ContentControl로써 내부에 정의된 Content 객체를 출력한다.

첫번째 Item은 Text를 직접 Content 객체에 할당했고, 두 번째 Item은 여러 버튼을 담은 StackPanel을 할당했다. 세번째 Item의 경우는 CheckBox Control을 직접 할당했다.

 

Binding과 ItemTemplate

ListBox를 사용할 때, ListBoxItem을 직접 xaml단에서 추가할 수도 있고 비하인드 코드에서 코드로써 추가할 수도 있지만 Binding을 통해 원하는 목록을 ListBox로써 원하는 모습으로 출력할 수도 있다.

 

지금부터의 예시는 편의를 위해 ReactiveUI를 사용한 MVVM 패턴으로 구성 되어있다.

2022.02.28 - [Software Develop/C#, .NET] - ReactiveUI

2022.02.24 - [Software Develop/Design Pattern] - MVVM 패턴 (Model - View - ViewModel)

 

ListBox의 ItemTemplate를 정의하면 되는데 이는 ListBoxItem에 표현될 객체가 어떻게 보여질지에 대한 Control이라고 생각하면 된다. 아래 예시를 보자.

Xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <ListBox ItemsSource="{Binding Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- -->
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition Width="100"/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="0" Foreground="Blue" TextAlignment="Center" Text="{Binding Name}"/>
                        <TextBlock Grid.Column="1" Foreground="Red" TextAlignment="Center" Text="{Binding Age}"/>
                        <TextBlock Grid.Column="2" Foreground="Green" TextAlignment="Center" Text="{Binding Job}"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Window>

ViewModel

using ReactiveUI;
using System.Collections.ObjectModel;

namespace WpfApp1
{
    public class MainWindowViewModel : ReactiveObject
    {
        private ObservableCollection<Item> _items = new ObservableCollection<Item>();

        public ObservableCollection<Item> Items
        {
            get => _items;
            set => this.RaiseAndSetIfChanged(ref _items, value);
        }

        public MainWindowViewModel()
        {
            Items = new ObservableCollection<Item>()
            {
                new Item("철수", 19 , "학생"),
                new Item("영희", 25 , "직장인"),
            };
        }
    }
}

Item

using ReactiveUI;

namespace WpfApp1
{
    public class Item : ReactiveObject
    {
        public string Name { get; }
        public int Age { get; }
        public string Job { get; }

        public Item(string name, int age, string job)
        {
            Name = name;
            Age = age;
            Job = job;
        }
    }
}

결과

 

위의 예시처럼 ListBoxItem에서 표현될 방식을 ItemTemplate에 정의하여 ListBox를 나타낼 수 있다.

 

ItemTemplate UserControl 적용

이 부분은 ListBox를 사용하는 xaml 코드를 더욱 간단하고 깔끔하게 구현하기 위하여 제안하는 방식이다. 

ItemTemplate에 정의될 내용을 UserControl로 분리하여 Binding하면 훨씬 구조적으로 개념을 분리할 수 있으며 코드 관리 자체가 깔끔해진다.

 

Xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wpfapp1="clr-namespace:WpfApp1"
        mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <ListBox ItemsSource="{Binding Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- Item이 바인딩 됨 -->
                    <wpfapp1:ItemView DataContext="{Binding}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Window>

ItemView

DataContext에 Item 객체가 Binding된다.

<UserControl x:Class="WpfApp1.ItemView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Foreground="Blue" TextAlignment="Center" Text="{Binding Name}"/>
        <TextBlock Grid.Column="1" Foreground="Red" TextAlignment="Center" Text="{Binding Age}"/>
        <TextBlock Grid.Column="2" Foreground="Green" TextAlignment="Center" Text="{Binding Job}"/>
    </Grid>
</UserControl>

'Software Develop > C# , .NET , WPF' 카테고리의 다른 글

private, protected method 단위 테스트 코드 작성하기  (0) 2023.02.07
Adonis UI 사용기  (0) 2023.02.07
Lazy<T>  (0) 2023.01.16
ReactiveUI  (0) 2022.02.28
Crawling (Selenium)  (0) 2022.02.16