내급여 UWP 앱 개발

Part1

Part2

Part3

Part4

 

 

1. 나머지 컬럼 추가

 

어제 3개의 컬럼까지 추가해 보았습니다. 오늘은 나머지 컬럼을 모두 추가하도록 하겠습니다.

 

아래와 같이 나머지 컬럼도 추가하면서 아래 기능도 함께 추가합니다.

 

UserEditMode="Inline" : 줄 단위로 수정 할 수 있도록 합니다.

UserGroupMode="Disabled" : 쓸데 업는 그룹핑 기능은 막습니다.

FrozenColumnCount="1" : 첫번째 컬럼을 고정 컬럼으로 놓습니다.

 

SizeMode="Fixed" : 날짜나 시간을 수정하려면 컬럼이 대폭 넓어져서 가로로 스크롤이 생깁니다. 그래서 사이즈를 고정 시켜 버립니다.

 

            <tg:RadDataGrid ColumnDataOperationsMode="Flyout" x:Name="grid"
                            ItemsSource="{Binding Works}" AutoGenerateColumns="False" UserEditMode="Inline"
                            UserGroupMode="Disabled" FrozenColumnCount="1">
                <tg:RadDataGrid.Columns>
                    <tg:DataGridDateColumn PropertyName="WorkDay" Header="Work Day" CellContentFormat="{}{0:d}"
                                           SizeMode="Fixed" />
                    <tg:DataGridBooleanColumn Header="Holiday" PropertyName="IsHoliday" SizeMode="Fixed" Width="80" />
                    <tg:DataGridTimeColumn PropertyName="StartWork" Header="Start" CellContentFormat="{}{0:t}"
                                           SizeMode="Fixed" />
                    <tg:DataGridTimeColumn PropertyName="EndWork" Header="End" CellContentFormat="{}{0:t}"
                                           SizeMode="Fixed" />
                    <tg:DataGridTimeColumn PropertyName="BasicWorkTime" Header="1.0" CellContentFormat="{}{0:HH:mm}"
                                           CanUserEdit="False" />
                    <tg:DataGridTimeColumn PropertyName="OverTime15" Header="1.5" CellContentFormat="{}{0:HH:mm}"
                                           CanUserEdit="False" />
                    <tg:DataGridTimeColumn PropertyName="OverTime20" Header="2.0" CellContentFormat="{}{0:HH:mm}"
                                           CanUserEdit="False" />
                    <tg:DataGridTimeColumn PropertyName="OverTime25" Header="2.5" CellContentFormat="{}{0:HH:mm}"
                                           CanUserEdit="False" />

                    <tg:DataGridTimeColumn PropertyName="TodayWorkTime" Header="Total" CellContentFormat="{}{0:HH:mm}"
                                           CanUserEdit="False" />
                    <tg:DataGridTextColumn Header="Description" PropertyName="Description" />
                </tg:RadDataGrid.Columns>
            </tg:RadDataGrid>

 

실행 상태입니다.

 

 

일자를 수정하기 위해 클릭하면 날짜 선택 팝업이 출력되면서 입력할 수 있습니다.

 

 

 

2. 급여 기본 정보 작업 시작

 

설정창에서 급여 기본 정보를 입력 받아서 처리를 해야하니..그 쪽 작업을 좀 하도록 하겠습니다.

 

먼저!

 

모델을 추가해 볼까요?

 

위치는 Models 폴더에 PayInformation이라는 모델을 추가합니다.

 

using System;

namespace MyPay.Models
{
    /// <summary>
    ///     급여 정보
    /// </summary>
    public class PayInformation
    {
        /// <summary>
        ///     아이디 1.0, 1.5, 2.0, 2.5
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        ///     시작시간
        /// </summary>
        public DateTime StartTime { get; set; }

        /// <summary>
        ///     종료시간
        /// </summary>
        public DateTime EndTime { get; set; }

        /// <summary>
        ///     시급
        /// </summary>
        public int TimePay { get; set; }
    }
}

 

 

3. SettingsViewModel 뷰 모델에 디자인 타임 데이터 만들기

 

