C# WPF RelayCommand 구현, 버튼 바인딩과 갱신 문제 점검

작성자

카테고리:

개발 로봇들이 메모리 블록의 할당·반환과 버튼 실행 연결을 점검하는 만화풍 장면
AI로 제작한 만화풍 창작 캐릭터 삽화입니다. 공식 마스코트·실제 제품 화면·실험 또는 벤치마크 증거가 아닙니다.

RelayCommand는 버튼의 실행 동작과 실행 가능 조건을 ViewModel에 연결합니다. 코드만 붙여 넣는 것으로 끝나지 않습니다. DataContext, Command 바인딩, 조건이 바뀌었을 때의 재평가까지 연결해야 버튼이 기대한 대로 움직입니다.

대상: Windows의 WPF 프로젝트. 아래 첫 코드는 기존 글의 동기식 구현을 유지한 예시입니다. 일반 .NET 콘솔 프로젝트나 다른 UI 프레임워크에 그대로 적용하는 코드는 아닙니다.

버튼에서 ViewModel까지의 흐름

DataContext → Command 바인딩 → CanExecute · 직접 제작한 설명 도해

Execute는 실제 동작, CanExecute는 실행 허용 조건입니다. 이 구현의 canExecute를 생략하면 실행 가능한 것으로 판단합니다. execute에는 실제 동작을 전달해야 하며 null을 넘기면 실행 시 오류가 발생할 수 있습니다.

기존 RelayCommand 구현

using System;
using System.Windows.Input;

public class RelayCommand : ICommand    
{    
    private Action<object> execute;    
    private Func<object, bool> canExecute;    
     
    public event EventHandler CanExecuteChanged    
    {    
        add { CommandManager.RequerySuggested += value; }    
        remove { CommandManager.RequerySuggested -= value; }    
    }    
     
    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)    
    {    
        this.execute = execute;    
        this.canExecute = canExecute;    
    }    
     
    public bool CanExecute(object parameter)    
    {    
        return this.canExecute == null || this.canExecute(parameter);    
    }    
     
    public void Execute(object parameter)    
    {    
        this.execute(parameter);    
    }    
}

이 구현은 CanExecuteChanged의 구독을 CommandManager.RequerySuggested에 연결합니다. 모든 속성 변경을 자동으로 감시한다는 뜻은 아닙니다.

ViewModel과 XAML을 연결하는 최소 예시

아래는 이름이 비어 있으면 저장 버튼을 끄는 예시입니다. Save 메서드의 본문은 실제 저장 기능으로 채워야 합니다. 예제 ViewModel과 앞의 RelayCommand를 같은 프로젝트에 둡니다.

public class EditorViewModel
{
    private string name = "";
    public string Name
    {
        get { return name; }
        set
        {
            if (name == value) return;
            name = value;
            CommandManager.InvalidateRequerySuggested();
        }
    }

    public ICommand SaveCommand { get; private set; }

    public EditorViewModel()
    {
        SaveCommand = new RelayCommand(
            _ => Save(),
            _ => !string.IsNullOrWhiteSpace(Name));
    }

    private void Save()
    {
        // 예시: 여기에 실제 저장 처리를 구현합니다.
    }
}

Window 생성자에서 InitializeComponent(); 다음에 DataContext = new EditorViewModel();을 설정합니다. 아래 XAML은 기존 Window 내부에 넣는 조각입니다.

<StackPanel Margin="20">
    <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
    <Button Content="저장" Command="{Binding SaveCommand}" />
</StackPanel>

입력할 때마다 Name에 전달되도록 UpdateSourceTrigger=PropertyChanged를 지정했습니다. 이 최소 예시는 입력창 → ViewModel 흐름에 한정합니다. 코드에서 Name을 바꾼 값을 입력창에도 표시하려면 INotifyPropertyChanged 알림을 별도로 구현해야 합니다.

버튼 활성화가 바로 바뀌지 않는 이유

Microsoft 문서에 따르면 CommandManager는 키보드 포커스 변화 등 특정 조건에 주목합니다. 조건 변경을 감지하지 못하는 상황에서는 InvalidateRequerySuggested()로 재평가를 요청할 수 있습니다. 따라서 기존 글의 “자동 갱신” 설명을 모든 변경에 대한 보장으로 읽으면 안 됩니다.

증상 확인할 연결
클릭해도 동작 없음 DataContext가 원하는 ViewModel인지, SaveCommand 이름이 맞는지
입력 뒤에도 비활성 Name에 값이 전달되는지, CanExecute가 true인지
다른 곳을 클릭해야 바뀜 속성 갱신 시점과 커맨드 재평가 요청
코드에서 바꾼 이름이 화면에 안 보임 INotifyPropertyChanged 알림 구현 여부

적용 후 직접 확인할 세 가지

① 빈 입력에서 저장 버튼이 비활성인지 확인합니다. ② 글자를 입력하면 활성화되는지 봅니다. ③ 다시 지우거나 공백만 넣었을 때 비활성으로 돌아가는지 확인합니다. 이 글의 실행 예시는 설명용이며 이번 수정에서는 Windows WPF 실행 검증을 수행하지 않았습니다.

네트워크 요청이나 오래 걸리는 저장 작업은 이 동기식 예시의 범위를 벗어납니다. 중복 실행 방지, 취소, 예외 처리까지 별도로 설계해야 합니다.

공식 참고 자료

Microsoft Learn: InvalidateRequerySuggested · 확인일 2026년 9월 30일. 재평가 동작의 근거이며 예제와 점검표는 이 글에서 구성했습니다.