쉬운 것부터 해보자.
json 파일에 장치(설비) 목록을 구성했다. 나중에 이 파일의 내용은 DB에서 관리되도록 수정할 것이다.
이 장치 목록을 화면에 보여주는 페이지를 추가해보자.
WPF-UI 화면을 추가하기 위해 다음 작업을 수행한다.
MVVM 패턴이 어떻게 동작하는지 살펴보자
MVVM 지원을 위해 사용하고 있는 CommunityToolkit도 다시 살펴보자
장치 정보를 정의한다.
struct로 정의해도 되겠지만 향후 확장성을 위해 class로 만든다.
json 파일에 정의한 장치의 속성을 그대로 옮기면 된다.
{
// direct connected device
"energy": "PV",
"name": "PV X호기",
"ip": "192.168.0.101",
...
},
{
// gateway and units
"energy": "PV",
"name": "PV-Gateway",
"ip": "192.168.0.103",
"units": [
{
"name": "PV 201-1",
"unitId": 1,
...
},
...
]
},
Model 클래스는 아래와 같이 구성할 수 있다.
public class Device
{
public string Energy { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
public string Ip { get; set; } = String.Empty;
public List<DevUnit> Units { get; set; } = new List<DevUnit>();
...
}
장치정보를 멤버 데이터로 가진다. DevicePage에 데이터를 제공할 것이다.
json 파일을 읽어서 장치정보를 구성하는 기능은 Model 클래스에 staic 함수로 구현하는 것이 좋겠다. Model 클래스가 지저분해지는 것이 싫으면 ModelHelper 클래스를 별도로 두는 것도 좋겠다.
ModelHelper 클래스는 파일로부터 모델 정보를 구성하거나, 변경된 Model 정보를 다시 파일에 기록하는 기능을 제공한다.
GridView를 사용해서 장치 목록을 보여준다.
일반적으로 이 작업은 매우 단순한데, 설비의 구성 때문에 좀 까다로운 부분이 있다.
등록된 장치가 Modbus/TCP로 통신하는 설비들인데 대부분이 Modbus Gateway를 통해서 통신하게 된다.
Gateway를 사용하게 되면 각 장치들은 Gateway 아래에 Unit의 목록으로 구성된다.
그래서 DeviceView 화면도 직접 통신하는 장치와, Gateway를 통하는 장치를 구분해서 Gateway의 경우 상세 Unit의 목록을 보여주는 계층적 View가 되는 것이 자연스럽다.
대략 이런 형태의 화면이 될 것이다.
여기서 중요한 점은 View가 INavigableView 인터페이스를 구현한다는 점이다.
class DevicePage : INavigableView<DeviceViewModel>
WPF-UI 라이브러리가 제공하는 인터페이스로 생성자를 통해 View에 ViewModel에 대한 의존성을 주입하고, MainWindow의 NavigationView를 통해 Page(View)를 관리할 수 있게 한다.
나중에 구현
메뉴는 MainWindow가 담당한다. MainWindow 클래스에서 직접 메뉴를 등록할 수도 있지만, ViewModel을 통해 메뉴 목록을 제공하는 것이 권장된다. MainWindow는 메뉴의 스타일(UI)만 담당하고, 메뉴의 내용은 ViewModel에서 제공하는 것이다.
생성된 코드에 아래와 같이 DevicePage
를 가리키는 메뉴를 하나 추가하면 된다.
// MainWindowViewModel.cs
[ObservableProperty]
private ObservableCollection<object> _menuItems = new()
{
...
new NavigationViewItem()
{
Content = "Device",
Icon = new SymbolIcon { Symbol = SymbolRegular.DeviceEq24 },
TargetPageType = typeof(Views.Pages.DevicePage)
}
};
[ObservableProperty]
은 CommunityToolkit.Mvvm에서 제공하는 Annotation이다. _menuItems
에 이 Annotation을 추가해서 컴파일할 때 MenuItems라는 Property 코드가 자동으로 생성되도록 한다. 이 Property는 값이 변경될 때마다 PropertyChanged 이벤트를 발생시킨다. 이를 통해 View(화면)에서는 동적으로 메뉴 목록을 업데이트할 수 있다.
참고로 MainWindow는 WPF-UI 라이브러리에서 제공하는 NavigationView 클래스로 구성되고, 이 NavigationView에는 Menu를 표시하는 영역이 있다. 이 Menu 영역에 ViewModel이 제공하는 MenuItems를 바인딩한다.
<ui:NavigationView
MenuItemsSource="{Binding ViewModel.MenuItems, Mode=OneWay}
생성자를 통한 의존성 주입이 가능하도록 Host 클래스의 서비스를 통해 클래스를 등록한다.
// App.xaml.cs
HostBuilder.ConfigureService(...) => {
...
services.AddSingleton<DevicePage>();
services.AddSingleton<DeviceViewModel>();
NavigationView에서 메뉴를 선택하면 해당되는 Host 서비스를 통해 Page를 찾는다. Page가 아직 생성되지 않았으면 인스턴스를 생성한다. Page에서 참조하는 ViewModel도 생성되지 않았으면 생성한다. Host의 서비스 제공자(ServiceProvider)가 DI(Dependency Injection) 컨테이너 역할을 담당해서 View와 ViewModel을 관리한다.