디자인 화면 작업을 위해서 디자인 타임 데이터를 추가해 놓습니다. 나중에는 저장되어 있던 내용을 불러와서 설정해 줘야 합니다.

 

        }
        {
            SwitchThemeCommand = new RelayCommand(async () => { await ThemeSelectorService.SwitchThemeAsync(); });

            PayInformations = new List<PayInformation>
            {
                new PayInformation{Id = "1.0", StartTime = DateTime.Parse("08:00"), EndTime = DateTime.Parse("18:00"), TimePay = 10000},
                new PayInformation{Id = "1.5", StartTime = DateTime.Parse("08:00"), EndTime = DateTime.Parse("18:00"), TimePay = 10000},
                new PayInformation{Id = "2.0", StartTime = DateTime.Parse("08:00"), EndTime = DateTime.Parse("18:00"), TimePay = 10000},
                new PayInformation{Id = "2.5", StartTime = DateTime.Parse("08:00"), EndTime = DateTime.Parse("18:00"), TimePay = 10000},
            };

        }

 

빌드를 하고 SettingPage.xaml.cs을 열어 줍니다.

 

 

4. SettingPage.xaml.cs

 

기존에 작성되어있던 ViewModel을 삭제하고 아래와 같이 변경해 줍니다. 기 작성되어 있던 녀석은 x:Bind를 사용하기 위해서 사용된 것인데...저는 x:Bind 보다는 일반 Binding 사용하는 것을 좋아 하기 때문입니다.

 

x:Bind와 Binding의 차이점은 구글에서 x:bind vs binding으로 검색하시면 여러가지 내용이 나오며 아래 링크를 참고 하셔도 될 것 같습니다.

https://stackoverflow.com/questions/37398038/difference-between-binding-and-xbind 

 

    }
    {
        public SettingsPage()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 뷰모델
        /// </summary>
        public SettingsViewModel ViewModel => DataContext as SettingsViewModel;

    }

 

 

5. SettingPage.xaml

 

SettingPage.xaml을 열어서 DataContext에 뷰모델을 연결하고, 기존에 바인딩되어 있던 녀석들을 수정해 줍니다.

 

Page를 선택하고, 프로퍼티창에서 DataContext를 찾고 New 버튼을 클릭합니다. Select Object 창에서 setting이라는 단어를 입력해서 필터링하고 SettingViewModel을 선택하고 OK를 눌러서 마무리 합니다.

 

SettingViewModel을 보이지 않는다면, 빌드를 하신 후 다시 시도하시면 됩니다.

 

 

기존 토글스위치 컨트롤에 InOn 프로퍼티에 연결된 바인딩을 수정해 줍니다.

 

ToggleSwitch를 선택하고 InOn 프로퍼티를 찾습니다. 오른쪽 노란색 네모를 클릭하면 컨텍스트 메뉴가 출력되고 Create Data Binding...을 선택합니다.

Create Data Binding for (ToggleSwitch).IsOn 이라는 팝업에서 IsLightThemeEnabled를 선택하고 OK를 클릭합니다.

 

팝업이 아래와 같이 나오지 않는다면, DataContext에 SettingViewModel이 연결이 되어 있지 않기 때문입니다.

 

 

InvokeCommandAction을 선택하고 Command 프로퍼티 오른쪽에 노란색 네모를 클릭해서 Create Data Binding... 메뉴를 선택합니다. 그리고 팝업에서 SwitchThemeCommand를 선택하고 OK를 클릭합니다.

 

화면에서 사용자의 인터렉션(마우스 클릭, 아이템 선택 변경 등)이 발생했을 때 Button이외의 컨트롤은 뷰모델에 변경 사항을 알려주는 방법이 InvokeCommandAction을 이용해서 뷰모델의 ICommand를 호출하는 것입니다. 

 

 

그외 x:Bind를 사용하는 곳도 동일한 방법으로 수정하시면 됩니다.

 

그리고, GridPage에 있던 RadDataGrid를 맨 첫번째 줄에 추가해 주고, 컬럼 4개도 정의를 해줍니다.

여러가지 삽질을 해본 결과 아래와 같은 형태가 제일 무난 한 것 같습니다.

 

전체 소스는 아래 와 같습니다.

 

