티스토리 뷰

반응형

2022.02.24 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part2 - Frame Navigation

2022.02.21 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part1

 

애플리케이션에서 시간이 오래 걸리는 작업을 진행할 때 화면에 Busy...라는 메시지를 출력하면서 전체 화면을 Dim처리하는 방법에 대해서 간단히 알아 보도록 하겠습니다.

1. MainWindow.xaml

MainWindow.xaml에 Busy가 출력되도록 수정합니다.

Busy 상태에서는 화면에 다른 컨트롤을 사용할 수 없도록 Border의 Background 컬러를 추가하고, ViewModel의 IsBusy 프로퍼티와 Converter를 이용해서 Border의 보이기 속성을 변경시켜 줍니다.

        <Grid>
            <Frame NavigationUIVisibility="Hidden">
                <b:Interaction.Behaviors>
                    <behaviors:FrameBehavior NavigationSource="{Binding NavigationSource, Mode=TwoWay}" />
                </b:Interaction.Behaviors>
            </Frame>
            <Border Background="#66000000" Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibilityConverter}}">
                <Border
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Background="White"
                    CornerRadius="5">
                    <StackPanel Width="100" Margin="10">
                        <ProgressBar Height="10" IsIndeterminate="True" />
                        <TextBlock
                            Margin="0,5,0,0"
                            HorizontalAlignment="Center"
                            Text="Busy..." />
                    </StackPanel>
                    <Border.Effect>
                        <DropShadowEffect />
                    </Border.Effect>
                </Border>
            </Border>
        </Grid>

2. BoolToVisibilityConverter.cs

