이 글은 [WPF MVVM Tutorials]을 수강하며 정리한 글입니다.
👉 기본 환경
- Language: C#, xaml
- IDE: Visual Basic 2022
Views
🚨ListView - ListItem Alignment
⭐Textblock - Alignment 속성이 아닌 ListView.ItemContainerStyle 사용
<ListView
Grid.Row="1"
Margin="0 25 0 0"
>
<ListViewItem/>
<ListViewItem/>
<ListViewItem/>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="RoomID">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="12"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<ListView
Grid.Row="1"
Margin="0 25 0 0"
ItemsSource="{Binding Reservations}"
>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="RoomID">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding RoomID}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
ListView ItemsSource: Reservations
- 상위의 ItemsSource가 있을 때, 하위의 Binding은 상위 객체에 대한 Property로 연결됨
- 예: TextBlock의 Text Binding은 Reservations의 RoomID 필드
<TextBox
Grid.Row="1" Grid.Column="0"
Margin="0 5 0 0"
Text="{Binding FloorNumber, UpdateSourceTrigger=PropertyChanged}"
/>
🚨 UpdateSourceTrigger=PropertyChanged
- Textbox의 UpdateSourceTrigger의 기본값은 Lost Focus이므로 포커스를 잃을 때 바인딩된 변수에 값이 업데이트 됨
→ 값이 입력될 때마다 업데이트를 원한다면 PropertyChanged로 변경 필요
ViewModels
🚨View와 Model의 분리
- Model이 변경되었을 때 View를 자동으로 업데이트하는 것이 어렵고,
- View와 Model 사이의 결합이 강할수록 테스트와 유지보수가 어려움
⭐ViewModel
- Model의 데이터를 View에 적합한 형태로 변환하고,
- 사용자의 입력을 Model의 명령으로 변환
→ 이를 통해 View와 Model 사이의 의존성을 줄이고, 더 유연하고 재사용 가능한 코드를 작성할 수 있음
<ListView
Grid.Row="1"
Margin="0 25 0 0"
ItemsSource="{Binding Reservations}"
/>
namespace WPF_MVVM_Tutorial.Models
{
public class Reservation
{
public RoomID RoomID { get; }
public string Username { get; }
public DateTime StartTime { get; }
public DateTime EndTime { get; }
public TimeSpan Length => EndTime.Subtract(StartTime);
public Reservation(RoomID roomID, string username, DateTime startTime, DateTime endTime)
{
RoomID = roomID;
Username = username;
StartTime = startTime;
EndTime = endTime;
}
public bool Conflict(Reservation reservation)
{
if(reservation.RoomID != RoomID)
{
return false;
}
return !(EndTime < reservation.StartTime || StartTime > reservation.EndTime);
// existingReservation : StartTime, EndTime
// reservation: InComingReservation
}
}
}
namespace WPF_MVVM_Tutorial.ViewModels
{
public class ReservationViewModel : ViewModelBase
{
private readonly Reservation _reservation;
public string RoomID => _reservation.RoomID.ToString();
public string Username => _reservation.Username;
public DateTime StartTime => _reservation.StartTime;
public DateTime EndTime => _reservation.EndTime;
public ReservationViewModel(Reservation reservation)
{
_reservation = reservation;
}
}
}
namespace WPF_MVVM_Tutorial.ViewModels
{
public class ReservationListingViewModel : ViewModelBase
{
private readonly ObservableCollection<ReservationViewModel> _reservations;
public IEnumerable<ReservationViewModel> Reservations => _reservations;
// private ICommand _makeReservationCommand;
public ICommand MakeReservationCommand { get; }
public ReservationListingViewModel()
{
_reservations = new ObservableCollection<ReservationViewModel>();
}
}
}
⭐ ObservableCollection<T>와 IEnumerable<T>
- ObservableCollection<T>
- 컬렉션이 변경될 때 이벤트를 발생시키는 기능을 제공
→ View가 컬렉션의 변경 사항을 자동으로 반영할 수 있게 됨
⭐ ViewModel에서 컬렉션의 변경을 감지하고 이에 따른 처리를 해야 하는 경우가 아니라면, IEnumerable<T>를 사용하는 것이 더 간단하고 적절
- IEnumerable<T>
- 컬렉션을 순회하는 기본적인 기능만 제공
→ 복잡성을 줄이고 코드를 더 간결하게 만들 수 있음
- IEnumerable<T>를 통해 컬렉션을 외부(View나 다른 ViewModel 등)에 노출하면, 외부에서 컬렉션의 변경을 감지하거나 요소를 추가/제거하는 등의 작업을 할 수 없으므로, 컬렉션의 안정성을 높일 수 있음
⭐ 따라서 일반적으로는 ViewModel에서 ObservableCollection<T>를 내부적으로 사용하되, 외부에는 IEnumerable<T>를 통해 이를 노출하는 것이 바람직
<Grid MaxWidth="600" Margin="20 10">
<views:ReservationListingView DataContext="{Binding CurrentViewModel}"/>
</Grid>
namespace WPF_MVVM_Tutorial.ViewModels
{
public class MainViewModel : ViewModelBase
{
public ViewModelBase CurrentViewModel { get; }
public MainViewModel()
{
CurrentViewModel = new MakeReservationViewModel();
}
}
}
Data Context 설정
namespace WPF_MVVM_Tutorial
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
🚨View의 Behind(.xaml.cs)에 ViewModel을 직접적으로 연결시키는 것은 결합도가 높아지는 단점 존재
namespace WPF_MVVM_Tutorial
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = new MainWindow()
{
DataContext = new MainViewModel()
};
MainWindow.Show();
base.OnStartup(e);
}
}
}
⭐ App.xaml.cs에서 MainWindow를 설정하고 DataContext를 설정하는 것이 더 바람직
🚨 단, App.xaml의 StartupUri와 중복되므로 삭제 처리 필요
// 기존(09/02/2024 12:00:00 AM)
public DateTime StartDate => _reservation.StartTime;
public DateTime EndDate => _reservation.EndTime;
// 변경(2024-02-09)
public string StartDate => _reservation.StartTime.ToString("d");
public string EndDate => _reservation.EndTime.ToString("d");
날짜 형식 지정
📚 참고 자료
'C# > WPF' 카테고리의 다른 글
[WPF] ListView와 ItemsSource (0) | 2024.03.09 |
---|---|
[WPF_Mastereclass] MVVM Binding (1) | 2024.02.11 |
[SingletoneSean] WPF MVVM Tutorials(Commands / Navigation) (1) | 2024.02.10 |
[RJ Code Advance EN] Login Form (1) | 2024.02.09 |
[WPF_Mastereclass] Calculator (0) | 2024.02.05 |