티스토리 뷰

반응형

2022.06.27 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part5 Converter

2022.06.15 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part4-2 Data Binding

2022.06.13 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part4-1 Data Binding

2022.06.07 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-3

2022.06.02 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-2

2022.05.30 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-1

2022.05.11 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part2

2022.05.09 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part1

1. ICommand

  • 뷰에서 사용자의 인터렉션을 뷰모델에 전달하기 위한 인터페이스입니다.
  • 구현 클래스
    • Microsoft.Toolkit.Mvvm에서는 RelayCommand, AsyncRelayCommand를 사용할 수 있습니다.
    • 다른 라이브러리의 구현 클래스는 여기를 참고하시기 바랍니다.
  • Binding 방법
    • 컨트롤에 Command 프로퍼티가 있는 경우 직접 바인딩 할 수 있습니다.
    • 컨트롤에 Command 프로퍼티가 없는 경우 EventTrigger와 InvokeCommandAction을 이용하여 바인딩 할 수 있습니다.
    • CommandParameter
      • Command를 실행하기 위해 필요한 추가 데이터를 전달할 때 사용합니다.

CommandParameter로 뷰의 컨트롤을 직접 넘겨서 뷰모델에서 사용하는 것은 추천하지 않습니다.
특히, field에 컨트롤을 연결해 놓고 사용하는 것은 mvvm 패턴을 사용하지 않는 것과 동일하다고 보시면 될 것 같습니다.

2. RelayCommand 생성과 사용

MainViewModel.cs

//IRelayCommand는 ICommand를 상속받았고, NotifyCanExecuteChanged()를 호출하기 위해 사용
public IRelayCommand NormalCommand { get; set; }

public MainViewModel()
{  
    NormalCommand = new RelayCommand(OnNormal);
}

private void OnNormal()
{
    Message = "Normal Button을 클릭했습니다.";
}

MainWindow.xaml

<Button Command="{Binding NormalCommand}" Content="Normal" />

3. AsyncRelayCommand 생성과 사용

AsyncRelayCommand 처럼 Task 대응 커맨드는 Microsoft.toolkit.mvvm nuget에만 존재 합니다.

MainViewModel.cs

  • RelayCommand와 다른점은 실행부를 Task로 만들어서 바로 사용할 수 있다는 점입니다.
  • 물론 RealyCommand를 이용해서 Task 메소드를 실행할 수도 있습니다.
//IAsyncRelayCommand는 IRelayCommand와 ICommand를 상속 받은 인터페이스 입니다.
public IAsyncRelayCommand AsyncCommand { get; set; }

public MainViewModel()
{
    //AsyncRelayCommand를 이용해서 Task 메소드를 호출하는 방법
    AsyncCommand = new AsyncRelayCommand(OnNormalAsync);
    //RelayCommand를 이용해서 Task 메소드 호출하는 방법
    //NormalCommand = new RelayCommand(async () => await OnNormalAsync());
}

private async Task OnNormalAsync()
{
    Message = "비동기 커맨드 시작";
    await Task.Delay(3000);
    Message = "비동기 커맨드 종료";
}

MainWindow.xaml

<Button Command="{Binding AsyncCommand}" Content="Async" />

4. Command property가 없는 컨트롤에서 사용하기

CommandParameter로 전달하는 데이터는 모델이나, EventArgs 정도를 전달하는 것을 추천합니다.
UIElement를 CommandParameter로 전달해서 사용하는것은 추천하지 않습니다.

 

Microsoft.Xaml.Behaviors.Wpf nuget package가 필요합니다.

MainViewModel.cs

  • SelectionChangedCommand 생성하면서, CommandParamerter로 Person이 입력된다고 정의합니다.
  • OnSelectionChanged 메서드에서는 전달된 Person 모델 값을 입력 받습니다.
  • CommandParameter 값을 전달 받기 위해서는 어떤 데이터 유형이 전달되는지를 반드시 지정해야 합니다.
  • 어떤 유형인지 잘 모른다면, object로 지정하고, 메소드에서 형변환해서 사용합니다.