<Page
    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:ic="using:Microsoft.Xaml.Interactions.Core"
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:grid="using:Telerik.UI.Xaml.Controls.Grid"
    xmlns:ViewModels="using:MyPay.ViewModels"
    x:Class="MyPay.Views.SettingsPage"
    mc:Ignorable="d">

    <Page.DataContext>
        <ViewModels:SettingsViewModel />
    </Page.DataContext>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid Margin="{StaticResource MediumLeftRightMargin}">
            <Grid.RowDefinitions>
                <RowDefinition Height="48" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <TextBlock
                Grid.Row="0"
                x:Uid="Settings_Title"
                x:Name="TitlePage"
                Style="{StaticResource PageTitleStyle}" />

            <Grid Grid.Row="1" Margin="0,16,0,0">
                <grid:RadDataGrid ColumnDataOperationsMode="Flyout" UserEditMode="Inline"
                                  UserGroupMode="Disabled" AutoGenerateColumns="False"
                                  ItemsSource="{Binding PayInformations}" HorizontalAlignment="Left">
                    <grid:RadDataGrid.Columns>
                        <grid:DataGridTextColumn Header="Id" PropertyName="Id" CanUserEdit="False" SizeMode="Auto"/>
                        <grid:DataGridTimeColumn PropertyName="StartTime" Header="Start" CellContentFormat="{}{0:t}"
                                               SizeMode="Auto" />
                        <grid:DataGridTimeColumn PropertyName="EndTime" Header="End" CellContentFormat="{}{0:t}"
                                               SizeMode="Auto" />
                        <grid:DataGridNumericalColumn Header="Hour Pay" PropertyName="TimePay" SizeMode="Auto" CellContentFormat="{}{0:n0}"/>

                    </grid:RadDataGrid.Columns>
                </grid:RadDataGrid>
            </Grid>

            <StackPanel Grid.Row="2" Margin="0,16,0,0">
                <TextBlock
                    x:Uid="Settings_Theme"
                    Style="{StaticResource BodyTextStyle}" />
                <ToggleSwitch
                    x:Uid="Settings_ThemeToggle"
                    IsOn="{Binding IsLightThemeEnabled}"
                    Margin="0,4,0,0">
                    <i:Interaction.Behaviors>
                        <ic:EventTriggerBehavior EventName="Toggled">
                            <ic:InvokeCommandAction Command="{Binding SwitchThemeCommand, Mode=OneWay}" />
                        </ic:EventTriggerBehavior>
                    </i:Interaction.Behaviors>
                </ToggleSwitch>
            </StackPanel>

            <StackPanel Grid.Row="3" Margin="0,16,0,0">
                <TextBlock
                    x:Uid="Settings_About"
                    Style="{StaticResource BodyTextStyle}" />
                <TextBlock
                    Text="{Binding AppDescription, Mode=OneWay}"
                    Style="{StaticResource BodyTextStyle}"
                    Margin="0,4,0,0" />
                <TextBlock
                    x:Uid="Settings_AboutDescription"
                    Style="{StaticResource BodyTextStyle}" />
                <HyperlinkButton
                    x:Uid="Settings_PrivacyTermsLink"
                    Margin="0,8,0,0" />
            </StackPanel>
        </Grid>
    </Grid>
</Page>

 

수정하기 전 화면

 

 

데이터 수정 화면

 

 

데이터 수정시 컨트롤의 너비가 넓어지고 여러가지 버튼들이 추가로 보이는 이유는 터치 기반 UI를 제공하기 때문입니다.

 

 

6. 선택된 탭이 변경될 때마다 커맨드 실행

 

처음에 탬플릿을 만들 때 Tab을 이용한 템플릿을 만들었기 때문에 GridPage와 SettingPage 사이를 이동할 때 Navigation을 타지 않습니다. 그래서, PivotPage에서 SelectedItem을 이용해서 LoadedCommand, UnloadedCommand를 실행 시키도록 합니다.

 

이 작업을 하려면 우선 인터페이스를 하나 추가합니다.

 

