728x90

이 글은 [WPF MVVM Tutorials] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022


Command 유효성 검사

namespace WPF_MVVM_Tutorial.Commands
{
    public abstract class CommandBase : ICommand
    {
        public event EventHandler? CanExecuteChanged;

        public virtual bool CanExecute(object? parameter)
        {
            return true;
        }

        public abstract void Execute(object? parameter);

        protected void OnCanExecutedChanged()
        {
            CanExecuteChanged?.Invoke(this, new EventArgs());
        }
    }
}

 

namespace WPF_MVVM_Tutorial.Commands
{
    public class MakeReservationCommand : CommandBase
    {
        private readonly MakeReservationViewModel _makeReservationViewModel;

        public override bool CanExecute(object? parameter)
        {
            return !string.IsNullOrEmpty(_makeReservationViewModel.Username) && base.CanExecute(parameter);
        }
    }
}

 

CommnadBase를 상속받은 MakeReservationCommand에서

virtual 메서드로 선언하여 재정의할 수 있도록 만든 CanExecute 메서드에서 Validation 진행

Username이 공란일 경우, Submit Button에 연결된 MakeReservationCommand 비활성화

 

PropertyChanged 등록

public MakeReservationCommand(ViewModels.MakeReservationViewModel makeReservationViewModel, Hotel hotel)
{
    _makeReservationViewModel = makeReservationViewModel;
    _hotel = hotel;

    _makeReservationViewModel.PropertyChanged += OnViewModelPropertyChanged;
}

public override bool CanExecute(object? parameter)
{
    return !string.IsNullOrEmpty(_makeReservationViewModel.Username) 
        && _makeReservationViewModel.FloorNumber > 0
        && _makeReservationViewModel.RoomNumber > 0
        && base.CanExecute(parameter);
}

private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
    if(e.PropertyName == nameof(MakeReservationViewModel.Username)
        || (e.PropertyName == nameof(MakeReservationViewModel.FloorNumber))
        || (e.PropertyName == nameof(MakeReservationViewModel.RoomNumber)))
    {
        OnCanExecutedChanged();
    }
}

⭐ makeReservationViewModel. PropertyChanged += OnViewModelPropertyChanged;

    - makeReservationViewModel의 PropertyChanged 이벤트에 이벤트 핸들러인 OnViewModelPropertyChanged 메서드 추가

    - PropertyChanged 이벤트가 발생하면 OnViewModelPropertyChanged 메서드를 호출

      _makeReservationViewModel의 어떤 속성이 변하면, 그 변화를 감지하고 OnViewModelPropertyChanged 메서드를 실행하라는 지시

    - OnViewModelPropertyChanged

        - 이벤트 핸들러로, 이벤트가 발생했을 때 수행할 작업을 정의

        - e.PropertyName를 통해 특정 속성의 변경에 따라 UI를 업데이트하거나 다른 작업을 수행하도록 함

 

Store View Change Event 설정

namespace WPF_MVVM_Tutorial.Stores
{
    public class NavigationStore
    {
        private ViewModelBase _currentViewModel;
        public ViewModelBase CurrentViewModel
        {
            get => _currentViewModel;
            set
            {
                _currentViewModel = value;
                OnCurrentViewModelChanged();
            }
        }

        public event Action CurrentViewModelChanged;

        private void OnCurrentViewModelChanged()
        {
            CurrentViewModelChanged?.Invoke();
        }

    }
}

NavigationStore

    - CurrentViewModel 프로퍼티의 값이 변경될 때마다 CurrentViewModelChanged 이벤트가 발생
    - 애플리케이션의 네비게이션 로직을 중앙에서 관리하고, 현재 활성화된 뷰모델이 변경될 때마다 이를 구독하는 객체에 알림

 

ViewModel과 View 연결

<Grid MaxWidth="600" Margin="20 10">
    <Grid.Resources>
        <DataTemplate DataType="{x:Type vms:MakeReservationViewModel}">
            <views:MakeReservationView/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type vms:ReservationListingViewModel}">
            <views:ReservationListingView/>
        </DataTemplate>
    </Grid.Resources>
    <ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>

ContentControl

    - DataContext에서 데이터를 가져와 표시하는 컨트롤

    - ViewModel을 바인딩 → ViewModel에 DataTemplate로 view 바인딩

 

CurrentViewModel Change

public class NavigationStore
{
    private ViewModelBase _currentViewModel;
    public ViewModelBase CurrentViewModel
    {
        get => _currentViewModel;
        set
        {
            _currentViewModel = value;
            OnCurrentViewModelChanged();
        }
    }

    public event Action CurrentViewModelChanged;