private IList<Person> _people;
/// <summary>
/// People
/// </summary>
public IList<Person> People
{
    get { return _people; }
    set { SetProperty(ref _people, value); }
}

public ICommand SelectionChangedCommand { get; set; }

public MainViewModel()
{
    SelectionChangedCommand = new RelayCommand<Person>(OnSelectionChanged);
}    

private void OnSelectionChanged(Person obj)
{
    Message = obj == null ? "선택한 아이템이 없습니다." : $"{obj.Name}을 선택했습니다.";
}

MainWindow.xaml

  • Behavior 네임스페이스 xmlns:b=http://schemas.microsoft.com/xaml/behaviors 를 추가합니다.
  • ListView 컨트롤의 이벤트를 이용하기 위해서는 컨트롤 내부에 아래와 같은 코드를 추가해야 합니다.
  • EventTrigger를 추가하고 EventName을 입력합니다.
  • 내부에 InvokeCommandAction의 Command, CommandParameter 프로퍼티를 이용해서 ViewModel의 SelectionChangedCommand를 실행 시킬 수 있습니다.
<ListView x:Name="PeopleListView" ItemsSource="{Binding People}">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="SelectionChanged">
            <b:InvokeCommandAction Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=PeopleListView, Path=SelectedItem}" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
    <ListView.View>
        <GridView>
            <GridView.Columns>
                <GridViewColumn DisplayMemberBinding="{Binding Id}" Header="Id" />
                <GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" />
                <GridViewColumn DisplayMemberBinding="{Binding Sex}" Header="Sex" />
                <GridViewColumn DisplayMemberBinding="{Binding Description}" Header="Description" />
            </GridView.Columns>
        </GridView>
    </ListView.View>
</ListView>

5. 이벤트를 Command로 전달할 때 EventArgs도 함께 전달하기

MainViewModel.cs

AddingNewItemCommand를 이용해서 EventArgs를 전달받기 위해서 object유형으로 지정합니다.

OnAddingNewItem 메서드에서 obj를 AddingNewItemEventArgs 형으로 변환해서 사용합니다.

 

public ICommand AddingNewItemCommand { get; set; }

public MainViewMode()
{
    AddingNewItemCommand = new RelayCommand<object>(OnAddingNewItem);
}

private void OnAddingNewItem(object obj)
{
    if(obj is AddingNewItemEventArgs args)
    {
        args.NewItem = new Person { Id = People.Max(p => p.Id) + 1 };
    }
}

MainWindow.xaml

  • DataGrid의 AddingNewItem 이벤트를 이용하기 위해 컨트롤 내부에 Interaction.Triggers -> EventTrigger -> InvokeCommandAction을 추가합니다.
  • InvokeCommandAction에 PassEventArgsToCommand를 True로 지정하면, EventArgs가 CommandParameter로 전달 됩니다.
<DataGrid ItemsSource="{Binding People}">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="AddingNewItem">
            <b:InvokeCommandAction Command="{Binding AddingNewItemCommand}" PassEventArgsToCommand="True" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
</DataGrid>
InvokeCommandAction에는 Converter를 연결해서, CommandParameter에 전달되는 데이터를 정규화 시킬 수도 있습니다.

6. Command의 IsEnabled 변경

Command 객체를 생성할 때 Command의 사용 여부를 변경할 수 있는 Func<bool> canExecute를 지정할 수 있습니다.

canExecute 조건을 확인해서 결과가 true이면 Command 객체를 사용할 수 있으며, false이면 사용할 수 없습니다.

Command 객체와 연결된 Button의 IsEnabled 속성을 변경하는데 사용할 수 있습니다.

