앱 개발 템플릿을 보면 기본적인 모양의 타일들로만 구성이 되어 있다. 하지만, 기본으로 제공하는 앱들 중 여행 앱을 보면 메인 화면에 다양한 형태의 타일들이 붙어 있는 것을 볼 수 있다. 이런 다양한 모양의 타일을 가지는 앱을 만들기 위한 방법을 살펴 보기로 하자.

 

 

1. HubApp-Beta1 online template

Visual Studio 2012에 File -> New -> Project -> Online -> Template -> Visual C# -> 2page -> HubAppExtension 선택 -> OK

 

 

온라인 템플릿을 선택해서 프로젝트를 생성 한 후 실행을 해보자

 

 

다양한 모양의 타일을 가진 템플릿을 볼 수 있다.

여행 앱 처럼 더 다양하지는 않지만..그래도 기본 템플릿에 비하면 엄청난 발전이 느껴지는 템플릿이다. 이제 어떻게 구성된 템플릿인지 좀더 자세하게 살펴 보아야 할 것 같다.

 

2. GridViewVariableWrapPanel

MainHubPage.xaml 디자인 타임 화면을 열고 그리드 뷰를 찍어보았다.

 

        <!-- Horizontal scrolling grid used in most view states -->
        <ctrl:GridViewVariableWrapPanel x:Name="itemGridView"
            AutomationProperties.AutomationId="ItemGridView"
            AutomationProperties.Name="Grouped Items"
            Grid.RowSpan="2"
            Padding="116,137,40,46"
            ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
            SelectionMode="None"
            IsSwipeEnabled="false"
            IsItemClickEnabled="True"
            ItemClick="ItemView_ItemClick"           
            GroupStyleSelector="{StaticResource HubPageGroupStyleSelector}"
            ItemTemplateSelector="{StaticResource HubPageItemTemplateSelector}"
            ItemsPanel="{StaticResource HorizontalHubItemPanel}" />

 

이게 전부다.

우선 GridViewVariableWrapPanel이라는 녀석의 정체부터 파악해보자

Controls -> GridViewVariableWrapPanel.cs 파일에 있는 컨트롤로 GridView 컨트롤을 상속 받았다

 

    public class GridViewVariableWrapPanel : GridView
    {
        [DebuggerNonUserCode]

        protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
        {
            try
            {
                if (item is IVariableGridSize)
                {
                    dynamic _Item = item;
                    element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, _Item.ColumnSpan);
                    element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.RowSpanProperty, _Item.RowSpan);
                }
            }
            catch // Ignoring Exceptions here is by design
            {
                element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, 1);
                element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.RowSpanProperty, 1);
            }
            finally
            {
                base.PrepareContainerForItemOverride(element, item);
            }
        }
    }

 

아이뎀을 그려줄 때 그 아이템이 IVariableGridSize라는 인터페이스를 상속받은 모델인지 확인해서, 상속 받은 모델이라면 타일 엘리먼트의 ColumnSpan, RowSpan을 설정해주는 작업을 한다. 큰 타일, 작은 타일을 동적으로 구성하는데 도움을 준다.

 

3. ItemsSource

ItemsSource에 바인딩된 데이터를 한번 살펴 보도록 하자

 

ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"

 

뷰 상단에 리소스로 등록되어있는 CollectionViewSource를 찾고

        <CollectionViewSource
            x:Name="groupedItemsViewSource"
            Source="{Binding Groups}"
            IsSourceGrouped="true"
            d:Source="{Binding SampleItems, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>

Source 프로퍼티에 바인딩 되어있는 것이 Groups라는 것을 찾았다.

 

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            // TODO: Create an appropriate data model for your problem domain to replace the sample data
            SampleDataSource data = new SampleDataSource();
            this.DefaultViewModel["Groups"] = data.SampleItems;
        }

 

Groups는 뷰의 cs에서 data.SampleItems를 넣어주는 것이고,

 

DataModel -> SampleDataSource.cs 에서 SampleDataSource를 찾아서 보니

 

        private ObservableCollection<GroupInfoList<object>> _sampleItems = new ObservableCollection<GroupInfoList<object>>();
        public ObservableCollection<GroupInfoList<object>> SampleItems
        {
            get { return this._sampleItems; }
        }

 

GroupInfoList<object>형을 사용하는 것을 알 수 있다.

사실 여기까지는 크게 새로운 것이 없었는데..

 

4. GroupStyleSelector="{StaticResource HubPageGroupStyleSelector}" ItemTemplateSelector="{StaticResource HubPageItemTemplateSelector}"

 

이 부분이 바로 문제의 핵심으로, 먼저 GroupStyleSelector를 보면, HubPageGroupStyleSelector로, HubAppStyles.xaml에 존재한다.

* GroupStyleSelector는 그룹의 스타일을 조건에 따라 변경할 수 있도록 만들어진 프로퍼티이다.

 

<TS:HubPageGroupStyleSelector x:Key="HubPageGroupStyleSelector" DefaultGroupStyle="{StaticResource HubPageGroupStyle}" />

 

* HubPageGroupStyleSelector는 비교적 간단한 셀렉터로 DefaultGroupStyle이라는 프로퍼티를 반환한다. 즉 이넘은 여러개의 템플릿을 교환하는 용도가 아니라, 다음에 이야기할 셀렉터의 중간 다리 역할을 할 뿐이다.

    public class HubPageGroupStyleSelector : GroupStyleSelector
    {
        public GroupStyle DefaultGroupStyle { get; set; }

        protected override GroupStyle SelectGroupStyleCore(object group, uint level)
        {
            return DefaultGroupStyle;
        }
    }

 

DefaultGroupStyle에 연결되어 있는 HubPageGroupStyle의 내용을 살펴보자

 

    <GroupStyle x:Key="HubPageGroupStyle"
                ContainerStyleSelector="{StaticResource HubPageContainerStyleSelector}"
                HeaderTemplateSelector="{StaticResource HubPageGroupTemplateSelector}"
                Panel="{StaticResource NormalGroupItemsPanel}" />

 

 

* 그룹 스타일을 보니 ContainerStyleSelector, HeaderTemplateSelector, Panel 이렇게 3개의 프로퍼티를 가지고 있는 걸 알 수 있다. 여기서 다시 한단계 더 들어가보면 HubPageContainerStyleSelector 가 있다.

 

-> 이것도 바로 위에 존재 한다.

 

    <TS:HubPageContainerStyleSelector x:Key="HubPageContainerStyleSelector"
                                      HubGroupContainerStyleDefault="{StaticResource DefaultContainerStyle}" />

 

* HubPageContainerStyleSelector의 소스를 보면 기본 스타일 셀렉터를 상속 받았고, 내부에 HubGroupContainerStyleDefault라는 프로퍼티를 가지고 있다. 스타일 셀렉터에서는 스타일을 변경할 때 SelectStyleCore 메소드를 override 받아서 사용하는데, GroupContainerDataTemplate라는 곳에 템플릿을 반환하는 XamlResourceHelper의 기능이 좀 특이하다.

 

    public class HubPageContainerStyleSelector : StyleSelector
    {
        public Style HubGroupContainerStyleDefault { get; set; }

 

        protected override Style SelectStyleCore(object item, DependencyObject container)
        {
            try
            {
                var GroupContainerDataTemplate = XamlResourceHelper.GetGroupContainerStyleFromPage(item, container);

                if (GroupContainerDataTemplate != null)
                {
                    return GroupContainerDataTemplate;
                }
                else
                {
                    return HubGroupContainerStyleDefault;
                }
            }
            catch (System.Exception ex)
            {
                if (Debugger.IsAttached)
                {
                    Debug.WriteLine("Error in HubPageContainerStyleSelector.SelectStyleCore, details: " + ex);
                    Debugger.Break();
                }

                return base.SelectStyleCore(item, container);
            }
        }
    }

 

5. XamlResourceHelper.cs

이 프로젝트의 핵심 기능이 들어 있다고 생각한다.

 

        public static Style GetGroupContainerStyleFromPage(object item, DependencyObject container)
        {
            Style output = null;

            string GroupKey = string.Empty;

            //그룹의 Key값을 찾음

            if (item != null && item is GroupInfoList<object>)
            {
                GroupKey = ((GroupInfoList<object>)item).Key;
            }
            else if (item != null && item is ICollectionViewGroup)
            {
                var groupView = item as ICollectionViewGroup;
                var groupCollection = groupView.Group as GroupInfoList<object>;

                GroupKey = groupCollection.Key;
            }
            else
            {
                Debug.WriteLine("MainPageGroupTemplateSelector cannot determine the item type to retrieve the key");
                return null;
            }

            // Get the datatemplate from resource dictionary within the host page, Key값 + "Container"라는 이름의 스타일을 찾음
            output = XamlResourceHelper.GetStyleFromPage(container, GroupKey + "Container");

            return output;
        }

 

