RelayCommand CanExecute 연결고리가 어떻게 되는지요?

RelayCommand를 사용하는데 텍스트 박스에 입력이 될때 length가 0보다 크면 버튼을 활성화하려고 합니다.

그럼 버튼design코드에 버튼 command parameter에 textbox 객체 넘겨주면 되는데 텍스트 박스의 길이가 변경되도 버튼 활성화가 되지 않아서 relaycommand를 살펴보고 있습니다.

아래는 RelayCommand인데 add => CommandManager.RequerySuggested += value;
이 부분의 연결고리가 어떻게 되는지 모르겠네요.

Relay Command(Action Execute,Function CanExecute) 생성하고
Design 에서 <Button command="{Binding Execute}" commandparameter={binding tbxInputString}>
이렇게 하면 CommandManager.RequerySuggested += value 이 부분 value에 tbxInputString이 전달되는건지요?

public sealed class RelayCommand : IRelayCommand
{
    readonly Action<object> _execute;

    readonly Func<bool> _canExecute;

    /// <summary>
    /// Event occuring when encapsulated canExecute method is changed.
    /// </summary>
    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    /// <summary>
    /// Creates new instance of <see cref="RelayCommand"/>.
    /// </summary>
    /// <param name="execute">Action to be executed.</param>
    public RelayCommand(Action execute) : this(execute, null)
    {
        // Delegated to RelayCommand(Action execute, Func<bool> canExecute)
    }
}
좋아요 1

먼저 Command는 MVVM일 때 유용합니다. MVVM이 아닐 경우 그냥 이벤트를 사용하면 됩니다.


CanExecuteChanged 이벤트가 발생하면 Command를 소비하는 컨트롤은 Command의 CanExecute()를 호출해서 명령어를 실행할 수 있는지를 확인합니다. 실행할 수 있거나/없는 상태일 경우 컨트롤을 활성화/비활성화 하도록 합니다.

그런데 명령어가 많을 때 이게 번거롭습니다. 그래서 CanExecuteCahgned이벤트는 CommandManager.RequerySuggested이벤트를 대신 사용하기도 합니다.

CommandManager.RequerySuggested이벤트는 CommandManager.InvalidateRequerySuggested() 명령어로 일괄 발생시킬 수 있습니다. 이를 통해 어떤 변화가 생겼을 때 명령어를 처리하는 모든 컨트롤에 일괄 활성화/비활성화 처리를 할 수 있습니다.


텍스트 박스의 입력이 있을 경우 일반적으로 텍스트박스에 값을 바인딩 하며 값이 변경되었을 때 CommandManager.InvalidateRequerySuggested()를 호출하도록 하면 원하는 동작을 할 것으로 보입니다.

좋아요 3

source(textbox) 에서 수정이 될 경우 event를 발생시켜서 명령어를 처리하는 모든 컨트롤러에 통지하는건가보네요.
내부적으로 구독이 어떻게 연결된건지 자세히 봐야겠네요.

좋아요 2

텍스트박스의 값 변경에 따라 CanExecute를 처리하신다면 CommandManager.RequerySuggested까지 갈 필요 없이 Binding Property의 속성을 변경하는 것으로 처리 가능해 보이는데 제가 잘못 이해한 걸까요?

<TextBox Text="{Binding OriginalText, UpdateSourceTrigger=PropertyChanged}" /> 

참조

좋아요 5

위 인용문에 대해 궁금하신것 같아 대략 설명해 드리면

RequerySuggested 이벤트는 내부적으로
PrivateRequerySuggested 이벤트 이름으로 사용됩니다.
PrivateRequerySuggested 는 내부 구현 코드를 살펴보면

if (e.StagingItem.Input.RoutedEvent == Keyboard.KeyUpEvent ||
                     e.StagingItem.Input.RoutedEvent == Mouse.MouseUpEvent ||
                     e.StagingItem.Input.RoutedEvent == Keyboard.GotKeyboardFocusEvent ||
                     e.StagingItem.Input.RoutedEvent == Keyboard.LostKeyboardFocusEvent)
{
                CommandManager.InvalidateRequerySuggested();
}

이렇게 키보드, 마우스 관련 이벤트 발생마다 PrivateRequerySuggested 이벤트를 호출합니다.

[이벤트 호출 구현부]

if (PrivateRequerySuggested != null)
                PrivateRequerySuggested(null, EventArgs.Empty);
좋아요 5

감사합니다. 안되는 이유를 찾아보니 참고한 라이브러리가 CommandManager.RequerySuggested 를 Icommand 상속받은곳에 구현을 안했더라구요… 이걸 알았으면 삽질 안했을 텐데…

좋아요 3

@핸썸가이 좋은 질문 주셔서 감사합니다.
저도 답변 읽어보면서 많이 도움 되었네요.

그리고 잘 해결 하셨으니! 질문과는 벗어나는 내용이지만 저는 CommunityToolkit.Mvvm을 통해 구현하는 방법에 대해서도 간략하게 남겨두겠습니다.

만약 이 라이브러리를 통한 Command 구현을 필요로 하시는 분들에게는 유용할 것입니다. :smile:
(모든 상황에서 만능은 아니지만 지금 같은 상황에서는 아주 심플하고 간단하게 처리할 수 있습니다.)


xaml은 크게 다른게 없네요.

<StackPanel>
    <TextBox Text="{Binding Keywords, UpdateSourceTrigger=PropertyChanged}"/>
    <Button Content="Save" Command="{Binding SaveCommand}"/>
</StackPanel>

ViewModel에서는 CommunityToolkit.Mvvm 라이브러리에서 제공하는 기술 총 동원~
(각각의 어트리뷰트를 유심히 추척해보면 됩니다.)

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace JamesCommand
{
    public partial class MyViewModel : ObservableObject
    {
        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
        private string _keywords;

        private bool CanSave(object o)
        {
            return !string.IsNullOrWhiteSpace(Keywords) && Keywords.Length > 0;
        }

        [RelayCommand(CanExecute = nameof(CanSave))]
        private void Save(object o)
        {
            // Save.
        }
    }
}

결과

CanSave false

image

CanSave true

image

여기까지 입니다~
읽어주셔서 감사합니다. :smile:

좋아요 3