간단하게 컨버터를 하나 만들어 줍니다. Mvvm 패턴에서는 컨버터를 사용해야하는 경우가 많기 때문에 만드는 것에 대해서 부담을 가지지 마시고, 필요하면 계속 만들어서 사용합니다.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WpfFramework.Converters
{
    /// <summary>
    /// Bool을 Visibility로 변환해주는 컨버터
    /// </summary>
    /// <remarks>
    /// 컨버터 생성시 꼭 public을 붙여 준다. 디자인 타임에 생성이 않되서, 디자인 타임자체가 출력되지 않을 수 있음
    /// </remarks>
    public class BoolToVisibilityConverter : IValueConverter
    {
        /// <summary>
        /// True일때 반환할 값 - 반대로 반환해야하는 경우 여기 속성만 변경한 인스턴스를 추가해서 사용한다.
        /// </summary>
        public Visibility TrueValue { get; set; } = Visibility.Visible;
        /// <summary>
        /// False일때 반환할 값 - 반대로 반환해야하는 경우 여기 속성만 변경한 인스턴스를 추가해서 사용한다.
        /// </summary>
        public Visibility FalseValue { get; set; } = Visibility.Collapsed;

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            //value를 boolValue로 형변환
            if (value is bool boolValue)
            {
                //true면 trueValue반환, false이면 falseValue반환
                if (boolValue)
                {
                    return TrueValue;
                }
                else
                {
                    return FalseValue;
                }
            }
            return Binding.DoNothing;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

3. _ConverterRd.xaml

컨버터를 모든 화면에서 사용하기 위해서 리소스 딕셔너리에 추가합니다. 컨버터는 1개를 만들었지만, 리소스에 등록할 때는 정상적인것과 반대되는 것 2개를 만들어서 등록하면 나중에 사용하기에도 좋습니다.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:converters="clr-namespace:WpfFramework.Converters">

    <!--  true이면 보이고, false이면 않보임  -->
    <converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
    <!--  true이면 않보이고, false이면 보임  -->
    <converters:BoolToVisibilityConverter
        x:Key="BoolToVisibilityReverseConverter"
        FalseValue="Visible"
        TrueValue="Collapsed" />
</ResourceDictionary>

4. App.xaml 수정

모든 리소스딕셔너리를 App.Resources에 머지를 하면, 모든 화면에서 사용이 가능합니다.

<Application
    x:Class="WpfFramework.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfFramework"
    StartupUri="/Views/MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Styles/_ConverterRd.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

5. BusyMessage.cs

Busy 메시지를 추가합니다. 이 메시지를 이용해서 Busy 화면의 보기 속성을 컨트롤 합니다.

using Microsoft.Toolkit.Mvvm.Messaging.Messages;

namespace WpfFramework.Models
{
    /// <summary>
    /// 비지 메시지
    /// </summary>
    public class BusyMessage : ValueChangedMessage<bool>
    {
        /// <summary>
        /// BusyId
        /// </summary>
        public string BusyId { get; set; }
        /// <summary>
        /// BusyText
        /// </summary>
        public string BusyText { get; set; }
        /// <summary>
        /// todtjdwk
        /// </summary>
        /// <param name="value"></param>
        public BusyMessage(bool value) : base(value)
        {
        }
    }
}

6. HomeViewModel.cs

HomePage.xaml에는 버튼을 하나 추가하고, 뷰모델에 아래 코드를 추가해 줍니다.

 

기본적으로 커맨드는 ICommand를 사용하면됩니다. ICommand에 연결할 인스턴스는 RelayCommand를 이용해서 만들면되는데, 비동기 메소드를 실행해야하는 경우에는 비동기 메소드 전문인 AsyncRelayCommand를 이용하면 편하게 사용할 수 있습니다.

RelayCommand에 대한 자세한 사항은 여기를 참고하시기 바랍니다.

AsyncRelayCommand에 대한 자세한 사항은 여기를 참고하시기 바랍니다.

using Microsoft.Toolkit.Mvvm.Input;
using Microsoft.Toolkit.Mvvm.Messaging;
using System.Threading.Tasks;
using System.Windows.Input;
using WpfFramework.Bases;
using WpfFramework.Models;

namespace WpfFramework.ViewModels
{
    public class HomeViewModel : ViewModelBase
    {
        public static int Count { get; set; }

        /// <summary>
        /// Busy 테스트 커맨드
        /// </summary>
        public ICommand BusyTestCommand { get; set; }

        /// <summary>
        /// 생성자
        /// </summary>
        public HomeViewModel()
        {
            Title = "Home";

            Init();
        }

        private void Init()
        {
            BusyTestCommand = new AsyncRelayCommand(OnBusyTestAsync);
        }

        /// <summary>
        /// OnBusyTest
        /// </summary>
        /// <returns></returns>
        private async Task OnBusyTestAsync()
        {
            WeakReferenceMessenger.Default.Send(new BusyMessage(true) { BusyId = "OnBusyTestAsync" });
            await Task.Delay(5000);
            WeakReferenceMessenger.Default.Send(new BusyMessage(false) { BusyId = "OnBusyTestAsync" });
        }

        public override void OnNavigated(object sender, object navigatedEventArgs)
        {
            Count++;
            Message = $"{Count} Navigated";
        }
    }
}

7. MainViewModel.cs 수정

추가된 부분에 대해서만 작성을 했습니다. 전체소스는 github를 참고하시기 바랍니다.

        /// <summary>
        /// Busy 목록
        /// </summary>
        private IList<BusyMessage> _busys = new List<BusyMessage>();

...

        private bool _isBusy;
        /// <summary>
        /// IsBusy
        /// </summary>
        public bool IsBusy
        {
            get { return _isBusy; }
            set { SetProperty(ref _isBusy, value); }
        }
        
...        

            //BusyMessage 수신 등록
            WeakReferenceMessenger.Default.Register<BusyMessage>(this, OnBusyMessage);

...

        /// <summary>
        /// 비지 메시지 수신 처리
        /// </summary>
        /// <param name="recipient"></param>
        /// <param name="message"></param>
        private void OnBusyMessage(object recipient, BusyMessage message)
        {
            if(message.Value)
            {
                var existBusy = _busys.FirstOrDefault(b => b.BusyId == message.BusyId);
                if(existBusy != null)
                {
                    //이미 추가된 녀석이기 때문에 추가하지 않음
                    return;
                }
                _busys.Add(message);
            }
            else
            {
                var existBusy = _busys.FirstOrDefault(b => b.BusyId == message.BusyId);
                if(existBusy == null)
                {
                    //없기 때문에 나감
                    return;
                }
                _busys.Remove(existBusy);
            }
            //_busys에 아이템이 있으면 true, 없으면 false
            IsBusy = _busys.Any();
        }

8. 실행

실행 후 화면

Busy Test 버튼 클릭 후 화면 5초후에 원래 화면으로 복귀합니다.

9.소스

프로젝트 : WpfFramework, branch part3/add-Busy-function

kaki104/WpfFramework at part3/add-Busy-function (github.com)

 

GitHub - kaki104/WpfFramework

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

github.com

 

반응형
댓글