...

 

        private static object GetDataTemplateFromPage(DependencyObject container, string TemplateName, string TemplateSuffix)
        {
            object output = null;

            Page ParentPage = container.FindParentPage() as Page; // Note: Have to use the FindParentPage since App.Current.Resources does not contain items from page, 확장메소드로 컨테이너가 속한 페이지를 반환

            // Create proper template name, 템플릿 이름 구성하고
            string NameOfResource = TemplateName + TemplateSuffix;
            Debug.WriteLine("GetDataTemplateFromPage is looking for a template with name " + NameOfResource);

            // 페이지에서 해당 이름의 데이터를 찾고

            if (ParentPage != null && ParentPage.Resources.Count > 0)
            {
                // See if it exists based on key
                if (ParentPage.Resources.ContainsKey(NameOfResource))
                {
                    // If it does, get it and return it
                    var ResourceObject = ParentPage.Resources[NameOfResource] as object;

                    output = ResourceObject;
                }
            }

            if (output == null)
            {
                // Finaly attempt, try looking in global resource dictionary, 페이지에서 찾지 못하면 전역 리소스에서 찾고
                try
                {
                    Debug.WriteLine("Looking for style in global resource dictionary, key: " + NameOfResource);

                    output = Application.Current.Resources[NameOfResource] as object;
                }
                catch (System.Exception)
                {
                    // Empty by design
                }
            }

            return output;
        }

 

...

 

그룹스타일 셀렉터, 아이템 템플릿 셀렉터.. 각각의 셀렉터들이 모두 비슷한 동작을 한다. 즉, 클래스 이름을 기반으로 스타일이나, 템플릿을 찾아서 사용하도록 구성되어 있는 것이다.

 

6. 그렇다면, 클래스의 이름 기반 템플릿이 아닌 다른 이름의 템플릿을 사용해야 하는 경우는 어떻게 처리하나?

 

SampleDataSource.cs

원래대로라면 SampleDataItem3라는 이름의 템플릿을 찾아서 사용해야 하겠지만, 이 템플릿은 SampleDataItemThree라는 이름의 템플릿을 사용하도록 지정되어있다. TemplateItemName이란 속성은 사용자가 추가한 속성이다. 그래서, 템플릿의 이름을 찾을 때 확장 메소드  GetTemplateName을 사용해서 이름을 반환 받아야 한다.

 

    [TemplateItemName("SampleDataItemThree")] // If you dont want to use class name for template selection, use this attribute to override
    public class SampleDataItem3 : SampleDataCommon, ISampleDataItem, IVariableGridSize
    {
        public SampleDataItem3(String uniqueId, String title, String subtitle, String imagePath, String description, String content, GroupInfoList<object> group)
            : base(uniqueId, title, subtitle, imagePath, description)
        {
            this._content = content;
            this._group = group;
        }

        private string _content = string.Empty;
        public string Content
        {
            get { return this._content; }
            set { this.SetProperty(ref this._content, value); }
        }

        private GroupInfoList<object> _group;
        public GroupInfoList<object> Group
        {
            get { return this._group; }
            set { this.SetProperty(ref this._group, value); }
        }

        public int RowSpan { get; set; }
        public int ColumnSpan { get; set; }
    }

 

7. Extension Methods 확장 메소드

 

이 프로젝트에서 사용되는 확장 메소드는 2가지가 있다.

        //부모 페이지를 반환한다.

        public static DependencyObject FindParentPage(this DependencyObject child)
        {
            var parent = VisualTreeHelper.GetParent(child);

            if (parent == null)
            {
                return null;
            }

            if (parent is Page)
            {
                return parent;
            }
            else
            {
                return parent.FindParentPage();
            }
        }

 

        //템플릿 이름을 반환한다.

        public static string GetTemplateName(this object item)
        {

            string output = string.Empty;

            // Try to get name from attribute
            System.Reflection.MemberInfo info = item.GetType().GetTypeInfo();
            foreach (object attrib in info.GetCustomAttributes(true))
            {
                if (attrib is TemplateItemName)
                {
                    var TemplateNameAttribute = attrib as TemplateItemName;

                    output = TemplateNameAttribute.Name;
                }
            }

            // If not found in attribute, try to get name from Type (class name)
            if (output == string.Empty)
            {
                output = item.GetType().GetTypeInfo().Name;
            }

            if (item is IVariableGridSize)
            {
                var itemWithSize = item as IVariableGridSize;

                if (itemWithSize.RowSpan > 1 || itemWithSize.ColumnSpan > 1)
                {
                    output = output + String.Format("{0}by{1}", itemWithSize.RowSpan, itemWithSize.ColumnSpan);
                }
            }

            return output;
        }

 

8. 템플릿 활용하기.

아주 멋지게 만들어진 프로젝트 템플릿이라는 생각이 든다. 사용자가 약간만 자신의 취향에 맞게 수정해서 사용하면 멋진 앱을 만들 수 있을 것이다.

 

'Windows 8&8.1 > Samples' 카테고리의 다른 글

HubApp using a dynamic style app development  (0) 2013.08.01
Using Task, Async, Await  (0) 2013.07.28
Custom Behavior Sample  (0) 2012.12.25
Text file encoding sample  (0) 2012.11.28
WinRT File Based Database sample  (0) 2012.11.12
Tile + BackgroundTask = LiveTile APP!  (0) 2012.10.30
블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

.Net Framework 4.5에서 아주 많이 사용되고 있는 Task, Async, Await에 대해서 설명을 하려고 한다.

 

기존 .Net Framework 4.0에서는 async, await라는 키워드 세트를 이용해서 멀티 스레드 처리를 하지 않고, 별도의 스레드 관리 방법으로 사용을 했다. 그런데, 사용이 쉽지 않고, 관리가 어려워서 잘 사용을 하지 않은 것 같다. MS에서도 그 점을 알았는지, 쉽고 편하게 멀티스레드를 사용할 수 있도록 만들고, .Net Framework 4.5는 Async 기반 프레임웍이라고 이야기를 했다.

 

참고 링크

Best Practices in Asynchronous Programming

http://msdn.microsoft.com/en-us/magazine/jj991977.aspx

간편한 비동기 프로그래밍:async/await (1) -> 동기 비동기에 관한 기본적인 내용에 대해서 알아보기 좋내요

http://www.simpleisbest.net/post/2013/02/06/About_Async_Await_Keyword_Part_1.aspx

 

 

1. 데이터 로딩 작업(1)

앱이 구동된 후에 3가지 데이터를 로딩 하는 작업이 있다고 가정하자.

셈플 코드를 만들면 아래와 같다.

 

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DataLoding();
        }

        private void DataLoding()
        {
            DataLoading1();
            DataLoading2();
            DataLoading3();

            listBox.Items.Add("DataLoading...");
        }

        private async void DataLoading1()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading1 Complete");
        }

        private async void DataLoading2()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading2 Complete");
        }
       
        private async void DataLoading3()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading3 Complete");
        }

실행을 한다. xaml에는 ListBox를 한개 추가해 놓았다.

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <ListBox x:Name="listBox" FontSize="48"/>
    </Grid>

 

 

3개의 데이터가 모드 로드 완료되었다. 그런데, 이상한 점은 시작은 1,2,3 순서로 했는데 표시된 순서는 2,1,3으로 표시가 되어있다.

위의 소스처럼 각 메소드를 async, void 호출을 하면,

장점은 Main Thread에서 await를 하지 않기 때문에 각 메소드가 Work Thread로 분리되어서 처리가 진행 되어, 화면 진행에 Lock을 발생시키지 않는 것이다. 그래서, DataLoading...이라는 글씨가 맨 위에 들어가고 나머지 완료 메시지들이 아래 부분에 표시된다.

단점은 완료 시점을 명확하게 알 수 없다는 부분과, 어떤 작업 순서에 의해, 제일 중요한 데이터 실행 후 다른 작업을 진행해야하는데, 그렇게 할 수 없다는 것이다.

 

 

2. 데이터 로딩 작업(2)

DataLoading1번 작업이 완료된 후에 DataLoading2, 3번 작업이 진행되도록 진행 순서를 변경하여 보자.

 

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DataLoding();
        }

        private async void DataLoding()
        {
            await DataLoading1();
            DataLoading2();
            DataLoading3();
            listBox.Items.Add("DataLoading...");
        }

        private async Task DataLoading1()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading1 Complete");
        }

        private async void DataLoading2()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading2 Complete");
        }
       
        private async void DataLoading3()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading3 Complete");
        } 

 

 

위의 결과를 보면 DataLoading1 작업이 완료된 후 나머지 작업들이 완료된 모습을 볼 수 있다.

소스를 보면 굵게 표시한 부분이 변경된 것들이다.

 

일반적인 경우라면 위의 처리 방법으로 처리를 해도 무난할 것이라고 생각된다.

 

3. 데이터 로딩 작업(3)

 

여기서 모든 작업 완료 후 DataLoading all complete라는 메시지를 출력해야 한다면 어떻게 해야할까?

 

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DataLoding();
        }

        private async void DataLoding()
        {
            listBox.Items.Add("DataLoading...");
            await DataLoading1();
            await DataLoading2();
            await DataLoading3();
            listBox.Items.Add("DataLoading all complete");
        }

        private async Task DataLoading1()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading1 Complete");
        }

        private async Task DataLoading2()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading2 Complete");
        }

        private async Task DataLoading3()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading3 Complete");
        }

 

 

위와 같이 각 메소드에 Task를 걸고 모두 순차 적으로 awit를 붙여주면 쉽게 처리가 가능하다. 하지만, 이것이 정말 원하는 결과일까? 이렇게 사용하게되면, 최종 메시지가 출력되기 까지 100 + 100 + 100 라는 시간이 필요할 것이다.

 

