티스토리 뷰

반응형

2022.03.14 - [WPF] - [기초] ComboBox, ListBox 중요 프로퍼티 사용법 part1

 

이번 포스트에서는 SelectionChanged 이벤트를 이용하는 기본 방법과 이벤트 Arguments를 사용하는 방법에 대해서 알아 보도록 하겠습니다.

 

먼저, part1에서 만들었던, 리스트박스에서 아이템을 선택하면 Delete 버튼이 활성화되는 예제를 SelectionChanged 이벤트를 이용해서 만들어 보도록 하겠습니다.

 

1. 컨트롤의 이벤트가 발생했을 때 뷰모델에 전달하는 방법

컨트롤의 이벤트를 뷰모델에 전달하기 위해서는 Microsoft.Xaml.Behaviors.Wpf nuget package가 필요 합니다. 프로젝트에 해당 nuget package를 설치합니다. 이 녀석에 대한 더 자세한 사항은 여기를 참고하시기 바랍니다.

방금 추가한 nuget package를 xaml에서 사용하기 위해서는 xmlns를 입력해야합니다. xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
  • 사용하려는 컨트롤 내부에 <b:Interaction.Triggers></b:Interaction.Triggers>를 이용해서 트리거를 추가하도록 합니다.
  • 여기서 사용할 트리거는 EventTrigger입니다. 이벤트 트리거 말고도 여러가지 트리거가 있으니 찾아보고, 사용하면 더 좋은 응용 프로그램을 만들 수 있습니다.
  • EventTrigger에는 EventName을 입력해서 이벤트가 발생할때 트리거를 실행할 수 있습니다. 여기서는 SelectionChanged 이벤트가 발생하면 트리거가 실행됩니다.
  • InvokeCommandAction는 트리거가 발생되면 Command를 실행하는 용도로 사용합니다. 
  • 아래 코드를 해석하자면, ListBox에서 SelectionChanged 이벤트가 발생하면, 뷰모델의 ListSelectionChangedCommand를 실행해라~라는 것이 됩니다.
        <ListBox
            Grid.Row="1"
            Grid.Column="0"
            DisplayMemberPath="Name"
            ItemsSource="{Binding Persons}"
            SelectedItem="{Binding SelectedListItem, Mode=TwoWay}">
            <b:Interaction.Triggers>
                <b:EventTrigger EventName="SelectionChanged">
                    <b:InvokeCommandAction Command="{Binding ListSelectionChangedCommand}" />
                </b:EventTrigger>
            </b:Interaction.Triggers>
        </ListBox>

Next1Window.xaml 전체 소스

<Window
    x:Class="BasicControlSample.Next1Window"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:BasicControlSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="Next1Window"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <!--  SelectionChanged 사용  -->
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <TextBlock
                Grid.Row="0"
                Grid.Column="0"
                Text="ListBox5" />
            <TextBlock
                Grid.Row="0"
                Grid.Column="0"
                Margin="4,0"
                HorizontalAlignment="Right"
                Text="{Binding SelectedListItem.Name}" />
            <Button
                Grid.Row="1"
                Command="{Binding DeleteListItemCommand}"
                Content="Delete ListItem" />
        </Grid>
        <ListBox
            Grid.Row="1"
            Grid.Column="0"
            DisplayMemberPath="Name"
            ItemsSource="{Binding Persons}"
            SelectedItem="{Binding SelectedListItem, Mode=TwoWay}">
            <b:Interaction.Triggers>
                <b:EventTrigger EventName="SelectionChanged">
                    <b:InvokeCommandAction Command="{Binding ListSelectionChangedCommand}" />
                </b:EventTrigger>
            </b:Interaction.Triggers>
        </ListBox>
        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <TextBlock Text="ComboBox5" />
            <TextBlock
                Margin="4,0"
                HorizontalAlignment="Right"
                Text="{Binding SelectedComboItem.Name}" />
            <Button
                Grid.Row="1"
                Command="{Binding DeleteComboItemCommand}"
                Content="Delete ComboItem" />
        </Grid>
        <ComboBox
            Grid.Row="3"
            Grid.Column="0"
            VerticalAlignment="Top"
            DisplayMemberPath="Name"
            ItemsSource="{Binding Persons}"
            SelectedItem="{Binding SelectedComboItem, Mode=TwoWay}">
            <b:Interaction.Triggers>
                <b:EventTrigger EventName="SelectionChanged">
                    <b:InvokeCommandAction Command="{Binding ComboSelectionChangedCommand}" />
                </b:EventTrigger>
            </b:Interaction.Triggers>
        </ComboBox>

    </Grid>
</Window>