Interfaces 폴더를 하나 추가하고, 아래 코드를 이용해서 ILoadedUnloaded.cs 를 추가 합니다.

 

    /// <summary>
    /// ILoadedUnloaded
    /// </summary>
    public interface ILoadedUnloaded
    {
        ICommand LoadedCommand { get; }
        ICommand UnloadedCommand { get; }
    }

 

이 인터페이스를 GridViewModel.cs와 SettingViewModel.cs에 추가해서 인터페이스를 구현 합니다.

 

GridViewModel을 예로 적었습니다. SettingViewModel도 아래 내용을 참고해서 수정하시면 됩니다.

 


    /// 그리드 뷰모델
    /// </summary>
    public class GridViewModel : Observable, ILoadedUnloaded
    {

        /// <summary>
        ///     기본 생성자
        /// </summary>
        public GridViewModel()
        {

            ...

            Init();

        }

        ...

        private void Init()
        {
            LoadedCommand = new RelayCommand(OnLoaded);
            UnloadedCommand = new RelayCommand(OnUnloaded);
        }

 

        public ICommand LoadedCommand { get; private set; }
        public ICommand UnloadedCommand { get; private set; }

    }

 

 

 

 

인터페이스를 사용하기 위해서 PivotPage에 PivotViewModel을 연결하고 Pivot 컨트롤에 SelectedItem 프로퍼티를 바인딩합니다.

 

기존 PivotItem 내부에 <Frame></Frame>은 지워버립니다.

 

<Page
    x:Class="MyPay.Views.PivotPage"
    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:model="using:MyPay.Models"
    xmlns:views="using:MyPay.Views"
    xmlns:viewModels="using:MyPay.ViewModels"
    mc:Ignorable="d">
    <Page.DataContext>
        <viewModels:PivotViewModel />
    </Page.DataContext>

   
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Pivot x:Uid="PivotPage" SelectedItem="{Binding SelectedView, Mode=TwoWay}">
            <PivotItem x:Uid="PivotItem_Grid">
                <views:GridPage/>
            </PivotItem>

            <PivotItem x:Uid="PivotItem_Settings">
                <views:SettingsPage/>
            </PivotItem>

        </Pivot>
    </Grid>
</Page>

 

 

PivotViewModel.cs를 아래와 같이 수정합니다.

 

    /// <summary>
    /// 피벗 뷰모델
    /// </summary>
    public class PivotViewModel : Observable
    {
        private PivotItem _selectedView;

        /// <summary>
        /// 기본 생성자
        /// </summary>
        public PivotViewModel()
        {
        }
 
        /// <summary>
        /// 탭이 변경될 때마다 커맨드 실행
        /// </summary>
        public PivotItem SelectedView
        {
            get => _selectedView;
            set
            {
                if (_selectedView == value) return;
                var view = _selectedView?.Content as FrameworkElement;
                var viewModel = view?.DataContext as ILoadedUnloaded;
                viewModel?.UnloadedCommand.Execute(null);
                Set(ref _selectedView ,value);
                view = _selectedView?.Content as FrameworkElement;
                viewModel = view?.DataContext as ILoadedUnloaded;
                viewModel?.LoadedCommand.Execute(null);
            }
        }
    }

 

이렇게 하고 GridViewModel과 SettingViewModel에 OnLoaded 메소드에 브레이크 포인트를 걸고 테스트를 하면 실행이 되는 것을 볼 수 있습니다.

 

이 작업을 하는 이유는 설정 화면의 경우 OnLoaded 실행 될 때 저장되어 있던 급여 정보를 불러와서 화면에 뿌려주고, OnUnloaded가 실행되면 화면에 정보를 저장하기 위해서 사용됩니다.

 

 

7. 긴급 상황 오류 발생

 

작업 중 아래와 같은 오류가 발생해서 더이상 디자인 타임이 보이지 않습니다. ㅡㅡ;;;

 

해결 방법은 아직 없는 상태 입니다. 알려진 이슈로 수정 중으로 알고 있습니다. 불편하지만 디자인 화면 무시하고 그냥 XAML을 직접 수정하고 실행해서 결과를 확인하도록 하겠습니다. 나중에 이 오류에 대한 해결 방법이 나오면 공유 하도록 하겠습니다.

 