아래와 같이 수정하도록 하자

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DataLoding();
        }

        private async void DataLoding()
        {
            listBox.Items.Add("DataLoading...");
            var tasks = new Task[] { DataLoading1(), DataLoading2(), DataLoading3() };
            await Task.WhenAll(tasks);
            listBox.Items.Add("DataLoading all complete");
        }

        private async Task DataLoading1()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading1 Complete");
        }

        private async Task DataLoading2()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading2 Complete");
        }

        private async Task DataLoading3()
        {
            await Task.Delay(100);
            listBox.Items.Add("DataLoading3 Complete");
        }

 

 

위의 문장으로 수정하면, 3개의 Task를 한꺼번에 await 걸어 놓는 것과 같다. WhenAll 이외에도 여러가지 기능들이 있으니 참고하고,

Async, Await를 이용하면 유연한 프로그램을 할 수 있고, 성능개선에도 많은 도움이 된다. 이번 포스트는 여기까지 하고, 소스는 따로 올리지 않겠다.

 

'Windows 8&8.1 > Samples' 카테고리의 다른 글

HubApp using a dynamic style app development  (0) 2013.08.01
Using Task, Async, Await  (0) 2013.07.28
Custom Behavior Sample  (0) 2012.12.25
Text file encoding sample  (0) 2012.11.28
WinRT File Based Database sample  (0) 2012.11.12
Tile + BackgroundTask = LiveTile APP!  (0) 2012.10.30
블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

실버라이트 개발시에는 Trigger와 Behavior를 이용해서 MVVM pattern으로 Loosly-coupled한 개발이 가능했는데, Windows 8으로 오면서 Blend에서 Trigger와 Behavior가 존재하지 않아서 cs페이지에 이벤트 코딩을 추가해야 하는 경우가 많아 졌다.

그래서, Custom Behavior를 직접 만들어서 추가하고, 사용하는 방법에 대해서 알아보도록 하겠다.

 

Custom Behavior 장점

1) xaml.cs에 컨트롤에 대한 이벤트 코딩을 할 필요가 없다. 

2) 각 화면별로 중복되는 cs코드가 있는 경우 코드를 줄이는 역할을 한다.

3) 좀 더 개선된 Behavior를 만들 수 있다면, ViewModel의 Command를 직접 호출하는 Behavior도 만들어서 사용 한다면, 더 좋은 효과를 얻을 수 있다.

 

Custom Behavior의 단점

1) 블랜드에서 직접 Drag&Drop으로 사용 할 수 없다. 수동으로 처리해야 한다.

 

참고 & 필수 패키지

WinRT XAML Toolkit

http://winrtxamltoolkit.codeplex.com/

Using Behaviors in Windows 8 Store Apps

http://blogs.u2u.be/diederik/post/2012/10/28/Using-Behaviors-in-Windows-8-Store-Apps.aspx

 

1. Split App(XAML) 새 프로젝트를 생성한다.

여기서 만들 Behavior는 참고 포스트에서 다루고 있는 TextBox 컨트롤에 포커스가 되었을 때 모든 텍스트를 선택하는 것이다. 다른 점은 WinRT XAML Toolkit에 있는 Behavior를 이용하고, 내용도 약간 수정을 했다.

 

프로젝트에 WinRT XAML Toolkit을 설치한다.

 

 

2. SelectAllOnFocusBehavior.cs

 

    public class SelectAllOnFocusBehavior : Behavior<TextBox>
    {
        /// <summary>
        /// 디자인 타임 프로퍼티 추가
        /// </summary>
        public static readonly DependencyProperty SelectAllOnFocusProperty =
            DependencyProperty.RegisterAttached
            (
                "SelectAllOnFocus",
                typeof(bool),
                typeof(SelectAllOnFocusBehavior),
                new PropertyMetadata(false, OnSelectAllOnFocus)
            );

 

        /// <summary>
        /// 접근 프로퍼티 추가 - 소스에서 이 부분 수정 필요
        /// </summary>
        public bool SelectAllOnFocus
        {
            get { return (bool)GetValue(SelectAllOnFocusProperty); }
            set { SetValue(SelectAllOnFocusProperty, value); }
        }


        /// <summary>
        /// 프로퍼티 변경 이벤트 처리
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        protected static void OnSelectAllOnFocus(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var source = (SelectAllOnFocusBehavior)d;
            source.SelectAllOnFocus = (bool)e.NewValue;
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public SelectAllOnFocusBehavior()
        {
            SelectAllOnFocus = true;
        }

        /// <summary>
        /// 컨트롤에 Attach될 때
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();
            //AssociatedObject는 Behavior<TextBox> 문장에 의해서 TextBox가 됨
            if (AssociatedObject != null)
            {
                //GetFocus 이벤트 처리 추가
                AssociatedObject.GotFocus += AssociatedObject_GotFocus;
            }
        }

        /// <summary>
        /// 컨트롤에서 Detach될 때
        /// </summary>
        protected override void OnDetaching()
        {
            if (AssociatedObject != null)
            {
                //GetFocus 이벤트 처리 제거
                AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
            }
            base.OnDetaching();
        }

        /// <summary>
        /// GetFoucs 처리
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
        {
            if (AssociatedObject != null)
            {
                if (SelectAllOnFocus)
                {
                    //모든 텍스트 선택 메소드 실행
                    AssociatedObject.SelectAll();
                }
            }
        }
    }

 

위의 코딩 방식은 사용자 정의 컨트롤을 만들 때에도 사용이 되므로 익혀 놓으면 도움이 된다.

F6을 눌러서 빌드를 한다.

 

3. ItemsPage.xaml

 

네임스페이스 추가해준다.

    xmlns:interactivity="using:WinRTXamlToolkit.Interactivity"

 

타이틀 부분에 아래 코드를 추가해 준다.

        <!-- Back button and page title -->
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
            <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{StaticResource AppName}" IsHitTestVisible="false" Style="{StaticResource PageHeaderTextStyle}"/>
            <TextBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200">
                <interactivity:Interaction.Behaviors>
                    <local:SelectAllOnFocusBehavior SelectAllOnFocus="{Binding IsSelectAllFocus, Mode=TwoWay}"/>
                </interactivity:Interaction.Behaviors>

            </TextBox>

            <CheckBox x:Name="checkBox" Grid.Column="1" HorizontalAlignment="Right" Content="Select All Text" Margin="0,0,50,0"/>

        </Grid>

디자인 타임에 프로퍼티를 보면 SelectAllOnFocus 프로퍼티를 볼 수 있다.

(비헤이비어 프로퍼티에 접근이 않되면 인터렉션 부분을 다른 영역으로 이동해서 바인딩하고 다시 원래 위치로 이동한다)

 

 

CheckBox는 SelectAllOnFocus 프로퍼티를 변경하기 위해 추가했다.

 

4. ItemsPage.xaml.cs

 

    public sealed partial class ItemsPage : BehaviorSample.Common.LayoutAwarePage
    {
        public ItemsPage()
        {
            this.InitializeComponent();

            checkBox.Click +=
                (s, e) =>
                {
                    var cb = e.OriginalSource as CheckBox;
                    this.DefaultViewModel["IsSelectAllFocus"] = cb.IsChecked;
                };

        }

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
            this.DefaultViewModel["Items"] = sampleDataGroups;

            //기본 설정
            checkBox.IsChecked = true;
            this.DefaultViewModel["IsSelectAllFocus"] = true;

        }

        void ItemView_ItemClick(object sender, ItemClickEventArgs e)
        {
            var groupId = ((SampleDataGroup)e.ClickedItem).UniqueId;
            this.Frame.Navigate(typeof(SplitPage), groupId);
        }
    }

 

5. 실행화면

 텍스트를 입력 후 텍스트박스에 포커스가 가면 모든 텍스트가 선택되는 것을 확인 할 수 있다.

 

Select All Text 체크박스를 클릭 후 다시 포커스를 이동하면 텍스트가 선택이 되지 않는다.

 

6. 비헤이비어를 잘 활용하면 여러가지 기능을 추가할 수 있다.

다음에 기회가 된다면 추가된 기능을 가지는 비헤이비어 만드는 방법에 대해서도 포스트 하겠다.

 

소스 

BehaviorSample.zip

 

7. PageBehavior

Page의 Load, Unload, SizeChanged 이벤트를 커맨드로 바로 연결해서 사용한다.

 

using System.Windows.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using WinRTXamlToolkit.Interactivity;

 

namespace kaki104.MetroCL.Behaviors
{
    public class PageBehavior : Behavior<Page>
    {
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached
            (
                "Command",
                typeof(ICommand),
                typeof(PageBehavior),
                new PropertyMetadata(null, CommandPropertyChanged)
            );

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (PageBehavior)d;
            ICommand oldCommand = (ICommand)e.OldValue;
            ICommand newCommand = target.Command;
            target.OnCommandChanged(oldCommand, newCommand);
        }