MainViewModel.cs

  • SelectedPerson 프로퍼티를 추가합니다.
  • SelectedPerson 프로퍼티에 value가 입력되면, person => {} 구분이 실행 됩니다. 이때 ref 문구가 없는 것에 유의하시기 바랍니다.
  • person => {} 구분에는 DelectCommand의 사용 가능 여부를 확인하는 NotifyCanExecuteChanged() 메서드를 호출하고 있습니다.
  • DeletedCommand를 추가합니다.
  • DeletedCommand를 RelayCommand로 생성합니다. 이때, 사용 가능 여부를 확인하는 조건은 SelectedPerson != null 입니다.
  • 사용 가능 여부를 확인하는 조건은 처음 생성시 확인하고, 이 후에는 NotifyCanExecuteChanged() 메서드를 호출해야 재확인 할 수 있습니다.
  • OnSelectionChanged() 메서드에서 SelectedPerson에 선택된 Person 객체를 입력합니다.
  • 순서를 정리하면 OnSelectionChanged() 메서드가 실행되고, 선택된 Person 객체를 SelectedPerson에 입력하면, SelectedPerson 프로퍼티에 연결되어있는 person => {} action이 실행됩니다. 내부에서 DeleteCommand의 사용 가능 여부를 확인할 수 있는 NotifyCanExecuteChanged()메소드를 실행해서 사용 가능 여부를 재확인 합니다.
여기서 사용하는 SetProperty의 기능과 NotifyCanExecuteChanged() 메소드명은 Microsoft.toolkit.mvvm에서만 지원됩니다. 다른 nuget package에서는 다른 방식과 다른 이름을 가지고 있습니다. 사용하는 nuget package의 도움말을 참고하시기 바랍니다.
private Person _selectedPerson;
/// <summary>
/// 선택된 Person
/// </summary>
public Person SelectedPerson
{
    get { return _selectedPerson; }
    set
    {
        SetProperty(_selectedPerson, value,
            person =>
            {
                _selectedPerson = person;
                DeleteCommand.NotifyCanExecuteChanged();
            });
    }
}

public IRelayCommand DeleteCommand { get; set; }

public MainViewModel()
{
    DeleteCommand = new RelayCommand(OnDelete, () => SelectedPerson != null);
    
    People = new ObservableCollection<Person>
    {
        new Person { Id = 1, Name = "kaki104", Sex = true, Description = "hehehe" },
        //...
    };
}

private void OnDelete()
{
    if (MessageBox.Show("선택한 아이템을 삭제하시겠습니까?", "삭제 확인", MessageBoxButton.YesNo, MessageBoxImage.Question)
        == MessageBoxResult.Yes)
    {
        People.Remove(SelectedPerson);
    }
}

private void OnSelectionChanged(Person obj)
{
    Message = obj == null ? "선택한 아이템이 없습니다." : $"{obj.Name}을 선택했습니다.";
    SelectedPerson = obj;
}

MainWindow.xaml

  • TextBlock의 Text 프로퍼티에 SelectedPerson.Name을 바인딩해서, SelectedPerson이 어떤 객체인지 확인합니다.
  • Delete Button에 DeleteCommand를 바인딩했습니다.
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
    <TextBlock Text="{Binding SelectedPerson.Name}" />
    <Button
        Width="70"
        Margin="10,0,0,0"
        Command="{Binding DeleteCommand}"
        Content="Delete" />
</StackPanel>
<ListView x:Name="PeopleListView" ItemsSource="{Binding People}">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="SelectionChanged">
            <b:InvokeCommandAction Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=PeopleListView, Path=SelectedItem}" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
    <ListView.View>
        <GridView>
            <GridView.Columns>
                <GridViewColumn DisplayMemberBinding="{Binding Id}" Header="Id" />
                <GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" />
                <GridViewColumn DisplayMemberBinding="{Binding Sex}" Header="Sex" />
                <GridViewColumn DisplayMemberBinding="{Binding Description}" Header="Description" />
            </GridView.Columns>
        </GridView>
    </ListView.View>
</ListView>

7. 실행 화면

선택 전

선택 후

8. 소스

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

 

GitHub - kaki104/WpfTest

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

github.com

커맨드에 대해서 궁금하신 사항은 하단에 리플 남겨주시면 확인하고 추가하겠습니다.

반응형
댓글
댓글쓰기 폼
반응형
Total
698,835
Today
92
Yesterday
213
«   2022/08   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
08-14 22:47
글 보관함