이슈 등록된 내용

https://developercommunity.visualstudio.com/content/problem/48426/xaml-designer-fails-to-render-content-with-message.html

 

System.Runtime.InteropServices.COMException
The deployment operation failed because the specified application needs to be registered first. (Exception from HRESULT: 0x80073D0F)
   at Microsoft.VisualStudio.DesignTools.UwpDesignerHost.AppPackage.AppPackageNativeMethods.IApplicationActivationManager.ActivateApplication(String appUserModelId, String activationContext, ActivateOptions options, Int32& processId)
   at Microsoft.VisualStudio.DesignTools.UwpDesignerHost.AppPackage.AppPackageHelper.ActivateApplication(String appUserModelId, Boolean designerMode, String activationContext, Object site)
   at Microsoft.VisualStudio.DesignTools.UwpDesignerHost.UwpHostPlatform.ActivateApplication(String appUserModelId, Boolean designerMode, String activationContext, Object site)
   at Microsoft.VisualStudio.DesignTools.XamlDesignerHost.Platform.AppContainerProcessDomainFactory.ActivateApplicationInternal(String appUserModelId, String activationContext, Object site)
   at Microsoft.VisualStudio.DesignTools.XamlDesignerHost.Platform.AppContainerProcessDomainFactory.CreateDesignerProcess(String applicationPath, String clientPort, Uri hostUri, IDictionary environmentVariables, Int32& processId, Object& processData)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.Primitives.ProcessDomainFactory.ProcessIsolationDomain..ctor(ProcessDomainFactory factory, IIsolationBoundary boundary, AppDomainSetup appDomainInfo, IIsolationTarget isolationTarget, String baseDirectory)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.Primitives.ProcessDomainFactory.CreateIsolationDomain(IIsolationBoundary boundary)
   at Microsoft.VisualStudio.DesignTools.XamlDesignerHost.Platform.AppContainerProcessDomainFactory.CreateIsolationDomain(IIsolationBoundary boundary)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.Primitives.IsolationBoundary.Initialize()
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.Primitives.IsolationBoundary.CreateInstance[T](Type type)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.IsolatedObjectFactory.Initialize()
   at Microsoft.VisualStudio.DesignTools.DesignerHost.Services.VSIsolationService.CreateObjectFactory(IIsolationDomainFactory isolationDomainFactory, IObjectCatalog catalog)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.IsolationService.CreateLease(IIsolationDomainFactory domainFactory)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.IsolatedDesignerService.CreateLease(IIsolationDomainFactory domainFactory, CancellationToken cancelToken, DesignerServiceEntry& entry, IServiceProvider serviceOverrides)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.IsolatedDesignerService.IsolatedDesignerView.CreateDesignerViewInfo(CancellationToken cancelToken)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.IsolatedTaskScheduler.InvokeWithCulture[T](CultureInfo culture, Func`2 func, CancellationToken cancelToken)
   at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.IsolatedTaskScheduler.<>c__DisplayClass10_0`1.<StartTask>b__0()
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()

 

 

8. 지금까지 작업 내용

 

두개의 화면에 디자인 타임 데이터를 대충 뿌려서 화면 디자인을 완료했습니다. 그나마 천만 다행입니다. 하하;;;

다음에는 로컬 저장소에 급여 정보를 저장하고 불러오는 방법을 추가하고, 앱이 시작하면 그리드에 근무 정보를 입력하면 자동으로 계산이 되도록 기능을 추가해 보도록 하겠습니다.

 

'Windows App(Universal App) > Beginner' 카테고리의 다른 글

내급여 UWP 앱 개발 part5  (0) 2017.09.18
내급여 UWP 앱 개발 part4  (0) 2017.09.04
내급여 UWP 앱 개발 part3  (0) 2017.08.26
내급여 UWP 앱 개발 part2  (4) 2017.08.24
내급여 UWP 앱 개발 part1  (0) 2017.08.22
UWP 앱 개발 궁금증 해결 포스트  (0) 2017.08.08
블로그 이미지

kaki104

/// Microsoft MVP - Windows Development - Apr 2014 ~ Mar 2018 /// email : kaki104@daum.net, twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바