        protected virtual void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
        {
            Command = newCommand;

        }

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.RegisterAttached
            (
                "CommandParameter",
                typeof(object),
                typeof(PageBehavior),
                new PropertyMetadata(null, OnCommandParameterChanged));

        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        private static void OnCommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (PageBehavior)d;
            object oldCommandParameter = (object)e.OldValue;
            object newCommandParameter = target.CommandParameter;
            target.OnCommandParameterChanged(oldCommandParameter, newCommandParameter);
        }

        protected virtual void OnCommandParameterChanged(object oldCommandParameter, object newCommandParameter)
        {
            CommandParameter = newCommandParameter;
        }

        public static readonly DependencyProperty EventNameProperty =
            DependencyProperty.RegisterAttached
            (
                "EventName",
                typeof(string),
                typeof(PageBehavior),
                new PropertyMetadata(null, OnEventNameChanged));

        public string EventName
        {
            get { return (string)GetValue(EventNameProperty); }
            set { SetValue(EventNameProperty, value); }
        }

        private static void OnEventNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (PageBehavior)d;
            string oldEventName = (string)e.OldValue;
            string newEventName = target.EventName;
            target.OnEventNameChanged(oldEventName, newEventName);
        }

        protected virtual void OnEventNameChanged(string oldEventName, string newEventName)
        {
            EventName = newEventName;
        }

        public PageBehavior()
        {
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            if (AssociatedObject != null)
            {
                switch (EventName)
                {
                    case "SizeChanged":
                        AssociatedObject.SizeChanged += AssociatedObject_SizeChanged;
                        break;
                    case "Loaded":
                        AssociatedObject.Loaded += AssociatedObject_LoadUnloaded;
                        break;
                    case "Unloaded":
                        AssociatedObject.Unloaded += AssociatedObject_LoadUnloaded;
                        break;
                    default:
                        break;
                }
            }
        }

        void AssociatedObject_LoadUnloaded(object sender, RoutedEventArgs e)
        {
            if (Command != null)
            {
                if (CommandParameter != null)
                    Command.Execute(CommandParameter);
                else
                    Command.Execute(e);
            }
        }

        void AssociatedObject_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (Command != null)
            {
                if (CommandParameter != null)
                    Command.Execute(CommandParameter);
                else
                    Command.Execute(e);
            }
        }

        protected override void OnDetaching()
        {
            if (AssociatedObject != null)
            {
                switch (EventName)
                {
                    case "SizeChanged":
                        AssociatedObject.SizeChanged -= AssociatedObject_SizeChanged;
                        break;
                    case "Loaded":
                        AssociatedObject.Loaded -= AssociatedObject_LoadUnloaded;
                        break;
                    case "Unloaded":
                        AssociatedObject.Unloaded -= AssociatedObject_LoadUnloaded;
                        break;
                }
            }

            base.OnDetaching();
        }
    }
}

사용예

 

xmlns:i="using:WinRTXamlToolkit.Interactivity"
xmlns:b="using:kaki104.MetroCL.Behaviors"

 

    <i:Interaction.Behaviors>
        <b:PageBehavior EventName="SizeChanged" Command="{Binding PageSizeChangedCommand, Mode=OneWay, Source={StaticResource Locator}}" />
        <b:PageBehavior EventName="Loaded" Command="{Binding LoadedCommand, Mode=OneWay}" />
        <b:PageBehavior EventName="Unloaded" Command="{Binding UnloadedCommand, Mode=OneWay}" />
    </i:Interaction.Behaviors>

 

'Windows 8&8.1 > Samples' 카테고리의 다른 글

HubApp using a dynamic style app development  (0) 2013.08.01
Using Task, Async, Await  (0) 2013.07.28
Custom Behavior Sample  (0) 2012.12.25
Text file encoding sample  (0) 2012.11.28
WinRT File Based Database sample  (0) 2012.11.12
Tile + BackgroundTask = LiveTile APP!  (0) 2012.10.30
블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

Windows 8 store app에서 여러가지 작업을 하다가 접하는 문제 중에 하나가 파일을 읽어 들여서 사용해야 하는 경우에 Encodig문제가 발생한다. 그래서 간단하게 셈플을 만들었다.

(방법을 알면 정말 쉽게 할 수 있는데..모르면 정말..)

 

How to read a text file with other encoding than UFT8 or UTF16 in WinRT?

http://stackoverflow.com/questions/12935238/how-to-read-a-text-file-with-other-encoding-than-uft8-or-utf16-in-winrt

 

1. 화면

 

 

 

2. ItemDetailPage.xaml.cs

//ToArray()를 사용하기 위해 필요
using System.Runtime.InteropServices.WindowsRuntime;

    public sealed partial class ItemDetailPage : FileReadSample.Common.LayoutAwarePage
    {
        public ItemDetailPage()
        {
            this.InitializeComponent();

            //스크롤 뷰어 뷰 체인지 이벤트를 이용해서 위치 표시
            scrollViewer.ViewChanged +=
                (s, e) =>
                {
                    scrollPos.Text = scrollViewer.HorizontalOffset.ToString();
                };
        }

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
        }

        protected override void SaveState(Dictionary<String, Object> pageState)
        {
        }

        /// <summary>
        /// 택스트 파일 열기
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            //파일 피커
            FileOpenPicker openPicker = new FileOpenPicker();
            openPicker.ViewMode = PickerViewMode.List;
            openPicker.SuggestedStartLocation = PickerLocationId.Downloads;
            openPicker.FileTypeFilter.Add(".txt");
            //파일 하나 선택
            StorageFile file = await openPicker.PickSingleFileAsync();
            if (file != null)
            {
                //파일이 존재하면
                //내용을 버퍼에 읽어 들이고
                var buffer = await FileIO.ReadBufferAsync(file);
                //배열로 바꾸고
                var fileData = buffer.ToArray();
                //euc-Kr로 인코딩
                var encoding = Encoding.GetEncoding("euc-Kr");
                //파일명 출력
                title.Text = file.Name;
                //파일 패스 출력
                subtitle.Text = file.Path;
                //내용 출력
                var contents = encoding.GetString(fileData, 0, fileData.Length);
                textContent.Text = contents;
            }
        }
    }

 

3. ItemDetailPage.xaml

...

        <UserControl Margin="0,0,0,0" Grid.RowSpan="2">
            <ScrollViewer x:Name="scrollViewer" Style="{StaticResource HorizontalScrollViewerStyle}" Grid.Row="1">
                <common:RichTextColumns x:Name="richTextColumns" Margin="117,120,117,47">

                    <common:RichTextColumns.ColumnTemplate>
                        <DataTemplate>
                            <RichTextBlockOverflow Margin="80,0,0,0" Width="560">
                                <RichTextBlockOverflow.RenderTransform>
                                    <TranslateTransform X="-1" Y="4"/>
                                </RichTextBlockOverflow.RenderTransform>
                            </RichTextBlockOverflow>
                        </DataTemplate>
                    </common:RichTextColumns.ColumnTemplate>
                   
                    <RichTextBlock x:Name="richTextBlock" IsTextSelectionEnabled="False" Style="{StaticResource ItemRichTextStyle}" Width="560">
                        <Paragraph>
                            <Run x:Name="title" FontWeight="Light" FontSize="26.667" />
                            <LineBreak/>
                            <LineBreak/>
                            <Run x:Name="subtitle" FontWeight="Normal" FontSize="20" />
                            <LineBreak/>
                        </Paragraph>
                        <Paragraph>
                            <Run x:Name="textContent" FontWeight="SemiLight" FontSize="18" />
                        </Paragraph>
                    </RichTextBlock>

                </common:RichTextColumns>

            </ScrollViewer>
        </UserControl>

...

 

4. Source

 

FileReadSample.zip

'Windows 8&8.1 > Samples' 카테고리의 다른 글

Using Task, Async, Await  (0) 2013.07.28
Custom Behavior Sample  (0) 2012.12.25
Text file encoding sample  (0) 2012.11.28
WinRT File Based Database sample  (0) 2012.11.12
Tile + BackgroundTask = LiveTile APP!  (0) 2012.10.30
InputPane using Popup sample  (0) 2012.10.29
블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

Windows 8 Store app은 기본적으로 local database를 지원하지 않는다. 그리고, Sqlite는 CPU별로 따로 패키지를 만들어야하는 불편함이 존재한다. 그래서, 찾아보다가 로컬 파일 기반으로 db를 관리하는 것을 Korea Bus Information 앱에서 사용해 보았다.

 

장점 : 

1) 사용이 간단하다

2) CPU별로 앱을 만들 필요가 없다

 

단점 :

1) 성능이 뛰어나지는 않다.

2) db의 저장 공간을 많이 차지 한다.

 

1. 참고

WinRT File Based Database

http://winrtdatabase.codeplex.com/

 

참고에 있는 Download를 눌러서 소스와 셈플을 받은 후 실행해서 만들어지는 dll을 사용해야 한다.

Nuget으로 1차 설치해서 Reference에 추가를 한 후 새로 빌드한 dll 파일을 복사해서 넣어준다.

 

 

 

2. PersonModel.cs

 

namespace GridAppSample
{
    public class PersonModel : GridAppSample.Common.BindableBase
    {
        private Guid personID;

        public Guid PersonID
        {
            get { return personID; }
            set
            {
                personID = value;
                OnPropertyChanged();
            }
        }

        private string firstName;

        public string FirstName
        {
            get { return firstName; }
            set
            {
                firstName = value;
                OnPropertyChanged();
            }
        }

        private string lastName;

        public string LastName
        {
            get { return lastName; }
            set
            {
                lastName = value;
                OnPropertyChanged();
            }
        }

        private int age;