Next1ViewModel.cs 전체 소스

  • ListSelectionChangedCommand가 실행되면, OnListSelectionChanged 메소드에서, DeleteListItemCommand.NotifyCanExecuteChanged(); 를 호출해서, DeleteListItemCommand의 사용 가능 여부를 확인해줍니다.
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace BasicControlSample
{
    public class Next1ViewModel : ObservableObject
    {
        private IList<Person> _persons = new ObservableCollection<Person>
            {
                new Person{ Name = "kaki0104", Sex = true, Age = 11, Address = "Seoul1" },
                new Person{ Name = "kaki0143", Sex = false, Age = 150, Address = "Seoul140" },
            };
        public IList<Person> Persons { get { return _persons; } }

        private Person _selectedListItem;
        public Person SelectedListItem
        {
            get { return _selectedListItem; }
            set { SetProperty(ref _selectedListItem, value); }
        }

        private Person _selectedComboItem;
        public Person SelectedComboItem
        {
            get { return _selectedComboItem; }
            set { SetProperty(ref _selectedComboItem, value); }
        }

        /// <summary>
        /// 리스트 셀렉션 체인지 이벤트 커맨드
        /// </summary>
        public IRelayCommand ListSelectionChangedCommand { get; set; }
        /// <summary>
        /// 리스트 아이템 삭제 커맨드
        /// </summary>
        public IRelayCommand DeleteListItemCommand { get; set; }
        /// <summary>
        /// 콤보 셀렉션 체인지 이벤트 커맨드
        /// </summary>
        public IRelayCommand ComboSelectionChangedCommand { get; set; }
        /// <summary>
        /// 콤보 아이템 삭제 커맨드
        /// </summary>
        public IRelayCommand DeleteComboItemCommand { get; set; }

        public Next1ViewModel()
        {
            Init();
        }

        private void Init()
        {
            ListSelectionChangedCommand = new RelayCommand(OnListSelectionChanged);
            DeleteListItemCommand = new RelayCommand(OnDeleteListItem,
                                        () => SelectedListItem != null && SelectedListItem.Age % 2 == 0);
            ComboSelectionChangedCommand = new RelayCommand(OnComboSelectionChanged);
            DeleteComboItemCommand = new RelayCommand(OnDeleteComboItem,
                                        () => SelectedComboItem != null && SelectedComboItem.Age % 2 == 1);
        }

        private void OnDeleteComboItem()
        {
            Persons.Remove(SelectedComboItem);
        }

        private void OnDeleteListItem()
        {
            Persons.Remove(SelectedListItem);
        }

        private void OnComboSelectionChanged()
        {
            DeleteComboItemCommand.NotifyCanExecuteChanged();
        }

        private void OnListSelectionChanged()
        {
            DeleteListItemCommand.NotifyCanExecuteChanged();
        }
    }
}

실행 화면

시작하면, Delete ListItem 버튼은 비활성 상태입니다. 

SelectedItem이 변경되면서, SelectionChanged 이벤트가 발생했고, ListSelectionChangedCommand가 실행되면서, Delete ListItem 버튼의 활성 여부가 변경 되는 것을 확인 할 수 있습니다.

2. 이벤트 Arguments를 뷰모델에 전달하는 방법

위의 방법만 보면, 기존 PropertyChanged 이벤트를 이용하는 방법과의 차이가 거의 없습니다. 딱히 좋아 보이는 것도 없는데 왜 사용하는 것일까요?

이벤트가 발생했을 때 커맨드를 실행하는 방법은 이벤트의 Arguments를 뷰모델에도 전달할 수 있습니다. 이제 그 방법을 알아 보도록 하겠습니다.

  • InvokeCommandAction에는 PassEventArgsToCommand라는 프로퍼티가 있으며 이 프로퍼티가 True이면, 이벤트 아규먼트를 CommandParameter로 전달하도록 되어있습니다.
        <ListBox
            Grid.Row="1"
            Grid.Column="0"
            DisplayMemberPath="Name"
            ItemsSource="{Binding Persons}"
            SelectedItem="{Binding SelectedListItem, Mode=TwoWay}">
            <b:Interaction.Triggers>
                <b:EventTrigger EventName="SelectionChanged">
                    <b:InvokeCommandAction Command="{Binding ListSelectionChangedCommand}" PassEventArgsToCommand="True" />
                </b:EventTrigger>
            </b:Interaction.Triggers>
        </ListBox>
  • Init 메소드에서 RelayCommand를 생성할 때 <object>를 커맨드 파라메터로 받을 수 있게 선언하고, OnListSelectionChanged(object arg)로 수정을 하면, 커맨드 파라메터로 전달된 이벤트 아규먼트가 들어오게 됩니다.
  • var eventArgs = arg as SelectionChangedEventArgs; 이렇게해서 arg를 eventArgs로 변경해서 내부에서 사용할 수 있습니다.
            ListSelectionChangedCommand = new RelayCommand<object>(OnListSelectionChanged);

        private void OnListSelectionChanged(object arg)
        {
            var eventArgs = arg as SelectionChangedEventArgs;
            if(eventArgs == null)
            {
                return;
            }
            Debug.WriteLine($"List AddedItems.Count : {eventArgs.AddedItems.Count}");
            Debug.WriteLine($"List RemovedItems.Count : {eventArgs.RemovedItems.Count}");
            DeleteListItemCommand.NotifyCanExecuteChanged();
        }

이렇게 이벤트가 발생했을 때 커맨드를 실행하면서, 이벤트 아규먼트까지 넘기고 사용하는 방법을 알아 보았습니다.

제가 기본적인 사용법에 대해서는 포스팅을 하지만, 이 내용을 이해해서, 실제 프로젝트를 할 때는 응용해서 여러가지 기능을 만들어서 사용해야 합니다. 이 포스트에 대해 궁금하신 사항은 질문 남겨주시면 답변드리도록 하겠습니다.

3. 소스

WpfTest/BasicControlSample at master · kaki104/WpfTest (github.com)

 

GitHub - kaki104/WpfTest

Contribute to kaki104/WpfTest development by creating an account on GitHub.

github.com

PS.

SelectedValue, SelectedValuePath, SelectedIndex 프로퍼티의 사용법은 다음 포스트에서 마무리 하도록 하겠습니다.

반응형
댓글