[C# WPF] 공부하기 -2

우롱밀크티당도70·2023년 8월 21일
0

WPF

목록 보기
2/22
post-custom-banner

1. 배경

WPF 강의 마저 듣고 정리하기...


2. 개발환경

  • Visual Studio Community 2022 / .NET 7.0

3. 내용 정리

3-1. Command

(추가하기)

3-2. RelayCommand

(추가하기)

3-3. 비동기, Task, 비동기 Command

  1. AsyncRelayCommand
  • RelayCommand와 방식은 비슷하지만 Execute가 비동기식으로 동작한다.
  • 완성되어있는 것 사용.
    1. 종속성 > NuGet 패키지 관리에서 AsyncRelayCommand를 검색한다.
    2. 강의는 22년도 3월 기준으로 microsoft.toolkit.mvvm의 7.1.2 버전을 설치한다.

기존 작성한 RelayCommand의 ExecuteMyButton 함수에서 wait를 호출했다. 그럴 경우 작업이 오래걸리면 UI 쓰레드도 같이 멈추게 된다.

  • (MainViewModel.cs)
namespace MyFirstProject.ViewModels
{
    class MainViewModel : INotifyPropertyChanged
    {
        private int progressValue;
        public ICommand TestClick
        {
            get; set;
        }

        public MainViewModel()
        {
            TestClick = new RelayCommand<object>(ExecuteMyButton, CanMyButton);
        }

        public int ProgressValue
        {
            get { return progressValue; }
            set 
            { 
                progressValue = value; 
                NotifyPropertyChanged(nameof(ProgressValue));
            
            }
        }

        bool CanMyButton(object param)
        {
            if(param == null)
            {
                return true;
            }
            return param.ToString().Equals("ABC") ? true : false;
        }

        void ExecuteMyButton(object param)
        {
            int w = 0;
            Task task1 = Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    ProgressValue = i;
                }
            });

            Task rtn2 = Task.Run(() =>
            {
                for (int i = 0; i < 50; i++)
                {
                    ProgressValue = i;
                    w = i;
                    Thread.Sleep(2000);
                }
            });

            rtn2.Wait();
            MessageBox.Show(w + "");
        }

        
    }
}


예) RelayCommand 사용 시 ExecuteMyButton 메소드에서 Thread.Sleep(2000);을 했을 때 Wait를 사용할 경우 해당 비동기 작업이 끝날 때 까지 해당 코드에서 대기하기 때문에 어떠한 버튼도 동작을 하지 않게 된다.

  1. MainViewModel로 돌아와서 생성자에 작성한 기존 코드를 주석 처리 하고 AsyncRelayCommand로 작성한다.
  2. 그 후 AsyncRelayCommand에서 사용할 ExecuteMyButton2 함수를 생성한다.
  3. return 타입은 Task이고 키워드는 async이다.
  • async는 비동기 메소드를 의미하고 Task와 같은 awaitable 클래스의 객체가 완료되길 기다리는 await 키워드를 사용할 수 있게 된다.
  • 만약 비동기 작업이 끝난 후 UI를 변경해야 된다면 Wait 함수를 사용해야 하는데
    보다 간단하게 ExecuteMyButton2에서 async wait를 사용하면 UI 쓰레드를 정지시키지 않고 작업을 기다릴 수 있게 된다.
  • (ExecuteMyButton2 함수)
		.
        .
        .
        
public MainViewModel()
        {
            //TestClick = new RelayCommand<object>(ExecuteMyButton, CanMyButton);
            TestClick = new AsyncRelayCommand(ExecuteMyButton2);
            Task t = ExecuteMyButton2();
        }
        
		.
        .
        .
        
public async Task ExecuteMyButton2()
        {
            int w = 0;
            Task<int> rtn2 = Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    ProgressValue = i;
                    w = i;
                    Thread.Sleep(2000);
                }
                int j = 5;
                return j;
            });

            w = await rtn2;

            MessageBox.Show(w + "");
        }
  • Wait 함수 대신에 await 함수를 사용하고 return 값을 w에 할당한다.
    int형인 w를 return 하기 때문에 Class에도 return 타입을 명시해주어야 한다.
  • 오래 걸리는 작업을 Wait() 함수로 기다려야 하는 코드 작성이 필요하다면 RelayCommand 대신에 AsyncRelayCommand를 사용하는 것이 매끄러운 UI 흐름을 만들 수 있다.

3-4. UserControl, CustomControl

  • UserControl
    컨트롤을 여러 개 복합해서 사용하여 나만의 컨트롤을 만드는 것
  • CustomControl
    기존 컨트롤을 수정해서 사용하는 것
  1. UserControl
  • UserControl은 컨트롤을 여러 개 합쳐서 동일한 구조를 쉽게 생성할 수 있도록 도구 상자에서 도와준다.