        public int Age
        {
            get { return age; }
            set
            {
                age = value;
                OnPropertyChanged();
            }
        }

    }
}

 

3. SamplePageViewModel.cs

 

//완전 중요!!!
using System.Runtime.InteropServices.WindowsRuntime;

namespace GridAppSample
{
    public class SamplePageViewModel : GridAppSample.Common.BindableBase
    {
        public const string DatabaseName = "SampleDb";

        /// <summary>
        /// 데이터베이스
        /// </summary>
        private Database _database;

        private Table<PersonModel> people;
        /// <summary>
        /// 피플 테이블
        /// </summary>
        public Table<PersonModel> People
        {
            get { return people; }
            set
            {
                people = value;
                OnPropertyChanged();
            }
        }

        private PersonModel selectedPerson;
        /// <summary>
        /// 선택된 사람
        /// </summary>
        public PersonModel SelectedPerson
        {
            get { return selectedPerson; }
            set
            {
                selectedPerson = value;
                OnPropertyChanged();
            }
        }


        public SamplePageViewModel()
        {
            if (Windows.ApplicationModel.DesignMode.DesignModeEnabled == false)
            {
                Initialise();
            }
            else
            {
                People = new Table<PersonModel>();
                People.Add(new PersonModel { FirstName = "Park", LastName = "kaki104", Age = 11 });
                People.Add(new PersonModel { FirstName = "Park", LastName = "2222222", Age = 22 });
                People.Add(new PersonModel { FirstName = "Park", LastName = "kaki103", Age = 33 });
                People.Add(new PersonModel { FirstName = "Park", LastName = "4444444", Age = 44 });
                People.Add(new PersonModel { FirstName = "Park", LastName = "kaki105", Age = 55 });
            }
        }

        public async void Initialise()
        {
            var exists = await Database.DoesDatabaseExistsAsync(DatabaseName, StorageLocation.Local);
            //db존재여부 확인
            if (!exists)
            {
                _database = await Database.CreateDatabaseAsync(DatabaseName, StorageLocation.Local);
                //파일 생성시 이름을 주는 방법도 있는데 사용하면 오류남..
                _database.CreateTable<PersonModel>();
                //변경사항 저장
                await _database.SaveAsync();
            }
            else
            {
                //db열기
                _database = await Database.OpenDatabaseAsync(DatabaseName, true, StorageLocation.Local);
            }
            People = await _database.Table<PersonModel>();

            _database.PropertyChanged += new PropertyChangedEventHandler(_database_PropertyChanged);
        }

        void _database_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
        }

        private DelegateCommand addCommand;
        /// <summary>
        /// 추가
        /// </summary>
        public DelegateCommand AddCommand
        {
            get
            {
                if (addCommand == null)
                {
                    addCommand = new DelegateCommand(
                        async _ =>
                        {
                            var newItem = new PersonModel
                            {
                                PersonID = new Guid(),
                                FirstName = "Input First Name",
                                LastName = "Input Last Name",
                                Age = 0
                            };
                            People.Add(newItem);
                            await _database.SaveAsync();
                        });
                }
                return addCommand;
            }
        }

        private DelegateCommand removeCommand;
        /// <summary>
        /// 삭제
        /// </summary>
        public DelegateCommand RemoveCommand
        {
            get
            {
                if (removeCommand == null)
                {
                    removeCommand = new DelegateCommand(
                        async _ =>
                        {
                            if (SelectedPerson != null)
                            {
                                People.Remove(SelectedPerson);
                                await _database.SaveAsync();
                            }
                        });
                }
                return removeCommand;
            }
        }

        private DelegateCommand updateCommand;
        /// <summary>
        /// 수정
        /// </summary>
        public DelegateCommand UpdateCommand
        {
            get
            {
                if (updateCommand == null)
                {
                    updateCommand = new DelegateCommand(
                        async _ =>
                        {
                            await _database.SaveAsync();
                        });
                }
                return updateCommand;
            }
        }

    }
}

 

4. Source

 

WinRT_File_Based_Database_Sample.zip

'Windows 8&8.1 > Samples' 카테고리의 다른 글

Custom Behavior Sample  (0) 2012.12.25
Text file encoding sample  (0) 2012.11.28
WinRT File Based Database sample  (0) 2012.11.12
Tile + BackgroundTask = LiveTile APP!  (0) 2012.10.30
InputPane using Popup sample  (0) 2012.10.29
GridView item drag&drop move sample  (0) 2012.10.15
블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

MSDN의 셈플 프로젝트를 보면 Tile sample과 BackgroundTask sample이 각 각 존재 하지만, Live Tile을 구현하는 예제는 없다. 그래서, 이번에 2가지의 셈플 프로젝트를 참고해서 LiveTile을 만들어 보도록 하겠다.

 

* 참고

Introduction to Background Tasks(꼭 읽어보도록 한다)

http://www.microsoft.com/en-us/download/details.aspx?id=27411

 

Background task sample

http://code.msdn.microsoft.com/windowsapps/Background-Task-Sample-9209ade9

 

App tiles and badges sample

http://code.msdn.microsoft.com/windowsapps/App-tiles-and-badges-sample-5fc49148

 

Visual Studio 2012 Debuging tip

http://kaki104.tistory.com/entry/Visual-Studio-2012-Debuging-tip

 

Syndication sample

http://code.msdn.microsoft.com/windowsapps/Syndication-sample-07ef6b0d

 

Example Metro app /WinRT: background task that uppdates internet connection and metering (roaming) information based on network changes -> 시스템 트리거를 이용한 포스트인데 정리가 잘되어있네요

http://www.irisclasson.com/2012/07/04/example-metro-app-winrt-background-task-that-uppdates-internet-connection-and-metering-roaming-information-based-on-network-changes/comment-page-1/#comment-4104

 

WinRT: Example of Using Periodic Notifications for Live Tiles

http://mikaelkoskinen.net/winrt-example-of-using-periodic-notifications-for-live-tiles

 

 

 

 

1. 솔루션 구성

 

1) Split App template project 생성 - 메인 앱

2) Class Library 추가 - RSS Reading, Tile Update 작업용

3) Windows Runtime Component 추가 - BackgroundTask 용

4) App tiles and badges sample 프로젝트에 포함되어있는 NotificationsExtensions 추가

 

* 레퍼런스는 프로젝트 참고

 

2. RssReaderViewModel.cs

RSS 피드를 읽어오는 기능을 한다.

 

namespace BackgroundLiveTileSample.CL
{
    public class RssReaderViewModel : BindableBase
    {
        SyndicationClient client;

        private static RssReaderViewModel instance;
        //인스턴스 객체
        public static RssReaderViewModel Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new RssReaderViewModel();
                }
                return instance;
            }
        }

        private SyndicationFeed currentFeed;
        /// <summary>
        /// 현재 피드
        /// </summary>
        public SyndicationFeed CurrentFeed
        {
            get { return currentFeed; }
            set
            {
                currentFeed = value;
                OnPropertyChanged();
            }
        }

        public RssReaderViewModel()
        {
            client = new SyndicationClient();
            client.BypassCacheOnRetrieve = true;
            client.SetRequestHeader("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
        }

        public async Task<bool> GetFeed(Uri feedUri)
        {
            try
            {
                CurrentFeed = await client.RetrieveFeedAsync(feedUri);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }
    }
}

 

3. ItemsPageViewModel.cs

 

namespace BackgroundLiveTileSample
{
    public class ItemsPageViewModel : BackgroundLiveTileSample.CL.BindableBase
    {
        const string feedUri = "http://web2.ruliweb.daum.net/daum/rss.htm?bbs=1&id=547&bbsId=G007&c1=6&c2=5";

        public RssReaderViewModel RssReader { get; set; }

        public ItemsPageViewModel()
        {
            RssReader = RssReaderViewModel.Instance;
        }

        public async void GetFeed()
        {
            var result = await RssReader.GetFeed(new Uri(feedUri));
            if (result == true)
            {
            }
        }
    }
}

 

4. ItemsPage.xaml.cs

 

namespace BackgroundLiveTileSample
{
    public sealed partial class ItemsPage : BackgroundLiveTileSample.Common.LayoutAwarePage
    {
        public ItemsPageViewModel ViewModel
        {
            get { return this.DataContext as ItemsPageViewModel; }
            set
            {
                this.DataContext = value;
            }
        }

        public ItemsPage()
        {
            this.InitializeComponent();

            ViewModel = new ItemsPageViewModel();
            ViewModel.GetFeed();

            if (Windows.ApplicationModel.DesignMode.DesignModeEnabled == true)
            {
            }
            else
            {
            }

        }

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            //ViewModel.GetFeed();
        }

        void ItemView_ItemClick(object sender, ItemClickEventArgs e)
        {

            //아이템 클릭하면 브라우저 실행시켜서 보여준다.
            SyndicationItem item = e.ClickedItem as SyndicationItem;
            if (item != null)
            {
                var link = item.Links.FirstOrDefault();
                Launcher.LaunchUriAsync(link.Uri);
            }
        }
    }
}

 

xaml과 ItemTemplate는 소스를 참고

 

5. TileUpdateTask.cs

 

namespace BackgroundLiveTileSample.BackgroundTasks
{
    public sealed class TileUpdateTask : IBackgroundTask
    {
        const string feedUri = "http://web2.ruliweb.daum.net/daum/rss.htm?bbs=1&id=547&bbsId=G007&c1=6&c2=5";