    private void OnCurrentViewModelChanged()
    {
        CurrentViewModelChanged?.Invoke();
    }
}

 

namespace WPF_MVVM_Tutorial.ViewModels
{
    public class MainViewModel : ViewModelBase
    {
        private readonly NavigationStore _navigationStore;

        public MainViewModel(NavigationStore navigationStore)
        {
            _navigationStore = navigationStore;

            _navigationStore.CurrentViewModelChanged += OnCurrentViewModelChanged;
        }

        private void OnCurrentViewModelChanged()
        {
            OnPropertyChanged(nameof(CurrentViewModel));
        }

    }
}

CurrentViewModel이 변경될 때마다 UI가 업데이트 되도록 CurrentViewModelChanged에 메서드 추가

 

728x90

'C# > WPF' 카테고리의 다른 글

[WPF] ListView와 ItemsSource  (0) 2024.03.09
[WPF_Mastereclass] MVVM Binding  (1) 2024.02.11
[SingletoneSean] WPF MVVM Tutorials(Models / Views / ViewModels)  (1) 2024.02.09
[RJ Code Advance EN] Login Form  (1) 2024.02.09
[WPF_Mastereclass] Calculator  (0) 2024.02.05
728x90

이 글은 [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

 

728x90

'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
728x90

이 글은 [RJ Code Advance EN] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022

 


Solution: RJ_Code_Advance_EN

 

🫠영상과 다른 설정을 한 부분

1. 버튼 설정

<!-- 기존 -->
<Button
    Grid.Column="1"
    x:Name="buttonMinimize"
    Content="-"
    BorderThickness="0"
    Foreground="White"
    FontSize="16"
    Cursor="Hand"
    Click="buttonMinimize_Click"
    >
    <Button.Style>
        <Style TargetType="Button">
            <Setter
                Property="Background"
                Value="#28AEED"
                />
            <Style.Triggers>
                <Trigger
                    Property="IsMouseOver"
                    Value="True"
                    >
                    <Setter
                        Property="Background"
                        Value="#2788EF"
                        />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Button.Style>

    <Button.Template>
        <ControlTemplate>
            <Border
                Width="18" Height="18"
                CornerRadius="9"
                Background="{TemplateBinding Background}"
                >
                <ContentPresenter
                    VerticalAlignment="Top"
                    HorizontalAlignment="Center"
                    />
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>

 

<!-- 변경 -->
<Button
    Grid.Column="1"
    x:Name="buttonMinimize"
    BorderThickness="0"
    Foreground="White"
    FontSize="16"
    Cursor="Hand"
    Click="buttonMinimize_Click"
    >
    <Button.Style>
        <Style TargetType="Button">
            <Setter
                Property="Background"
                Value="#28AEED"
                />
            <Style.Triggers>
                <Trigger
                    Property="IsMouseOver"
                    Value="True"
                    >
                    <Setter
                        Property="Background"
                        Value="#2788EF"
                        />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Button.Style>

    <Button.Template>
        <ControlTemplate>
            <Border
                Width="18" Height="18"
                CornerRadius="9"
                Background="{TemplateBinding Background}"
                >
                <ContentPresenter
                    Content="-"
                    VerticalAlignment="Top"
                    HorizontalAlignment="Center"
                    />
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>

 

Button 태그의 Content 속성을 통해 내용 표시

🚨 Content가 표시되지 않는 문제 발생

    💡ControlTemplate을 통해 버튼 모양을 변경할 경우, Button의 Content 속성이 아닌 ContentPresenter의 Content 속성에서 표시

 

🚨 Image가 실행창에서 보이지 않는 문제 발생

    💡이미지를 새로 추가한 경우, 리소스 등록 및 재빌드 과정을 거쳐야 함

         이미지 우클릭 → 속성 → 빌드 작업: 리소스 → 재빌드

 

 

 

📚 참고 자료

 

[WPF]Image 가 보이지 않을 때 , 나타나지 않을 때

WPF 에서 이미지를 Resource 를 등록해서 나타나게 할 때 화면에 보이지 않는 경우가 있다. 대부분의 경우 리소스 등록과 빌드 설정으로 인해 나타나지 않는 경우가 대부분이다. - 먼저 우측 탐색기

gdpark.tistory.com

728x90
728x90

이 글은 [WPF_Mastereclass] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022


 

Solution: CalculatorApp

 

계산기

 

🫠개선하면 좋은 점

* 괄호 추가

* 현재 계산중인 식 추가

* 연산자 클릭 시, 0이 아닌 계산된 숫자로 변경

* 이전 계산값(등호가 클릭된) 저장

 

728x90