Skip to main content

[WinUI] ToggleSwitch에 Command 속성 추가

Updated 2 min readMarkdown

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의 ToggleSwitch는 Tapped이벤트가 발생하지 않는 것인데요, 다행히 PointerReleased이벤트는 발생하여 이것을 이용하였습니다.

No comments yet. Be the first to comment.

More from this blog