        BackgroundTaskDeferral _deferral = null;

        RssReaderViewModel RssReader;

        //백그라운드테스크 이벤트 처리부분
        public async void Run(IBackgroundTaskInstance taskInstance)
        {
            // Get the deferral object from the task instance, and take a reference to the taskInstance;
            _deferral = taskInstance.GetDeferral();

            RssReader = RssReaderViewModel.Instance;

            var result = await RssReader.GetFeed(new Uri(feedUri));
            if (result == true)
            {
                UpdateTile();
            }
            //백그라운드 작업 완료
            _deferral.Complete();
        }

        //타일 업데이트
        public void UpdateTile()
        {
            //타일 로테이션 가능하도록 변경
            TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true);

            foreach (var item in RssReader.CurrentFeed.Items.Take(5))
            {
                //큰타일
                ITileWideText09 tileContent = TileContentFactory.CreateTileWideText09();
                tileContent.TextHeading.Text = item.Title.Text;
                tileContent.TextBodyWrap.Text = item.Summary.Text;

                //작은타일
                ITileSquareText02 squareContent = TileContentFactory.CreateTileSquareText02();
                squareContent.TextHeading.Text = item.Title.Text;
                squareContent.TextBodyWrap.Text = item.Summary.Text;
                tileContent.SquareContent = squareContent;

                //타일노티 만들고
                TileNotification tileNotification = tileContent.CreateNotification();
                //tileNotification.Tag = item.Id;

                //센드 타일
                TileUpdateManager.CreateTileUpdaterForApplication().Update(tileNotification);
            }
        }
    }
}

 

6. BackgroundTaskUtil.cs

백그라운드 테스크를 등록하거나 해지하는 유틸

 

namespace BackgroundLiveTileSample
{
    class BackgroundTaskUtil
    {
        public const string BackgroundTaskEntryPoint = "BackgroundLiveTileSample.BackgroundTasks.TileUpdateTask";
        public const string BackgroundTaskName = "TileUpdateTask";
        public static bool BackgroundTaskRegistered = false;

        /// <summary>
        /// Register a background task with the specified taskEntryPoint, name, trigger,
        /// and condition (optional).
        /// </summary>
        /// <param name="taskEntryPoint">Task entry point for the background task.</param>
        /// <param name="name">A name for the background task.</param>
        /// <param name="trigger">The trigger for the background task.</param>
        /// <param name="condition">An optional conditional event that must be true for the task to fire.</param>
        public static BackgroundTaskRegistration RegisterBackgroundTask(String taskEntryPoint, String name, IBackgroundTrigger trigger, IBackgroundCondition condition)
        {
            var builder = new BackgroundTaskBuilder();

            builder.Name = name;
            builder.TaskEntryPoint = taskEntryPoint;
            builder.SetTrigger(trigger);

            if (condition != null)
            {
                builder.AddCondition(condition);
            }

            BackgroundTaskRegistration task = builder.Register();

            UpdateBackgroundTaskStatus(name, true);

            //
            // Remove previous completion status from local settings.
            //
            var settings = ApplicationData.Current.LocalSettings;
            settings.Values.Remove(name);

            return task;
        }

        /// <summary>
        /// Unregister background tasks with specified name.
        /// </summary>
        /// <param name="name">Name of the background task to unregister.</param>
        public static void UnregisterBackgroundTasks(string name)
        {
            //
            // Loop through all background tasks and unregister any with SampleBackgroundTaskName or
            // SampleBackgroundTaskWithConditionName.
            //
            foreach (var cur in BackgroundTaskRegistration.AllTasks)
            {
                if (cur.Value.Name == name)
                {
                    cur.Value.Unregister(true);
                }
            }

            UpdateBackgroundTaskStatus(name, false);
        }

        /// <summary>
        /// Store the registration status of a background task with a given name.
        /// </summary>
        /// <param name="name">Name of background task to store registration status for.</param>
        /// <param name="registered">TRUE if registered, FALSE if unregistered.</param>
        public static void UpdateBackgroundTaskStatus(String name, bool registered)
        {
            switch (name)
            {
                case BackgroundTaskName:
                    BackgroundTaskRegistered = registered;
                    break;
            }
        }

        /// <summary>
        /// Get the registration / completion status of the background task with
        /// given name.
        /// </summary>
        /// <param name="name">Name of background task to retreive registration status.</param>
        public static String GetBackgroundTaskStatus(String name)
        {
            var registered = false;
            switch (name)
            {
                case BackgroundTaskName:
                    registered = BackgroundTaskRegistered;
                    break;
            }

            var status = registered ? "Registered" : "Unregistered";

            var settings = ApplicationData.Current.LocalSettings;
            if (settings.Values.ContainsKey(name))
            {
                status += " - " + settings.Values[name].ToString();
            }

            return status;
        }
    }
}

 

7. App.xaml.cs

 

OnLaunched 메소드에

 

                //백그라운드 테스크 등록
                RegisterBackgroundTask();

 

하단에 메소드 추가

 

        /// <summary>
        /// 백그라운드 테스크 등록
        /// </summary>
        private async void RegisterBackgroundTask()
        {
            //백그라운드 등록 서비스 알림
            await BackgroundExecutionManager.RequestAccessAsync();
            //백그라운드 서비스 등록해제
            BackgroundTaskUtil.UnregisterBackgroundTasks(BackgroundTaskUtil.BackgroundTaskName);
            //백그라운드 서비스 등록(15분 간격으로 발생, 인터넷이 연결되어있을때만 실행)
            var task = BackgroundTaskUtil.RegisterBackgroundTask(BackgroundTaskUtil.BackgroundTaskEntryPoint,
                                                                   BackgroundTaskUtil.BackgroundTaskName,
                                                                   new TimeTrigger(15, false),
                                                                   new SystemCondition(SystemConditionType.InternetAvailable));
        }

 

8. 타일 노티피케이션을 사용하기 위한 작업

 

1) BackgroundLiveTileSample 프로젝트의 Package.appxmanifest open

2) Lock screen notifications -> Badge and Tile Text 선택, Wide logo, Badge logo 추가

(필요한 이미지는 셈플 프로젝에 있는 이미지 복사해서 사용)

3) Declarations -> Background Tasks 추가 -> Properties : Timer 선택 -> App settings , Entry point : BackgroundLiveTileSample.BackgroundTasks.TileUpdateTask 입력

 

9. 실행

처음 실행하면 아래와 같이 확인 창이 출력된다. 허용을 클릭한다.

 

 

앱 실행 화면

 

백그라운드 테스크가 디버깅은  상단에 참고에 있는 Visual Studio 2012 Debuging tip 을 참고해서 디버그 실행 중 TileUpdateTask를 선택하면 바로 디버깅을 할 수 있다. 이 방법을 모르면 15분 기다려야 한다는..

 

10. 소스

 

BackgroundLiveTileSample.BackgroundTasks.zip

'Windows 8&8.1 > Samples' 카테고리의 다른 글

Text file encoding sample  (0) 2012.11.28
WinRT File Based Database sample  (0) 2012.11.12
Tile + BackgroundTask = LiveTile APP!  (0) 2012.10.30
InputPane using Popup sample  (0) 2012.10.29
GridView item drag&drop move sample  (0) 2012.10.15
ISupportIncrementalLoading Sample  (1) 2012.10.07
블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

엡에서 Page는 가상 키보드가 나올 때 자동으로 위로 살짝 올라가는데, 팝업은 자동으로 위치가 이동되지 않는다. 그래서, 팝업을 띄운 후 가상 키보드를 사용 할 때의 문제점을 해결하는 셈플을 만들어 보았다.

 

기본적인 팝업에서는 키보드를 띄우면 아래와 같이 입력 창이 가려져서 입력 작업을 할 수가 없다.

 

* 참고 포스트

Turn any UserControl into a pleasing Dialog/Flyout in Windows 8

http://socialeboladev.wordpress.com/2012/10/14/turn-any-usercontrol-into-a-pleasing-dialogflyout-in-windows-8/

 

1. 핵심

팝업을 출력한 후 팝업 내부에서 InputPane을 가지고 있다가, InputPane이 보이거나 숨길때 팝업의 위치라던가 크기를 변경 시켜야 한다.

 

 

2. ItemPage.xaml.cs

 

namespace InputPanelUsingPopupSample
{
    /// <summary>
    /// A page that displays a collection of item previews.  In the Split Application this page
    /// is used to display and select one of the available groups.
    /// </summary>
    public sealed partial class ItemsPage : InputPanelUsingPopupSample.Common.LayoutAwarePage
    {
        Popup normalPopup;

        public ItemsPage()
        {
            this.InitializeComponent();
        }

 

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            // TODO: Create an appropriate data model for your problem domain to replace the sample data
            var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
            this.DefaultViewModel["Items"] = sampleDataGroups;

            itemGridView.ItemClick += itemGridView_ItemClick;
        }

 