1-1. 새 폴더 UserControls를 추가하고 사용자 정의 컨트롤(WPF)을 추가한다.

1-2. 세로 150, 가로 300 사이즈에 Label과 Button을 도구 상자에서 끌어와 배치한 후 Ctrl + Shift + B로 Build한다.

1-3. MainWindow.xaml로 돌아와 도구 상자에 ThreeControls가 추가 되어있는지 확인한다.

1-4. 도구 상자에 추가 된 ThreeControls를 화면에 배치하면 ThreeControls.xaml에서 배치한 컨트롤이 그대로 배치된다.

1-5. 수정할 때에도 UserControl쪽을 수정하면 일괄 적용이 된다.

  • 다만 여기까지 했을 경우 MainWindow에서 배치한 ThreeControl의 Label Content는 변경할 수 없기 때문에 추가 작업이 필요하다.

1-6. 다시 ThreeControls.xaml로 돌아와 코드 보기에서 DependencyProperty를 생성해준다.
(propdp적은 후 Tab키를 빠르게 두 번 누르면 코드가 자동 완성된다.)
1-7. 들어갈 내용이 String 타입이기 때문에 string으로 변경 후 property 이름을 MyTest로 수정한다.
1-8. 이름, 타입, 소유 클래스, 기본 값을 파라미터로 갖는데 ownerclass를 ThreeControls로 변경 후 기본 값을 0에서 string.Empty로 변경한다.

