WPF 강의 마저 듣고 정리하기...
(추가하기)
(추가하기)
- AsyncRelayCommand
- RelayCommand와 방식은 비슷하지만 Execute가 비동기식으로 동작한다.
- 완성되어있는 것 사용.
1. 종속성 > NuGet 패키지 관리에서 AsyncRelayCommand를 검색한다.
2. 강의는 22년도 3월 기준으로 microsoft.toolkit.mvvm의 7.1.2 버전을 설치한다.
기존 작성한 RelayCommand의 ExecuteMyButton 함수에서 wait를 호출했다. 그럴 경우 작업이 오래걸리면 UI 쓰레드도 같이 멈추게 된다.
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를 사용할 경우 해당 비동기 작업이 끝날 때 까지 해당 코드에서 대기하기 때문에 어떠한 버튼도 동작을 하지 않게 된다.
- MainViewModel로 돌아와서 생성자에 작성한 기존 코드를 주석 처리 하고 AsyncRelayCommand로 작성한다.
- 그 후 AsyncRelayCommand에서 사용할 ExecuteMyButton2 함수를 생성한다.
- return 타입은 Task이고 키워드는 async이다.
- async는 비동기 메소드를 의미하고 Task와 같은 awaitable 클래스의 객체가 완료되길 기다리는 await 키워드를 사용할 수 있게 된다.
- 만약 비동기 작업이 끝난 후 UI를 변경해야 된다면 Wait 함수를 사용해야 하는데
보다 간단하게 ExecuteMyButton2에서 async wait를 사용하면 UI 쓰레드를 정지시키지 않고 작업을 기다릴 수 있게 된다.
.
.
.
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 흐름을 만들 수 있다.
- UserControl
컨트롤을 여러 개 복합해서 사용하여 나만의 컨트롤을 만드는 것- CustomControl
기존 컨트롤을 수정해서 사용하는 것
- 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 속성을 추가한 후 내용을 입력하면 된다.
<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>
<UserControls:ThreeControls MyText="내가 만든 라벨" HorizontalAlignment="Left" Margin="293,395,0,0" VerticalAlignment="Top"/>
- 같은 방식으로 Button에 Command를 추가할 수 있다.
2-1. 앞서 추가한 UserControls 폴더에서 사용자 정의 컨트롤(WPF)을 MyLabel.xaml이라는 이름으로 추가한다.
2-2. UserControl을 Label로 변경한 후 가로, 세로 길이와 Grid를 삭제한다.
2-3. Content를 MyText123, Background는 Black, Foreground를 White로 지정한다.
<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"/>
- 옵션 창, 새로운 작업 창을 열어야 할 경우를 학습한다.
- 윈폼 사용자에게 익숙하고 가장 쉬운 방법이다.
프로젝트 마우스 우클릭 후 새폴더 Views 추가 후 새 항목에서 창(WPF)을 SecondView라는 이름으로 추가한다.
Grid 안에 Button과 ProgressBar를 배치한다.
ViewModels 폴더에서 SecondViewModel이라는 이름의 클래스 파일을 추가한다.
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));
}
}
}
<ProgressBar HorizontalAlignment="Left" Height="10" Margin="138,146,0,0" VerticalAlignment="Top" Width="100" Value="{Binding ProgreesValue}"/>
이제부터 MainWindow에서 Button을 누르면 SecondView가 나오도록 하기 위해 우선 Button을 배치한다.
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);
}
<Button Command="{Binding SecondShow}" Content="SecondView 보기" HorizontalAlignment="Left" Margin="640,359,0,0" VerticalAlignment="Top"/>
Focus 이동이 가능한 창 생성은 ShowDialog() 대신 Show()를 사용하는 것이다.
- MVVM 패턴은 M은 Model, V는 View, VM은 ViewModel이다.
- UI와 비즈니스 로직의 분리를 위해 MVVM 패턴으로 작성한다.
<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">
.
.
.
<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>
6강까진 괜찮은데 7, 8강은 아리까리 하더니 9, 10강은 또 괜찮고..,,
MVVM 패턴의 M과 V는 알겠는데 VM은 어렵다.