        void itemGridView_ItemClick(object sender, ItemClickEventArgs e)
        {
            var group = ((SampleDataGroup)e.ClickedItem);

            //현재 윈도우 바운드값
            var bounds = Windows.UI.Xaml.Window.Current.CoreWindow.Bounds;
            //팝업 생성
            normalPopup = new Popup();
            //내용 생성
            var content = new InputPopupUserControl();
            //선택된 아이템을 내용의 DataContext에 넣어서 표시
            content.DataContext = group;
            //내용의 전체 크기 변경 - 전체 화면을 덮도록
            content.Width = bounds.Width;
            content.Height = bounds.Height;
            //팝업의 차일드로 내용 추가
            normalPopup.Child = content;
            //팝업을 내용에 추가 - 나중에 팝업을 닫기 위해 사용
            content.Parent = normalPopup;
            //팝업 오픈
            normalPopup.IsOpen = true;

        }

 

        protected override void SaveState(Dictionary<string, object> pageState)
        {
            itemGridView.ItemClick -= itemGridView_ItemClick;
        }
    }
}

 

 

3. InputPopupUserControl.xaml.cs

 

namespace InputPanelUsingPopupSample
{
    public sealed partial class InputPopupUserControl : UserControl
    {
        public Popup Parent { get; set; }
        /// <summary>
        /// 가상 키보드 패널
        /// </summary>
        private Windows.UI.ViewManagement.InputPane popupInputPane;

 

        public InputPopupUserControl()
        {
            this.InitializeComponent();

            if (Windows.ApplicationModel.DesignMode.DesignModeEnabled == true)
            {
                //디자인타임소스
                var sampleDataGroup = SampleDataSource.GetGroup("01");
                this.DataContext = sampleDataGroup;
            }
            else
            {
                //런타임소스
                this.btnOK.Click += (s, e) =>
                {
                    Parent.IsOpen = false;
                };
                this.btnCancel.Click += (s, e) =>
                {
                    Parent.IsOpen = false;
                };

 

                //가상키보드 처리
                popupInputPane = Windows.UI.ViewManagement.InputPane.GetForCurrentView();
                popupInputPane.Showing += popupInputPane_ShowHide;
                popupInputPane.Hiding += popupInputPane_ShowHide;

 

                //윈도우 리사이즈
                Window.Current.SizeChanged += Current_SizeChanged;

            }
        }

        void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
        {
            //노말에서 스냅 뷰 모드로 변경되거나 반대의 경우에도 각각 팝업의 크기 및 위치 조정이 필요할 수 있다.
        }

 

        void popupInputPane_ShowHide(Windows.UI.ViewManagement.InputPane sender, Windows.UI.ViewManagement.InputPaneVisibilityEventArgs args)
        {
            //인풋패널 크기
            var inputPanelRect = args.OccludedRect;
            //윈도우 크기
            Rect rect = Window.Current.CoreWindow.Bounds;
            if (inputPanelRect.Top == 0)
            {
                rect.Y = inputPanelRect.Bottom;
            }
            rect.Height -= inputPanelRect.Height;

            //키보드 뜰때 세로만 변경
            //1. 팝업의 세로 오프셋을 변경하는 방법
            //Parent.VerticalOffset = rect.Top - inputPanelRect.Height;
            //2. 그리드의 높이를 변경하는 방법
            //popupRoot.Height = rect.Height;
            //3. UserControl자체의 높이를 변경하는 방법
            this.Height = rect.Height;
            //등등 여러가지를 적용해서 제일 알맞는 방법을 사용한다.
        }

    }
}

 

4. 완료 화면

 

가상 키보드가 나타나면 팝업이 살짝 위로 올라간다.

 

5. 소스

 

InputPanelUsingPopupSample.zip

블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

기본적으로 제공하는 템플릿 소스에 drag&drop으로 아이템을 이동하는 간단한 소스를 만들어 보았다.

앱 개발에 적용하기 위해서는 추가적인 작업이 필요하니 참고하기 바란다.

 

 

 

1. GroupedItemsPage.xaml

CanDragItems, AllowDrop, CanRecorderItems 프로퍼티를 변경한다.

VirtualizingStackPanel은 데이터의 표현 방법에 따라서 여러가지로 변경하면서 확인한다.

 

        <GridView
            x:Name="itemGridView"
            AutomationProperties.AutomationId="ItemGridView"
            AutomationProperties.Name="Grouped Items"
            Grid.RowSpan="2"
            Padding="116,137,40,46"
            ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
            ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
            SelectionMode="None"
            IsSwipeEnabled="false"
            IsItemClickEnabled="True"
            ItemClick="ItemView_ItemClick"
            CanDragItems="True"
            AllowDrop="True"
            CanReorderItems="True">

            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
            <GridView.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <Grid Margin="1,0,0,6">
                                <Button
                                    AutomationProperties.Name="Group Title"
                                    Click="Header_Click"
                                    Style="{StaticResource TextPrimaryButtonStyle}" >
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
                                        <TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
                                    </StackPanel>
                                </Button>
                            </Grid>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                    <GroupStyle.Panel>
                        <ItemsPanelTemplate>
                            <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
                        </ItemsPanelTemplate>
                    </GroupStyle.Panel>
                </GroupStyle>
            </GridView.GroupStyle>
        </GridView>

 

2. GroupedItemsPage.xaml.cs

namespace ItemDragDropSample
{
    /// <summary>
    /// A page that displays a grouped collection of items.
    /// </summary>
    public sealed partial class GroupedItemsPage : ItemDragDropSample.Common.LayoutAwarePage
    {
        public GroupedItemsPage()
        {
            this.InitializeComponent();
        }

        protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
        {
            var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
            this.DefaultViewModel["Groups"] = sampleDataGroups;

            itemGridView.DragItemsStarting += itemGridView_DragItemsStarting;
            itemGridView.DragOver += itemGridView_DragOver;
            itemGridView.Drop += itemGridView_Drop;

        }

 

        object dragOverItem = null;

 

        /// <summary>
        /// 드롭 처리
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void itemGridView_Drop(object sender, DragEventArgs e)
        {
            object gridSource;
            e.Data.Properties.TryGetValue("gridSource", out gridSource);
            if (gridSource != sender)
                return;

            object sourceItem;
            e.Data.Properties.TryGetValue("item", out sourceItem);
            if (sourceItem == null)
                return;

            if (dragOverItem == null) return;

            var groups = this.DefaultViewModel["Groups"] as ObservableCollection<SampleDataGroup>;
            var sourceGroupItem = (from kkk in groups
                                   from jjj in kkk.Items
                                   let sIndex = kkk.Items.IndexOf(jjj)
                                   where jjj.UniqueId == (sourceItem as SampleDataCommon).UniqueId
                                   select new { Sgroup = kkk, Sitem = jjj, SItemIndex = sIndex} ).FirstOrDefault();
            var targetGroupItem = (from kkk in groups
                                   from jjj in kkk.Items
                                   let tIndex = kkk.Items.IndexOf(jjj)
                                   where jjj.UniqueId == (dragOverItem as SampleDataCommon).UniqueId
                                   select new { Tgroup = kkk, Titem = jjj, TItemIndex = tIndex }).FirstOrDefault();
            if (sourceGroupItem.Sgroup.Equals(targetGroupItem.Tgroup))
            {
                sourceGroupItem.Sgroup.Items.Move(sourceGroupItem.SItemIndex, targetGroupItem.TItemIndex);
            }
            else
            {
                sourceGroupItem.Sgroup.Items.Remove(sourceGroupItem.Sitem);
                targetGroupItem.Tgroup.Items.Insert(targetGroupItem.TItemIndex, sourceGroupItem.Sitem);
            }
        }

 

        /// <summary>
        /// 아이뎀이 어떤 위치에있는지 확인
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void itemGridView_DragOver(object sender, DragEventArgs e)
        {
            FrameworkElement element = e.OriginalSource as FrameworkElement;
            var groups = this.DefaultViewModel["Groups"] as ObservableCollection<SampleDataGroup>;
            if (element != null )
            {
                var item = element.DataContext as SampleDataCommon;
                if(item != null)
                {
                    dragOverItem = groups.SelectMany(p => p.Items).FirstOrDefault(p => p.UniqueId == item.UniqueId);
                }
            }
        }

 

        /// <summary>
        /// 아이템 드래그 시작
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void itemGridView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
        {
            var item = e.Items.FirstOrDefault();
            if (item == null)
                return;

            e.Data.Properties.Add("item", item);
            e.Data.Properties.Add("gridSource", sender);
        }


        void Header_Click(object sender, RoutedEventArgs e)
        {
            var group = (sender as FrameworkElement).DataContext;

            this.Frame.Navigate(typeof(GroupDetailPage), ((SampleDataGroup)group).UniqueId);
        }

        void ItemView_ItemClick(object sender, ItemClickEventArgs e)
        {
            var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
            this.Frame.Navigate(typeof(ItemDetailPage), itemId);
        }
    }
}

 

3. Source

 

ItemDragDropSample.zip

블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

Windows 8부터 대용량의 데이터를 한번에 조회를 하지 않고, 스크롤이 될때 조금씩 계속 조회하는 것을 지원한다. 그에 대한 예제를 MSDN에서 찾아 볼 수 있는데..좀 복잡하게 되어 있어서 이해하기가 쉽지 않아서 간단한 예제를 만들어 보았다. 기존에 작성되었던 WebAPI Sample 프로젝트에 기능을 추가했다.

 

1. 참고

ISupportIncrementalLoading interface

http://msdn.microsoft.com/en-us/library/windows/apps/Hh701916

 

Data virtualization example using ISupportIncrementalLoading