namespace MyFirstProject.UserControls
{
    /// <summary>
    /// ThreeControls.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class ThreeControls : UserControl
    {
        public ThreeControls()
        {
            InitializeComponent();
        }

        public string MyText
        {
            get { return (string)GetValue(MyTextProperty); }
            set { SetValue(MyTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyTextProperty =
            DependencyProperty.Register("MyText", typeof(string), typeof(ThreeControls), new PropertyMetadata(string.Empty));


    }
}

1-9. ThreeControls.xaml로 돌아와서 x:Name을 root로 지은 후, Label의 Content를 {Binding MyText, ElementName=root}로 지정한다.

1-10. Ctrl + Shift + B로 Build한 후 MainWindow.xaml에서 추가한 UserControls에 MyText 속성을 추가한 후 내용을 입력하면 된다.

  • (ThreeControls.xaml)
<UserControl x:Class="MyFirstProject.UserControls.ThreeControls"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MyFirstProject.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="150" d:DesignWidth="300"
             x:Name="root">
    <Grid>
        <Label Content="{Binding MyText, ElementName=root}" HorizontalAlignment="Left" Margin="31,26,0,0" VerticalAlignment="Top"/>
        <Button Content="Button" HorizontalAlignment="Left" Margin="31,60,0,0" VerticalAlignment="Top"/>
    </Grid>
</UserControl>
  • (MainWindow.xaml)
<UserControls:ThreeControls MyText="내가 만든 라벨" HorizontalAlignment="Left" Margin="293,395,0,0" VerticalAlignment="Top"/>

  • 같은 방식으로 Button에 Command를 추가할 수 있다.
  1. CustomControl

2-1. 앞서 추가한 UserControls 폴더에서 사용자 정의 컨트롤(WPF)을 MyLabel.xaml이라는 이름으로 추가한다.
2-2. UserControl을 Label로 변경한 후 가로, 세로 길이와 Grid를 삭제한다.
2-3. Content를 MyText123, Background는 Black, Foreground를 White로 지정한다.

  • (MyLabel.xaml)
<Label x:Class="MyFirstProject.UserControls.MyLabel"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MyFirstProject.UserControls"
             mc:Ignorable="d" Content="Mytext123" Background="Black" Foreground="White" FontFamily="Arial">
</Label>

2-4. 코드보기에서 UserControl이 아닌 Label을 상속받는 것으로 수정한다.

namespace MyFirstProject.UserControls
{
    /// <summary>
    /// MyLabel.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MyLabel : Label
    {
        public MyLabel()
        {
            InitializeComponent();
        }
    }
}

2-5. Ctrl + Shift + B로 Build한 후 도구 상자에 MyLabel이 있는지 확인한 다음 MainWindow에 배치한다.

2-6. MyLabel은 Label을 상속받기 때문에 Label이 기존에 갖고 있는 속성을 모두 이용할 수 있다. 그래서 별도의 추가 작업 없이 Content의 내용을 수정할 수 있다.

<UserControls:MyLabel Content="내가 수정한 라벨" HorizontalAlignment="Left" Margin="449,435,0,0" VerticalAlignment="Top"/>

3-5. 다른 창 호출.ShowDialog

  • 옵션 창, 새로운 작업 창을 열어야 할 경우를 학습한다.
  • 윈폼 사용자에게 익숙하고 가장 쉬운 방법이다.
  1. 프로젝트 마우스 우클릭 후 새폴더 Views 추가 후 새 항목에서 창(WPF)을 SecondView라는 이름으로 추가한다.

  2. Grid 안에 Button과 ProgressBar를 배치한다.

  3. ViewModels 폴더에서 SecondViewModel이라는 이름의 클래스 파일을 추가한다.

  4. MainViewModel 클래스에서 했던 것 처럼 INotifyPropertyChanged를 상속 받는다. MainViewModel 클래스에서 작성한 내용 중 관련된 내용을 복사해왔다.

namespace MyFirstProject.ViewModels
{
    class SecondViewModel : INotifyPropertyChanged
    {

        private int progressValue;

        public int ProgressValue
        {
            get { return progressValue; }
            set
            {
                progressValue = value;
                NotifyPropertyChanged(nameof(ProgressValue));

            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        // This method is called by the Set accessor of each property.  
        // The CallerMemberName attribute that is applied to the optional propertyName  
        // parameter causes the property name of the caller to be substituted as an argument.  
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }


}
  1. SecondView.xaml에서 미리 배치했던 ProgressBar에 Value 속성을 추가하고 Binding한다.
  • (SecondView.xaml)
<ProgressBar HorizontalAlignment="Left" Height="10" Margin="138,146,0,0" VerticalAlignment="Top" Width="100" Value="{Binding ProgreesValue}"/>
  1. 이제부터 MainWindow에서 Button을 누르면 SecondView가 나오도록 하기 위해 우선 Button을 배치한다.

  2. MainViewModel 클래스에서 앞서 생성했던 요소를 복사하여 붙여넣는다.

		.
		.
		.
		public ICommand SecondShow
        {
            get; set;
        }

        public MainViewModel()
        {
            //TestClick = new RelayCommand<object>(ExecuteMyButton, CanMyButton);
            TestClick = new AsyncRelayCommand(ExecuteMyButton2);
            Task t = ExecuteMyButton2();
            SecondShow = new AsyncRelayCommand(ExecuteMyButton3);
        }
        .
        .
        .
        public async Task ExecuteMyButton3()
        {
            SecondView secondView = new SecondView();
            SecondViewModel aa = new SecondViewModel();
            aa.ProgressValue = 70;
            secondView.DataContext = aa;
            secondView.ShowDialog();
            await Task.Delay(0);
        }
        
  1. MainWindow에서 배치한 Button의 Command를 바인딩한다.
<Button Command="{Binding SecondShow}" Content="SecondView 보기" HorizontalAlignment="Left" Margin="640,359,0,0" VerticalAlignment="Top"/>
  1. 실행해서 SecondView 보기 버튼을 누르면 ProgressBar의 Value가 70인 Secondview를 확인할 수 있다.

Focus 이동이 가능한 창 생성은 ShowDialog() 대신 Show()를 사용하는 것이다.

3-6. MVVM 복습 및 이론, 실습

  • MVVM 패턴은 M은 Model, V는 View, VM은 ViewModel이다.
  • UI와 비즈니스 로직의 분리를 위해 MVVM 패턴으로 작성한다.

3-7. ComboBox + 다양한 Event를 ModelView에서 처리하기

  1. 종속성 우클릭하여 NuGet 패키지 관리에서 XamlBehaviorsWpf 를 검색하여 설치한다.
    • 손쉽게 이벤트를 추가해주는 기능을 제공하는 라이브러리이다.

  1. MainWindow.xaml에서 라이브러리를 추가한다.
    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyFirstProject"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:UserControls="clr-namespace:MyFirstProject.UserControls" x:Class="MyFirstProject.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800">
        .
        .
        .
        
  1. 도구 상자에서 ComboBox를 끌어와 배치하고 속성 > 공용의 Items에서 ComboBoxItem으로 사과와 포도를 추가한다.

  1. ComboBox 태그 안에 Trigger를 추가한다.
    EventName = "MouseLeave" : 마우스가 ComboBox를 떠나게 되면
    Command = "{Binding TestClick}" : 앞서 MainViewModel에서 만들었던 TestClick Command가 실행된다.
		<ComboBox HorizontalAlignment="Left" Margin="640,455,0,0" VerticalAlignment="Top" Width="120">
            <ComboBoxItem Content="사과"/>
            <ComboBoxItem Content="포도"/>
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeave">
                    <i:InvokeCommandAction Command="{Binding TestClick}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ComboBox>

4. 섹션 1 까지 느낀점

6강까진 괜찮은데 7, 8강은 아리까리 하더니 9, 10강은 또 괜찮고..,,
MVVM 패턴의 M과 V는 알겠는데 VM은 어렵다.

profile
안뇽하세용
post-custom-banner

0개의 댓글