본문 바로가기
C#/WPF

[SingletoneSean] WPF MVVM Tutorials(Models / Views / ViewModels)

by HJ0216 2024. 2. 9.

이 글은 [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# - TextBox 바인딩을 했는데 값이 바로 안바뀐다구요?

TextBox 바인딩을 하고 UpdateSourceTrigger를 활용하여 업데이트 타이밍을 지정해주어야 합니다.

hwanine.github.io

 

'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