http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/8f7a1a5b-6a8d-4971-8f38-2075295298b5/

 

 왼쪽 리스트박스에 데이터가 스크롤 될 때 조금씩 조회되는 형태이다.

 

2. PersonILCollection.cs

 

    public class PersonILCollection : ObservableCollection<Person>, ISupportIncrementalLoading
    {
        private const string ApiRoot = "http://localhost:11666//api/person?$orderby=Id&$skip={0}&$top={1}";
        private int page;
        private bool _busy;

        #region ISupportIncrementalLoading
        public bool HasMoreItems
        {
            get { return true; }
        }

        public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            if (_busy)
            {
                return null;
            }
            _busy = true;

            return AsyncInfo.Run((c) => LoadMoreItemsAsync(c, count));
        }
        #endregion

        /// <summary>
        /// 점진적 조회 구현, 참고용이니 새롭게 구현하기 바람
        /// </summary>
        /// <param name="c"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        async Task<LoadMoreItemsResult> LoadMoreItemsAsync(CancellationToken c, uint count)
        {
            try
            {
                ObservableCollection<Person> items = new ObservableCollection<Person>();

                //http 설정
                using (var http = new HttpClient())
                {
                    //전체 데이터 조회
                    var size = 10;
                    var skip = (page - 1) * size;
                    var getQuery = string.Format(ApiRoot, skip, size);

                    var resp = await http.GetStringAsync(getQuery);
                    if (resp != null)
                    {
                        items = JsonConvert.DeserializeObject(resp, typeof(ObservableCollection<Person>)) as ObservableCollection<Person>;
                        //컬렉션에 데이터 추가
                        foreach (var i in items)
                        {
                            this.Add(i);
                        }

                        //페이지 카운터 증가
                        page++;
                    }
                }

                return new LoadMoreItemsResult { Count = (uint)items.Count() };
            }
            finally
            {
                _busy = false;
            }
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public PersonILCollection()
        {
            page = 1;
        }
    }

 

3. MainPageView.xaml

 

        <ListView Grid.Row="2" ItemsSource="{Binding PeopleILC}"
                  ItemTemplate="{StaticResource PersonDataTemplate}" Padding="120,0,0,0"/>

 

4. MainPageViewModel.cs

생성자에서 초기화 시킴

 

        private PersonILCollection peopleILC;

        public PersonILCollection PeopleILC
        {
            get { return peopleILC; }
            set
            {
                peopleILC = value;
                OnPropertyChanged();
            }
        }

 

5. 기본 원리

ISupportIncrementalLoading를 상속 받은 컬렉션을 ListView, GridView에 바인딩 시키면 각 컨트롤에서 데이터가 필요하다고 생각이 되면 LoadMoreItemsAsync 이벤트가 발생이 되고, 이때 필요한 데이터를 로딩하면 된다.

(IncrementalLoadingTrigger, IncrementalLoadingThreshold 프로퍼티가 존재하는 컨트롤은 지원이 된다.)

 

6. 소스

W8RTM_c#_ISupportIncrementalLoading_sample.zip

 

http://sdrv.ms/14WaiAf

 

 

블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

포스팅 할 내용을 찾아서 돌아다니던 중 Multilingual App Toolkit이라는 것을 발견하고, 뭐하는 물건인지 살펴보게 되었는데, 내용을 보니 기존 방법보다 편하고, 빠르게 Multi Language app을 만들수 있다고 하여, 셈플을 만들게 되었는데, 정확한 사용 방법을 몰라 한참을 헤메다가 몇 군데 사이트를 통해서 겨우 완벽한 셈플을 만드는데 성공하여, 포스팅을 한다.

 

1. 참고 사이트들

Multilingual App Toolkit for Visual Studio 2012 RC

http://msdn.microsoft.com/en-us/windows/apps/hh848309

Develop your app for everyone?localize your UI

http://timheuer.com/blog/archive/2012/04/26/localize-windows-8-application.aspx

 

2. 기존 다국어 지원 앱 셈플

Application resources and localization sample

http://code.msdn.microsoft.com/windowsapps/Application-resources-and-cd0c6eaa

 

셈플을 다운로드 받아서 보면, 각 언어별로 Resources.resw 파일이 존재하는 것을 알 수 있다. 하나의 문자열을 리소스로 추가하면, 지원하는 언어마다 리소스 파일을 열어서 문자열을 추가해 주어야 하는데, 이는 매우 귀찮은 작업이고, 번역도 해야하니 쉬운일은 아니다.

 

3. Multilingual App Toolkit을 사용하면?

하나의 리소스 파일을 이용해서 앱을 만들어 놓은 후 빌드하면, 추가해 놓은 언어 별로 자동으로 문자열이 추가되고, 간단한 몇 가지 작업을 하면, 모든 문자열이 각 업어별로 번역이 되어서 바로 사용이 가능하다. 한마디로, 기존 방법에 비해서는 무척 심플한 방법이라고 할 수 있겠다.

 

 

 

4. 작업 순서

 

4-1. Multilingual App Toolkit을 다운 받아서 설치한다.

Multilingual App Toolkit for Visual Studio 2012 RC

http://msdn.microsoft.com/en-us/windows/apps/hh848309

 

여기서 어떤것을 다운 받을 것인가라는 문제가 있는데, 기준언어를 어떤 것을 사용하는지에 따라 다운 받을 것이 다르다. 여기서는 기준 언어를 영어로 선택했다. 즉, 영어 -> 다른 언어로가 되는 것이다.

 

4-2. Visual Studio 2012에서 Multilingual App Toolkit을 사용 가능한 상태로 바꾼다.

  

 

4-3. SampleApp Project에서 마우스 오른쪽 클릭 -> Add translation languages 선택

 

4-4. Translation Languages 팝업 -> 변환할 언어 선택 여기서는 프랑스어를 체크(한국어, 일어, 중국어는 이미 체크되어 있는 상태라..) -> French를 확장해서 French (France)를 추가로 선택해 준다.

 

4-5. 프로젝트에 추가된 French.xlf 파일을 열어보면 아래와 같이 아무런 내용이 없음을 알 수 있다.

* 추가 French (France).xlf 파일 - 자세한 사항은 소스 참조

 

4-6. F6을 눌러서 빌드를 한 후에 다시 내용을 살펴 보면 en-US/Resources.resw 파일에 있는 내용이 추가된 것을 볼 수 있다.

 

 

 

4-7. 내용을 확인 한 후 French.xlf 파일을 오른쪽 마우스 버튼으로 클릭하고, Send for translation을 선택 한다. -> 팝업이 출력된 후 OK를 선택한다 -> 저장 위치를 선택하고 저장한다.

 

4-8. 저장된 폴더로 이동 후 SampleApp_French.xlf 파일을 더블 클릭해서 실행한다.

 

4-9. Editer가 실행이되면서, 내용이 보인다.

 

4-10. 모두 선택 후 변환을 클릭해서 번역하고, State를 Translated로 변경한 후 저장한다.

 

4-11. 다시 VS2012로 돌아와서 French.xlf 파일을 오른쪽 마우스 버튼으로 클릭하고, Import translation을 선택 한다. -> 방금 작업한 SampleApp_French.xlf 을 선택 하면 번역된 내용이 입력된 것을 볼 수 있다.(약간 변경되었으며, 자세한 사항은 소스를 참고)

 

4-12. 이후에는 윈도우의 기본 언어에 프랑스어(프랑스)를 추가하고, 언어팩을 설치 한 후 기본 언어를 변경하고 앱을 실행 시켜보자

 

 

 

 

5. 결과 화면

번역된 내용이 정확한지는 확인을 못했다.

 

 

6. MainPage.xaml

 

<Page
    x:Class="SampleApp.MainPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SampleApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*"/>
            <ColumnDefinition Width="0.5*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Margin="20,0,0,0">
            <TextBlock Text="Multilingual App Toolkit Sample" Style="{StaticResource PageSubheaderTextStyle}" Padding="0,20,0,0"/>
            <TextBlock x:Uid="Message_Title" Text="Person Information" Style="{StaticResource ItemTextStyle}" />
            <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                <TextBlock x:Uid="PersonInfo_Name" Text="Name :" Margin="0,10,10,0" Width="70" TextAlignment="Right"/>
                <TextBox VerticalAlignment="Center" Width="200"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                <TextBlock x:Uid="PersonInfo_Age" Text="Age :" Margin="0,10,10,0" Width="70" TextAlignment="Right"/>
                <TextBox VerticalAlignment="Center" Width="200"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                <TextBlock x:Uid="PersonInfo_Sex" Text="Sex :" Margin="0,10,10,0" Width="70" TextAlignment="Right"/>
                <TextBox VerticalAlignment="Center" Width="200"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
                <TextBlock x:Uid="PersonInfo_Desc" Text="Description :" Margin="0,10,10,0" Width="70" TextAlignment="Right"/>
                <TextBox VerticalAlignment="Center" Width="200"/>
            </StackPanel>
            <TextBlock x:Uid="Message_Input" Text="Input your information please" Margin="0,10,10,0" Style="{StaticResource ItemTextStyle}"/>
        </StackPanel>
    </Grid>
</Page>

7. 이렇게 여러 언어를 지원하는 앱을 쉽게 만들 수 있다.

더 자세한 사항은 소스를 참고 하기 바란다.

 

8. 소스

 

SampleApp.zip

블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바