Skip to main content

Command Palette

Search for a command to run...

[WinUI] ToggleSwitch에 Command 속성 추가

Updated
2 min read

UWP 및 WinUI 3의 [ToggleSwitch](https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.toggleswitch?view=winrt-22621) 컨트롤은 특이하게도 Command 속성이 없습니다. MVVM에서 사용하기 위해서는 CanExecute() 에 따라 컨트롤을 활성화/비활성화 할 필요가 있습니다.

Microsoft.Xaml.Interactivity.Behavior를 이용해서 다음 처럼 사용하고 싶은데요

<ToggleSwitch IsOn="{x:Bind SelectedAttribute.IsKey, Mode=OneWay}">
    <interactivity:Interaction.Behaviors>
        <extensions:ToggleSwitchCommandBehavior Command="{x:Bind TogglePKCommand}" />
    </interactivity:Interaction.Behaviors>
</ToggleSwitch>

ToggleSwitchCommandBehavior 는 다음처럼 구현할 수 있습니다.

using System.Windows.Input;

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.Xaml.Interactivity;

using Windows.System;

namespace WinUI.Extensions;

public class ToggleSwitchCommandBehavior : Behavior<ToggleSwitch>
{
    public readonly static DependencyProperty CommandProperty =
        DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(ToggleSwitchCommandBehavior),
            new PropertyMetadata(null, OnIsExternalChanged));

    public readonly static DependencyProperty CommandParameterProperty =
        DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(ToggleSwitchCommandBehavior),
            new PropertyMetadata(null));

    private bool _isAccessUserInputProperty;

    public ICommand? Command
    {
        get => (ICommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }

    public object? CommandParameter
    {
        get => GetValue(CommandParameterProperty);
        set => SetValue(CommandParameterProperty, value);
    }


    protected override void OnAttached()
    {
        AssociatedObject.PointerReleased += ToggleSwitch_PointerReleased;
        AssociatedObject.PreviewKeyDown += ToggleSwitch_PreviewKeyDown;
        AssociatedObject.Toggled += ToggleSwitch_Toggled;

        if (Command is not null)
        {
            Command_CanExecuteChanged(this, EventArgs.Empty);
        }
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PointerReleased -= ToggleSwitch_PointerReleased;
        AssociatedObject.PreviewKeyDown -= ToggleSwitch_PreviewKeyDown;
        AssociatedObject.Toggled -= ToggleSwitch_Toggled;
    }

    private static void OnIsExternalChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        if (sender is not ToggleSwitchCommandBehavior @this)
            return;

        if (args.OldValue is ICommand oldCommand)
            oldCommand.CanExecuteChanged -= @this.Command_CanExecuteChanged;

        if (args.NewValue is ICommand newCommand)
        {
            newCommand.CanExecuteChanged += @this.Command_CanExecuteChanged;
            if (@this.AssociatedObject is not null)
                @this.Command_CanExecuteChanged(@this, EventArgs.Empty);
        }
    }

    private void Command_CanExecuteChanged(object? sender, EventArgs e)
    {
        if (Command is null)
            return;

        AssociatedObject.IsEnabled = Command.CanExecute(CommandParameter);
    }

    private void ToggleSwitch_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
    {
        if (e.Key is VirtualKey.Space)
        {
            _isAccessUserInputProperty = true;
        }
    }

    private void ToggleSwitch_PointerReleased(object sender, PointerRoutedEventArgs e)
    {
        _isAccessUserInputProperty = true;
    }

    private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
    {
        var toggleSwitch = (ToggleSwitch)sender;

        // 사용자 입력일 때만 처리하도록 한다.
        if (_isAccessUserInputProperty is false)
            return;
        _isAccessUserInputProperty = false;

        if (Command is null)
            return;

        // 커멘드에 의해 값이 변하도록 IsOn 값을 원복한다.
        toggleSwitch.IsOn = !toggleSwitch.IsOn;

        if (Command.CanExecute(CommandParameter))
            Command.Execute(CommandParameter);
    }
}

ToggleSwitch가 토글 되었을 때 사용자 입력에 위해서인지를 확인하기 위해서는 마우스 입력이나 키보드 입력을 감지해야 합니다.
특이한 점은 WinUI의 ToggleSwitchTapped이벤트가 발생하지 않는 것인데요, 다행히 PointerReleased이벤트는 발생하여 이것을 이용하였습니다.

More from this blog

개발, 테스트, 운영에서의 도커 활용

핵심 원칙: "한 번 빌드하고, 어디서든 실행한다 (Build once, run anywhere)" 도커의 가장 큰 장점은 환경 일관성입니다. 동일한 도커 이미지를 사용하여 개발, 테스트, 운영 환경을 구성함으로써 "제 PC에서는 됐는데..." 하는 문제를 최소화할 수 있습니다. 1. 개발 단계 (Development) 목표: 빠른 코드 변경 반영, 쉬운 디버깅, 실제 운영 환경과 유사한 환경 구성. Docker 사용 방안: Dockerf...

May 9, 20256 min read15

[EF Core] 데이터 삭제 시 소프트 삭제 적용

DB에서 데이터를 삭제하면 일반적으로 복구할 수 없습니다. 또한 관계에 따라 영구 삭제 자체가 어려울 수도 있습니다. 그래서 데이터를 영구 삭제하는 대신 IsDeleted 속성을 true로 주고 IsDeleted 속성을 필터링해서 조회하는 방법을 사용하기도 합니다. 이를 소프트 삭제라고 합니다. 그런데 EF에서 알아서 데이터 삭제 시 소프트 삭제를 하고 쿼리시 IsDeleted 속성을 체크해서 삭제한 데이터를 제외한 데이터만 쿼리하게 하는 ...

Mar 18, 20243 min read19

[EF Core] ValueConverter를 이용해서 엔터티 속성의 도메인 관리

EF Core를 사용하면서 문자열 길이 등의 특성을 일일이 지정하는 것은 번거롭습니다. ... [MaxLength(32)] public string? 제목 { get; set; } 엔터티가 한 개일 때는 상관이 없으나 제목 유형이 여러 엔터티에 사용될 경우 유형을 지정하기 번거롭습니다. 속성 유형을 도메인으로 관리하면 참 편할텐데요, ValueConverter를 이용할 수 있습니다. 그런데 이것을 인터페이스 정적 추상를 사용해서 다음처럼 ...

Mar 16, 20242 min read8

디모이 블로그

154 posts

.NET 관련 기술을 선호하고 새로운 언어를 배우는데 관심이 있습니다.